HISE Logo Forum
    • Categories
    • Register
    • Login

    Circular XY pad

    Scheduled Pinned Locked Moved Scripting
    13 Posts 5 Posters 279 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.
    • mmprodM
      mmprod
      last edited by

      I’m working on creating a circular XY pad and I’ve run into some difficulties.

      I’ve already taken the code from this post: https://forum.hise.audio/topic/9665/detect-if-two-paths-intersect/6. However, I’m unsure how to map the mouse pointer’s position within the circle to these knob values:

      • Upper Left of Circle: x=0, y=100
      • Upper Right of Circle: x=100, y=100
      • Lower Left of Circle: x=0, y=0
      • Lower Right of Circle: x=100, y=0

      The only example of this behavior I can find is in blepfx's destruqtor plugin. Heres a demonstration:
      https://www.youtube.com/watch?v=MqpRXpoaBHU.

      This is my preliminary code which does not have the desired behavior. In the corners of the circle the values cannot reach 0 or 100:

      HiseSnippet 1904.3oc4X8zbaabEGTRHwD0IsdlbNyN5RAsgjHHojsGlL5+JkShb3Xk3ZOpbbAAVRti.wxAXokPq0wdteExmfbJe.7L4bldnG6o7MnW5jisu2t.DKjnjUzDmKURPj69du88a+su8s6Cci49zjDdrQEquJcB0nxcMOJMRLZ2QdrHiN6YT42XteXHaRB84uvXmzIdIIz.iJUV7yPEpTcIC4O+6M2wKzKxmVzkgwy3Le5WvFyDE81cqOmEFdfW.8qXi0zt0Vc74Q6xC4SAvrnYciId9m3Mj9DOTsELM9CdIiLpbeyG0p0iBFz2Mf1Ln458WuEs4Ca9n9ObiMZNXi5TuVq2pu2Ca7XiJu29ALAO9HgmflXTYoc3AoGMheZjxAOikv5GRwFtFGAdV08A7v.bJh8Zr6HVXP2bVJw.FktEb1hJN6iLOjEvl0eA286jBHEVnSfUVnL7VrD7b0gWcM3MGHUQCRKofz8LOxOlMQTHQsV1IRPiG3AqS5PQoqwBKeWyc4fFQhUG6cB8fXnwLKr2ndcGB7uZssrVaMxIQ8edpErnkHTem7oji6AxFviI1LnU81DF4SHMfOdvCpY8WspJ0a0ISSFYm6ngTwt7wS3QPC6kkJrL4ADVMvMJ8Ol0a0DTqHQLObWuvv9PngMORJMqaP6ykvpqWDMLCVuxKlLIJTBs46OozkAiuVwy28RgEtG78gc1qCIqmPZbRFLFyCRPxwppbuE5i8GLf5CNX2Q73oItKWy4JE1X4ZV8TTtziVVxOtQXBMZ2XJDfQd9KHS7BbHft7SoADOnanUT.weZLDfnQYmk10SLRiy7kCA1oMLnEJlMV2PsU94JUDw5YoRbAHULpDf1F6Env0cV2oQy53CvIJftpWPPVNJakl4ybkCUCl765pdrq65N3SCX3p2qVavnKB1L2lYLrzrCeZTPhs6p0qIcw1Zr4EQcF6jCcoaf+jO8rz3NcToYT1r3P9zDJYDrRExhFVr3K6e1R+foQ9BFOxl9JfZka1XCHpVqFD6Mj75WSTs7CY9mPCpYUETRpkNV.7Kf7WI1Gqz9LG0mo8pAVHMopXDKY0.Og2prjNQIr.JLAEwSo.hKI9Ln+rgocYIoyjj1tJH5b3gFlPuVWLvCzPNP3CvMCXP7qXDkvvrTITIEPlvglj9TwoTZDwmhxH7AkB8kQ9ikTaV7OLh3pVoQ5S0Cww0+NZRsOVaw53F8VqgidGMgN54bIVzQRT0lMKJSV5d+XLD+RjVIMbkZbNx5Rs7gvjgz.X6DL1macNFBwhf3FJIO.gTNuosed1NGHnMDPFF5j+60EdXqRNWuGxKOCM0t18gP6ZO.ByWw891YJ3dIEVoQi50TgSpE6ppf54GQU8hZf7z0471Wz.j1d6.RZFFFlGGdsXqHTTgue4CFqFSGVZnZ7tNbbtL87hHuH4NmXRjIsloodboEdPMlXauXuSwb.wohRI15BwWhmxmB8RKxqMDBLgQDSq6E5OMDOSCosD3JTTIsqnteeh7nNdBSR+VxM03DQei1JZ42kTUakdokzKsrdMU5IwP.hczo4mXAvE2lYUcn5fY7dr1xgBVlrWlIniUctr7hMCwbxmV9TKGRSb+ZogWdZRLPH4QJy7yZqUdLzVqcbqcIbpN9eV30aCkMxg4.315xSnUV5b7YNoNkHOmRTTO7hX3zPtD+T5DbwThfI3EyfrO3bBy7LmDS52dY9IlB49dgxkyYhyVSamKL8xBSkzAdMLLiALy2VHhY8mJn1vUZsOaEX6+ZMj2rUok6bzxFRRrRpRO45zrTPI4YSJORyx1TH+RiQAPiUTEdSnyaaoQJOgKneYjsJu74VjKJZvf4Jq3ZnyULV8U70Ynczzw8owkNU.TDpvnbYKlWcYK5UU4qtymlh7nNQLwWNgFcU0ZYjcQQnDmExPEnpPVfyGlUfyQgP93XCFTGycLkLdcCIhwRK+9+i8e5e72+tMMt4V6VX8O9W9nu8mdwOrowW2YOHJBKzJCP.HmPiELb9WYO5qfxbUkcU0bOZxIB9DvSyJi.psV40OHunLbmfzouuoLj23L8xlS0abJKPLpninsFQYCGIz6QtyO+hf.h9slvsRIEczeXdY020rUiG25wa.UGutQwl8qTPiKHwPaNszaecPaV8hszlUhs9YMPtW0.8lS2x3xk9BEcyCvyGJWIN99JxD.w4kJ+EOEENWWjp+9L9Eq77aJDumYWlvez7w3ByAi31h2AXL6kZ7AlpxPK.3RlG772QuAiEz7+6q7+cLUk+Jcc0rFtk7+SA2lQG+Yba9eTauxa93+1+ZyCnz.bOPVea0r4+by8ngdkWnW7FOKbudRTcmkgG5AGa.grlOY53ifcQ9TfCifs7X1xJKf40TsqisQPbDMJP13+B+jIzEaWISnatPiaHQ03pHpuAyn8+CD0uF9XrmeL+kY2mAiiuirGXdGIeqkUMODaSzNTIeq6XV.6k99kGpKYXiaqgMusF151Z352VC231Z3CusF9n2tg36sc6oB9XUlXCiC6tu7R.UprejGDkK2WY7+3BnViJ
      

      My first thought is to calculate the distance from the point to the left side of the circle. I got this somewhat working however the values get wonky when you're outside the square 😲

      The main changes were made in the onpnlXyControl(), and I'm only doing the x values for now

      HiseSnippet 1858.3oc4X07aaajEmx1rMhaa2VfdtXfurjancDojbRfp2nD+QWgVmJDmM0AFFETjijFXJNpjihLwVea6o9uzdn+Ozi8Ohs.85Br66MCk3P+U7ZztWpfosl22yu2ad7MteJOjlkwSMpY8x7oTiZum4g4Ihw6LNfkXzaWiZefYBc9dwwroYzidswyxmFjkQiLpUa0OCkoV80Lje94m7rf3fjPZIICiWwYgzufMgIJo1u6myhi2OHh9R1DMoa0sWHOYGdLeFDOqZ1vXZP3oAinOO.EaESi+ZP1XiZ+YyG0p0ihFNvKh1LpY6AsaQa9vlOZvC2ZqlC2pAMnU6VCBdn+iMp8N6EwD7zCEABZlQs0dFOJ+vw74IJG7JVFaPLEW3YbH3YE484wQ3VDoZryXVbT+E.UlAXk9kv1pJX6iMOfEwVRuD99PICRoF5.XsUpFdqVI77zCuFZg2UDR0zBo0TgzGYdXXJapnjCFO+AydIBZ5v.HOoGJJYMV4eXYtCGjHQr4jfSo6mBKVpg8VMZ3Rfe4zwxxBRVYBxaBRImlL3n7FjsIKTcDUrCexTdBrvdcE60AktfJd2rJdqK8C74AOfzOHgFqYfoIwGkes5K4hpeir2LColHR4w6DDGO.J2r4IRlEjAK.99fd61iTPIlllUDFS3QYPDbrUc4YFzG6MbHMDbvNi4oyxfMf60xzecGqS5H2aROZYI+ysJlPk1IkBUMjidMYZPjKAjkOmFQB.xvpjHR3rTHqqAYmk2OPLVCyBkl.IZWI4TXqaozJ+bsBhw5Y4x3BhTw3JAzSQp.D11ssqeyF3CfIp.cyfnnhFO1JIWryUNTYL420E8XOu1t3iOXtFm3zoi0kB1B2VnLjZdFeVRTls2lMbjt3oZn4Ei5BzYQnKcC7i74DKMrSOpzTpXWb.eVFkLFxTwrjQkIeI8ko9gyRBELdhM8M.z5X82spyFRTq1LJMXD4a+VhZUXLK7TZjiUcPHoT5wBD+BnoTl8wJoOyU827Sb.MjpTWLlksYTfHXSVVujLVDE1fhzYTHhqv9LfdgY5TkS9RN4cpCrNGdnwYzazECC.IjFBe.rYHCpeEioDF15IiJg.xTNrjLfJlSoIjPJxivGVozWV4OQBsE0+fEwrVEKssdINl+6ow09Xsj0w9m7.eWcBMABm3dITzUBTNK2EUAKcueLVheIPqhDdRINGQcoTgPYxHZDbbBr84VmWzWDPpcSCli.dZtnRUTeHWKdAeFPkVVDMBJf.KfmgBhCmEiMPP.KCdIDUB3JP6OkI6qvyXR3xRhf39PeWsg1gIIL0QIWdE4xqJWSkbxXHBiczoKZO.gKbV.pUFo5BhSBXKMEjhrWmInSTDW2AA5Q3Af4UaQ3RZhfSEyKO5lB.xhZDM+naAsrrqmiLJUB7Brs8wsa3B+30R9.4aeTDc+nZEurt6ssI7WrKFBiCI6Vpzz83ybycqfstUPPnm1xR.VBtSHKxvjpupvNbw67bgVWwP8I1.IlGFDKSmKYWjS6rfY9kYlqf0HVl.mxiH3jX5PAAOCuPskL2lb.dxJ6aRE11xuNkO21CGev2YikDr88arQtyFELbjoNryk8YNjOcahmeCGB12nHj2Er+y4oSfuucoytOw9LzDNa32nCd9XoI9K2rEfF1P040aGq5x4PfCrXp7UH.BlszDNOvWMKT8RrJkNEO5gu467hoWvyoRCU7RD42wWcfu9eH7lLaFrpQGBi7oDe3O2+9x7jTtMmNKar8MLkz5PbybT0hpvkcxUODgja4PDmqUz7btf9kI1R2ZctE4hrFN7J4UNSzUxFmuO8lTzNY1jAzT8hSTPXF1pCFad8CFqO2dnBkzDjmzKgI9xozjqaZdiBnEFhdkhnBDUHGg9CJFg9vXnFO0fASJeOS0zrFxHFu7hf+Ke++7e+udhwsWauRsk27o6Wz03u0aW3TFNJeQ.AA4TZpfg6+Z6ReCbQJ0f80M2klcpfOE7zxhA3BbJu99KF6GmWV5z20T1Rv3L8KlkquXNKRLtjPR2wT1nwBcJxdiKpkfH5OZBiHQJILXzhKt8dls7ebqGuEb+q1Fkc6tVF9Wfig1dZs2ddPaW85tZ6JQ2+mLj20YneXdWiKe4J3Zc7H78mUuqGdi3BFPcdkKXgSX.y6Hx0uw7uZW.71FhejYelHb7UGiqbEwHdr32fXr3Zyuuo5NQkA3Zl6ezuQ2QdEM++tJ+eOS0cwjttdwBuJ9+EfaKfitMa9iO4qzNq7Cex28SOYeJMBOCnKztz3fpI5Uu06BuaFDUyzMBdEZJCJYMe9rIGBmhBo.Fl.G4wtk0VA6qoV2.WiAwgzjH4h+C7ofoGttVASuELMtk.k+u6Ap+e3iIAgo7uNT8xErN9dRJv9NQ9+Eqt4A3ZxEeoB7YBKh80ggUM0kTz+tpXy6phstqJ19tp3V2UEe3cUwG81UD+OC9zYB9DUmXCiC5umbHfZ01KI.pxkmqL9uTOXTqC
      

      Other ideas, fixes?

      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

      LindonL hujackusH 2 Replies Last reply Reply Quote 1
      • LindonL
        Lindon @mmprod
        last edited by Lindon

        @mmprod not distance from the centre and angle?

        HISE Development for hire.
        www.channelrobot.com

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

          @Lindon how would I use that?

          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
          • 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

                              28

                              Online

                              1.7k

                              Users

                              11.7k

                              Topics

                              102.3k

                              Posts