Forum
    • Categories
    • Register
    • Login

    XY Pad with Optional Motion + Speed Control (HISE Script)

    Scheduled Pinned Locked Moved Scripting
    4 Posts 2 Posters 65 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • J
      JamesC
      last edited by

      Hey everyone,

      After my question yesterday I wanted to share a the script I’ve been working on. Would love to hear thoughts on updates as to whether this is the most sensible approach. Particular mention should go to @David-Healey whose original XY Pad videos sent me down the rabbit whole with this and did a lot of the heavy lifting!

      Overall it’s an XY Pad that supports:

      • Manual user control via mouse or XY knobs
      • Optional motion driven by a simple sine-wave “LFO”
      • Adjustable motion speed
      • Fully modular and easy to extend

      The idea is that you can move the XY pad automatically, but still allow the user to take manual control at any time.

      Features

      • btnMotion: toggle automatic motion on/off
      • knbMotionSpeed: adjust the speed of the motion
      • Motion is applied as a small offset on top of the manual X/Y position
      • Gains are mapped from the XY pad to two gain modules, with scaling

      How it works

      • baseX/baseY store the manual (user) position
      • lfoX/lfoY store motion offsets generated by the LFO
      • updateXY() combines base + motion values and clamps them 0–1
      • LFO speed is multiplied by motionSpeed for easy adjustment
      /// ===============================
      /// XY PAD STATE
      /// ===============================
      
      // Manual (user) position
      reg baseX = 0.5;
      reg baseY = 0.5;
      
      // Motion offsets
      reg lfoX = 0.0;
      reg lfoY = 0.0;
      
      // Motion enable
      reg motionEnabled = false;
      
      /// Speed
      reg motionSpeed = 1.0;
      
      /// ===============================
      /// COMPONENTS
      /// ===============================
      
      const var xypad = Content.getComponent("xypad");
      const var btnMotion = Content.getComponent("btnMotion");
      const var knbMotionSpeed = Content.getComponent("knbMotionSpeed");
      
      const SIZE = 10;
      
      /// ===============================
      /// GAINS
      /// ===============================
      
      const gains1 = [
      	Synth.getEffect("Simple Gain1"),
      	Synth.getEffect("Simple Gain2")
      ];
      
      /// ===============================
      /// XY PAD CORE CALLBACK
      /// ===============================
      
      xypad.setControlCallback(onxypadControl);
      
      inline function onxypadControl(component, value)
      {
      	local x = component.data.x;
      	local y = component.data.y;
      	
      	local gainX = -50 + (65 * x);
      	local gainY = -50 + (65 * (1 - y));
      	
      	gains1[0].setAttribute(gains1[0].GainValue, gainX);
      	gains1[1].setAttribute(gains1[1].GainValue, gainY);
      	
      	knbxy[0].setValue(x);
      	knbxy[1].setValue(y);
      	
      	component.repaint();
      }
      
      /// ===============================
      /// PAINT ROUTINE
      /// ===============================
      
      xypad.setPaintRoutine(function(g)
      {
      	g.fillAll(this.get("bgColour"));
      	
      	var x = Math.range(this.data.x * this.getWidth(), 0, this.getWidth() - SIZE);
      	var y = Math.range(this.data.y * this.getHeight(), 0, this.getHeight() - SIZE);
      	
      	g.setColour(this.get("itemColour"));
      	g.fillEllipse([x, y, SIZE, SIZE]);
      });
      
      /// ===============================
      /// CENTRAL XY UPDATE
      /// ===============================
      
      inline function updateXY()
      {
      	local modX = motionEnabled ? lfoX : 0.0;
      	local modY = motionEnabled ? lfoY : 0.0;
      	
      	xypad.data.x = Math.range(baseX + modX, 0, 1);
      	xypad.data.y = Math.range(baseY + modY, 0, 1);
      	
      	xypad.changed();
      }
      
      /// ===============================
      /// MOUSE INPUT
      /// ===============================
      
      xypad.setMouseCallback(function(event)
      {
      	if (event.clicked || event.drag)
      	{
      		baseX = Math.range(event.x / this.getWidth(), 0, 1);
      		baseY = Math.range(event.y / this.getHeight(), 0, 1);
      		
      		updateXY();
      	}
      });
      
      /// ===============================
      /// XY KNOBS
      /// ===============================
      
      const knbxy = [];
      
      for (i = 0; i < 2; i++)
      {
      	knbxy.push(Content.getComponent("knbxy" + i));
      	knbxy[i].setControlCallback(onknbxyControl);
      }
      
      inline function onknbxyControl(component, value)
      {
      	baseX = knbxy[0].getValue();
      	baseY = knbxy[1].getValue();
      	
      	updateXY();
      }
      
      /// ===============================
      /// MOTION BUTTON
      /// ===============================
      
      
      inline function onbtnMotionControl(component, value)
      {
      	motionEnabled = value;
      };
      
      Content.getComponent("btnMotion").setControlCallback(onbtnMotionControl);
      
      /// ===============================
      /// SPEED CONTROL
      /// ===============================
      
      
      inline function onknbMotionSpeedControl(component, value)
      {
      	motionSpeed = value;
      };
      
      Content.getComponent("knbMotionSpeed").setControlCallback(onknbMotionSpeedControl);
      
      
      /// ===============================
      /// LFO ENGINE
      /// ===============================
      
      const LFO_RATE_X = 0.35;
      const LFO_RATE_Y = 0.25;
      const LFO_DEPTH = 0.25;
      
      reg phaseX = 0.0;
      reg phaseY = 0.0;
      
      xypad.startTimer(30);
      
      xypad.setTimerCallback(function()
      {
      	phaseX += LFO_RATE_X * motionSpeed * 0.03;
      	phaseY += LFO_RATE_Y * motionSpeed * 0.03;
      	
      	if (phaseX > 1.0) phaseX -= 1.0;
      	if (phaseY > 1.0) phaseY -= 1.0;
      	
      	var waveX = Math.sin(phaseX * Math.PI * 2);
      	var waveY = Math.sin(phaseY * Math.PI * 2);
      	
      	lfoX = waveX * LFO_DEPTH * 0.5;
      	lfoY = waveY * LFO_DEPTH * 0.5;
      	
      	updateXY();
      });
      
      1 Reply Last reply Reply Quote 1
      • X
        xm4ddy
        last edited by

        any reason you used sin twice with extra logic rather than cos?

        X 1 Reply Last reply Reply Quote 0
        • X
          xm4ddy @xm4ddy
          last edited by

          I think something like this should do about the same.

          /// ===============================
          /// LFO ENGINE
          /// ===============================
          
          const LFO_RATE = 0.3;
          const LFO_DEPTH = 0.25;
          const TIME_SCALE = 0.03;
          
          reg phase = 0.0;
          
          xypad.startTimer(30);
          
          xypad.setTimerCallback(function()
          {
          	phase = (phase + LFO_RATE * motionSpeed * TIME_SCALE) % 1.0;
          
          	const angle = phase * Math.PI * 2;
          
          	lfoX = Math.sin(angle) * LFO_DEPTH * 0.5;
          	lfoY = Math.cos(angle) * LFO_DEPTH * 0.5;
          
          	updateXY();
          });
          
          J 1 Reply Last reply Reply Quote 0
          • J
            JamesC @xm4ddy
            last edited by

            @xm4ddy Thanks for this I will have to drop it in and see how it works. Plenty to learn still!

            1 Reply Last reply Reply Quote 0
            • First post
              Last post

            17

            Online

            2.1k

            Users

            13.2k

            Topics

            114.5k

            Posts