Note Off by ID doesn't work on same note

  • For the past couple of hours I've been trying to work out why I can't get my same note legato script to work and I think I've figured it out. Synth.noteOffByEventID() doesn't work if the MIDI note number of the note you are turning off (via the given ID) is the same as the note number associated with another ID.

    <Processor Type="ScriptProcessor" ID="Script Processor" Bypassed="0" Script="reg noteIDs = [];function onNoteOn()&#10;{&#13;&#10;&#9;noteIDs[1] = noteIDs[0];&#13;&#10;&#9;noteIDs[0] = Message.getEventId();&#13;&#10;&#9;&#13;&#10;&#9;Synth.noteOffByEventId(noteIDs[1]);&#10;}&#10;function onNoteOff()&#10;{&#10;&#9;Message.ignoreEvent(true);&#10;}&#10;function onController()&#10;{&#10;&#9;&#10;}&#10;function onTimer()&#10;{&#10;&#9;&#10;}&#10;function onControl(number, value)&#10;{&#10;&#9;&#10;}&#10;">
      <EditorStates BodyShown="1" Visible="1" Solo="0" contentShown="1" onInitOpen="0"
                    onNoteOnOpen="1" onNoteOffOpen="0" onControllerOpen="0" onTimerOpen="0"

  • Yes it is currently only storing the last ID for each note on command (however it stores artificial events and real events separately).

    I thought it would be enough because you don't have more than one active event per note number, but I'll try to think of something more robust here.

  • In the case of same note legato the previous event keeps going - like the sustain pedal is held - until the next note is played (which is the same note number), and the two are crossfaded together. I didn't know that real and artificial events were stored separately though so that might provide the solution I need.

  • I tried to wrap my head around a solution, but it all comes with a performance penalty and since this is a edge case scenario, I'd prefer not to sacrifice CPU cycles for this.

    BTW, if you are holding the sustain pedal, the event will not keep going internally - the note off event is getting processed (it just does not kill the voice). Instead, if you keep adding note off events, they are all ignored because of the sustain logic - so technically you are barking up the wrong tree 🙂

    The only case where you really have more than one active event is when you have overlapping notes in the sequencer, which is generally bad practice anyway.

    In your case, you might just want to kill the voices with Synth.addVolumeFade().

    Check out this patch:

    HiseSnippet 979.3oc4W80aaaCDmx1Ba1coXcXCXOpG5C1XoBxo+YEHHHN1wtyqMIdQdd6sVFI5XhRQ5QQ4N2h739drW22l8AXeH52fsih1QxINI1csa.aD1.h+t6H+cmt6HUOoHfDGKjHqa0e5XBx5Sr8mxUiZMBS4nt6irts8A3XEQ5XfZNcLNNlDhrrJ9DMfU4RHDpwe38q61Dyv7.xLnzw.AMf7LZDUkg9hFOkxXcvgj9znbZ+fFcCD7VBlHA3SQaOzXbvKwmRNDqUqfMZBk7pXjkm882xU9x5m758bgQyuYv2se6GRbc68sttOsae9WkhaFMccQAIRIgqF.lirrs9SXXY2NjpDReEVQf0rTSQ3T+QhWwMa8.ZL8DFQOoNxG3jAtifEpcdMJp0HJKr27PXLBV6dYAzhl.5mae.MjdNdVf8SSE3jYQ9PqUgqid0uY54sL5YkidkLz6N19AR5XUlDM29rYnWA6LBQE9MK6MpHIm5vfTj1SfPb2Pmcb71NC8PghbXRzIPBjVvvDdfhJ3NBtVxQ7p0p7lJkoCqll14Ri8ShUPHpGIDy1G72p0psQkxuA9q05.fLPFg6ojbqb0ZN6ryE1MsQFqJaVYbX3.AKIhnS7pliva5rk2lN2qtmWssA0OqR4y.yfeK5T414YfUqs8LsVvIuBJtckypbQue3Pi6unnVBtRJXLsUWVptlQdMlUkmtga5LAyRHy0CRIVLgpxpkPAEjJvYyonf2kSUGMlLqPY9Kx4H0Qm6b4UJyoxil5LKQsLnKmEiZY3D7zkSmghJQXBCqVrRS2oZl.HXsPtbW8hESUSyZD81cWixOuUt6vpR26X2ipBFsb9VXI7Epr+Py2YMy1vt8vgj.UFYKY24GeW6b8tQkaanxsr8obRZscJQ9hz4N+.dBw4IDNQpCpW0wUv32e+eb0QAJX26Kw73wh3EVXeRDsufqCHYfeeLoij7SGqeImGuk.KWpn2taGvGWpM9XUhLMaYuHQBWsP1wEp8sWsZ+EKZxo+MbJSo+END78eU3e+lFEWsv7MbN8GMmi9znwLRa9DBS.HlNE6SFhSXpyQuQRtmRAWp5bjG+KMNlvH37Yq2swyfrLrD7cxGd+65dG7k1F55nq7b9uQC7h++rA9whDEke5AXkj9yPKH3ZQ9Piy.BvDNmvzskrJna9Yl6oma5bxC8leq8YBqqmaMSX84B+GYOhvARwyCLWSQmK+woHfeyS+JkxvmKAycpiRu+kI1DA83ddPfNXeOH5rbK1Zss39qsEOXss3gqsEOZss3qWaKd70Xg9.k8RThHSoGB8W9CIAoB

    If you press the sustain pedal and play a note twice, it will kill the old one (I added a long attack time so you can hear the behavior more clearly).

  • I actually got this working, I think. That tip you gave about script generated notes being handled separately gave me the solution 🙂

  • I'm not sure if this is related but I'm experiencing an intermittent issue with turning off artificial and real notes that share the same note number.

    This simple script should automatically retrigger which ever key is played when the key is released. Each time the key is played or released the old note is turned off so only 1 voice should be active at a time, the notes alternate between real on a key press and artificial on a key released. What I'm finding is that sometime playing a note just kills all notes and the result is no notes playing.

    reg noteIds = [];
    reg lastNote;
    function onNoteOn()
    	noteIds[1] = noteIds[0]; //Store old note ID
    	noteIds[0] = Message.getEventId(); //Store new note ID
    	lastNote = Message.getNoteNumber(); //Store note number
    	Synth.noteOffByEventId(noteIds[1]); //Turn off old note by its ID
    function onNoteOff()
    	noteIds[0] = Synth.playNote(lastNote, 64); //Play artificial note and store its ID
    function onController()
    function onTimer()
    function onControl(number, value)

  • Sorry in advance for the long post that is about to follow (I'll probably move this answer in the blog section later).

    I was rewriting the event ID handling this weekend. This is extremely complicated because there a loads of different scenarios and coming up with a robust yet simple solution is not trivial.

    The solution I came up with is pretty drastic - it may break some of your scripts, but the overall increase of robustness
    may justify this - we`re still in beta-land after all :).

    First of all we need to distinguish between real and artificial events - I am sure you know this already, but at some fictional point in the future somebody else might read this :):

    • Real Events are actual MIDI events coming into HISE either from your MIDI keyboard, the sequencer (or the onboard virtual keyboard).
    • Artificial Events are created by scripts.

    I based the new system on these assumptions.

    1. Only one active "real" note on event per note number

    There is only one real note on event active at the same time in a valid scenario. If you hold the sustain pedal and repeat the note, the note offs will still invalidate the note ons (=delete them from the queue), but the voices won't be killed until the pedal release. The only case where you have multiple note ons is when you have overlapping notes in the sequencer (or you start the sampler playback in the middle of a note). In this case the second note on and note off will be ignored before they slip into HISE at all.

    2. Multiple artificial events

    You can have multiple artificial events (events which are created by a script). You can create artificial events in every sound generator, so there are many cases where two artificial note on events with the same note number need to be stored. The only robust way to handle artificial note ons is by adressing them via event ID, which will be provided when you create a note on artificially.

    3. Never kill real events

    You must never try to kill real events artificially (I added a safe check that throws a script error if you try to do this). This eliminates a big chunk of weird edge cases.

    Why is this important? As soon as you call Synth.addNoteOffFromEventId() with a real event ID, it will invalidate the note on event (=delete them from the queue). At the time the real event comes in, it would be ignored and the note off callback will never be called for this event because calling Synth.addNoteOffFromEventId() won't trigger a callback in the same script for safety reasons.
    The solution is pretty easy: As soon as you are tinkering with artificial notes, use the new Message.makeArtificial() in the onNoteOn and onNoteOff callback. It will create an artificial copy of the current event (with a new event ID in case of a note on and a matching event ID in case of a note off) and swap the message (so that other script processors will also get the new event). This makes the event types consistent and removes the issues described above (since the original message will be untouched, it still triggers the onNoteOff callback.


    So internally, real events are stored in a simple array and adressed via note number (this yields the lowest overhead). Artificial events are stored in a array of fixed-sized stacks (currently 16) and also adressed via note number. This slightly increases the overhead for the scripting call Synth.noteOffFromEventId() because it has to do more searching, but allows multiple artificial notes with the same note number being active at the same time.

    With this system it should be possible to write pretty robust and encapsulated MIDI scripts. However it might break older scripts (I had to change the Legato Retrigger Script for example).

  • This sounds like a good solution, I'll try it out tomorrow

  • Love this warning Hell breaks loose if you kill real events artificially!

  • If you attempt to turn off an artificial note that has already been turned off, or pass a none-existent ID to the Synth.noteOffById() function it throws the hell breaking loose error and says that a note with the given ID can't be found, the second error is obviously correct but the first isn't since this is a none-existent note rather than a real note.

    Could we have a function to check if a note ID is valid? i.e. a note event for the given ID exists. I have a script with notes being turned off in different places, sometimes one of the notes may have already been turned off by a different part of the script. If I could put a simple check to see if a note ID exists before trying to turn it off it would help.

    A little demo of the issue

    <Processor Type="ScriptProcessor" ID="Note off Test" Bypassed="0" Script="reg id;&#10;reg last;&#10;&#10;function onNoteOn()&#10;{&#10;&#9;Message.makeArtificial();&#10;&#9;id = Message.getEventId();&#10;&#9;last = Message.getNoteNumber();&#10;}&#10;&#10;function onNoteOff()&#10;{&#10;&#9;if (Message.getNoteNumber() == last)&#10;&#9;{&#10;&#9;&#9;Synth.noteOffByEventId(id);&#13;&#10;&#9;&#9;Synth.noteOffByEventId(id);&#10;&#9;}&#10;&#10;}&#10;&#10;function onController()&#10;{&#10;&#9;&#10;}&#10;function onTimer()&#10;{&#10;&#9;&#10;}&#10;function onControl(number, value)&#10;{&#10;&#9;switch (number)&#10;&#9;{&#10;&#9;}&#10;}">
      <EditorStates BodyShown="1" Visible="1" Solo="0" contentShown="1" onInitOpen="0"
                    onNoteOnOpen="0" onNoteOffOpen="1" onControllerOpen="0" onTimerOpen="0"

  • Nice catch, the duplicate message issue was indeed annoying (the fix was rather trivial).

    About your other request, what do you think about removing the error message for the invalidated event alltogether and just returning a bool from Synth.noteOffByEventId() if the message existed? If you want, you could still implement your error message like this:

        Console.print("Invalid note event ID");

    Since a error message stops the further execution of the script (it aborts the callback) the current method is a bit harsh for this case which is indeed pretty easy to recreate.

  • Yes that would be perfect, thanks!

  • I just came across this wonderful project. First off all congratulations.
    But I have to respond to this issue.
    Your first assumption unfortunately is wrong! I have a LinnStrument as a MIDI controller which has several keys for the same note. I create Max/MSP patches as well. Both have to rely on a way to play multiple notes on the same note value. It is not only unavoidable, it makes a lot of musical sense...
    The only problem to face is, which of the events to switch off on a note off event. Easiest solution - switch off the oldest...
    On the other hand, as I mentioned the LinnStrument (same applies to Roli Seaboard and Block) I would love to see native MPE support. That is what I am really after... In this case the multiple same notes have simply different MIDI channels, and thus its easy to know which note to switch off...
    Off course the outlined scripts could be abused for that functionality somehow. But, why differentiating between artificial and real notes at all? In the end, All notes which have been switched on, need to be switched off anyway at a certain point...

  • I'll need to read the MPE specs to get a full image of how it works, but internally I am using an own message format which allows a little bit more information than just the pure MIDI messages (eg. 256 MIDI channels etc). I think adding MPE support shouldn't be too hard. It's a kind of niche thing right now (apart from Bigwig I don't know any host that supports this yet), but in the future it will become interesting.

    But if I understand you correctly, even with the Linnstrument it's only one active note per MIDI channel? If that's the case, my assumption is still correct, I'll just need to add the storage for different channels (which I need to do anyway for any sort of multitimbral sampler).