I am writing a script that needs to collect multiple note events, but wait to process them until a certain amount of time has passed after the last event (let’s say 10 ms for example, but it needs to be changeable as needed). The reason I need to wait is because when I strike a chord, say C-Maj, the “Note On” is called 3 times in quick succession in some random order (well I guess it’s the order that the notes were struck but it may as well be random given that’s not controllable when playing). I only need to process the full chord, not the intermediate stages.
That is just one simple example, but in playing live, I cannot easily create a rule to “know” when a chord has fully been received. The best rule I think will be to assume that 10ms (or whatever I need to tweak the time to) after the last Note On event is received, that is the final state of what I need to process in that moment.
What is the most reliable way to achieve this in the Scriptlet? Some things I have unsuccessfully tried:
Generators / Ramps / One Shot Ramps → this is not relaible, the OnGeneratorRunning and GeneratorEndCycle callbacks for the Ramp are called every 20 or 40ms or 80ms randomly. The “coarseness” setting for the Ramp doesn’t seem to do anything at all, regardless if I try to set it to extremes, 1 or 1000, has zero impact.
TimerTick callback → also not reliable, called randomly at intervals that aren’t fast enough
Most promising idea I thought may work based on another post was to use a SendLater event and then use the assocaited callback to process this. However, this unfortuantely does not work in a Scriptlet and I have to move the entire script to the Global Rack script. This is a much messier solution than I’d like because this functionality ideally should be contained neatly in one scriptlet that receives midi input and spits out midi output / is portable, etc.
Is there a better way to achieve this somehow? Maybe I’m missing something with the one shot ramps, which would be great if their timing / callbacks were reliable.
‘Reliable’ is the possibly problematic word. The sleep function will delay your script at least the number of milliseconds you specified, but it might be a lot more depending on the platform and the availability of hi-res timers.
(Google for ‘nanosleep’, just for some background information. It’s the kind of function that gives me headaches )
As far as I can see, the only way to do this reliably is in a plugin (VST, AU). There you get the MIDI messages along with the audio samples. The messages are marked with a time stamp (being the sample number from the audio sample at which the message arrived). By using this information you could cook an artificial and precise sub-millisecond clock. Writing plugins has its own way of creating headaches
If you want to keep things a little less complicated, you could try the jsfx plugin from Cockos: REAPER | ReaPlugs
This plugin gives you a rather high level to write your own plugin, without the need for a complete development environment. My main concern with this plugin is that it isn’t maintained. There was an alternative (ysfx), but I guess (don’t know for sure) the maintainer from that repo is no longer with us anymore.
My thoughts (and again far too much too text, sorry).
You could write an extension using the Gig Performer SDK. Through that system you can write C++ code to manage your messages any way you want.
That by itself would not stop the audio engine — (nothing stopped that – what’s getting delayed is MIDI input processing) for example, if your script was only responding to a single MIDI note for callback, other incoming MIDI notes would not be delayed and you would hear them immediately. However, the sleep function will most certainly hold up the callback and consequently cause subsequent messages tied to other callbacks
For example, the callback below which responds only to C3 will delay all other callbacks (because callbacks are sequential) if you play C3 but other MIDI messages for which no callbacks are defined will still play immediately.
This is the reason the sleep function has a help message that says, “experimental - maybe even dangerous”
On NoteEvent(m : NoteMessage) Matching C3 from Keyboard
Sleep(1000)
SendNow(Keyboard, m)
End
I did something similar in the following Scriptlet. It took me a quite bit of time to find the good working trick. Feel free to test it and tell us if it works for you:
Hi all, thank you very much for all the ideas! I played around with some things and went with the solution @David-san posted at the end.
For those wondering and in summary, the solution is to use a “timer” parameter that you keep toggling between 0 and 1 while waiting the timeout period. That generates a bunch of ParameterValueChanged callbacks where you can place the logic to process the notes once a time threshold is reached. The NoteEvent callbacks will continue to be called so you can continue collecting all the notes into your NoteTacker while this is happening. Clever idea, so thanks for that and it does what I want.
Also thanks for the tips on developing a VST via ReaPlugs or PureData / or creating a GigPerformer extension with C++. I may eventually explore those other routes if I want something more performant (generating all these ParameterValueChanged callbacks is propbably not the most efficient but seems fine for now). This was the simplest solution to incorporate into my existing script and logic, was able to work it in in about 15-20 minutes after studying David’s script without having to delve into a totally new platform.
A vst with a parameter keeping changing for a definite time should not be dangerous. The Envelope Follower in GP also has ever changing parameters (although their values are proportional to the incoming audio signal and not artificially going forth and back between 0.0 … 1.0).