HISE Logo Forum
    • Categories
    • Register
    • Login

    Synthetic Legato

    Scheduled Pinned Locked Moved Presets / Scripts / Ideas
    38 Posts 8 Posters 6.7k 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.
    • d.healeyD
      d.healey
      last edited by

      A synthetic legato script that includes automatic chord detection, has the ability to play formant correct glides, and includes an auto-trill feature. The code is well commented and I've added tooltips for most of the controls.

      /**
       * Title: Synthetic Legato v3.5
       * Author: David Healey
       * Date: 27/01/2017
       * Modified: 11/04/2017
       * License: GPLv3 - https://www.gnu.org/licenses/gpl-3.0.en.html
      */
      
      reg lastNote = -1;
      reg lastEventId = -1;
      reg retriggerNote = -1;		
      reg lastVelo = 0;
      reg lastTime;
      reg interval;
      reg fadeTime;
      
      reg bendAmount;
      reg bendLookup = []; //Bend amount lookup table - generated on init
      
      reg glideBend; //The bend amount for glides, either 100 or -100 depending on if an up or down glide
      reg glideNote; //Currently sounding note during a glide/trill
      reg rate; //Timer rate for glide/trill
      reg notes = []; //Origin and target notes for glide/trill
      reg count; //Counter for switching between origin and target notes for trill
      
      reg CHORD_THRESHOLD = 25; //If two notes are played within this many milliseconds then it's a chord
      
      //Get all child sample start constant modulators
      const var modulatorNames = Synth.getIdList("Constant"); //Get child constant modulator names
      const var startModulators = []; //For offsetting sample start position
      
      for (modName in modulatorNames)
      {
      	if (Engine.matchesRegex(modName, "(?=.*tart)(?=.*ffset)")) //Sample start offset
      	{
      		startModulators.push(Synth.getModulator(modName));
      	}
      }
      
      //GUI
      
      Content.setHeight(150);
      
      const var btnBypass = Content.addButton("Bypass", 0, 10);
      btnBypass.set("radioGroup", 1);
      
      const var btnLegato = Content.addButton("Legato", 150, 10);
      btnLegato.set("radioGroup", 1);
      
      const var btnGlide = Content.addButton("Glide", 300, 10);
      btnGlide.set("radioGroup", 1);
      
      const var btnTrill = Content.addButton("Trill", 450, 10);
      btnTrill.set("radioGroup", 1);
      
      const var btnWholeStep = Content.addButton("Whole Step Glide", 600, 10);
      btnWholeStep.set("tooltip", "When active each step of a glide will be a whole tone rather than chromatic.");
      
      const var knbBendTm = Content.addKnob("Bend Time", 0, 50);
      knbBendTm.setRange(-50, 50, 0.1);
      knbBendTm.set("suffix", "ms");
      knbBendTm.set("tooltip", "The pitch bend lasts the same duration as the crossfade time by default but can be adjusted with this knob. If the bend time ends up being less than 0 it will automatically (behind the scenes) be set to 10ms.");
      
      const var knbMinBend = Content.addKnob("Min Bend", 150, 50);
      knbMinBend.setRange(0, 100, 1);
      knbMinBend.set("suffix", "ct");
      knbMinBend.set("tooltip", "The amount of pitch bend in cents (ct) for an interval of 1 semitone");
      
      const var knbMaxBend = Content.addKnob("Max Bend", 300, 50);
      knbMaxBend.setRange(0, 100, 1);
      knbMaxBend.set("suffix", "ct");
      knbMaxBend.set("tooltip", "The amount of pitch bend in cents (ct) for an interval of 12 semitones");
      
      const var knbFadeTm = Content.addKnob("Fade Time", 450, 50);
      knbFadeTm.setRange(10, 500, 0.1);
      knbFadeTm.set("suffix", "ms");
      knbFadeTm.set("tooltip", "Maximum crossfade time in milliseconds, the actual time used will vary based on playing speed and velocity.");
      
      const knbFadeOutRatio = Content.addKnob("Fade Out Ratio", 600, 50);
      knbFadeOutRatio.setRange(0, 100, 1);
      knbFadeOutRatio.set("defaultValue", 100);
      knbFadeOutRatio.set("text", "Fade Out Ratio");
      knbFadeOutRatio.set("suffix", "%");
      knbFadeOutRatio.set("tooltip", "Shortens the fade out time to a percentage of the fade time. 100% = the same as fade in time.");
      
      const var knbOffset = Content.addKnob("SS Offset", 0, 100);
      knbOffset.set("tooltip", "The value to set sample start constant modulators to during a legato phrase.");
      
      const var knbRate = Content.addKnob("Rate", 150, 100);
      knbRate.set("mode", "TempoSync");
      knbRate.set("max", 11);
      knbRate.set("tooltip", "Rate for glide and trill timer relative to current tempo. If velocity is selected then the glide time will be based on the played velocity (doesn't apply to trills which will instead play at maximum speed)");
      
      //FUNCTIONS
      /**
       * Sets the sample start offset constant modulators to the given value.
       * @param {number} value Constant modulator value between 0 and 1
       */
      inline function setSSOffset(value)
      {
      	if (startModulators.length > 0)
      	{
      		for (mod in startModulators)
      		{
      			mod.setIntensity(value);
      		}
      	}
      }
      
      /**
       * A lookup table is used for pitch bending. 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;
      	}
      }
      
      /**
       * The fade time to be used when crossfading legato notes
       * @param  {number} interval Distance between the two notes that will be crossfaded
       * @param  {number} velocity Velocity of one of the note that will be faded in, a higher velocity = shorter fade time
       * @return {number}          Crossfade time in ms
       */
      inline function getFadeTime(interval, velocity)
      {
      	local timeDif = (Engine.getUptime() - lastTime) * 1000; //Get time difference between now and last note
      	local fadeTime = timeDif; //Default fade time is played time difference
      
      	if (timeDif <= knbFadeTm.getValue() * 0.5) fadeTime = knbFadeTm.getValue() * 0.5; //Fade time minimum is 50% of knbFadeTm.getValue() - when playing fast
      	if (timeDif >= knbFadeTm.getValue()) fadeTime = knbFadeTm.getValue();
      
      	fadeTime = fadeTime + (interval * 2); //Adjust the fade time based on the interval size
      
          if (velocity > 64) fadeTime = fadeTime - (fadeTime * 0.2); //If a harder velocity is played reduce the fade time by 20%
      
      	return fadeTime;
      }
      
      /**
       * Returns the timer rate for glides and trills.
       * @param  {number} interval [The distance between the two notes that will be glided/trilled]
       * @param  {number} velocity [A velocity value for velocity based glide rates]
       * @return {number}          [Timer rate]
       */
      inline function getRate(interval, velocity)
      {
      	reg rate = knbRate.getValue(); //Get rate knob value
      
      	if (btnTrill.getValue()) //Trill
      	{
      		if (rate > knbRate.get("max")) rate = max-1; //Cap rate at max rate
      		rate = Engine.getMilliSecondsForTempo(rate) / 1000;
      	}
      	else //Glide
      	{
      		//If rate knob is set to the maximum then the actual rate will be determined by velocity
      		if (rate == knbRate.get("max"))
      		{
      			rate = Math.floor((velocity / (knbRate.get("max") - 1)));
      
      			if (rate > knbRate.get("max")) rate = max-1; //Cap rate at max rate
      		}
      
      		rate = (Engine.getMilliSecondsForTempo(rate) / 1000) / interval; //Get rate based on host tempo and selected knob value		
      	}
      
      	if (rate < 0.04) rate = 0.04; //Cap lowest rate at timer's minimum
      
      	return rate;
      }
      
      function onNoteOn()
      {
      	if (!btnBypass.getValue())
      	{
      		Synth.stopTimer();
      
      		if ((Engine.getUptime() - lastTime) * 1000 > CHORD_THRESHOLD) //Not a chord
      		{
      			Message.ignoreEvent(true);
      
      			if (lastNote != -1) //First note of phrase has already been played
      			{
      				interval = Math.abs(Message.getNoteNumber() - lastNote); //Get played interval
      				fadeTime = getFadeTime(interval, Message.getVelocity()); //Get fade time
      				bendTime = fadeTime + knbBendTm.getValue(); //Get bend time
      				if (bendTime < 10) bendTime = 10; //Bend time can't be less than 10ms
      
      				//Get bend amount
      				interval > 12 ? bendAmount = bendLookup[11] : bendAmount = bendLookup[interval - 1]; //Get bend amount from lookup table
      				if (lastNote > Message.getNoteNumber()) bendAmount = -bendAmount; //Invert bend amount for down interval
      
      				setSSOffset(knbOffset.getValue()); //Set sample start offset modulators
      
      				if (btnGlide.getValue() || btnTrill.getValue()) //Glide mode 
      				{
      					count = 0; //Reset count, for trills
      					notes[0] = lastNote; //Origin
      					notes[1] = Message.getNoteNumber(); //Target
      					glideNote = lastNote; //First glide note is the same as the origin
      					lastVelo = Message.getVelocity();
      
      					rate = getRate(Math.abs(notes[0] - notes[1]), lastVelo);
      
      					Synth.startTimer(rate);
      				}
      				else //Legato mode
      				{
      					Synth.addVolumeFade(lastEventId, fadeTime / 100 * knbFadeOutRatio.getValue(), -100); //Fade out old note
      					Synth.addPitchFade(lastEventId, bendTime / 100 * knbFadeOutRatio.getValue(), 0, Message.getFineDetune() + bendAmount); //Pitch fade old note
      
      					retriggerNote = lastNote;
      
      					lastEventId = Synth.playNote(Message.getNoteNumber() + Message.getTransposeAmount(), Message.getVelocity()); //Play new note
      					Synth.addPitchFade(lastEventId, 0, Message.getCoarseDetune(), Message.getFineDetune()); //Pass on any message detuning to new note
      
      					Synth.addVolumeFade(lastEventId, 0, -99); //Set new note's initial volume
      					Synth.addVolumeFade(lastEventId, fadeTime, 0); //Fade in new note
      					Synth.addPitchFade(lastEventId, 0, Message.getCoarseDetune(), Message.getFineDetune() - bendAmount); //Set new note's initial detuning
      					Synth.addPitchFade(lastEventId, bendTime, Message.getCoarseDetune(), Message.getFineDetune()); //Pitch fade new note to 0 (or fineDetune)		
      				}
      			}
      			else //First note of phrase
      			{
      				lastEventId = Synth.playNote(Message.getNoteNumber() + Message.getTransposeAmount(), Message.getVelocity()); //Play new note
      				Synth.addPitchFade(lastEventId, 0, Message.getCoarseDetune(), Message.getFineDetune()); //Pass on any message detuning to new note
      			}
      
      			lastNote = Message.getNoteNumber();
      			lastVelo = Message.getVelocity();
      			lastTime = Engine.getUptime();
      		}
      	}
      }
      
      function onNoteOff()
      {
      	if (!btnBypass.getValue())
      	{
      		Synth.stopTimer();
      
      		if (Message.getNoteNumber() == retriggerNote)
      		{
      			retriggerNote = -1;
      		}
      
      		if (Message.getNoteNumber() == lastNote)
      		{
      			Message.ignoreEvent(true);
      
      			if (retriggerNote != -1)
      			{
      				Synth.addVolumeFade(lastEventId, fadeTime / 100 * knbFadeOutRatio.getValue(), -100); //Fade out old note
      				Synth.addPitchFade(lastEventId, bendTime / 100 * knbFadeOutRatio.getValue(), 0, Message.getFineDetune() + bendAmount); //Pitch fade old note
      
      				lastEventId = Synth.playNote(retriggerNote, lastVelo);
      				Synth.addVolumeFade(lastEventId, 0, -99); //Set new note's initial volume
      				Synth.addVolumeFade(lastEventId, fadeTime, 0); //Fade in new note
      
      				lastNote = retriggerNote;
      				retriggerNote = -1;
      			}
      			else
      			{
      				Synth.noteOffByEventId(lastEventId);
      				lastEventId = -1;
      				lastNote = -1;
      				setSSOffset(1); //Reset sample start offset modulators
      			}
      		}
      	}
      	else //Script is bypassed
      	{
      		//Turn off any hanging notes
      		if (lastEventId != -1)
      		{
      			Synth.noteOffByEventId(lastEventId);
      			lastEventId = -1;
      			lastNote = -1;
      			setSSOffset(1); //Reset sample start offset modulators
      		}
      	}
      }
      
      function onController()
      {
      	
      }
      function onTimer()
      {
      	if (!btnBypass.getValue())
      	{
      		if (btnGlide.getValue()) //Glide
      		{
      			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 (lastEventId != -1 && notes[0] != -1 && ((notes[1] > notes[0] && glideNote <= notes[1]) || (notes[1] < notes[0] && glideNote >= notes[1])))
      			{
      				glideBend = 100;
      				if (notes[0] > notes[1]) glideBend = -glideBend;
      			}
      			else 
      			{
      				notes[0] = notes[1]; //Origin becomes target
      				glideNote = notes[1];
      				Synth.stopTimer();
      			}
      		}
      		else if (btnTrill.getValue()) //Trill
      		{
      			count = 1-count; //Toggle count - to switch between origin and target notes
      			glideNote = notes[count];
      			glideBend = bendAmount; //Trill uses same bend settings as normal legato
      		}
      
      		if (Synth.isTimerRunning()) //Timer may have been stopped if glide target reached, so check before proceeding
      		{
      			Synth.addPitchFade(lastEventId, rate*1000, 0, glideBend); //Pitch fade old note to bend amount
      			Synth.addVolumeFade(lastEventId, rate*1000, -100); //Fade out last note
      
      			lastEventId = Synth.playNote(glideNote, lastVelo); //Play new note
      
      			Synth.addVolumeFade(lastEventId, 0, -99); //Set new note's initial volume
      			Synth.addVolumeFade(lastEventId, rate*1000, 0); //Fade in new note
      			Synth.addPitchFade(lastEventId, 0, 0, -glideBend); //Set new note's initial detuning
      			Synth.addPitchFade(lastEventId, rate*1000, 0, 0); //Pitch fade new note to 0
      		}
      	}
      }
      
      function onControl(number, value)
      {
      	switch (number)
      	{
      		case btnBypass: case btnLegato: case btnGlide: case btnTrill:
      			Synth.stopTimer();
      		break;
      
      		case knbMaxBend:
      			updateBendLookupTable(knbMinBend.getValue(), knbMaxBend.getValue()); //Update the bend amount lookup table
      		break;
      
      		case knbRate:
      			//If timer's already started then update its Rate
      			if (Synth.isTimerRunning())
      			{
      				rate = getRate(Math.abs(notes[0] - notes[1]), lastVelo);
      				Synth.startTimer(rate);
      			} 
      
      			knbRate.set("text", "Rate"); //Default
      			if (knbRate.getValue() == knbRate.get("max"))
      			{
      				knbRate.set("text", "Velocity");
      			}
      		break;
      	}
      }
      1 Reply Last reply Reply Quote 4
      • Christoph HartC
        Christoph Hart
        last edited by

        Nice! The glide time is a bit too long for my taste (or maybe I did not find a way to make them shorter). Also if playing fast passages, the glide mode ignores some notes.

        1 Reply Last reply Reply Quote 0
        • d.healeyD
          d.healey
          last edited by

          The rate knob controls the glide time. I intended it for the odd glide between legato notes rather than playing repeated glides in a row.

          1 Reply Last reply Reply Quote 0
          • Christoph HartC
            Christoph Hart
            last edited by

            You mean something like the octave step in "Somewhere over the rainbow"? :)

            1 Reply Last reply Reply Quote 1
            • d.healeyD
              d.healey
              last edited by

              Not really, more for a smooth portamento on a violin, you just need to play the first and last note of the glide and it will smoothly go between them.

              1 Reply Last reply Reply Quote 0
              • d.healeyD
                d.healey
                last edited by

                I added an update :D same note legato is now possible.

                /**
                 * Title: Synthetic Legato v3.5.1
                 * Author: David Healey
                 * Date: 27/01/2017
                 * Modified: 11/04/2017
                 * License: GPLv3 - https://www.gnu.org/licenses/gpl-3.0.en.html
                */
                
                reg lastNote = -1;
                reg lastEventId = -1;
                reg retriggerNote = -1;		
                reg lastVelo = 0;
                reg lastTime;
                reg interval;
                reg fadeTime;
                
                reg bendAmount = 0;
                reg bendLookup = []; //Bend amount lookup table - generated on init
                
                reg glideBend; //The bend amount for glides, either 100 or -100 depending on if an up or down glide
                reg glideNote; //Currently sounding note during a glide/trill
                reg rate; //Timer rate for glide/trill
                reg notes = []; //Origin and target notes for glide/trill
                reg count; //Counter for switching between origin and target notes for trill
                
                reg CHORD_THRESHOLD = 25; //If two notes are played within this many milliseconds then it's a chord
                
                //Get all child sample start constant modulators
                const var modulatorNames = Synth.getIdList("Constant"); //Get child constant modulator names
                const var startModulators = []; //For offsetting sample start position
                
                for (modName in modulatorNames)
                {
                	if (Engine.matchesRegex(modName, "(?=.*tart)(?=.*ffset)")) //Sample start offset
                	{
                		startModulators.push(Synth.getModulator(modName));
                	}
                }
                
                //GUI
                
                Content.setHeight(150);
                
                const var btnBypass = Content.addButton("Bypass", 0, 10);
                btnBypass.set("radioGroup", 1);
                
                const var btnLegato = Content.addButton("Legato", 150, 10);
                btnLegato.set("radioGroup", 1);
                
                const var btnGlide = Content.addButton("Glide", 300, 10);
                btnGlide.set("radioGroup", 1);
                
                const var btnTrill = Content.addButton("Trill", 450, 10);
                btnTrill.set("radioGroup", 1);
                
                const var btnWholeStep = Content.addButton("Whole Step Glide", 600, 10);
                btnWholeStep.set("tooltip", "When active each step of a glide will be a whole tone rather than chromatic.");
                
                const var knbBendTm = Content.addKnob("Bend Time", 0, 50);
                knbBendTm.setRange(-50, 50, 0.1);
                knbBendTm.set("suffix", "ms");
                knbBendTm.set("tooltip", "The pitch bend lasts the same duration as the crossfade time by default but can be adjusted with this knob. If the bend time ends up being less than 0 it will automatically (behind the scenes) be set to 10ms.");
                
                const var knbMinBend = Content.addKnob("Min Bend", 150, 50);
                knbMinBend.setRange(0, 100, 1);
                knbMinBend.set("suffix", "ct");
                knbMinBend.set("tooltip", "The amount of pitch bend in cents (ct) for an interval of 1 semitone");
                
                const var knbMaxBend = Content.addKnob("Max Bend", 300, 50);
                knbMaxBend.setRange(0, 100, 1);
                knbMaxBend.set("suffix", "ct");
                knbMaxBend.set("tooltip", "The amount of pitch bend in cents (ct) for an interval of 12 semitones");
                
                const var knbFadeTm = Content.addKnob("Fade Time", 450, 50);
                knbFadeTm.setRange(10, 500, 0.1);
                knbFadeTm.set("suffix", "ms");
                knbFadeTm.set("tooltip", "Maximum crossfade time in milliseconds, the actual time used will vary based on playing speed and velocity.");
                
                const knbFadeOutRatio = Content.addKnob("Fade Out Ratio", 600, 50);
                knbFadeOutRatio.setRange(0, 100, 1);
                knbFadeOutRatio.set("defaultValue", 100);
                knbFadeOutRatio.set("text", "Fade Out Ratio");
                knbFadeOutRatio.set("suffix", "%");
                knbFadeOutRatio.set("tooltip", "Shortens the fade out time to a percentage of the fade time. 100% = the same as fade in time.");
                
                const var knbOffset = Content.addKnob("SS Offset", 0, 100);
                knbOffset.set("tooltip", "The value to set sample start constant modulators to during a legato phrase.");
                
                const var knbRate = Content.addKnob("Rate", 150, 100);
                knbRate.set("mode", "TempoSync");
                knbRate.set("max", 11);
                knbRate.set("tooltip", "Rate for glide and trill timer relative to current tempo. If velocity is selected then the glide time will be based on the played velocity (doesn't apply to trills which will instead play at maximum speed)");
                
                const var btnSameNote = Content.addButton("Same Note Legato", 300, 110);
                btnSameNote.set("tooltip", "When active releasing a note in normal legato mode will retrigger the note that was released with a transition.");
                
                //FUNCTIONS
                /**
                 * Sets the sample start offset constant modulators to the given value.
                 * @param {number} value Constant modulator value between 0 and 1
                 */
                inline function setSSOffset(value)
                {
                	if (startModulators.length > 0)
                	{
                		for (mod in startModulators)
                		{
                			mod.setIntensity(value);
                		}
                	}
                }
                
                /**
                 * A lookup table is used for pitch bending. 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;
                	}
                }
                
                /**
                 * The fade time to be used when crossfading legato notes
                 * @param  {number} interval Distance between the two notes that will be crossfaded
                 * @param  {number} velocity Velocity of one of the note that will be faded in, a higher velocity = shorter fade time
                 * @return {number}          Crossfade time in ms
                 */
                inline function getFadeTime(interval, velocity)
                {
                	local timeDif = (Engine.getUptime() - lastTime) * 1000; //Get time difference between now and last note
                	local fadeTime = timeDif; //Default fade time is played time difference
                
                	if (timeDif <= knbFadeTm.getValue() * 0.5) fadeTime = knbFadeTm.getValue() * 0.5; //Fade time minimum is 50% of knbFadeTm.getValue() - when playing fast
                	if (timeDif >= knbFadeTm.getValue()) fadeTime = knbFadeTm.getValue();
                
                	fadeTime = fadeTime + (interval * 2); //Adjust the fade time based on the interval size
                
                    if (velocity > 64) fadeTime = fadeTime - (fadeTime * 0.2); //If a harder velocity is played reduce the fade time by 20%
                
                	return fadeTime;
                }
                
                /**
                 * Returns the timer rate for glides and trills.
                 * @param  {number} interval [The distance between the two notes that will be glided/trilled]
                 * @param  {number} velocity [A velocity value for velocity based glide rates]
                 * @return {number}          [Timer rate]
                 */
                inline function getRate(interval, velocity)
                {
                	reg rate = knbRate.getValue(); //Get rate knob value
                
                	if (btnTrill.getValue()) //Trill
                	{
                		if (rate > knbRate.get("max")) rate = max-1; //Cap rate at max rate
                		rate = Engine.getMilliSecondsForTempo(rate) / 1000;
                	}
                	else //Glide
                	{
                		//If rate knob is set to the maximum then the actual rate will be determined by velocity
                		if (rate == knbRate.get("max"))
                		{
                			rate = Math.floor((velocity / (knbRate.get("max") - 1)));
                
                			if (rate > knbRate.get("max")) rate = max-1; //Cap rate at max rate
                		}
                
                		rate = (Engine.getMilliSecondsForTempo(rate) / 1000) / interval; //Get rate based on host tempo and selected knob value		
                	}
                
                	if (rate < 0.04) rate = 0.04; //Cap lowest rate at timer's minimum
                
                	return rate;
                }
                
                function onNoteOn()
                {
                	if (!btnBypass.getValue())
                	{
                		Synth.stopTimer();
                
                		if ((Engine.getUptime() - lastTime) * 1000 > CHORD_THRESHOLD) //Not a chord
                		{
                			Message.ignoreEvent(true);
                
                			if (lastNote != -1) //First note of phrase has already been played
                			{
                				interval = Math.abs(Message.getNoteNumber() - lastNote); //Get played interval
                				fadeTime = getFadeTime(interval, Message.getVelocity()); //Get fade time
                				bendTime = fadeTime + knbBendTm.getValue(); //Get bend time
                				if (bendTime < 10) bendTime = 10; //Bend time can't be less than 10ms
                
                				//Get bend amount
                				bendAmount = 0;
                				if (interval != 0) //Same note legato
                				{
                					interval > 12 ? bendAmount = bendLookup[11] : bendAmount = bendLookup[interval - 1]; //Get bend amount from lookup table
                					if (lastNote > Message.getNoteNumber()) bendAmount = -bendAmount; //Invert bend amount for down interval
                				}
                
                				setSSOffset(knbOffset.getValue()); //Set sample start offset modulators
                
                				if (btnGlide.getValue() || btnTrill.getValue()) //Glide mode 
                				{
                					count = 0; //Reset count, for trills
                					notes[0] = lastNote; //Origin
                					notes[1] = Message.getNoteNumber(); //Target
                					glideNote = lastNote; //First glide note is the same as the origin
                					lastVelo = Message.getVelocity();
                
                					rate = getRate(Math.abs(notes[0] - notes[1]), lastVelo);
                
                					Synth.startTimer(rate);
                				}
                				else //Legato mode
                				{
                					Synth.addVolumeFade(lastEventId, fadeTime / 100 * knbFadeOutRatio.getValue(), -100); //Fade out old note
                					Synth.addPitchFade(lastEventId, bendTime / 100 * knbFadeOutRatio.getValue(), 0, Message.getFineDetune() + bendAmount); //Pitch fade old note
                
                					retriggerNote = lastNote;
                
                					lastEventId = Synth.playNote(Message.getNoteNumber() + Message.getTransposeAmount(), Message.getVelocity()); //Play new note
                					Synth.addPitchFade(lastEventId, 0, Message.getCoarseDetune(), Message.getFineDetune()); //Pass on any message detuning to new note
                
                					Synth.addVolumeFade(lastEventId, 0, -99); //Set new note's initial volume
                					Synth.addVolumeFade(lastEventId, fadeTime, 0); //Fade in new note
                					Synth.addPitchFade(lastEventId, 0, Message.getCoarseDetune(), Message.getFineDetune() - bendAmount); //Set new note's initial detuning
                					Synth.addPitchFade(lastEventId, bendTime, Message.getCoarseDetune(), Message.getFineDetune()); //Pitch fade new note to 0 (or fineDetune)		
                				}
                			}
                			else //First note of phrase
                			{
                				lastEventId = Synth.playNote(Message.getNoteNumber() + Message.getTransposeAmount(), Message.getVelocity()); //Play new note
                				Synth.addPitchFade(lastEventId, 0, Message.getCoarseDetune(), Message.getFineDetune()); //Pass on any message detuning to new note
                			}
                
                			lastNote = Message.getNoteNumber();
                			lastVelo = Message.getVelocity();
                			lastTime = Engine.getUptime();
                		}
                	}
                }
                
                function onNoteOff()
                {
                	if (!btnBypass.getValue())
                	{
                		Synth.stopTimer();
                
                		if (Message.getNoteNumber() == retriggerNote)
                		{
                			retriggerNote = -1;
                		}
                
                		//Legato mode active and same note legato button enabled
                		if (btnLegato.getValue() && btnSameNote.getValue())
                		{
                			retriggerNote = lastNote; //Retrigger note becomes the last note
                		}
                
                		if (Message.getNoteNumber() == lastNote)
                		{
                			Message.ignoreEvent(true);
                
                			if (retriggerNote != -1)
                			{
                				Synth.addVolumeFade(lastEventId, fadeTime / 100 * knbFadeOutRatio.getValue(), -100); //Fade out old note
                				Synth.addPitchFade(lastEventId, bendTime / 100 * knbFadeOutRatio.getValue(), 0, Message.getFineDetune() + bendAmount); //Pitch fade old note
                
                				lastEventId = Synth.playNote(retriggerNote, lastVelo);
                				Synth.addVolumeFade(lastEventId, 0, -99); //Set new note's initial volume
                				Synth.addVolumeFade(lastEventId, fadeTime, 0); //Fade in new note
                
                				lastNote = retriggerNote;
                				retriggerNote = -1;
                			}
                			else
                			{
                				Synth.noteOffByEventId(lastEventId);
                				lastEventId = -1;
                				lastNote = -1;
                				setSSOffset(1); //Reset sample start offset modulators
                			}
                		}
                	}
                	else //Script is bypassed
                	{
                		//Turn off any hanging notes
                		if (lastEventId != -1)
                		{
                			Synth.noteOffByEventId(lastEventId);
                			lastEventId = -1;
                			lastNote = -1;
                			setSSOffset(1); //Reset sample start offset modulators
                		}
                	}
                }
                
                function onController()
                {
                }
                
                function onTimer()
                {
                	if (!btnBypass.getValue())
                	{
                		if (btnGlide.getValue()) //Glide
                		{
                			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 (lastEventId != -1 && notes[0] != -1 && ((notes[1] > notes[0] && glideNote <= notes[1]) || (notes[1] < notes[0] && glideNote >= notes[1])))
                			{
                				glideBend = 100;
                				if (notes[0] > notes[1]) glideBend = -glideBend;
                			}
                			else 
                			{
                				notes[0] = notes[1]; //Origin becomes target
                				glideNote = notes[1];
                				Synth.stopTimer();
                			}
                		}
                		else if (btnTrill.getValue()) //Trill
                		{
                			count = 1-count; //Toggle count - to switch between origin and target notes
                			glideNote = notes[count];
                			glideBend = bendAmount; //Trill uses same bend settings as normal legato
                		}
                
                		if (Synth.isTimerRunning()) //Timer may have been stopped if glide target reached, so check before proceeding
                		{
                			Synth.addPitchFade(lastEventId, rate*1000, 0, glideBend); //Pitch fade old note to bend amount
                			Synth.addVolumeFade(lastEventId, rate*1000, -100); //Fade out last note
                
                			lastEventId = Synth.playNote(glideNote, lastVelo); //Play new note
                
                			Synth.addVolumeFade(lastEventId, 0, -99); //Set new note's initial volume
                			Synth.addVolumeFade(lastEventId, rate*1000, 0); //Fade in new note
                			Synth.addPitchFade(lastEventId, 0, 0, -glideBend); //Set new note's initial detuning
                			Synth.addPitchFade(lastEventId, rate*1000, 0, 0); //Pitch fade new note to 0
                		}
                	}
                }
                
                function onControl(number, value)
                {
                	switch (number)
                	{
                		case btnBypass: case btnLegato: case btnGlide: case btnTrill:
                			Synth.stopTimer();
                		break;
                
                		case knbMaxBend:
                			updateBendLookupTable(knbMinBend.getValue(), knbMaxBend.getValue()); //Update the bend amount lookup table
                		break;
                
                		case knbFadeTm:
                			fadeTime = value; //Default fade time
                		break;
                
                		case knbRate:
                			//If timer's already started then update its Rate
                			if (Synth.isTimerRunning())
                			{
                				rate = getRate(Math.abs(notes[0] - notes[1]), lastVelo);
                				Synth.startTimer(rate);
                			} 
                
                			knbRate.set("text", "Rate"); //Default
                			if (knbRate.getValue() == knbRate.get("max"))
                			{
                				knbRate.set("text", "Velocity");
                			}
                		break;
                
                		case btnSameNote:
                
                			if (value == 0 && !Synth.isKeyDown(lastNote) && lastEventId != -1)
                			{
                				Synth.noteOffByEventId(lastEventId);
                				lastEventId = -1;
                				lastNote = -1;
                			}
                
                			retriggerNote = -1;
                
                		break;
                	}
                }
                1 Reply Last reply Reply Quote 0
                • d.healeyD
                  d.healey
                  last edited by

                  Here's a play test from an instrument I'm working on - another woodwind library :p - I've set it up so the front end script controls the legato script, I have the sustain pedal activating the same note legato or the glide depending on if I'm playing a legato transition or not.

                  1 Reply Last reply Reply Quote 6
                  • M
                    MIDIculous
                    last edited by

                    This is great for acoustic and woodwind sounds. I wonder how this would sound with traditional Synth lead and synth bass sounds. Could this be tweaked to accommodate the traditional Moog type synth legato and portamento functions?

                    Also does it work with polyphonic material?

                    That's one of the things missing from Hise is a good Poly/Mono portamento glide function. That was very smart to include a formant shaper to it, so it sounds realistic.

                    1 Reply Last reply Reply Quote 1
                    • d.healeyD
                      d.healey
                      last edited by

                      It's monophonic only although it automatically detects when a chord is played and switches to a standard sustain polyphonic mode. The glide works by playing all of the samples in between the first and last note so it retains the natural formants and only does a pitch bend between each semi-tone. I'm not sure how this would work with a synth but you can try it out and let me know :)

                      1 Reply Last reply Reply Quote 0
                      • d.healeyD
                        d.healey
                        last edited by

                        This post is deleted!
                        1 Reply Last reply Reply Quote 0
                        • d.healeyD
                          d.healey
                          last edited by

                          I've added an update to this script, just improving some of the code around the glide rate stuff, nothing major. I'm also posting an example project (HISE snippet) below - you'll need to add your own samples, and give them some s.mod time for the start offset to work.

                          /**
                           * Title: Synthetic Legato v3.5.3
                           * Author: David Healey
                           * Date: 27/01/2017
                           * Modified: 02/07/2017
                           * License: GPLv3 - https://www.gnu.org/licenses/gpl-3.0.en.html
                          */
                          
                          reg lastNote = -1;
                          reg lastEventId = -1;
                          reg retriggerNote = -1;		
                          reg lastVelo = 0;
                          reg lastTime;
                          reg interval;
                          reg fadeTime;
                          
                          reg bendAmount = 0;
                          reg bendLookup = []; //Bend amount lookup table - generated on init
                          
                          reg glideBend; //The bend amount for glides, either 100 or -100 depending on if an up or down glide
                          reg glideNote; //Currently sounding note during a glide/trill
                          reg rate; //Timer rate for glide/trill
                          reg notes = []; //Origin and target notes for glide/trill
                          reg count; //Counter for switching between origin and target notes for trill
                          
                          reg CHORD_THRESHOLD = 25; //If two notes are played within this many milliseconds then it's a chord
                          
                          //Get all child sample start constant modulators
                          const var modulatorNames = Synth.getIdList("Constant"); //Get child constant modulator names
                          const var startModulators = []; //For offsetting sample start position
                          
                          for (modName in modulatorNames)
                          {
                          	if (Engine.matchesRegex(modName, "(?=.*tart)(?=.*ffset)")) //Sample start offset
                          	{
                          		startModulators.push(Synth.getModulator(modName));
                          	}
                          }
                          
                          //GUI
                          
                          Content.setHeight(150);
                          
                          const var btnBypass = Content.addButton("Bypass", 0, 10);
                          btnBypass.set("radioGroup", 1);
                          
                          const var btnLegato = Content.addButton("Legato", 150, 10);
                          btnLegato.set("radioGroup", 1);
                          
                          const var btnGlide = Content.addButton("Glide", 300, 10);
                          btnGlide.set("radioGroup", 1);
                          
                          const var btnTrill = Content.addButton("Trill", 450, 10);
                          btnTrill.set("radioGroup", 1);
                          
                          const var btnWholeStep = Content.addButton("Whole Step Glide", 600, 10);
                          btnWholeStep.set("tooltip", "When active each step of a glide will be a whole tone rather than chromatic.");
                          
                          const var knbBendTm = Content.addKnob("Bend Time", 0, 50);
                          knbBendTm.setRange(-50, 50, 0.1);
                          knbBendTm.set("suffix", "ms");
                          knbBendTm.set("tooltip", "The pitch bend lasts the same duration as the crossfade time by default but can be adjusted with this knob. If the bend time ends up being less than 0 it will automatically (behind the scenes) be set to 10ms.");
                          
                          const var knbMinBend = Content.addKnob("Min Bend", 150, 50);
                          knbMinBend.setRange(0, 100, 1);
                          knbMinBend.set("suffix", "ct");
                          knbMinBend.set("tooltip", "The amount of pitch bend in cents (ct) for an interval of 1 semitone");
                          
                          const var knbMaxBend = Content.addKnob("Max Bend", 300, 50);
                          knbMaxBend.setRange(0, 100, 1);
                          knbMaxBend.set("suffix", "ct");
                          knbMaxBend.set("tooltip", "The amount of pitch bend in cents (ct) for an interval of 12 semitones");
                          
                          const var knbFadeTm = Content.addKnob("Fade Time", 450, 50);
                          knbFadeTm.setRange(10, 500, 0.1);
                          knbFadeTm.set("suffix", "ms");
                          knbFadeTm.set("tooltip", "Maximum crossfade time in milliseconds, the actual time used will vary based on playing speed and velocity.");
                          
                          const knbFadeOutRatio = Content.addKnob("Fade Out Ratio", 600, 50);
                          knbFadeOutRatio.setRange(0, 100, 1);
                          knbFadeOutRatio.set("defaultValue", 100);
                          knbFadeOutRatio.set("text", "Fade Out Ratio");
                          knbFadeOutRatio.set("suffix", "%");
                          knbFadeOutRatio.set("tooltip", "Shortens the fade out time to a percentage of the fade time. 100% = the same as fade in time.");
                          
                          const var knbOffset = Content.addKnob("SS Offset", 0, 100);
                          knbOffset.set("tooltip", "The value to set sample start constant modulators to during a legato phrase.");
                          
                          const var knbRate = Content.addKnob("Rate", 150, 100);
                          knbRate.set("mode", "TempoSync");
                          knbRate.set("max", 11);
                          knbRate.set("tooltip", "Rate for glide and trill timer relative to current tempo. If velocity is selected then the glide time will be based on the played velocity (doesn't apply to trills which will instead play at maximum speed)");
                          
                          const var btnSameNote = Content.addButton("Same Note Legato", 300, 110);
                          btnSameNote.set("tooltip", "When active releasing a note in normal legato mode will retrigger the note that was released with a transition.");
                          
                          //FUNCTIONS
                          /**
                           * Sets the sample start offset constant modulators to the given value.
                           * @param {number} value Constant modulator value between 0 and 1
                           */
                          inline function setSSOffset(value)
                          {
                          	if (startModulators.length > 0)
                          	{
                          		for (mod in startModulators)
                          		{
                          			mod.setIntensity(value);
                          		}
                          	}
                          }
                          
                          /**
                           * A lookup table is used for pitch bending. 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;
                          	}
                          }
                          
                          /**
                           * The fade time to be used when crossfading legato notes
                           * @param  {number} interval Distance between the two notes that will be crossfaded
                           * @param  {number} velocity Velocity of one of the note that will be faded in, a higher velocity = shorter fade time
                           * @return {number}          Crossfade time in ms
                           */
                          inline function getFadeTime(interval, velocity)
                          {
                          	local timeDif = (Engine.getUptime() - lastTime) * 1000; //Get time difference between now and last note
                          	local fadeTime = timeDif; //Default fade time is played time difference
                          
                          	if (timeDif <= knbFadeTm.getValue() * 0.5) fadeTime = knbFadeTm.getValue() * 0.5; //Fade time minimum is 50% of knbFadeTm.getValue() - when playing fast
                          	if (timeDif >= knbFadeTm.getValue()) fadeTime = knbFadeTm.getValue();
                          
                          	fadeTime = fadeTime + (interval * 2); //Adjust the fade time based on the interval size
                          
                              if (velocity > 64) fadeTime = fadeTime - (fadeTime * 0.2); //If a harder velocity is played reduce the fade time by 20%
                          
                          	return fadeTime;
                          }
                          
                          /**
                           * Returns the timer rate for glides and trills.
                           * @param  {number} interval [The distance between the two notes that will be glided/trilled]
                           * @param  {number} velocity [A velocity value for velocity based glide rates]
                           * @return {number}          [Timer rate]
                           */
                          inline function getRate(interval, velocity)
                          {
                          	reg rate = knbRate.getValue(); //Get rate knob value
                          
                          	//If rate knob is set to the maximum then the actual rate will be determined by velocity
                          	if (rate == knbRate.get("max"))
                          	{
                          		rate = Math.min(knbRate.get("max")-1, Math.floor((velocity / (knbRate.get("max") - 1)))); //Capped rate at max rate
                          	}
                          
                          	rate = Engine.getMilliSecondsForTempo(rate) / 1000; //Rate to milliseconds for timer
                          
                          	if (btnGlide.getValue()) rate = rate / interval; //For glides rate is per step
                          
                          	if (rate < 0.04) rate = 0.04; //Cap lowest rate at timer's minimum
                          
                          	return rate;
                          }
                          
                          function onNoteOn()
                          {
                          	if (!btnBypass.getValue())
                          	{
                          		Synth.stopTimer();
                          
                          		if ((Engine.getUptime() - lastTime) * 1000 > CHORD_THRESHOLD) //Not a chord
                          		{
                          			Message.ignoreEvent(true);
                          
                          			if (lastNote != -1) //First note of phrase has already been played
                          			{
                          				interval = Math.abs(Message.getNoteNumber() - lastNote); //Get played interval
                          				fadeTime = getFadeTime(interval, Message.getVelocity()); //Get fade time
                          				bendTime = fadeTime + knbBendTm.getValue(); //Get bend time
                          				if (bendTime < 10) bendTime = 10; //Bend time can't be less than 10ms
                          
                          				//Get bend amount
                          				bendAmount = 0;
                          				if (interval != 0) //Same note legato
                          				{
                          					interval > 12 ? bendAmount = bendLookup[11] : bendAmount = bendLookup[interval - 1]; //Get bend amount from lookup table
                          					if (lastNote > Message.getNoteNumber()) bendAmount = -bendAmount; //Invert bend amount for down interval
                          				}
                          
                          				setSSOffset(knbOffset.getValue()); //Set sample start offset modulators
                          
                          				if (btnGlide.getValue() || btnTrill.getValue()) //Glide mode 
                          				{
                          					count = 0; //Reset count, for trills
                          					notes[0] = lastNote; //Origin
                          					notes[1] = Message.getNoteNumber(); //Target
                          					glideNote = lastNote; //First glide note is the same as the origin
                          					lastVelo = Message.getVelocity();
                          
                          					rate = getRate(Math.abs(notes[0] - notes[1]), lastVelo);
                          
                          					Synth.startTimer(rate);
                          				}
                          				else //Legato mode
                          				{
                          					Synth.addVolumeFade(lastEventId, fadeTime / 100 * knbFadeOutRatio.getValue(), -100); //Fade out old note
                          					Synth.addPitchFade(lastEventId, bendTime / 100 * knbFadeOutRatio.getValue(), 0, Message.getFineDetune() + bendAmount); //Pitch fade old note
                          
                          					retriggerNote = lastNote;
                          
                          					lastEventId = Synth.playNote(Message.getNoteNumber() + Message.getTransposeAmount(), Message.getVelocity()); //Play new note
                          					Synth.addPitchFade(lastEventId, 0, Message.getCoarseDetune(), Message.getFineDetune()); //Pass on any message detuning to new note
                          
                          					Synth.addVolumeFade(lastEventId, 0, -99); //Set new note's initial volume
                          					Synth.addVolumeFade(lastEventId, fadeTime, 0); //Fade in new note
                          					Synth.addPitchFade(lastEventId, 0, Message.getCoarseDetune(), Message.getFineDetune() - bendAmount); //Set new note's initial detuning
                          					Synth.addPitchFade(lastEventId, bendTime, Message.getCoarseDetune(), Message.getFineDetune()); //Pitch fade new note to 0 (or fineDetune)		
                          				}
                          			}
                          			else //First note of phrase
                          			{
                          				lastEventId = Synth.playNote(Message.getNoteNumber() + Message.getTransposeAmount(), Message.getVelocity()); //Play new note
                          				Synth.addPitchFade(lastEventId, 0, Message.getCoarseDetune(), Message.getFineDetune()); //Pass on any message detuning to new note
                          			}
                          
                          			lastNote = Message.getNoteNumber();
                          			lastVelo = Message.getVelocity();
                          			lastTime = Engine.getUptime();
                          		}
                          	}
                          }
                          
                          function onNoteOff()
                          {
                          	if (!btnBypass.getValue())
                          	{
                          		Synth.stopTimer();
                          
                          		if (Message.getNoteNumber() == retriggerNote)
                          		{
                          			retriggerNote = -1;
                          		}
                          
                          		//Legato mode active and same note legato button enabled
                          		if (btnLegato.getValue() && btnSameNote.getValue())
                          		{
                          			retriggerNote = lastNote; //Retrigger note becomes the last note
                          		}
                          
                          		if (Message.getNoteNumber() == lastNote)
                          		{
                          			Message.ignoreEvent(true);
                          
                          			if (retriggerNote != -1)
                          			{
                          				Synth.addVolumeFade(lastEventId, fadeTime / 100 * knbFadeOutRatio.getValue(), -100); //Fade out old note
                          				Synth.addPitchFade(lastEventId, bendTime / 100 * knbFadeOutRatio.getValue(), 0, Message.getFineDetune() + bendAmount); //Pitch fade old note
                          
                          				lastEventId = Synth.playNote(retriggerNote, lastVelo);
                          				Synth.addVolumeFade(lastEventId, 0, -99); //Set new note's initial volume
                          				Synth.addVolumeFade(lastEventId, fadeTime, 0); //Fade in new note
                          
                          				lastNote = retriggerNote;
                          				retriggerNote = -1;
                          			}
                          			else
                          			{
                          				Synth.noteOffByEventId(lastEventId);
                          				lastEventId = -1;
                          				lastNote = -1;
                          				setSSOffset(1); //Reset sample start offset modulators
                          			}
                          		}
                          	}
                          	else //Script is bypassed
                          	{
                          		//Turn off any hanging notes
                          		if (lastEventId != -1)
                          		{
                          			Synth.noteOffByEventId(lastEventId);
                          			lastEventId = -1;
                          			lastNote = -1;
                          			setSSOffset(1); //Reset sample start offset modulators
                          		}
                          	}
                          }
                          
                          function onController()
                          {
                          	
                          }
                          function onTimer()
                          {
                          	if (!btnBypass.getValue())
                          	{
                          		if (btnGlide.getValue()) //Glide
                          		{
                          			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 (lastEventId != -1 && notes[0] != -1 && ((notes[1] > notes[0] && glideNote <= notes[1]) || (notes[1] < notes[0] && glideNote >= notes[1])))
                          			{
                          				glideBend = 100;
                          				if (notes[0] > notes[1]) glideBend = -glideBend;
                          			}
                          			else 
                          			{
                          				notes[0] = notes[1]; //Origin becomes target
                          				glideNote = notes[1];
                          				Synth.stopTimer();
                          			}
                          		}
                          		else if (btnTrill.getValue()) //Trill
                          		{
                          			count = 1-count; //Toggle count - to switch between origin and target notes
                          			glideNote = notes[count];
                          			glideBend = bendAmount; //Trill uses same bend settings as normal legato
                          		}
                          
                          		if (Synth.isTimerRunning()) //Timer may have been stopped if glide target reached, so check before proceeding
                          		{
                          			Synth.addPitchFade(lastEventId, rate*1000, 0, glideBend); //Pitch fade old note to bend amount
                          			Synth.addVolumeFade(lastEventId, rate*1000, -100); //Fade out last note
                          
                          			lastEventId = Synth.playNote(glideNote, lastVelo); //Play new note
                          
                          			Synth.addVolumeFade(lastEventId, 0, -99); //Set new note's initial volume
                          			Synth.addVolumeFade(lastEventId, rate*1000, 0); //Fade in new note
                          			Synth.addPitchFade(lastEventId, 0, 0, -glideBend); //Set new note's initial detuning
                          			Synth.addPitchFade(lastEventId, rate*1000, 0, 0); //Pitch fade new note to 0
                          		}
                          	}
                          }
                          
                          function onControl(number, value)
                          {
                          	switch (number)
                          	{
                          		case btnBypass: case btnLegato: case btnGlide: case btnTrill:
                          			Synth.stopTimer();
                          			btnSameNote.setValue(0);
                          		break;
                          
                          		case knbMaxBend:
                          			updateBendLookupTable(knbMinBend.getValue(), knbMaxBend.getValue()); //Update the bend amount lookup table
                          		break;
                          
                          		case knbFadeTm:
                          			fadeTime = value; //Default fade time
                          		break;
                          
                          		case knbRate:
                          			//If timer's already started then update its Rate
                          			if (Synth.isTimerRunning())
                          			{
                          				rate = getRate(Math.abs(notes[0] - notes[1]), lastVelo);
                          				Synth.startTimer(rate);
                          			} 
                          
                          			knbRate.set("text", "Rate"); //Default
                          			if (knbRate.getValue() == knbRate.get("max"))
                          			{
                          				knbRate.set("text", "Velocity");
                          			}
                          		break;
                          
                          		case btnSameNote:
                          
                          			if (value == 0 && !Synth.isKeyDown(lastNote) && lastEventId != -1)
                          			{
                          				Synth.noteOffByEventId(lastEventId);
                          				lastEventId = -1;
                          				lastNote = -1;
                          			}
                          
                          			retriggerNote = -1;
                          
                          		break;
                          	}
                          }
                          
                          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.```
                          1 Reply Last reply Reply Quote 1
                          • Evan C.E
                            Evan C.
                            last edited by

                            Really appreciate!

                            1 Reply Last reply Reply Quote 1
                            • d.healeyD
                              d.healey
                              last edited by

                              I've rewritten the majority of this script today to make it leaner and better. I've added support for a CC threshold note retrigger (intended for use with breath controllers).

                              I've removed the various mode buttons as they aren't really needed. I've added new functionality for the sustain pedal - it controls glide (as before) and can also control retrigger (as before) or detached legato (this is the new bit).

                              I've simplified the velocity > glide rate controls, now there is a separate button to toggle this on or off.

                              The legato fade time now takes into account the played interval, the speed at which the transition was played, and the velocity.

                              The trickiest thing with legato scripts (especially complex ones like this) is catching all the instances that can cause hanging notes. I think I've got them all but if you find such an issue please let me know how to reproduce it so I can fix it. And let me know if you find any other bugs or have suggestions to make script more efficient.

                              Happy New Year!!

                              Link Preview Image
                              File not found · davidhealey/HiseUtilityScripts

                              Contribute to davidhealey/HiseUtilityScripts development by creating an account on GitHub.

                              favicon

                              GitHub (github.com)

                              hisefiloH LindonL 2 Replies Last reply Reply Quote 4
                              • hisefiloH
                                hisefilo @d.healey
                                last edited by

                                @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?

                                d.healeyD 1 Reply Last reply Reply Quote 0
                                • d.healeyD
                                  d.healey @hisefilo
                                  last edited by

                                  @hisefilo Could you show me a snippet?

                                  hisefiloH 1 Reply Last reply Reply Quote 0
                                  • hisefiloH
                                    hisefilo @d.healey
                                    last edited by

                                    @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.

                                    1 Reply Last reply Reply Quote 0
                                    • d.healeyD
                                      d.healey
                                      last edited by

                                      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.

                                      hisefiloH 1 Reply Last reply Reply Quote 0
                                      • hisefiloH
                                        hisefilo @d.healey
                                        last edited by

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

                                        d.healeyD 1 Reply Last reply Reply Quote 0
                                        • d.healeyD
                                          d.healey @hisefilo
                                          last edited by

                                          @hisefilo It's the post before yours - https://forum.hise.audio/topic/228/synthetic-legato/13

                                          hisefiloH AxiomCruxA 2 Replies Last reply Reply Quote 1
                                          • hisefiloH
                                            hisefilo @d.healey
                                            last edited by hisefilo

                                            @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.

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

                                            54

                                            Online

                                            1.7k

                                            Users

                                            11.7k

                                            Topics

                                            101.8k

                                            Posts