Fade out artificial notes
-
I utilize choke scripts for my hi-hats, which are based on a choke script @Christoph-Hart provided a few years back.
I have several containers running in parallel and depending on the value of CC4 controller, only a single container loaded with one of those choke scripts will play the samples. Certain notes or notes in combination with CC4 value choke the sound of a currently-playing container/sampler.
"Choking" is done by either adding a note off or by fading out the notes, and it works perfectly when real notes are fed into it.
Here is one of the current choke scripts.
//onInit reg ccValue = 127; const var decayShank25 = Content.getComponent("decayShank25"); // An Array is also a constant, even if it will be populated later on const var evtList = []; // make sure it has enough storage to avoid allocation during the noteOn callback evtList.reserve(1200); function onNoteOn() { if (ccValue < 63) { Message.ignoreEvent(true); } if (ccValue > 84) Message.ignoreEvent(true); if (ccValue > 84 || Message.getNoteNumber() == 44) { local decayValue = decayShank25.getValue(); if(decayValue == 0) { // Always use the for ... in loop if you don't need the index for(eventId in evtList) { // Send the note off command for the given event id Synth.noteOffByEventId(eventId); } } else { // Always use the for ... in loop if you don't need the index for(eventId in evtList) { // Fade out the note Console.print("Choke shank25 event no is:" + eventId + " and is artificial:" + Message.isArtificial()); if (!Message.isArtificial()) if (Globals.auditionSusNotes.indexOf(eventId) == -1) //not an auditioning note Synth.addVolumeFade(eventId, decayValue, -100); } } // Clear all notes evtList.clear(); } else if (ccValue <85) { // This is necessary because you will kill the note artificially and HISE // can only kill artifical notes for stability reasons Message.makeArtificial(); // Add this ID to the list (it'll add the artificial event ID) evtList.push(Message.getEventId()); } } function onNoteOff() { } function onController() { if (Message.getControllerNumber() == 4) { ccValue = Message.getControllerValue(); } } function onTimer() { } function onControl(number, value) { }
I have since added a MIDI player to the master container, so MIDI files loaded into it are sending all artificial notes. The current script is not able to fade out those notes.
Commenting out the
if (!Message.isArtificial())
works only for a choke script that doesn't involve CC4 controller.
For most choke scripts, commenting out this line does not work. When playing a file in the MIDI player, I get an error for each choke script that the event Id can not be found.
Any ideas on how to get around this hurdle, so I can finally?
-
Narrowing it down a bit.
If I disable all Containers holding the choke scripts but one, that single Container and its script work as intended.
Also, I have a choke script for regular hats that don't involve the CC4, and it works fine if I don't touch the CC4 (so that the other scripts don't get involved).
A triangle Container likewise has the same choke scripts (without the CC4) and works fine.
So, a single variable hi-hat script works fine, but when several such scripts are used in parallel involving the CC4, things break down.
-
@gorangrooves I've taken a look at the problem and unfortunately it's a bit more complicated than I hoped. The problem is that if you call
sendNoteOffByEventId()
with a artificial event ID, it will pop the event of the global list of artificial events so that when the next container calls the method, it won't find the artificial ID anymore and fails to kill the note.I need to be very careful with a fix here as changing the behaviour of this might cause issues with all kinds of existing scripts. The solution to this problem is that we need to ensure that we'll always create a new eventID for each note in each container so that the
noteOffByEventId()
function doesn't render the subsequent containers unfunctional.Currently a call to
Message.makeArtificial()
is ignored if the Message is already artificial, but I can't change that behaviour. Therefore I've added a new methodMessage.makeArtificialOrLocal()
, which does the exact same thing asMessage.makeArtificial()
, but ensures that there is always a new event ID generated, even if you call it with already artificial notes (you can think of the new event as "local artificial note" hence the name). This is a minimal change that will not change the existing behaviour, but gives you the option of using "local" artificial notes that you know can be killed "locally" later without having to bother about other containers.So with the new function, all you need to do is to make sure that if you intend to kill a note in a script that might be processed by other scripts too, you need to call
Message.makeArtificialOrLocal()
instead of the old function. BTW you should also call this method in the noteOff callback to ensure that it cleans up the artificial event properly (that might or might not create problems later down the line).Here's a example snippet that demonstrates the problem. Two sine generators with a very simple legato script (just play two notes, it doesn't handle more complex cases). All notes fed into it are made artificial (you can deactivate this by bypassing the Artificializer script in the root container). If the notes are artificial, you'll get a stuck note because the second sound generator can't kill the note.
HiseSnippet 977.3oc6Xs0aaaCElx1bXx6BV2JF5iZ.6gDf0.6ttK.EA4huTXr5DiH2t8VAqzw1DQhTShxatE8+31+j7OX6PI6HoT4rTgz.3AqmLOmyG4GO7vOdfGEJcfnHYHwvb7h.fX7oT6EB0rNyXbAYPWhwWPOJTwmvc3LuwPjhb7h.VTD3RLLp+TcTFlMHIeWbvwLOlvAxLQHuPxcfmw84pLqiN7W3dd8YtvXtetne7gCbjhNROYLxn5zVj.ly4rovILcX0nDiOpmKWICsULEDQLZbrzcg8L4eHRi+E7H9q7.8f1DabhRM2W54pYr1JoyLtm6nU67HBNoixxC0SyC2mNj6xuzd97g1gUFh74CiZEoW8Bzqcd50JG8JgRF4nTiTJcOpsSHOPk44pmO7WCE4SJ.hweSslDKbTbovRJNQpfSE6ray2zzbHNSXBdOe14P1DsytOo4aa9NXlL4+BTSyqfqiTnBkddPXJzq3Ve9Wtmk.2QD6+JH76rly7hgKCDSJEyzz0moyWHfkWJPnxEnTLPvUmF.h0UdP5jBB+0yGzkoX5imk1v3B.LCnofQWXNVqmdXYR6BQmqjAX096bRh0PR2XOlpXgk91zRGXNnvA4.8pEwUKxea6VqZ6lRw6QGwUNyJmi0JgiXl5CAGWdG8yn8lLAbTYDrAs+u8g4BY9k+ySW9OgZyEPhbYxh+0Iis9U1bv5of.B0IuGcM5k+0MUuL3FqWdpiBW9wgLQTfLpvDaC97wRgNKjY74QP+P32OSeZl2dGIKrTWWbPebSVJFalJNLor3HeYrPUnLn90oa2th51M1f0s+JZmY.KXnTHClIEqS5tVWCZHL0BlipMCbs125gsexZzx4S1YUXeiNtcaZZlTdtmHU893E8R8uJNTw1zroY1j+dnrW3EAboWGTq82eE60DpvFY6yEahOWTnH2bEGs49AdPOwbvC2jIb7Kwc0DVrmZk0hpgWV66jWp3LPExmNEByy8R2PGoTX6gYVt+gmAd.Kur22d3yP4JVHlmfJlKZ+deguzyqGPSoqktF1Zy8Y95++9Y9yjwJtX5PFVD9mnNwIw913qqN.t5BA3o0NLpoukmNtkdb5yqB2jA+C9szYa8XikNaux4ldqDWbv1VI11Jw1VI11Jw1VI1vaknwM6b650NuCaq3VmuUuEiaGpbWztwcwZ3ybBkuzIU4TWK+wIVv8sH4ut0jNTO1pMIQMEwQasWKhO9N5Kcbzo6Gh4mxw7nJf46q.lGWAL+PEv7iU.yOUAL+70hQ2ByQwJoe5UQzvndIOwXXzSvvJ4jpdx+BzIuNZJ
Now if I use
Message.makeArtificialOrLocal()
it ensures that the event can be killed locally without affecting other messages:HiseSnippet 983.3oc6Xs0aaaCElx1bXx6BV6JF1iZ.6gDf0.6dYa.EA4hicgwhSLrb61aErTGaSDIRMJJu4Vr+ia+Sx+fMRI6HoT4zLgz.jBqmLOWH+34b324.OTJnPTjPhrrGuHDPVeN1cAWMqyLBii5eDx5qvGHUrILJi3OFhTnCWDRhh.OjkU8marxxtAJ4678Nj3S3THSDB8RAiBGyBXpLoC2+WX998HdvXVPNqex98oBdGguHVin53VnPB8LxT3DhwrZXj0mz0ioDRWEQAQHqFGJ7V3NS7G7T6eIKh8ZevrnMxUuQoh6I78LH1HE0YFy2a3padDRuoCyhC0SiCO.Of4wtPd93gQgSlG4iGV0JBu5EfW67vqUN3UBjrxAoFoP5dXWpjEpxzb47C6MPQ7j5.x5evNSh4TESvcD7SDJ3T9Va27sMsGn2Ic.dm.xYP1Fs01Oq4e07c7Yxj2mSMsujecDbkT36CxTWujZS9ubMKcbKdbvqA4O3Lm3GCWXnNnTLRiWejNegft7RAbUNCE79bl5zPfutxCTmTmz+5E8OhnHlzyRYZ6BAcDv.Aqif45Z8zjkM9HH5LkHTWs+NYRcMjvK1mnJVXYdMsTgNFTHQ12bZQL0h7u1twp1ttP7d3gLEcV4XrVIXTGo9PfwkuQ+Bb2IS.pJCfMv89sOLOHye7eY5w+YXWFGRnKSN7uIYsyuRlCNOG3fzD7dzUvW92WW9xvqMe4oTk93GKI7nPQTgM1EBXiEbSTHS3KhfdR32GYxl4k2QPjkp5785oujk5iKQEKSJKNHPDyUEJCpeU71sqHuci6v71eMtyLfDNPvEgyD70QcWajEVBScf4Z1l9dN657v1OaMb4rIasxruyX21MssSJO2gmxde3hto5WYmlw11toc1lWNy9oxiEz0PvWnwfFAumcvY2cWcWLvqv0ZSyi6hMOJTxauBitrfPenKeN3qujIX795a0DRruZkzhbiW7Rflm3XDnjroSAYdrW5E5.kROrXljGr+HvGH4IA+98OVSdQj53DTwXQ6+2O+KMe8s3T35Xpgct61zu9G2M8GIhUL9zADcQ3ep4ININvU2qkB5SmyAeC2gUMyq7z0sLqSa1x8RV7u5ukJaaVasTY6UJuqOXw46sYvhMCVrYvhMCVrYvhOpFrnw0Kuc0Lo2hCYbii2pOvwMCTtMF9313LBHTo3UzTlSSs7mlHQeu4I+st13Al0NsQIroZ+vs1oEJP2U8UTpIb+Pc7obedTE74wUvmmTAedZE74GqfO+TE74muReLCzbPrRDj9TTKXX2jVLVVc4DckbRUO5+b1ZYJG
I'm pretty sure that will solve your issues, but let me know if they still persist after you swapped each call to
Message.makeArtificialOrLocal()
. -
@Christoph-Hart Brilliant work! Thank soooo much for sorting this out. I am going to sleep really well tonight I greatly appreciate it.
When you say that I should call this method on noteOff callback as well, does it mean that I should be adding this to noteOff portion of the scripts even though I have no script for noteOff? Or is it only if I have a noteOff script?
Thanks a million!
-
Nice to hear it works now.
@gorangrooves said in Fade out artificial notes:
When you say that I should call this method on noteOff callback as well, does it mean that I should be adding this to noteOff portion of the scripts even though I have no script for noteOff? Or is it only if I have a noteOff script?
If you don't call
Message.makeArtificial()
in the note off callback after you called it for the respective note on message inonNoteOn()
it will cause a stuck note because these two events will not match.HiseSnippet 882.3oc2Vs0aaaCElx1pnNqqn2vPejOLfk.rlZ2qCXOjKNNEFKNwHJ8xaArTGaSDJRMJprYTr+i6mR9GrcnjSkTiPfiQaCZ0ShmC+z46bUmQFMGRRzFhW6ilECDua4GLSYm1aJSnHC1g3ca+grDKXn4h1dVLKIABIddMekSfW6Vjrmy1Xaljo3PgHB4MZAG1SDIrERGs4eHjxcYgvQhnR29YaNfqU8zRcJxml9cHwL9IrIv9L20Z3S7tQ+PgUaBrLKjf2Yac3rfo5+Rke+2HRDuWBtCcIA3GJWLo2TgLbz49ZBg34Opvyal64OvenHT7Q4EQf6jofVfnbLvqwkQotWAJ4UhRsxozc8C3FQrsPiiO2etzBF0sBkx0RZzzymNNUwsBshpU6qsvApUWakOrR6gHLLvtdD6DXKiULVvEL4pq86q7OqbALiGmC5wOl1SGEAJKUmZo1ohDpTn.JSERmoS+EojNArzDaJ+DpBglr3VpmVYMZoDL4F6ST6pTpWybfqpRidOX9U5oLYJ7wKhgxp4G+EK+fEhVzQKcQsZfRXOHFle9hYPRubP3audvNLKykTmKCuWLftuiBd6.mhcE4o3196.ImX0wXewEx+XkmNLUxrUKGc8cyUfwfJ49ANqkHryJ2WdEpQ6bo0nKJEuq+HgkOsdN1nFNhQpuDbbdm8O52e7XfaKHXK+ce2x1Fu3l+14l+G7CvtjrwpYF+mxNSeK6Tf9JPAFWvq6kLY8eWzIqwK7j0C3Vz7GYXpjXcRkOb.DINRqbQgBguNA10.+4gtrYY48zLSspNaicQmrVLALapIqrXqHcpxVoLnY07PqEqcsZGQo6Wyb1VWCi9+7zVcM14W4uSsOmiAhnXIzWcJHwwaYb7d37rwrTo8boUKrGpU53oZkfWNqeHXMhIS.SYtWqCsk0h6DTH4AadHHAV4J3edy8vJOlAiSvRFKt5+ot170C8yoK00MR+1chcyuumXeHtMiPMYHCKB+abCg8SiBvAkb.stRAR2VCdMbC6xO2wcNeRoJL6v+gOyU10c1atxtmq7qhMhXbi9Xd99Pt5zalIA8aU1BzswM4wyztjrcjPb9cVuCIBmvcLm6BwOBGXVOlmrDXd5Rf4YKAlmuDXdwRf4kKAle6Rw394xVoVcTdKGJXT+rEG875qXX0aVkN4+gmkdp+
Now if your samples do not process the note off message because they are ringing off that might not be a problem, but it's cleaner to do so (plus the never ending notes might clog a event list at some location so it's not just aesthetics).
-
@Christoph-Hart Excellent. Thanks for the explanation. It makes perfect sense. I shall implement it on the noteOff as well.
Thanks so much for everything!
-
@Christoph-Hart An interesting finding: adding the Message.makeArtificialOrLocal() to my hi-hat script's noteOff does not work. It is probably because drums are using single shots, so the note off is ignored. If I add this call in the noteOff, it starts throwing the same kind of error I originally had before you implemented the fix.
So, the correct way to utilize it, it seems, is:
- If you have lasting notes that follow note on/off, use the script on both noteOn and noteOff callbacks.
- If the samples are single-shots (and the sampler is set as such), use the script on noteOn only.
-
Thinking more about it, it is probably my script-specific that I can't use this on noteOff.
This is probably because within noteOn, an artificial note is created to kill an existing note lasting note already present in noteOn callback. I think that's where the mismatch happens.