HISE Logo Forum
    • Categories
    • Register
    • Login

    Circular XY pad

    Scheduled Pinned Locked Moved Scripting
    13 Posts 5 Posters 278 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.
    • hujackusH
      hujackus @mmprod
      last edited by

      @mmprod I had some fun working on your Circular XY pad (for a few hours🤣). The solution to your problem is that you need to define a transformation from Knobspace (x,y) to Circlespace (angle, distance) and back. The pixel coordinates of the XY cursor can't be transformed into the knob values in a linear way so I used some trigonometry to make it work.

      HiseSnippet 2356.3oc4Y80TiibDWFv2sVIWt6pJOe0D+RjwFijMdWtkkk+SBUB6Qsr2dPQn1LHM1dJj03SRFrtb7X9NkGy2gT4o7ddNeC1z8LRVi.afPkKuDphBOS+ueS28zc6giBEtrnHQnQIy2kLjYT5mW93jf396zmxCLNXWiR+rx646yGFwN4TisSFRihXdFkJM+uAYnTkELj+7u1XapOMvkkukgw6EbW1umOfGmu6Qa963996S8XuiOPi6U17.WQvNBewH.LyW11XH08RZO1anHayU132Ri5aTZwxdNq3zcUW5yW0YkVttsV8Es95W3xnc6xdtSmWrxpcWoK00tkQoOYOOdrH73XZLKxnzBaK7RNtu35.kAdOOhegOCW3XbLXY016K78viHtqwN849dGk4khL.sbTtOadkO6WV9PtGex949tuPRfjKgtCrzbEg27EfmiN7r0f2TfTIMHsfBReY4icC4CiyonhkGDDyBA2Cq.TT7ZL2+7yKui.3HHt4.5kr8CgESjvpiscCRaa6ZqYZt7xjKCt3jDSHnEEq9LYcxYmulYWQHwhCKrWivIuhzB9S850L+SlUjrcF+bfXlc5wh2QLXnH.VXUUxPURcBGLxD1aFgLEDGJ72g56eAjXXIBjTS2F39FyTrbEMjbxoe3sas6Ae6wfkbZYq.7NgLvSCzHCodMHz.Oh6nPv2nI33jinw80vmqTHbSKvH5LtEPAOxcZzoQq113uvoWoflTOuzqMVJNqsFAfv3DBEEaHviNdU3X1lURVWom43zoA9aKvv1mWaM77oThT22V0ofMUQfSeawn.uHKmlxvYN6CC7O4zVyL.oHWEjQ8ooGZTzxiMn2mGviIw8YfeumOS5883fQiEjHw.VbedPuIZ0iFSapXDxiZ1YsJEnHEDhr1XjEU9aYCg6bJ8OjFv7Ih.hqx9l7.ed.izcTfaLG1+V3yxM630.7.9iXxTUegK0mLF8YYjU1drJPR3QDaD7.Hx3N4tbmH4NoH2lUFH7hNyVlXuUbbH+hQwLK3103kcv6WjTNbtKGIJNLytcnTx6QbakIMXx.QLIZTHiv6RRDiHWSAuiBADHGIGKJs3nokjbs3wFxfrDzYhd1KCDWP.fw.qmeNCU9dLO8FUn9PwnHFoODhAGeOsLEIgI4IYADK1Ufdjdc.spUM8Bo8H+3ORTqb84tWx7pYVAXpBlmFRu9DvcqHOdoIW3WKm9o.8I6ujhSU7nKbIBBJQ7dAni.q0AKoignzPAlGMZHnlTMklrcHdsd.OvB7NMjKh99vXKDGKJAScoMwOeZsZYvXRNrztpUfUbgaePYUhKj3b40bva0MTL.xpPLzDjk2Up4e0510vSbkL8HMLjXEfjOcYjmZfkkL+J6MHsaBcG670sddmWZKAwML+HVAUfB9ZahNuK2h7RxR5qkhp7AvMynhWHk+EOPHI73PUWbHWvhulwBHsO5.Pk3U7kvOUTKo9S7O2mNrWRlehxt7xvU0HgOq4vPLSCOr0q1nZcYPtNo5RultN10PBL4wVK10Zha6hHK4GbEQVJVmDmPFaeGFifvsFiYZS920WG8g.Fseobikk6hmH5UBNVb6JtGbbRH+.KTjJc6ToaOUoa+.R2ROMTtQCoXpdKYNR7lZfHb.0m+CLUQVbvrLWIQUESU.VVhBaIoKAMBqZ.UqulADnjnueDxAz6RVZsPvbbFjx8oKJQ1hM6.AjNp7daxqVGRte05pPZt3IYhm6omk3ISD+1oCjpiWuZccLUuZRgcRH0VqhV1rKTapGyyRt6Ml2f0SyqRcDVN6shQwPWi7hT8fBT.2XeVpu6HebTBzGFAivwjNcUy0ecjbBCQDW1rwTldgtIc.tjVa4yZct79FxWRA9RJxW6b9nE3SceTQIr.E4kLSErgJpWKwY1PHvIDaLZVompKNN5skTVnmuUUdLafZypxz+dXM4qKNUCLOnrWDndPERsisBUsY6l1N1iLBBu8HghXIETUxOyrVBzvYSpQ2XxmNu1cPsZrsrI1dPL2RBZjutv22PNHkRzF41CKYe2PQCHKIusws7+MJvbiBTQPKSkj8.OX2CHoSX3yBiRGwBaqiCMZVQ90rPTuG7EWbAruSeQ3nHmp0ZLShvgx7bUO1KXcw7NXHNP4vEbOzCiNhFjg9LJzQINLABFhKQBzXxr5BWaJyGoOZ8CMdzjwP5kM.AF6xGHZx.F5zMqnONGplwKJmlqv9n3Io6aVQwUKaYigzgvBw6wXQObuIyznjSmyjYvYFPehsogtziyZQe2NzIKOV1ad7zZLOoublbIOldx2TzA8zaHmcvKzVQNPy3Eg5mf6KQ9cJdvlJ4pYq6q8opp9Z57u880WdB+SYreUKP5XYKvsjs.2tl5zCkjciGkZ.Dfp.lKSFFsxB70TEWJ1FoJJibLh6XSXDiF+gXfj5bVWtpdpgQckJgdekaL0tP8FQL6aBrjWc.JjaSpa2oRKu9wTIiufR38InUvnAWvB0u3hLZTZghO8P4Y+zC5uLhq5qDpwnH3.3q08Mv2RXVuWhQ52izvnzbonBXMV9HE+hzGo3XeXVmPCtmQomUVVuv1PhX82Wx3wKsSAo+i+ku5uugw2dvtP3DerjT.AfbHKLlim+R6xth6xTOcRkx6xhtLVLDrzju4K3xdXLOdBd2bzlI4f+caZ7ehhbloh7JnnJJE8YYO0CNbVpdT4iZ5w305540adM2Kte9FAa1mw60OVeGnAg35r1DfS5yKukuOIei7dsfTehgwG+HJmVGX41ez3ip286NOQ0WT9PgGNGUwWLCeWwTBPtbgmoBeJp.XtpD87h+q8LZOVH9kkOhG61e5XbtofQL0+m.Ll93ieVY0LB4.bgx6exOQuz3bZ1+SU1+YkUylHMckzENEr+aAypGx9Nsju+5W8m+GarOi4gIUo6sY61+sM1k4SKFnm+QeJbtemnZ19dPejPNbEo7aFM3XHg0kA9v.3RDVQrzbXsK0ZabMBhiYAdxEeD9IknCttTJQmLhFORGUqY4njUs9+AG0+Krw.pan3CouGHlG+L4Nv4NP9eWnR4Cw0Dma21wvX.2i+AW2hp5NB15oJX6mpfq7TErySUvm+TE7EOUAW8gED++qr0nXw.UkXCiCOZOYi9Rk1KfBY4x6UF+a+In0rA
      
      Content.makeFrontInterface(500, 300);
      
      // knbXy
      const knbXy = [];
      for (i = 0; i < 2; i++)
      {
      	knbXy[i] = Content.getComponent("knbXy" + i);
      	knbXy[i].setControlCallback(onknbXyControl);
      }
      
      const var XY_RADIUS = 120;
      
      // Create XY pad, and cursor
      const var xyPath = Content.createPath();
      const var xyArea = [5,5,230,230];
      xyPath.addEllipse(xyArea); // xy area path
      
      const var cursor = Content.createPath();
      cursor.addEllipse([115,115,20,20]);// Cursor path
      const var cursorArea = cursor.getBounds(1.0);
      
      const var pnlXY2 = Content.getComponent("pnlXY2");
      pnlXY2.setControlCallback(onpnlXY2Control);
      
      // init the angle and dist to something;
      pnlXY2.data.angle = 0.5;	
      pnlXY2.data.dist = 100;
      
      
      // Repaint the panel on control
      inline function onpnlXY2Control(component, value)
      {
      	local x = component.data.x; // x is 0 to 100
      	local y = component.data.y; // y is 0 to 100
      	
      	mods[0].setAttribute(0, x/100); 
      	mods[1].setAttribute(0, y/100);
      
      	knbXy[0].setValue(x/100);  //not sure if you want 0 to 1 or 0 to 100
      	knbXy[1].setValue(y/100);  //depends on the knob mode
      
      	component.repaint();
      };
      
      // Mouse handling
      pnlXY2.setMouseCallback(function(event)
      {
      	if (event.drag || event.clicked)
      	{
      		var rawX = event.x-XY_RADIUS;
      		var rawY = XY_RADIUS-event.y; // flip y sign to make y axis point up
      		
      		var dist = Math.min(100,Math.sqrt(rawX*rawX + rawY*rawY));
      		var angle = 0; // angle is counter clockwise from x axis.
      		if(rawX!=0){
      			angle = Math.atan(rawY/rawX) + (rawX<0? 3.14159265:0);
      		}else{
      			angle = rawY>0 ? 3.14159265/2 : -3.14159265/2;
      		}
      		
      		this.data.angle = angle; //this is a value between 3PI/2 and -PI/2	
      		this.data.dist = dist; //this is a value between 0-100
      		
      		//Console.print(rawX+","+rawY + "->a=" + angle);
      		
      		var dist2 = Math.abs(Math.cos(angle));
      		var dist3 = Math.abs(Math.sin(angle));
      		dist2 = dist2==0 ? 1000: dist/dist2; //avoid divide by zero
      		dist3 = dist3==0 ? 1000: dist/dist3; //avoid divide by zero
      		dist2 = Math.min(dist2,dist3); // this is the normalized distance
      		
      		// data.x and data.y are normalized as if it were a square xy panel
      		this.data.x = Math.cos(angle)*dist2*.5 + 50; // 0 <= x <=100
      		this.data.y = Math.sin(angle)*dist2*.5 + 50; // 0 <= y <=100
      		//Console.print( "x="+this.data.x +"y="+this.data.y );	
      		
      		this.changed();	
      	}
      });
      
      pnlXY2.setPaintRoutine(function(g)
      {	
      	// Calculate and store the cursor's XY position 
      	var x = this.data.x - cursorArea[2]/2;
      	var y = this.data.y - cursorArea[3]/2;
      	var a = this.data.angle;
      	var r = this.data.dist;
      	
      	// draw the xy area outline
      	g.setColour(this.get("itemColour"));
      	g.drawEllipse(xyArea, 3);
      
      	// set the location of the pad using rotation
      	g.rotate(-a, [XY_RADIUS,XY_RADIUS]);
      	
      	// draw the XY pad cursor
      	g.setColour(this.get("itemColour2"));
      
      	g.fillPath(cursor,[XY_RADIUS + r - cursorArea[2]/2,
      		XY_RADIUS-cursorArea[3]/2,cursorArea[2],cursorArea[3]]);
      });
      
      
      // MIDI Controllers
      const mods = [
      	Synth.getEffect("Chorus1"),
      	Synth.getEffect("Chorus2")
      ];
      
      // before understanding this, please try looking at pnlXY2.setMouseCallback()
      inline function onknbXyControl(component, value)
      {
      	local x = knbXy[0].getValue();
      	local y = knbXy[1].getValue();
      
      	pnlXY2.data.x = x*100;
      	pnlXY2.data.y = y*100;
      	
      	x = x*200-100; // x range is -100 to 100
      	y = y*200-100; // y range is -100 to 100
      	
      	local angle = 0; // angle is counter clockwise from x axis.
      	if(x!=0){
      		angle = Math.atan(y/x) + (x<0? 3.14159265:0);
      	}else{
      		angle = y>0 ? 3.14159265/2 : -3.14159265/2;
      	}
      	pnlXY2.data.angle = angle; //this is a value between 3PI/2 and -PI/2	
      
      	local dist2 = Math.sqrt(x*x + y*y);// this is the normalized distance
      	local distA = Math.abs(Math.sin(angle))*dist2;
      	local distB = Math.abs(Math.cos(angle))*dist2;
      	pnlXY2.data.dist = Math.max(distA,distB); //the actual distance from center (0 to 100)
      	
      	//Console.print("dist=" + pnlXY2.data.dist + ",\t" +distA +",\t"+distB);
      	
      	pnlXY2.changed();	
      }
      
      

      Feel free to ask for more details about how it works. I know the comments are not completely understandable.

      Check out the music of Conway's Game of Life - https://www.youtube.com/@hujackus
      ConwayMatrix - http://hujackus.altervista.org/conwaymatrix/

      mmprodM 1 Reply Last reply Reply Quote 2
      • mmprodM
        mmprod @hujackus
        last edited by

        @hujackus thanks!! Once I get access to my computer again I’ll test this. Conceptually, how is the distance and angle used to calculate the x and y knob values?

        For God so loved the world that he gave his one and only Son, that whoever believes in him shall not perish but have eternal life.
        John 3:16

        hujackusH 1 Reply Last reply Reply Quote 0
        • hujackusH
          hujackus @mmprod
          last edited by hujackus

          @mmprod said in Circular XY pad:

          Conceptually, how is the distance and angle used to calculate the x and y knob values?

          It's the only transformation that I could think of that I knew how to code. Here's a picture comparing a square XY pad to the circular one I made.
          Circular XY Pad.png

          Basically, I calculate the angle the XY cursor pad makes with the center of the circle relative to the x-axis. Then I calculate the distance between that point from the center. The tricky part is that I multiply this distance by a scaling factor so that every point on the boundary of the circle corresponds to a point on the boundary on the square. Doing it this way allows a one-to-one mapping from XY pad to the knobs and vise versa. The angle relative to the center is also preserved with this transformation.

          Check out the music of Conway's Game of Life - https://www.youtube.com/@hujackus
          ConwayMatrix - http://hujackus.altervista.org/conwaymatrix/

          hujackusH 1 Reply Last reply Reply Quote 2
          • hujackusH
            hujackus @hujackus
            last edited by

            @hujackus said in Circular XY pad:

            It's the only transformation that I could think of that I knew how to code.

            Is there any interest in trying a different transformation? There are others out there that have different pros and cons.
            Some equations on stack overflow
            Schwarz-Christoffel transformation
            squircular.blogspot.com<- I think this is the transformation I wrote from scratch. Not sure.
            Square/Disc mappings Includes an interactive demo!

            Check out the music of Conway's Game of Life - https://www.youtube.com/@hujackus
            ConwayMatrix - http://hujackus.altervista.org/conwaymatrix/

            mmprodM 1 Reply Last reply Reply Quote 2
            • mmprodM
              mmprod @hujackus
              last edited by

              @hujackus Just tested it, and it works perfectly! Thanks so much. I really appreciate the extra resources too—I still need to wrap my head around the math.

              For God so loved the world that he gave his one and only Son, that whoever believes in him shall not perish but have eternal life.
              John 3:16

              hujackusH 1 Reply Last reply Reply Quote 0
              • hujackusH
                hujackus @mmprod
                last edited by

                @mmprod said in Circular XY pad:

                Just tested it, and it works perfectly! Thanks so much. I really appreciate the extra resources too—I still need to wrap my head around the math.

                I've spent the day coding a more polished version of the XY pad for each of the different transformations. I downloaded the destruqtor plugin and determined that it uses the an elliptic transformation, so I'm excited to see how that looks.
                elliptical.png

                Check out the music of Conway's Game of Life - https://www.youtube.com/@hujackus
                ConwayMatrix - http://hujackus.altervista.org/conwaymatrix/

                hujackusH 1 Reply Last reply Reply Quote 1
                • hujackusH
                  hujackus @hujackus
                  last edited by

                  @hujackus

                  An Assortment of Square and Circular XY Pads!

                  Ok here it is. I put five XY pads and some fun knobs into one snippet for you all to enjoy. I hope you find the code interesting. It should be possible to cut out the parts you need and put them in your projects. I decided not to use broadcasters to keep things simple, but I did have some difficulty linking the X and Y knobs to processors in the module tree because of this. Let me know what you all think.
                  Fun with Circular XY Pads2.png

                  // An Assortment of Square and Circular XY Pads
                  // By Jack Huppert
                  //
                  // Helpfull Reading
                  // Analytical Methods for Squaring the Disc by Chamberlain Fong
                  // 	https://arxiv.org/pdf/1509.06344
                  // Elliptification of Rectangular Imagery By Chamberlain Fong
                  // 	https://arxiv.org/pdf/1709.07875
                  // Square/Disc mappings by Marc B. Reynolds
                  // 	https://marc-b-reynolds.github.io/math/2017/01/08/SquareDisc.html 
                  
                  Content.makeFrontInterface(810, 250);
                  
                  // init X and Y knobs
                  const knbX = Content.getComponent("knbX");
                  const knbY = Content.getComponent("knbY");
                  knbX.setControlCallback(onknbXYControl);
                  knbY.setControlCallback(onknbXYControl);
                  
                  // button to enable updating all XY pads when one XY pad is changed
                  const btnLink = Content.getComponent("btnLink");
                  
                  reg curRadius = 8;
                  const XY_MARGIN = 5;
                  const XY_INSET = 5;
                  
                  
                  // init XY pads	
                  const var pnlXY = [];
                  for(i=0; i<5; i++){
                  	pnlXY[i] = Content.getComponent("pnlXY"+i);
                  	pnlXY[i].setControlCallback(onpnlXYControl);
                  	pnlXY[i].setMouseCallback(pnlXYMouseCallback);
                  	pnlXY[i].setPaintRoutine(paintpnlXY);
                  	pnlXY[i].data.shape = i;
                  	pnlXY[i].data.u = 0;
                  	pnlXY[i].data.v = 0;
                  	pnlXY[i].data.x = 0;
                  	pnlXY[i].data.y = 0;
                  }
                  
                  //just for fun knob to change the cursor size
                  inline function onknbCursorSizeControl(component, value)
                  {
                  	curRadius = value;
                  	for(i=0; i<5; i++){
                  		pnlXY[i].repaint();
                  	}
                  };
                  Content.getComponent("knbCursorSize").setControlCallback(onknbCursorSizeControl);
                  
                  //just for fun knob to change the aspect ratio
                  inline function onknbAspectRatioControl(component, value)
                  {
                  	local h = Math.range(150*value,50,150);
                  	local w = Math.range(150*(1/value),50,150);
                  	for(i=0; i<5; i++){
                  		pnlXY[i].set("height",h);
                  		pnlXY[i].set("width",w);
                  		pnlXY[i].repaint();
                  	}
                  };
                  Content.getComponent("knbAspectRatio").setControlCallback(onknbAspectRatioControl);
                  
                  
                  
                  // Update knobs and repaint the panel on control
                  inline function onpnlXYControl(component, value)
                  {
                  	local x = component.data.x;
                  	local y = component.data.y;
                  	knbX.setValue(x);
                  	knbY.setValue(y);
                  };
                  
                  
                  
                  // Mouse handling
                  inline function pnlXYMouseCallback(event)
                  {
                  	if (!event.drag && !event.clicked){
                  		return;
                  	}
                  
                  	// get the mouse position in the right coordnate system	
                  	local b = this.getLocalBounds(0);
                  	local m = XY_MARGIN+XY_INSET+curRadius;
                  	this.data.u = (event.x-m)/(b[2]-2*m);
                  	this.data.v = (b[3]-event.y-m)/(b[3]-2*m);
                  	
                  	// clamp u and v to the bounds
                  	if(this.data.shape<=0||this.data.shape>4){//bounds = square
                  		this.data.u = Math.range(this.data.u,0,1);
                  		this.data.v = Math.range(this.data.v,0,1);
                  		// square needs no further calculations so just return
                  		this.data.x = this.data.u;
                  		this.data.y = this.data.v;
                  		if(btnLink.getValue()){ //
                  			squareToCircle(this.data.x,this.data.y);
                  		}else{
                  			this.changed();
                  		}
                  		return;
                  	}// else bounds = circle
                  	
                  	local uN = this.data.u-.5; //uN is -.5 to .5 if within the circle
                  	local vN = this.data.v-.5; //vN is -.5 to .5 if within the circle
                  	local dist = Math.sqrt(uN*uN + vN*vN); 
                  	if(dist>.5){ 
                  		this.data.u = uN *.5 / dist + .5;
                  		this.data.v = vN *.5 / dist + .5;
                  		dist=.5;	
                  	}//dist is now 0 to .5
                  	
                  	// now convert (u,v) --> (x,y) depending on the transformation
                  	if(this.data.shape==1){ // radial stretching (2017 Marc B. Reynolds)
                  		//[x,y] = sqrt(u^2 +v^2)/max(|u|,|v|) * [u,v]
                  		//we can cheat and use uN, vN, and dist again from above.
                  		this.data.x = (uN * dist/Math.max(Math.abs(uN),Math.abs(vN)+.00000001))+.5;
                  		this.data.y = (vN * dist/Math.max(Math.abs(uN),Math.abs(vN)+.00000001))+.5;
                  		
                  	}else if(this.data.shape==2){ // elliptic grid (2005 Philip Nowell)
                  		//x = .5*(sqrt(|2+u^2-v^2+sqrt(8)u|)-sqrt(|2+u^2-v^2-sqrt(8)u|))
                  		//y = .5*(sqrt(|2-u^2+v^2+sqrt(8)v|)-sqrt(|2-u^2+v^2-sqrt(8)v|))
                  		uN = this.data.u*2-1; //uN is now -1 to 1
                  		vN = this.data.v*2-1; //vN is now -1 to 1
                  		
                  		local SQRT8 = Math.sqrt(8);
                  		local temp1 = Math.abs(2 + (uN+SQRT8)*uN - vN*vN);
                  		local temp2 = Math.abs(2 + (uN-SQRT8)*uN - vN*vN);
                  		this.data.x = .25*(Math.sqrt(temp1) - Math.sqrt(temp2))+.5;
                  		temp1 = Math.abs(2 - uN*uN + (vN+SQRT8)*vN);
                  		temp2 = Math.abs(2 - uN*uN + (vN-SQRT8)*vN);
                  		this.data.y = .25*(Math.sqrt(temp1) - Math.sqrt(temp2))+.5;
                  		
                  	}else if(this.data.shape==3){ //FG-Squircle (2014 Chamberlain Fong)
                  		//x = sgn(uv)/(v*sqrt(2)*sqrt(u^2+v^2-sqrt((u^2+v^2)(u^2+v^2-4u^2v^2)))
                  		//y = sgn(uv)/(u*sqrt(2)*sqrt(u^2+v^2-sqrt((u^2+v^2)(u^2+v^2-4u^2v^2)))
                  		uN = this.data.u*2-1; //uN is now -1 to 1
                  		vN = this.data.v*2-1; //vN is now -1 to 1
                  		
                  		local temp1 = uN*uN + vN*vN;
                  		local temp2 = Math.sqrt(temp1*(temp1 - 4*uN*uN*vN*vN));
                  		temp2 = Math.sign(uN*vN) * Math.sqrt(temp1 - temp2)/Math.sqrt(2);
                  		this.data.x = vN==0 ? this.data.u : .5*temp2/vN + .5;
                  		this.data.y = uN==0 ? this.data.v : .5*temp2/uN + .5;
                  		
                  	}else if(this.data.shape==4){ //Concentric (1997 Peter Shirley and Kenneth Chiu)
                  		//x = u^2>v^2 ? sgn(u)sqrt(u^2+v^2) : 4/PI*sqrt(u^2+v^2)atan(u/|v|)
                  		//y = u^2>v^2 ? 4/PI*sqrt(u^2+v^2)atan(v/|u|) : sgn(v)sqrt(u^2+v^2)
                  		uN = this.data.u*2-1; //uN is now -1 to 1
                  		vN = this.data.v*2-1; //vN is now -1 to 1
                  		
                  		local temp = Math.sqrt(uN*uN + vN*vN);
                  		if(uN*uN>vN*vN){
                  			this.data.x = .5*temp*Math.sign(uN) + .5;
                  			this.data.y = 2/Math.PI*temp*Math.atan(vN/(Math.abs(uN)+.00000001)) + .5;
                  		}else{
                  			this.data.x = 2/Math.PI*temp*Math.atan(uN/(Math.abs(vN)+.00000001)) + .5;
                  			this.data.y = .5*temp*Math.sign(vN) + .5;
                  		}
                  	}
                  	
                  	if(btnLink.getValue()){
                  		squareToCircle(this.data.x,this.data.y);
                  	}else{
                  		this.changed();
                  	}	
                  	return;
                  }		
                  
                  // on paint function for all XY pads
                  inline function paintpnlXY(g)
                  {	
                  	g.fillAll(this.get("bgColour"));
                  	
                  	// Calculate and store the cursor's XY position 
                  	local b = this.getLocalBounds(XY_MARGIN);
                  	local u = this.data.u * (b[2]-2*(curRadius+XY_INSET)) + XY_MARGIN + XY_INSET;
                  	local v = (1-this.data.v) * (b[3]-2*(curRadius+XY_INSET)) + XY_MARGIN + XY_INSET;
                  
                  	// draw the xy area outline
                  	g.setColour(this.get("itemColour"));
                  	local bs = this.get("borderSize");
                  	if(this.data.shape==0){
                  		g.drawRect(b, bs);
                  	}else{
                  		b = this.getLocalBounds(XY_MARGIN+bs/2);
                  		g.drawEllipse(b, bs);
                  	}
                  	
                  	// draw the XY pad cursor
                  	g.setColour(this.get("itemColour2"));
                  	g.fillEllipse([u,v,curRadius*2,curRadius*2]);
                  }
                  
                  // update all the XY pads when the knobs are changed
                  inline function onknbXYControl(component, value)
                  {
                  	local x = knbX.getValue();
                  	local y = knbY.getValue();
                  
                  	squareToCircle(x,y);
                  }
                  
                  // helper function to convert (x,y) --> (u,v) for all XY Panels
                  inline function squareToCircle(x,y){
                  	//square
                  	pnlXY[0].data.u = x;
                  	pnlXY[0].data.v = y;
                  	
                  	//radial stretching inverse  (2017 Marc B. Reynolds)
                  	local xN = (x*2-1); //xN is -1 to 1
                  	local yN = (y*2-1); //yN is -1 to 1
                  	local temp = Math.max(Math.abs(xN),Math.abs(yN));
                  	local dist = Math.sqrt(xN*xN + yN*yN +.00000001); //dist is (0,sqrt(2)]
                  	pnlXY[1].data.u = .5*xN*temp/dist +.5;
                  	pnlXY[1].data.v = .5*yN*temp/dist +.5;
                  	
                  	//elliptic grid inverse  (2005 Philip Nowell)
                  	//u = x*sqrt(1-.5*y^2)
                  	//v = y*sqrt(1-.5*x^2)
                  	//use xN and yN from above
                  	pnlXY[2].data.u = .5*(xN * Math.sqrt(1-yN*yN*.5))+.5;
                  	pnlXY[2].data.v = .5*(yN * Math.sqrt(1-xN*xN*.5))+.5;
                  	
                  	//FG-Squircle (2014 Chamberlain Fong)
                  	//u = x*sqrt(x^2+y^2-x^2*y^2)/sqrt(x^2+y^2)
                  	//v = y*sqrt(x^2+y^2-x^2*y^2)/sqrt(x^2+y^2)
                  	//use xN, yN, and dist from above
                  	temp = Math.sqrt(xN*xN + yN*yN - xN*xN*yN*yN)/dist;
                  	pnlXY[3].data.u = .5*xN*temp + .5;
                  	pnlXY[3].data.v = .5*yN*temp + .5;
                  	
                  	//Concentric (1997 Peter Shirley and Kenneth Chiu)
                  	//u = |x|>=|y| ? x*cos(PI/4*y/x) : y*sin(PI/4*x/y)
                  	//v = |x|>=|y| ? x*sin(PI/4*y/x) : y*cos(PI/4*x/y)
                  	//use xN and yN from above
                  	temp = Math.PI/4;
                  	if(Math.abs(xN)>=Math.abs(yN)){
                  		pnlXY[4].data.u = .5*xN*Math.cos(temp*yN/xN) + .5;
                  		pnlXY[4].data.v = .5*xN*Math.sin(temp*yN/xN) + .5;
                  	}else{
                  		pnlXY[4].data.u = .5*yN*Math.sin(temp*xN/yN) + .5;
                  		pnlXY[4].data.v = .5*yN*Math.cos(temp*xN/yN) + .5;
                  	}
                  	
                  	//set x and y as well
                  	for(i=0; i<5; i++){
                  		pnlXY[i].data.x = x;
                  		pnlXY[i].data.y = y;
                  		pnlXY[i].changed();
                  	}
                  }
                  
                  HiseSnippet 3686.3oc2ZszbabbDdgjfKQ32U4bMdBqTN6h2.DTjxzTlRTR1LVDFVT1EYoh10hcG.rQK1EdeQrQjw9fSk+F4d9Cji9OPNlS4RtjS4R9G3z8L6iYwtfuRbpJ1UhM2Y5tmt+5tmo6Ay.GaMpqqsiToJOKbFUpzqU9fPKuI6NQ0vRZuGhCrqgiluopygGI8fvYpttTcoRkt4GgTTZkaIw9m+0G9.USUKMZ5PRRegsgF8IFSM7RGcvNehgo4iU0oOyXp.081YOMaqcsMs8As4lkaKMSU6Epio8UQxtQYoOV0chTopk06zqynM0Tuylc50USq6laz8tanQUGMhdmNquQuMG0ajpV6tRkdkGoa3Y6bfmpG0Upzsdfsd3ASrOwhu.eggqwPSJ9QGoCfUlO7isM0QSDGUZ2IFl5ChgIWIPJCRAsaxAs2o79F5FIimBduEaBRJGh.XoajU8tYF0qin50VP8JPkJInR2hqRuc4CzbLl4kNCpOuZ48r7nN.7PynJbZktwe5cK2pE49Vj6ir3MkZ4QrGQN3q8UcnDUKcRbv.4viHCT0cq.z+fPxuF7UjO1e1LpiGLDN5GSMmMx2zj7Tpptg03JLIqZF5YnoZR1m5MwV2kLx1gKefDh2DJ4gFtZjggD.EmNj5XBXI4w1b9WYhm2L22uUKUm4FAMscF2Zl9nVcVu8ca19Nq0qGRziLMAiwXDrLdF1VnA7Tplmp0Xlhu2THnxID05qxRrAtDar4FqiDwAjVLUcp5rYft6h579pNZjGzDVuPKHJxMi.mBS1XXCmn4ZN1vah+vlF1vLdSZ0scmMZ0tSq1a1hKdT5Mm3M0jToxt1feyxq4T0WPerC7QheTdyNsqS5tdakspfKmgkgG4Plu5HxKrrG5VAxqb8f+d3gjsIwRZL0aW6oyrsfOjWEmbUPBIjdz4Q5QHoHKMcwor7brM2U0zbHDEHaagybTzvbBO5RQHp9C887.elmMgZoBQ+D+Y5faDhM.1vftYPPG4jITvuZQiFfX3Rzl.NXpdjELzy5IFVuXoFQz7ncTwgNln467THJ02E3XyXX3vi9p8u+S+n85CCttvf60+fG8L9XBPNW0VIhr.HRalk4gHN97i2pBDlKarc6sHFev5v+pVMkWVYEFAO233kplLBVslAnlIDWLTxlMEJyP8919tzDZYyjYnEoe.jO38Tae.1oxyvOXylgLvon1zch5LJn7F4lwGFsctQCJbz4ENZHezyPD923CHJtQwHeKVLMFevc3rsL.uGrcEw032RqXXYBpMRoFO6Gix1kQvAv7QPjrVLHWGbUl9TkJf+PLJfMJnUE53R0UGJCfjQv4rJmsUkklzjpCqprzzgbJJOs3h..U2YvNbDGbCuhQf6yn3oHAmKDXZiaNOA.f8gMkZ5fKgLr+ZUFI0Wuc8NrsZhH7j7DJ2oEWbBDeAfHfFxqNgZLdh2p0mfLrvbmXn6MY05mjcpKO1KX8mC3mGiPzmkh+43tPT99or8ViVaF5OS0hZBvLQiyUAd.wzyyC3wbgjoiROR.6v7SFBSFuQ7WfxRdtBeniRGJDF5rXKgk4SfPGcS7P4EU076NHSCfkioiFiHx+B1mM0cTGSdu2iD8olog1Kn5L2pC0y2wh4SprBrhf2fASSYq7LaWC1JAm4hi5fdcvrrczsPH1Mz0iNckXadHXydSLbQe5SvQdfsuktqrPL3TfjjspqEu+bsjjYfPlDR1XhaQMm2XpRK4gOu6wM5VcpRFxvcpfoV63FbZCincsDZYlllo5zYDeVDQ.lShVzPlFhnkbp.YaU9Aa29zSWXr60S4ksZw4AVTW1Q+.JlUkExwDlnNjfwRIxp3ERbPBwfdyWEhEkBKpkM39c.M2g.vIVcG5ebIt1D19Nb+YlUYdrWgqGYUgvLSFfSBPQzItnejGUpn7RBTo3JqrBWadlMVaoonNOutfTY59YTSWJFjwWunC8k4ykI1CrRjVRBxpwjdkjHK+9YMhFMgslZ0BFFpl.9.cmv+Fh4OAJTKJZMVHbQDjUDAQhH3JHBcC.gibYtesimre+pfJTCjc0f9JaQXwQHU2q45.hkKx.HtJrFs3RpFrd4CHBJjF7isg+bEFZwlx.iFNgzlq3QA43HvNaAPo8DY+5AJjFMtGQdd8PEhNcF0BqtG29CsNOHtyE1teJKHpnjfs2tCyyCmVoa.HfqG3zzlfxPFqANWQzJrX1mCq2wrDDDj9xtjZAeYWEnz44xm5eZ8SCNUgTk7bP+NlQ+I.RqBaIOgp5wxOwMe76Ca4B+e7alAqNFq8eji8Th5P6.Zybg4xH.yHtEyIgKH6OTG5ByoTO4CvcUqYa9+zQA960ymWHG7elzpvy.HEAqc4vJk29iFYrigNBosWmL.ZYzXFou8Ivrb7DsslqWUlgmm1sF.oM.DsF66MU7OUowBS0HcJtHByJhF.c0DDQPpHhmpQ5TnHVLErZ2FcRyAw3tFcvPwN.sKlqESaPAzB+Od10Ae1Se1lYRu1jsUAeV3blYchmEwbHnB820Xrof4gMhyCyvT2BXpQwLkMXpYW.tRUFlB.oSjrC0MM3IuF1fDuCADhDqowKVdcKC4MVf7LglWUc67BEWiEJ93OpAzIKa6NVpcubMbmFJ5N1R1O.NiMnJas5pTMNSOMvI9Kkjg6A+ANfPDYhj7u9R5G8.yXGalc6WRPVp6nJ++.NkdUYbVkGnk246Zfn.aRX+lEDCvO2U1JchtEDsFze6saS9PQff79XFOiaz.yebSHynVju.Q97S467hg5whgfZl0fBvbf8yj6b26tAY.0CJT4fIFNlzP1F4eB0xh5MABtL7SCn.248.+InFr.BEwX.EPc50ZvdYBLTf0FHrEdRRRvTpTVB8AsfSeP4gqRP1U4+UARmW8C7huXCdO9PoUOktsD20TULzQIwIsf2sKOpAPiTd3PQ+VYNKS73qDgsPAbIpvRkpunTCVhTWbqrb1Sff8bF1YxJUVVQoUtJEjlXN4JG8LXEhqF8Lvgg8dg8Ww5ZLocKrmdgK1Je6XI29hLrY4KAYNt4HCSy6aZJG2Vj7pCGyu27UUR5LY2nJ442Zqqmsi3Uk7qbYqXbqXWTyVIcWk1zke13ZXSl3NojSZ7JoWLlmJ81zpkbIZIhi0uUmFBQ+JbQt1UWjL6G5O8DlAOG1jvgpRr88PnEAPV++HdI.gFP.iHHFAHtBHBfyPSpznqvYqhqssMK.ZL1e7I3sNKOrNHkLgJWHJWanaK99wb4vtPaWpfnhbxIFYz0fxctWrI1kai7PoXoikNWOAnq1U7uOVI5l33WGKkEyltvQWHKNPzMj.QawWHagWG0k9xPXWtQZ5Yl6BgcKGhyUYwLWrGkXMeB0bF0IUOvaPKtqFVuLrtZX82HjUN.uam74kErNuDcIwsuyuip1B2F57sVbTLlOLJeMeuPFnpAGMt7lhh.I7TC443gEJ3oEy4MeFeRQDZwHJLgnvhHR7njL8kLWrujv9B4G45dcd+pywrwv9Ug0PXuZbUiawTtc8nZNNNFS5HfTvt2fXPsgyAudyrzEvoKLOcHZlsEHQjrfdgfCiQ2C+r8NMPoxN6FN3EcPBiOOZbrSRvJwMVAiLs4wXkraViA.kL0f0oACcfNxiqkNKaQ1FfzKvFCbEXC0kKUM1YLQvJpAVXC3+xrzVhitncewDyAi5.RHzVsHljqBkrwHMHbyh8kBySlfHqUXTQ7A4YoIaDQLMnFd0KgjiWmN+z6s8ogmBE+Muplsq7f8Z0qZXq4X4d.9XXwGYdqvDXKCOITjvShTh4Y4gRhvFxB+7Fwbx6scljxzaTuWNXiQHt3rxhB6C6RjVOTVlBxxDZCEvTxgYEthgKx779vVNWvJFtnZlkony7fC1fyEX3EQEN2AxguvedgjhLmuUtgCi1DNc3LEwU4Lgys5a6Q+TKY1ITvgJjEmZznBmK5jNSpSgSiOCBmyiQYKeLgV77QjPoR2J66Gn7xe+.hOuAM9ORh.g1V6YY38oynVK6QOHE8KqHAqRjVAj5wdoAuQzKM3Are4VICcoRqTNpvZIlJK9JQjVF6GXZ.0VwX+UJim5mg2uc9e3eb448nL798+ke1e6Rw6aUN6OBz0S6eyxY9c7VTHjcj978dHD5guaiHXEfZ7gSXfdwROjFXnQ4uhiUJ+Pp6K7rmAhM4WzRpzqxW7WO9Mdf0nvV6aWlEF2VZN3nJUAVtvzU9WtC6mQKYfue3N7eyMwQfxdrOI9m9wEsFnUCR5.oEQhOMndcuau6dmM5d20ElnaxLs6rd6tRoUMmpK2dm3dVfwdke3G9g2gM7UvF6f1Xj31gtyOYsytB14ez6mt14Zh14u6mt1YOA6bv28+u1Y4r14STGlXmr+VL+TpmncpuiGctGRH+cNckDqX5v2NpHw9ZkOHoKpqjnEi.2wuHQWobzaKS6JIXQW9NeSQB9MKmTGO9F0tRRecwDmuqXDIsD3hD84dB9x7hFQB+MJydnUQOGvyS74Ogetn3Rk82ONIB4PxmXYO7pH0iVlWTPpGkWp29xWUvRxeSj+qWVj5o7WI6696+me1e9m+W+PnwZwrroF55lzAQWD2BkZjnbqboK1HU3+8uIqtsrUhriqGcFO6ub4NMaGqwwJRFMtWZHU5hJk+4m9Vk22VGu+wruFV7MCGMApDhOAU7AMZAJWn3Z+esmH6kUEe6xCLfsNJVGuQA5HTE2OF5XzCK90K+nQifPoTE7Vke7g+37Jhk3u3uwPSXNFX8i88md.b.fFEVcK7VvvZJuAdJ.+6132HBb.0Rm8AbNwODMYG76RQS1IdRoopZN1eUz6jBe5x2lMBnSVrm48Jk2G+lzYwxlkfvRciuRSKqnxwX2qKiqccYr20kw0utLdmqKiabcYbyKlQ7gteeeOa9C+.HX+AOh0lSoROh8RdYQqR+aPONMfL
                  

                  Check out the music of Conway's Game of Life - https://www.youtube.com/@hujackus
                  ConwayMatrix - http://hujackus.altervista.org/conwaymatrix/

                  ulrikU ustkU mmprodM 3 Replies Last reply Reply Quote 7
                  • ulrikU
                    ulrik @hujackus
                    last edited by

                    @hujackus Nice, thank you for this!

                    Hise Develop branch
                    MacOs 15.3.1, Xcode 16.2
                    http://musikboden.se

                    1 Reply Last reply Reply Quote 0
                    • ustkU
                      ustk @hujackus
                      last edited by

                      @hujackus Nice one! 👍

                      Can't help pressing F5 in the forum...

                      1 Reply Last reply Reply Quote 0
                      • mmprodM
                        mmprod @hujackus
                        last edited by

                        @hujackus awesome!

                        For God so loved the world that he gave his one and only Son, that whoever believes in him shall not perish but have eternal life.
                        John 3:16

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

                        14

                        Online

                        1.7k

                        Users

                        11.7k

                        Topics

                        102.3k

                        Posts