Synthetic Legato



  • @d-healey HI man! trying your legato script! I think it's kind of outdated, got this error:
    legato2:! onNoteOn() - Line 15, column 14: Unqualified assignments are not supported anymore. Use var or const var or reg for definitions {bGVnYXRvMnxvbk5vdGVPbigpfDQzNnwxNXwxNA==}

    how can I replace the deprecated assignments?



  • @hisefilo Could you show me a snippet?



  • @d-healey just use the snippet you posted erlier.
    HiseSnippet 5482.3oc67rsbabcjfRB1lvNYs85J693wrhsADIwERQoXckhj5BKSIRSPQmMpTbFLyA.ypAyfclAfBJQU1Oh8CY2+f7x9Er+H9OX2t6y0Yv.RPYIEksBqxxXNW5S28oueNybPbjKOIIJtzBe7QSFxKsvmTt8jvz9a22wOrzt6TZg+gxA7dNoQ26ENCFFvKs0jgNIIbuRKrvEe.NnEV7Rkn+9o6rkSfSnK2zToRGG46x2yefepo0+vlemePv8c73G4OvZzWYycciB2NJHZDfPWrbyRCcbetSO9icvgcgxkF6yOIozBMKu9Z0iedqNu7t0w+1tt5u814206JANO+ji2qd8i815Z8wVK4NJNlGldLL8RKTdg+W3uEJeOO+zn31oNob.lk2JxaR69QmDJV5i8S76.zK7PqRsAbRz78iB7PhG+8tgo73tNtbqoUZ699AdGn3qIk.HefgKeQAW9KJ+HeOec6Ft8mRcvLyvlcuvExhxWJCJ25rQ4VEfdkrvtKIvN.Ih7FE.a5YvLb2V1geTXFLC4DgI9oSrkFNGnay23n6mU9.+T29EiuWn.7E1ndaiuxM+eQ460sK2M0frWp78+sut6zudnxGMCs8Ot71QgovS732sZ5KbwSihmK0w2ST8Z9+GT8JjatfE9VQiu.SCkYhiBB.gFDeqTdPj2OzmyCNaL8II7ibHTwz1tgi4wo3jLRWlk3wiFzAVHqw2dPTTZ+rRX9atCuqynfzicBFY09AaZfDsv63j5TZgup7ZWQ5BIX8eyUPGJi2G9mtKiM8s3yC2eJKDWb9L.+2sh8VBU9BAp74kamFycF3G1qMEjhPLbwxsGkf1xd2ZI6fXdPjiWa+WZMr+xlaMBXWwYa8+bSZot6fnQgYVKIYbHeH2IE18yfdGd3ChiFML+r9o6PxJGECwLAbB6N1ODCQIMi9TbTRRWfbHXkX20Aih6kU46PNnPljsMPMD15CC4AXvSKr.zj.qejyPJVs7AXUY9TVfoefC.UqQtkeOKPisj0dn0P0jEoaa4+v1z1O3Ll2MJdf07ZySSAlVxoGI2G7dSjbmo8YoUkOqba2X+goldPb6ijwzmAmDCrzE9rMJ23xWtB6xri7SC3WmQgHvS8cY6QyhMd85aTeMbD2cTZ+n3qy1wYruG6gbm.9DrcvnJLw0tVilsZrVyVWCaC1u765y8tNq4ZMZdMc66AZ.gIvvevA6Mdc1pr9ooCStdiFmbxI06ENpdTbuFAhAkzn2vfUWudy57v58SGDT4xMpTIl2iE3jj93nTN6VrUacCcS2aLD1+tdVsFySi860C7inG8hKpG+wftKzXSCDP0cwS9X39icBDO0UZJ3Fh0uCOzSnQZlN11dQQOezPnsm9ravZzXKnIliXbAhtRQIUfr6wgft.9lGKJDVK+TAf6E36wwogS+n9bBpJP.RwhAjrBi6C6RwrVMaxfVWE++df8iPOPtl.YWlSHCVPnWOPRSLQyZf7CbM1VjqTvDVBrFzrCQVk2nX72NhQ2.3hAABNpiXhH2HldxfWVCCARhlQrOrI3GBXjGv..6Mox9KZltHsRnF9CXMvAkbBZsCwnN7zS37PftlMHE.if11Ob+C24GO5gGdu1Ob+81Avn01.A9tcYomDImiSLmMLvYBra.qCrLL3eRXCbBmvF.fxOgCl88RflgU1O8afovbAcAuJUZz3AvZ6DD.M.pnrDx1ECbEEmBzRH7CXqafxaeREpM1XmXSiXFuHyhz8pCzxtd64mjVcoskyeoZHNiKjXQlFtrPDFV.mP.cPFlsh6CiMpa2DgIvrn6vHH9BvDakJHWrJ.bDy.wybnZsJ+wJKBRXUuWHrGvqOvA1b3IGx6wegZVqvVp5ctU8Ki.tF8KZQqsTsZ.Vz1dYEnSkEAftXNzt9vQI8qp4K5NTqRsZ2nxhupxqn8gmrakJX7df7bc.fOj62qeZ0VazDFjEmoSZnvRHvSTC2wyaqQooQgUWRz2RqvZtBndASUOdDnUWJ1wyOh7iBio0TfVZ0rPPK5Cm1FV.Wz5bA7Gf5JECapKXRq2zBzTiyEjOBUYJFxTWvjthMRSMNWP9G5GA9lS4CKF5T2LpeEIbUaRPOcwhkFEEj5iqDLSPYzwM0eLmwcb6CRS.Ph5pLZA5x.I0gCOeBsHv5wQKVngyTHZFPYJNBjc8cquTVz94gcPivGMHKN+cgQc.ADzjLZ9SHiPhW5IfX4gNg83UWcCpSXL0akeDUWJABPz+EHYLHYoo61hLQu.CQqeBeAnWJxPDp5R1oonhXNhFcUgDwRALj0YB3VfxLh0YDX8.HZjg38uBwJKs1Ir08bfzpyPqhJmNz74nUOvKRGNZrH.BpPv4ZBVAE7WmQoBdHXBbBqZGNX9zSfefSbvVAtf.MAbeXOcPRAr5G4GR7zB30PWLrOkNihYKmhgaSRLMExeY62lW6ltTA8miYK81BBRVrc.O.xA37UcSqQNYbB0wHfisEPjPNDfHVADnyKlIA57BEARZtZBTLkYSfl9Kl.s6+MCAtllBSllDobkJTcA6QotbE68PwTLTXKpOaEFyHJTgwtaKRDHc+AiFjWU.8hY4KeERFELeLBHOZ.iRHMBPlFnoIrNNIhnyvHCHWkC4PCX.Gig3FcgLwskkk3y9i.pATHmIm.F.iFgxPmM6PM6YtsmePUWxypvGKQCdViLk+hTj+jCMl0vML7uZliwhqC4yDiEnfXqDWOBVBhwBZ9Nrg7XT9xoGGElzCBGPcDq+JfiospAFyndwHwvALs719TLCEwka2lI5T4BWxPDMVnBwXj4gnIBxyJ.Nbb5niEYVwF1OFjVJ.MOzIkWDRhsahCPhgXiB7CVMNgb7ACifnebWZpQ3faMsZkucKJ6vLwlKhTl7xmJBcmi4SOlna4IlvRw0ibCnDwYfugDd.2EcWPg+haRBHRatJmrZ0EreYvzZfT0KhmD9MPbxCGBdIfUjvjDvwrOX.hfgOv13NdzbYN.GWpFSpc0VZp3JfPH4xr5JHrBrWF0sNnKQjQp3JTS+TCq.3QbmDwVMkVDHPFFEO.LYH24wcJA9qS0j3.znAWkfWRPXV.GkCWGf5cBEgZKjYfvxexi29nc2+wsqHSGuM23lOenxyRrj1Z.7NTHQWGgylCchcFv9igTgTekTVe6oSfPzgJ8plj7RK.BMp3GF.Q4y5NJzkB0.vf1sEpSUoYoyGHez6A7vd.AeaVyZhv6U4UfLxbCFFAMjEgtwMEc8NkqADnODouLXeAO5tYypFDUIa33hX7rA6d0YGgw3nIftjrGs6jA.YjgG3GJy9F9OPXT7.gKI1bVCqcfLNlr9XoocpALTLvjwLLm.yx4bEVQaZiF5A1C1RWjBpjYUkn7Jpki1IoMIepxFLe1MAfC++kWFya6dTb1J7l1uL083o9OClTUXpKC9q.hpphHVUwavj+PjcYUC2H6N5Q19EPQ5NJ+xnVoxitHVTR+iRfuPFnl8riOJp6ZjswcWSx+BkToULcLCdEBSsAsiU+.38XVERWZVZ8R.RvBvkU.s99P9nf4AMPtEKgbZFaHYZUAKIihCMqp9usmNhljB0PgTkUEutphOrhdgoMY3mx.e1AzbukNWdXpOYH1b0Zv1lplX3tI3mpopLDz564iE4layZCiNgzWv4QbC0BoJfF5kWrlHnjmZi0VNnlJcdjaIpHrvnP3adKSTmHNSw+TEQyl02nl8xM6gQkCQuxf.I4uAvfMfnQfczBm4pBQQUPgcABMKlc6hWxyDm.2.KZMD8OWlo2BAzdMpRP2kRfKaTTYsdomRh+KAdGJ8fHoV161rqdkLnj9mqxpp+MxlDq3tXZ08ch8rEfMaVwbuQt77HzD1ZM+JfrjBzlhnZT3Oj5R3oKsfRJlXhaYFVc0T5SQiGdmCkcZA7Dkdj68rSWi+o207agqRDG0MIX9h3hPJH4Ymtp7SM0O8YyREFCfaVpupRwJDln3+rDkjZoz.vL6EXLrSPajllof6RUgNnh3RGlmL6HZ7JllGGvGPWAnVX+UgSBU.ABkAiDQpVS59WhwOxIsec.FUmdfq1ZEQ2cAOywUMBrMXELZPXsUM3OpbwPrknjHtDhvGoei9WpnVXiMtGgoB1VjJ38iho.sI7mbPIszQQPiA4YWCXp.y3lmzhjtLa155x0i9eMLmkfr9qRIapWTGhGS0tphEW7lflWyqnAD9fjHgPVNgmjpITBW9lDk8Ki5FUodTUSKSEEhQ7teXUc.aeoo3lVnuXyRT00jzngjrpvBEMq4yWAXiIW02wHH.LPW7bY.eOhmj.4EV2uGDaMmNDmpowTHeX23JpOvmuDOCGDN22OV5hgJjAk.FXfBrWDDCoQ.ZjboYZNtPhUZQswBoPnSmjppkGnFbID23.MEgMoUnjV6TPg.okMzhc6ZAeUXCUqoAow0OBLLXpoc.XJN3z535Z0InOTfTAiahEQkYAxVM0mJEYf10AyICTpME1CKQGw0WzB7h3O0Hn8IeoVTMiE1fZJqvuLdHQjZzHEaBlcgaiwBdmrGmlU7jsZ8L10mYuZn.FAdVF1g57xhiFjI.e4paKOca1L19qkccW07D4NjtzJSc5bzIskQ53UBlocJSlZQXoxg.sc9pOHy1y5viLaxSa0g8m9S5J4mwXDvYH+RTtp16Ct58QzXGWjYIzzJlSQKQLRx64Sahw2qXcly0ydHsvgLCVJcrgzY0Ilg9fHyAUgpsvWpHq6jLkFB+cj0JacTtEpqILin89n7rpM.nItUYJhn1J5CHVOck8PXmQXPjbWbC41L9u7.vFTiF6YJMfM2VLeGOuiiBFMfiVJpZcp0qXz3IOPfUz70byrotBcju0zQvh0ZKJvSFxclUitmHSuXZyByyh0LiUr6C192A7wDhBcKaomP3CsdxJ.pvHI+O2gwq2yqX1GMGguf.PKt3XloU5kswriv5pLLJQdIaPTe1VeO.KzTH+jyASKKeX6Hm3DMmXlrHwhgm2HdLM3QJKFGFJ0nPLIBLOVEdLmxJ.lr529sZyFpoCwAfWl.evp3XZlmSQO.vFgJrXWu8YOfVWNInYPPJ104S990eGyHGqPFbipIqJXarqdz0VbQiE.5ejVAJJ.ESfH+UWV+8.Qcs6QqqyyrbcTYtryKGjLbmoiR0tFh4iJta2etgEOqsMHinLl9zU5rfamTEIOIiWDU4nwTgSxEWEdtto.UvCwHb7pnCOPdsBrhO3q+Z65lmkxJFgr8Jent91zp2ARGZ.W3L1pZOBr+L3F53pOGY.jE0DoAXTmdm5a88OWqmp0jLbtLw07l2SyOeGMZ5QJBlA6EnbwpMFiu4EKBEp2aMQhE1XjjKL8sGLGZnZxNP9V0LwMeFwsKQOx1ixAg3JXhA21QdwLEFXffjwz2AfPlPgrx5otVdIULounPWsp.QxyKEWHAOM89ZStEXk0bU7EFZgts5UZLcdr.OyptHSzQwKzYjbalND+6Xt9iKuLjbo9oUWUjWmaLe.vVZ3wk+xbfmDqQTIOr1WsXUwRhFzkJdSMRxUbwBgIHtwQzMRRZfF1mk1nkWivybof0vAPb7LCzKSES9elaFUVK4RY+yK0abGSDgH8KrfJvrgzBw8a7LfWk4WmWGu9OPeX4Chw6dkfnruIlrIhz7JTbEcEoQJcKUqVDNCcXXI27VlTzv7cMy3lyXF21ZF1rG8MskpKhUkLzf41VKk8nW0bIcyFyWddOktrBFVWCVsqSStv1oBqmQE6rNsB2vXLQrtR4ghR7mZSoOnR2u0p56X6QQ85AxohdVktBDmHO4zS8Z1VoPbl.i.usYXYKch3lFNJAX.TrLTMTjWG0DFIuYc.61QTHXE9IDi3vQgX.kRxjJl9.GzZ4XtnzeHKCKHruRVVh+R40UXIQL3GtOGFd2H7F.i2ZdtmHCCKioy1QOV.fKik5j7RpI4Y4qVbdlYpm1Y5uzZIlNpDSbWUNqf.zaV1A.LUhAUdSGPv4g9lclmyQtJHRkcCXNxh77s+17TSK7T84UU30XEl45RH0yj8H8r4hEwV6465L0yhX4MOSt4LORpTWuxLsVj65tHrOzj5qCnN7bJJaBXlaNHAthuz.V2eR6HYst0g4Jr4SHvXtZoE7JQTHpHNiTBSrpxNwCK7ziKDHXs9ttw2r7zRTmR.EAixkrfbAeaI3ciiqbeMC6NFy8u1EV7LJq3qXjFY1K3k7V7Q2frZVrAE1N8wANyijSg+Et.p7pWx3wIGy0Rr5557zDmMJrhMQ+veoh28c7I6DcRntv6T1nEFF6aqP2Ew1TTtCFJC0em5kSdNeU1bEWBMqAFEtKXzY+gb8yhieS0RyR5ROX2jINY6VIYiBFlooBd6IkWLtRkV3WHGNLzT5cE6WJeWwDWXtR9d3KJlvtSowh2h1xKX+x3d5yTXgRMSyKi37L6OrLYO67ureXYxx24ehed47u1A4w7SANswwGSv4iKqeY.xCfe8lyC.pTVcC2yO+aOuyWdAxyO+Gu47Q.5qmcd.76mK.7okydchyAk+8imSzPeqcy.fMWe8+m6LO.3CJiFvxSBe8lymvP9KKpFNefVX3uge48+PE911GSh6dg3cp.ZQ7JmK8dna8LQ36ll539bcKa9m27Pwsa0zTzl64Gxchy9tLetn04+SZRQuHrEt27OWVf5jzN6szdzq4Kvq7KzQkxJutDF+E5GYZ54M2GYgc3t9c3A4egyUKo02KgmT9pM0ezcDeeDFu+tW8eo62pab79i9cm7nksdd3K+9Gur03y78Un6j2299Jbw2NeeEl2c9OQfahuiFHx9OIZfQsLma++gm7j+64d6u3uiFzhdBtlV6+aTd8qVO6ei2+kSlrLs+eMb+LM29a59kdq+AL5c32lh4ca7yKePTvjg8iB8cuuePp7iSw+nUyLY6y9yTQ0+x8i4+ai3gtFYw+i+qM+damhY0Z+zM+9QNAmgr6qs90BWXNUv9Uk0HN6ug91Qgb+2y7Gr3o7A24yEeqILst9qiOgRkl1nvOcm2be3cdmac+WVV9Rk2FSk98gMwKobpqdaaj1BDGdBglxXde6ir+bzP9jxza2MCiz+uVJyGFMBKP7ibfz2eAjpGHd1NZTrK29yRyEvr1DO2TkNXaH2nlpOCfxNao+F1.c1R0YIWEn.ZS9ag+uKJO3F7yvT.GBT1VMR7dRReDbJI1YGfe3ZtDx4f1Z6LlKLVKVxY9874Q9tGH+9KfpKkuw6Fh9cwZLvwMN5GcE1VP16GQs.TYH8sdbQHKV3YVKUhWn.w.vH2O55hdyWEDIJdFqctmw5m6Ybky8L13bOiqdtmw0N2y32bJy.8nbW4axO3ArTo+O.4OHMd.



  • Ah now I see. That snippet and the legato script that goes with it is from 2017! The latest version which I linked to was 2019. So I wouldn't rely on that snippet. Also it seems there was a bug in that snippet, the reg bendTime variable seems to be missing from on init.



  • @d-healey can you post the link here? Can't find it on forum search (thanks in advance:)





  • @d-healey oh!!! Sorry. Now it's working! What a great script you've done!!

    2 questions.

    How can I do to dont kill notes on a chord when you play same time a legato note?
    Let's say I play G chord and also note D does a legato to E. Is there a way to keep not legato notes to not be killed?

    And: What's needed at my end to use part of your script? Shoul I credit you? Should I pay you? Sorry can't totally understand the license model.



  • @hisefilo I think it would require some significant changes to prevent chord notes being killed. One way to do it is when a note is pressed you check if a chord is already playing and if it is you stash those note ids and start a fresh legato phrase. Another way would be to stash all chords by default so that as soon as a chord is played the note IDs are stored independently of legato note ids. There are probably other approaches to this problem too. The tricky part will be trying to avoid all the situations that create hanging notes.

    All my none trivial scripts are licensed under the GNU GPL v3 - https://www.gnu.org/licenses/gpl-3.0.en.html - the basic gist is if you use my code in your project you must release your code under the same license.



  • @d-healey oh. I see. Thought a simple tweak will work but I need to do a new approach then.

    I think I will split my sound source into attack and decay so I can change behavior for both independently



  • Actually I just looked at the code again and what you're wanting is probably simpler than I thought.

    I've made a modified version that stores chord notes independently in a MIDI list. There are almost certainly some hanging note type bugs but I'll leave those for you to enjoy 🙂

    /*
        Copyright 2018, 2019 David Healey
    
        This file is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        (at your option) any later version.
    
        This file is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
    
        You should have received a copy of the GNU General Public License
        along with This file. If not, see <http://www.gnu.org/licenses/>.
    */
    
    Content.setWidth(730);
    Content.setHeight(200);
    
    reg coarseDetune = 0;
    reg fineDetune = 0;
    reg lastCoarseDetune = 0;
    reg lastFineDetune = 0;
    
    reg channel;
    reg note = -1;
    reg retriggerNote = -1;
    reg velocity;
    reg interval;
    reg fadeTm = 10;
    reg bendTm = 10;
    reg bendAmt = 0;
    reg eventId = -1;
    reg chordIds = Engine.createMidiList();
    reg lastTime;
    reg offset;
    reg rate;
    const var bendLookup = []; //Generated on init (onControl) and updated when bend amount changed
    reg glideNote;
    const var notes = [];
    notes.reserve(2);
    
    //Breath control
    reg lastBreathValue = 0;
    reg threshold;
    
    //GUI
    const var btnMute = Content.addButton("btnMute", 0, 10);
    btnMute.set("text", "Mute");
    btnMute.set("tooltip", "Enable this to bypass the script. Still responds to hanging notes.");
    
    const var knbOffset = Content.addKnob("knbOffset", 0, 50);
    knbOffset.set("text", "Offset");
    knbOffset.setRange(0, 1000, 1);
    knbOffset.set("suffix", "ms");
    knbOffset.set("tooltip", "Sample start offset time for legato and glide notes.");
    
    const var btnRetrigger = Content.addButton("btnRetrigger", 0, 110);
    btnRetrigger.set("text", "CC64 Retrigger");
    btnRetrigger.set("tooltip", "When enabled sustain pedal is used to retrigger notes, when disabled sustain pedal will hold legato notes.");
    
    const var knbFadeMin = Content.addKnob("knbFadeMin", 150, 0);
    knbFadeMin.set("text", "Fade Tm Min");
    knbFadeMin.setRange(0, 1000, 1);
    knbFadeMin.set("suffix", "ms");
    knbFadeMin.set("tooltip", "Minimum legato fade time.");
    
    const var knbFadeMax = Content.addKnob("knbFadeMax", 150, 50);
    knbFadeMax.set("text", "Fade Tm Max");
    knbFadeMax.setRange(0, 1000, 1);
    knbFadeMax.set("suffix", "ms");
    knbFadeMax.set("tooltip", "Maximum legato fade time.");
    
    const var knbFadeOut = Content.addKnob("knbFadeOut", 150, 100);
    knbFadeOut.set("text", "Fade Out Ratio");
    knbFadeOut.setRange(10, 100, 1);
    knbFadeOut.set("suffix", "%");
    knbFadeOut.set("tooltip", "Percentage of overall fade time to be used to fade out old notes.");
    
    const var knbBendTm = Content.addKnob("knbBendTm", 300, 0);
    knbBendTm.set("text", "Bend Tm");
    knbBendTm.setRange(-50, 50, 1);
    knbBendTm.set("suffix", "%");
    knbBendTm.set("tooltip", "Use this knob to set +-50% of fade time that will be used for bend time.");
    
    const var knbBendMin = Content.addKnob("knbBendMin", 300, 50);
    knbBendMin.set("text", "Bend Min");
    knbBendMin.setRange(0, 100, 1);
    knbBendMin.set("suffix", "ct");
    knbBendMin.set("tooltip", "Pitch bend amount for 1 semi-tone.");
    
    const var knbBendMax = Content.addKnob("knbBendMax", 300, 100);
    knbBendMax.set("text", "Bend Max");
    knbBendMax.setRange(0, 100, 1);
    knbBendMax.set("suffix", "ct");
    knbBendMax.set("tooltip", "Pitch bend amount for 1 octave.");
    
    const var knbRate = Content.addKnob("knbRate", 450, 0);
    knbRate.set("text", "Glide Rate");
    knbRate.set("mode", "TempoSync");
    knbRate.set("tooltip", "Rate for glide timer relative to current tempo.");
    
    const var btnGlideVel = Content.addButton("btnGlideVel", 450, 60);
    btnGlideVel.set("text", "Velocity = Rate");
    btnGlideVel.set("tooltip", "When enabled glide rate knob will be controlled by note on velocity.");
    
    const var btnWholeStep = Content.addButton("btnWholeStep", 450, 110);
    btnWholeStep.set("text", "Whole Step Glide");
    btnWholeStep.set("tooltip", "When enabled glides will be per whole step rather than each semi-tone.");
    
    const var knbMaxGlide = Content.addKnob("knbMaxGlide", 450, 150);
    knbMaxGlide.set("text", "Max Glide");
    knbMaxGlide.setRange(0, 11, 1);
    knbMaxGlide.set("tooltip", "Limits the maximum glide interval. A value of 0 means no limit");
    
    //Breath controller GUI
    const var btnBc = Content.addButton("btnBc", 600, 10);
    btnBc.set("text", "Breath Control");
    btnBc.set("tooltip", "Toggles breath controller mode.");
    
    const var knbBreath = Content.addKnob("knbBreath", 600, 50);
    knbBreath.set("text", "Breath");
    knbBreath.setRange(0, 127, 1);
    knbBreath.set("tooltip", "Breath controller knob, value should be set by CC.");
    knbBreath.setControlCallback(breathTrigger);
    
    const var knbThreshold = Content.addKnob("knbThreshold", 600, 100);
    knbThreshold.set("text", "Trigger Level");
    knbThreshold.setRange(10, 127, 1);
    knbThreshold.set("tooltip", "Breath controller trigger threshold.");
    
    /**
     * A lookup table is used for pitch bending to save CPU. This function fills that lookup table based on the min bend and max bend values.
     * @param  {number} minBend The amount of bend for an interval of 1 semitone
     * @param  {number} maxBend The amount of bend for an interval of 12 semitones
      */
    inline function updateBendLookupTable(minBend, maxBend)
    {
    	for (i = 0; i < 12; i++) //Each semitone
    	{
    		bendLookup[i] = ((i + 1) * (maxBend - minBend)) / 12 + minBend;
    	}
    }
    
    inline function setLegatoFadeTime(interval, velocity)
    {
        local fadeMin = knbFadeMin.getValue();
        local fadeMax = knbFadeMax.getValue();
    
        //fadeTm is time between notes / 2 (*500), limited to upper and lower range
        fadeTm = Math.range((Engine.getUptime() - lastTime) * 500, fadeMin, fadeMax);
    
        //Adjust fadeTm based on interval
        fadeTm = fadeTm + (interval * 2);
    
        //Reduce fadeTm by 30% is velocity > 64
        if (velocity > 64) fadeTm = fadeTm - (fadeTm * 0.3);
    }
    
    /**
     * Returns the timer rate for glides
     * @param  {number} interval [The distance between the two notes that will be glided]
     * @return {number}          [Timer rate]
     */
    inline function setRate(interval)
    {
    	rate = knbRate.getValue(); //Get rate knob value
    
    	rate = Engine.getMilliSecondsForTempo(rate) / 1000; //Rate to milliseconds for timer
    
    	rate = Math.max(0.04, rate / interval); //Rate is per glide step - capped at timer's min
    }
    
    //Breath controller handler;
    inline function breathTrigger(control, value)
    {
        if (!btnMute.getValue() && btnBc.getValue())
        {
            //Going up and reached the threshold
            if (value >= threshold && lastBreathValue < threshold && note != -1)
            {
                //Turn off old note
                if (eventId != -1) Synth.noteOffByEventId(eventId);
    
                //Play new note
                eventId = Synth.playNoteWithStartOffset(channel, note, velocity, 0);
                Synth.addPitchFade(eventId, 0, coarseDetune, fineDetune);  //Add any detuning
            }
            else if (value < threshold && eventId != -1)
            {
                Synth.noteOffByEventId(eventId);
                eventId = -1;
            }
    
            lastBreathValue = value;
        }
    }function onNoteOn()
    {
        if (!btnMute.getValue())
        {
            Synth.stopTimer(); //Stop the timer (if it's running)
    
            if ((Engine.getUptime() - lastTime) > 0.025) //Not a chord
            {
                //Pick up values have been applied to the notes before this point
                lastCoarseDetune = coarseDetune;
                lastFineDetune = fineDetune;
                coarseDetune = Message.getCoarseDetune();
                fineDetune = Message.getFineDetune();
    
                if (btnBc.getValue() && knbBreath.getValue() < threshold) //If breath controller enabled and the bcc is below the threshold
                {
                    Message.ignoreEvent(true); //Ignore the event
                }
                else if (note != -1 && eventId != -1) //If there is a last note
                {
                    //Calculate played interval - limit max to 12
                    interval = Math.min(Math.abs(note - Message.getNoteNumber()), 12);
    
                    if (Synth.isLegatoInterval() && Synth.isSustainPedalDown() && eventId != -1 && (knbMaxGlide.getValue() == 0 || interval <= knbMaxGlide.getValue())) //Glide
                    {
                        Message.ignoreEvent(true);
    
                        //If glide rate determined by velocity
                        if (btnGlideVel.getValue())
                            knbRate.setValue((Message.getVelocity()/127) * 18);
    
                        glideNote = note; //First note of glide (same as origin)
                        notes[0] = note; //Origin
                        notes[1] = Message.getNoteNumber(); //Target
    
                        //Get rate for timer
                        setRate(Math.abs(notes[0] - notes[1]));
    
                        //Start the timer
                        Synth.startTimer(rate);
                    }
                    else //Legato
                    {
                        //Set global fadeTm value
                        setLegatoFadeTime(interval, Message.getVelocity());
    
                        //Set global bendTm value
                        bendTm = fadeTm / 100 * (100 + knbBendTm.getValue());
    
                        //Set global bendAmt value
                        interval > 0 ? bendAmt = bendLookup[interval - 1] : bendAmt = bendLookup[0]; //Get value from lookup table
                        if (note > Message.getNoteNumber()) bendAmt = -bendAmt; //Invert for down interval
    
                        //Set start offset
                        Message.setStartOffset(offset);
    
                        //Get coarseDetune and fineDetune for old notes
                        local fadeCoarse = lastCoarseDetune + parseInt((bendAmt+lastFineDetune) / 100);
                        local fadeFine = ((bendAmt+lastFineDetune) % 100);
    
                        //Fade out old note
                        Synth.addVolumeFade(eventId, fadeTm / 100 * knbFadeOut.getValue(), -100); //Volume
                        Synth.addPitchFade(eventId, bendTm / 100 * knbFadeOut.getValue(), fadeCoarse, fadeFine); //Pitch
    
                        //Update eventId
                        eventId = Message.makeArtificial();
    
                        //Get coarseDetune and fineDetune for new note
                        fadeCoarse = coarseDetune + parseInt((-bendAmt+fineDetune) / 100);
                        fadeFine = ((-bendAmt+fineDetune) % 100);
    
                        //Fade in new note
                        Synth.addVolumeFade(eventId, 0, -99); //Set initial volume
                        Synth.addVolumeFade(eventId, fadeTm, Message.getGain());
    
                        Synth.addPitchFade(eventId, 0, fadeCoarse, fadeFine); //Set initial detuning
                        Synth.addPitchFade(eventId, bendTm, coarseDetune, fineDetune); //Pitch fade to fineDetune
                    }
                }
                else //First note of phrase
                {
                    Message.setGain(0);
                    eventId = Message.makeArtificial(); //Update eventId
                    Synth.addPitchFade(eventId, 0, coarseDetune, fineDetune); //Pitch
                }
    
                //Update variables
                channel = Message.getChannel();
                retriggerNote = note;
                note = Message.getNoteNumber();
                velocity = Math.max(1, Message.getVelocity());
                lastTime = Engine.getUptime();
            }
            else
            {
                if (eventId != -1)
                    chordIds.setValue(Message.getNoteNumber(), eventId); //Stash the old note id
                else
                    chordIds.setValue(Message.getNoteNumber(), Message.makeArtificial());
             
                eventId = -1;
            }
        }
    }
    function onNoteOff()
    {
        if (!btnMute.getValue())
        {
            if (Message.getNoteNumber() == retriggerNote)
            {
                retriggerNote = -1;
            }
    
            //Sustain pedal retrigger
    		if (Synth.isSustainPedalDown() && btnRetrigger.getValue())
    		{
                retriggerNote = note; //Retrigger note becomes the last note
    		}
    
            if (Message.getNoteNumber() == note)
            {
                if ((Synth.isSustainPedalDown() == 0 && btnRetrigger.getValue() == 0) || btnRetrigger.getValue()) //CC64 sustain
                {
                    Message.ignoreEvent(true);
    
                    if (retriggerNote != -1 && (knbBreath.getValue() > threshold || btnBc.getValue() == 0) && eventId != -1)
                    {
                        //Get coarseDetune and fineDetune for old notes
                        local fadeCoarse = coarseDetune + parseInt((bendAmt+fineDetune) / 100);
                        local fadeFine = ((bendAmt+fineDetune) % 100);
    
                        //Fade out old note
                        Synth.addVolumeFade(eventId, fadeTm / 100 * knbFadeOut.getValue(), -100); //Volume
                        Synth.addPitchFade(eventId, bendTm / 100 * knbFadeOut.getValue(), fadeCoarse, fadeFine); //Pitch
    
                        //Play new note
                        eventId = Synth.playNoteWithStartOffset(channel, retriggerNote+Message.getTransposeAmount(), velocity, offset);
    
                        //Get coarseDetune and fineDetune for the new note
                        coarseDetune = Message.getCoarseDetune();
                        fineDetune = Message.getFineDetune();
    
                        fadeCoarse = coarseDetune + parseInt((-bendAmt+fineDetune) / 100);
                        fadeFine = ((-bendAmt+fineDetune) % 100);
    
                        //Fade in new note
                        Synth.addVolumeFade(eventId, 0, -99); //Set initial volume
                        Synth.addVolumeFade(eventId, fadeTm, 0);
    
                        Synth.addPitchFade(eventId, 0, fadeCoarse, fadeFine); //Set initial detuning
                        Synth.addPitchFade(eventId, bendTm, coarseDetune, fineDetune); //Pitch fade to fineDetune
    
                        //Update variables
                        note = retriggerNote;
                        retriggerNote = -1;
                    }
                    else
                    {
                        if (eventId != -1) Synth.noteOffByEventId(eventId);
                        eventId = -1;
                        note = -1;
                    }
                }
    
                Synth.stopTimer();
            }
            else 
            {
                if (chordIds.getValue(Message.getNoteNumber()) != -1)
                    Synth.noteOffByEventId(chordIds.getValue(Message.getNoteNumber()));
                
                    chordIds.setValue(Message.getNoteNumber(), -1);
            }
        }
        else
        {
            if (eventId != -1) //Prevent hanging notes
            {
                //Turn off old note
                Synth.noteOffByEventId(eventId);
                eventId = -1;
                note = -1;
                Synth.stopTimer();
            }
            
            if (chordIds.getValue(Message.getNoteNumber()) != -1)
                Synth.noteOffByEventId(chordIds.getValue(Message.getNoteNumber()));
                
                chordIds.setValue(Message.getNoteNumber(), -1);
                
        }
    }
    function onController()
    {
        if (!btnMute.getValue())
        {
            //Turn off the last note if the sutain pedal is lifted and last note is still playing
            if (!Synth.isSustainPedalDown() && Synth.getNumPressedKeys() == 0 && eventId != -1)
            {
                Synth.noteOffByEventId(eventId);
                eventId = -1;
                note = -1;
            }
        }
    }
    function onTimer()
    {
        if (!btnMute.getValue())
        {
    	    local glideBend = 100;
    
            notes[1] > notes[0] ? glideNote++ : glideNote--; //Increment/decrement the glideNote number by 1 (a half step)
    
            //If the whole step button is enabled then increment/decrement the glideNote by another half step
            if (btnWholeStep.getValue()) notes[1] > notes[0] ? glideNote++ : glideNote--;
    
            //If glide has not completed - i.e. it hasn't reached the target note yet
            if (eventId != -1 && notes[0] != -1 && ((notes[1] > notes[0] && glideNote <= notes[1]) || (notes[1] < notes[0] && glideNote >= notes[1])))
            {
                if (notes[0] > notes[1]) glideBend = -glideBend; //Invert bend for down glide
            }
            else
            {
                notes[0] = notes[1]; //Origin becomes target
                glideNote = notes[1];
                Synth.stopTimer();
            }
    
    		if (Synth.isTimerRunning()) //Timer may have been stopped if glide target reached, so check before proceeding
    		{
                //Get coarseDetune and fineDetune for the old note
                local fadeCoarse = lastCoarseDetune + parseInt((glideBend+lastFineDetune) / 100); //Coarse
                local fadeFine = ((glideBend+lastFineDetune) % 100); //Fine
    
                //Reset old note's pitch trackers
                lastCoarseDetune = 0;
    			lastFineDetune = 0;
    
    		    //Fade out old note
    			Synth.addPitchFade(eventId, rate*1000, fadeCoarse, fadeFine); //Pitch fade old note to bend amount
    			Synth.addVolumeFade(eventId, rate*1000, -100); //Fade out last note
    
    			//Play new note
    			eventId = Synth.playNoteWithStartOffset(channel, glideNote, velocity, offset);
    
    			//Fade in new note
    			Synth.addVolumeFade(eventId, 0, -99); //Set new note's initial volume
    			Synth.addVolumeFade(eventId, rate*1000, 0); //Fade in new note
    			Synth.addPitchFade(eventId, 0, 0, -glideBend); //Set new note's initial detuning
    			Synth.addPitchFade(eventId, rate*1000, 0, 0); //Pitch fade new note to 0
    		}
    	}
    }
    function onControl(number, value)
    {
    	switch (number)
        {
            case knbOffset:
                offset = Engine.getSamplesForMilliSeconds(value);
            break;
    
            case knbBendMin: case knbBendMax:
                updateBendLookupTable(knbBendMin.getValue(), knbBendMax.getValue());
            break;
    
    		case knbRate:
    			if (Synth.isTimerRunning()) //If timer's already started then update its Rate
    			{
    				setRate(Math.abs(notes[0] - notes[1]));
    				Synth.startTimer(rate);
    			}
    		break;
    
    		case knbThreshold:
    		    threshold = value;
    		break;
        }
    }
    


  • @d-healey thanks man! trying it. The closest I can get to the behavior I want is changing this value. But still not sounding how I need it. I guess I need to figure some other way to get it. (it's for a guitar, not a wind instrument, so will need some custom work)

    if ((Engine.getUptime() - lastTime) > 0.325) //Not a chord
    

    Legato ugly example



  • @hisefilo That value checks the time between the first note and second note and if that time is greater than the threshold > 0.325 it assumes a legato phrase, if it's less than the threshold it means the two notes were played very fast and it assumes a chord.



  • @d-healey Hi, I'm about to start implementing this script (and thanks a LOT for it) , but theres one very small addition I'd like to add - My instrument has a toggle for Mono/Poly, so right now if toggle = Mono its turning off any/all existing note.

    I think I have a few ideas where to start - so do you want to talk about it here or in some chat session and share the results if I can get it to work?



  • @Lindon Let's keep it here for everyone to benefit 🙂



  • @d-healey sure no problem, well looking at the script I think all I need do is replace this line:

    if ((Engine.getUptime() - lastTime) > 0.025) //Not a chord
    

    with

    if ((Engine.getUptime() - lastTime) > 0.025)  || (MonoOnly = true)
    


  • @Lindon Looks good to me, just change = to ==. Actually you can drop the == true altogether.



  • @d-healey OK well I think I dont understand what i'm supposed to do with the script, as its not excuting .. where can I put it?

    It's currently in a container(with all the samplers in it) below the one containing the interface....

    Oh hang on getting there...

    zing. Done. Thanks.


Log in to reply
 

8
Online

503
Users

1.8k
Topics

13.7k
Posts