How to code some intricate legato logic... use onTimer() ?
-
I am hitting a brick wall with the following...
For any given note's noteOff, I want to have a 50ms analysis window to allow me to determine which noteOff sample to trigger. If another noteOn (on the same midi note) is triggered within that 50ms, then we will trigger release sample A. If the 50ms window elapses without a repeated note, we will trigger sample B.
It seems like this should be straight forward enough, but I'm struggling to cime up with an elegant solution (in truth, I'm struggling to come up with any solution!).
I am reluctant to use a timer, as my instinct tells me this will put heavy strain on the CPU, but perhaps I'm wrong?
-
@tonewolf use Engine.getUptime()
so your logic might go like this:
on note off...//we have no idea if there is another note coming so assume not:
- trigger Sample B
- record the off time -with Engine .getUptime()
on note on:... //is it within 50 ms of the last note off(Engine.getUptime -recorded time)
if true:
- stop Sample B
- start Sample A (with some offset probably based on how long Sample B has been running)
- start the next note (Im guessing delayed by some amount to allow the Sample A to play)
if false:
- start the next noteBut (in passing) I cant think of any instrument that actually works this way....and its not really legato
-
@Lindon how would that work in practice, though? I can’t work out how to say “50ms has now passed and another noteOn event has not been triggered, so you can now go ahead and trigger sample B”.
Here’s two different scenarios with an event timeline…
Scenario A
0ms — NoteOn eventID1
250ms — NoteOff eventID1
270ms — NoteOn event ID2At 0ms I trigger an attack sample (let’s call it attackSampleX). I don’t want anything to be triggered at that 250ms NoteOff event point. I want that initial NoteOn voice to keep holding. I want to wait for 50ms to see if we have another event incoming. As it happens, in this scenario A we do have another note on event, so at the same time (270ms) I trigger the new NoteOn attack sample (we can call that attackSampleY), I also want to trigger the appropriate “legato” release sample for the first note on event (ID1). Let’s call that “ReleaseSampleLegato”. I have managed to script this no problem using engineUptime, using Synth.playNote from the noteOn callback.
The issue is Scenario B
0ms — NoteOn eventID1
250ms — NoteOff eventID1
300ms — (there have been no new events)Triggering AttackSampleX on 0ms. After that, as before, I don’t want anything to be triggered at that 250ms NoteOff event point. I want the voice to continue holding until 300ms in case there are any new noteOn events. In this Scenario B there was not, so I want to go ahead and trigger a normal release sample (say, “ReleaseSampleNorm”)
I can’t work out how to do this, and it’s driving me crazy
-
@tonewolf said in How to code some intricate legato logic... use onTimer() ?:
I can’t work out how to say “50ms has now passed and another noteOn event has not been triggered, so you can now go ahead and trigger sample B”.
I just wrote a script that does almost exactly this.
In the on note on callback you record the current uptime and start the timer with an interval of 10ms.
In the timer callback you check how long has passed since the last on note on by comparing the current uptime to the one you stored in on note on.
Based on the time elapsed you carry out the action you want. In my case I'm just killing the note but in yours you would play a note.
The release samples should be in a separate sampler controlled by a separate script.
-
@d-healey great, so you understand what I’m trying to achieve… surprisingly tricky to actually put into plain English!
I suppose I was worried about having loads of timers pinging off. This is a piano library, so I’d need to set a timer on every single noteOff event, and it would need to be polyphonic! Should I be worried? What’s the most CPU friendly way of doing this?
-
@tonewolf said in How to code some intricate legato logic... use onTimer() ?:
This is a piano library, so I’d need to set a timer on every single noteOff event, and it would need to be polyphonic! Should I be worried?
Why not just have one timer and store the time elapsed for each note?
-
@d-healey in my case, I’m looking to analyse the amount of time between the last note off and the new note on. But let me see if I’ve got this correct…
I would set a timer (let’s say, 10ms intervals).
I log the time of the note off event. I log the time of the new note on event. Timer callback checks if logged new note on time minus logged note off time is less than 50ms. If yes, trigger Sample A, if no, trigger Sample B.
-
@tonewolf Yes that's basically it. Start simple with just two notes before trying to make this work with many.
Use a MIDIList to store your times, it's more efficient than an array.
This whole thing should be in a separate MIDI processor to the UI script.
You must use the Synth timer (not a timer object). You only get 1 synth timer per script and (I think) 6 per container, so you should try hard to make it work with a single timer - it can be done.
-
@d-healey okay, this makes sense. I thought I read something about the timer being limited to 40ms intervals, though? Not sure if this would have the resolution I need.
That aside, I have a follow up question regarding MidiList…
I’m logging quite a bit of information per note on event, and currently must admit to having them in an array, where each array index has an object. So I might set and get Middle C meta data at noteLog[60]["noteLength"] or noteLog[60]["noteOffTime"] etc.
Is there significant advantages to separating those all out into discrete MidiLists?
const var noteOffTime = Engine.newMidiList();
const var noteOnTime = Engine.newMidiList();
etc.
etc. -
@tonewolf said in How to code some intricate legato logic... use onTimer() ?:
I thought I read something about the timer being limited to 40ms intervals, though
Looks like 4ms to me - https://github.com/christophhart/HISE/blob/develop/hi_core/hi_dsp/modules/ModulatorSynth.cpp#L410
-
@tonewolf said in How to code some intricate legato logic... use onTimer() ?:
Is there significant advantages to separating those all out into discrete MidiLists?
Yes, do this
-
@d-healey ah, great. So is this something different from…
https://docs.hise.dev/hise-modules/midi-processors/list/scriptprocessor.html#the-ontimer-callback
Which says it is limited to 40ms…?
-
@tonewolf said in How to code some intricate legato logic... use onTimer() ?:
Which says it is limited to 40ms…?
Well the code says otherwise, unless my maths is wrong and 0.004 seconds is not 4ms
-
@d-healey great! Just wanted to make sure we were talking about the same Timer.
Wonderful. That’s been super helpful, thanks! I’ll see what I can come up with using this method.
-
Perhaps missing something obvious, but how can I use MidiList to store times, when it only seems to be able to store integer values between -128 and 128?
-
@tonewolf said in How to code some intricate legato logic... use onTimer() ?:
store integer values between -128 and 128?
hmmm, integer yes, but you can store any value (well I'm sure there'll be an upper limit).
So yeah you'll probably need to use arrays for the time for the precision you need.
-
@d-healey I think I must have misunderstood the online docs (or they are out of date), as it appears to state the values must be between -128 and 128. But testing, it seems I can have 9 figure integers. Perhaps it means there are indexes between -128 and 128?
-
@tonewolf Not sure which doc but the indexes go from 0 to 127, so you get one value for each MIDI note - hence the name, MIDI list.
-
@d-healey this is the doc…
https://docs.hise.audio/scripting/scripting-api/midilist/index.html#setvalue
But in reality, I think it can store a 32bit integer
-
@tonewolf Ah yeah, that's confusing and wrong :)