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

      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
                                    • d.healeyD
                                      d.healey
                                      last edited by

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

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

                                        @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

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

                                          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;
                                              }
                                          }
                                          
                                          hisefiloH 1 Reply Last reply Reply Quote 0
                                          • hisefiloH
                                            hisefilo @d.healey
                                            last edited by

                                            @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 https://www.youtube.com/watch?v=ixMj0eJVb0A

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

                                            50

                                            Online

                                            1.7k

                                            Users

                                            11.7k

                                            Topics

                                            101.9k

                                            Posts