What's new

Stuck notes on Kontakt's virtual keyboard

Mike Greene

Senior Member
Moderator
I'm getting stuck notes on the Kontakt virtual keyboard. No audible stuck notes or glitches. The notes ARE shutting off, it's just the annoyance of seeing stuck notes on the virtual keyboard.

This only happens when I combine the legato part of the script with the tone-shift option.

The legato portion is pretty ordinary, where I use the variable $poly to keep track of polyphony. If $poly is more than one, then I cut off the previously played note, so that it will play monophonically. (There's obviously much more going on than that in my real script, but I want to keep this example simple.)

The tone-shift part is basically a chipmunk/Darth Vader effect. If "shift' is set to -3, for instance, then when the user plays F5, the script tells Kontakt to ignore F5 and instead play D5 (3 semitones lower) and pitch shift that D5 back up 3 semitones so that it still sounds like F5. It's a cheap way to get a double.

Here's the stripped down code:

Code:
on init
   declare ui_menu ($shift)
   declare $oldnote
   declare $oldnoteid
   declare $noteplayed
   declare $oldnoteplayed
   declare $poly := 0
end on

on note
    $noteplayed := $EVENT_NOTE
    change_note ($EVENT_ID,$EVENT_NOTE + $shift)
    change_tune ($EVENT_ID,-$shift * 100000,1)
    $poly := $poly + 1
    if ($poly > 1)
        ignore_event ($EVENT_ID)
        note_off ($oldnoteid)
        $newdelayednote_id := play_note ($noteplayed,127,0,-1)
        change_note ($newdelayednote_id,$noteplayed + $shift)
        change_tune ($newdelayednote_id,-$shift * 100000,1)
        $poly := $poly + 1
    end if
    
    $oldnoteid := $EVENT_ID
    $oldnote := $EVENT_NOTE
    $oldnoteplayed := $noteplayed
end on

on release
    $poly := $poly - 1
end on

As I said, this plays perfectly. Notes shut off as they should and everything is cool. It's only the virtual Kontakt keyboard that displays stuck notes, and that only happens when $shift # 0. (The virtual keyboard is fine if there is no shift.)

If I make the $oldnoteid variable polyphonic, I stop getting stuck notes,but then the note_off command ceases to work, most likely because $oldnoteid is no longer the "old note."

Any thoughts on how to get that virtual keyboard to behave?
 
Hi Mike,

This is a very old problem with using change_note. Many years ago I found a couple of hoaky ways to fix this but right off hand I don't remember the details. Let me roll this around in my head for a while and I may be able to recall something. :roll: If I do, I'll post it (that is if no one else rises to the occasion in the meantime). :lol:

Rejoice,

Bob
 
You could have a 'ghost note' blip at zero velocity to turn it off - messy but it will work.

Justin
 
I seem to remember if you use a new play_note instead of a change_note, then this problem doesn't happen. But remember to adjust your poly count if you do that.
 
I finally remembered where I ran into this, it was when writing the orignal Ultra TKT Script. Both Justin and Dan have already provided the two key ideas that will allow you to circumvent this problem.

Simply stated, the problem arises because Kontakt apparently waits for a RCB of the 'original' note before it lifts the key on its display. So, when you change the original note in the NCB, the RCB that occurs when that note ends is now a different note.

In the UTKT I fixed this by using Justin's solution but in addition I muted the 'ghost' blip note. I think I used something like this is the RCB:
Code:
if shift # 0
  BlipID := play_note(EVENT_NOTE - shift,1,0,1)
  change_vol(BlipID,-200000,0)
end if

Dan's approach actually works the best unless for some reason you must use change_note. Since there are other problems with change_note, I pretty well stopped using it years ago in favor of just cloning and ignoring.

I might also mention that you can easily avoid change_note in your if Poly > 1 block by just adding Shift inside the play_note function itself.

Rejoice,

Bob
 
Well after a full day yesterday working on this (I freely admit I ain't the fastest ksp coder in town :mrgreen: ) I've finally got it going. Mostly.

Being the lazy type, my first try was the ghost blip note, but I just couldn't get it to eliminate the stuck notes either. Undoubtedly I did something wrong, but rather than spend any more time troubleshooting, I abandoned change_note in favor of Dan's "new play_note" method. I figure in the long run this would be cleaner anyway.

It took forever, though, as I discovered a lot of things you guys probably take for granted. For instance, after much hair pulling, I learned that the new play_note has to occur *after* I've enabled the group(s) I want. D-oh!

The one small problem I'm having is with the poly counter. ($poly keeps track of polyphony for my legato, so I can tell whether I'm playing the first note of a phrase, or if I'm in the middle of a phrase.) In legato situations, if I play fast and sloppy, my poly counter will sometimes be off by one (or more) and I'll wind up with $poly being a negative number (which should be impossible) or $poly sticking at 1 or 2 or 3 after all notes are released.

I can compensate for this with $poly self-corrections (not allow negatives, etc.) But I'd like to attack the root of the problem. Here's the relevant code, which even in this stripped down version will give occasional errant $poly counts:

Code:
on note
    disallow_group($ALL_GROUPS)
    $poly := $poly + 1
    $NEWEVENT_NOTE := $EVENT_NOTE - $shift
    ignore_event ($EVENT_ID)

    if ($poly > 1)
        {-------FIRST WE FADE OUT OLD NOTE--------}
        $tick := 1 
        while ($tick < 33)
            change_vol($oldnoteid,-2000,1)
            wait(1000)
            $tick := $tick + 1
        end while}
        note_off($oldnoteid)

        {------NOW PLAY NEW NOTE-------}
        $NEWEVENT_ID := play_note ($NEWEVENT_NOTE, 127, 0, -1)
        change_tune ($NEWEVENT_ID,$shift * 100000,1)
        $poly := $poly + 1
    end if
    
    if ($poly = 1)   {Start of phrase}
        allow_group($doo)
        $NEWEVENT_ID := play_note ($NEWEVENT_NOTE, 127, 0, -1)
        change_tune ($NEWEVENT_ID,$shift * 100000,1)
        $poly := $poly + 1
    end if
    
    $oldnoteid := $NEWEVENT_ID
end on

on release
    $poly := $poly - 1
    message("poly = " & $poly)
end on

It's worth noting that if take out that fade out while loop, the problem disappears and $poly is perfect. But I can't see why that would make it better.

The problem is pretty rare, mind you, because I have to play pretty fast and sloppy (multiple notes at once, which is not how legato should be played) to make it happen. But it does bug me. Anything stand out?
 
Hi Mike,

As you are beginning to discover, coming up with a 'bullet-proof' mono-mode (or solo-mode) logic design is anything but trivial. :shock: There are all kinds of potholes to fall into. Unfortunately right now, I'm very busy so I can't take too much time out for this sort of fun. :lol:

But, quickly looking at your posted example I think at least one of your problems is with re-entrance. During the wait of your old fade-out note what if a 3rd note comes in? Since you didn't include your declarations I don't know which vars might or might not be polyphonic so I can't be too precise in discussing this.

Suffice it to say that many, apparently simple solo-mode schemes work fine with clean input but they usually blow up when you take your finger and quickly brush the keyboard sideways producing a barrage of notes (many of them very short little blips). Whenever you think you have a solid design, give it that acid test for about 2 or 3 minutes continuously. If it never fails, you may have a solid design. If not, chances are there is a hole or two in your logic and it's usually related to unexpected re-entrance problems clobbering your non-poly-vars.

I know that this probably doesn't help you too much but right now but I wish you success.

God Bless,

Bob
 
Mike, sorry for steering you down that complex route. Maybe I can help with one more suggestion:

Something I've done previously if I only want to keep track of an actual pressed note count (and not worry about what the script generates) is vaguely like this:

Code:
on note
inc($poly)
$REAL_NOTE := $EVENT_ID
... blah blah blah, create other notes, etc
end on

on release
if ($EVENT_ID = $REAL_NOTE)
dec($poly)
end if
end on


Hope it makes sense... you only inc the poly on a real event, and only dec it on a real release. You ignore all the script generated stuff.

Hopefully not confusing matters further,
Dan

EDIT- $REAL_NOTE needs to be a polyphonic variable
 
+1 for the Ghost Note thing.

After some failed shemes to get my legato act as it should be, it can be useful to have 1 voice down for every note pressed down (at -200000 db if they are not "in focus").
So they became possible targets of your passionate legato playing :)

A target note can be the last note you press down, but if you press A-->B--->C notes, in this order, and you release them in C B A order, the legato should remodulate the pitches to follow the only notes that are still played. And if you press C A B notes and you release B there's should be a rule to let the script decide if it should go on the previous note (A) or the highest note (C). So Ghost Notes are the possible targets of your glide function.

I agree with Bob, about the difficulties of a good legato solution. You should keep track of every possibility in keyboard playing, to finally have a solution that just only plays in the natural way :)
Use the re-entrance possibility as a tool that you have to interrupt the glide in the middle of its action to re-target with the new note, and keep care of the release callbacks since they are important as the on note events (but they follow a bit harder logic).

I usually have a great help in writing a rushed rough script to test my initial idea, to check if there aren't KSP pitfalls that I didn't considered, and I usually rewrite it totally on paper, in a kind of metalanguage, where every step is a function, that should let me to see the concept on the whole.
 
Well, I wrote the last post but I didn't read your script. :-|

But, if your scheme is so simple why do you use that while, wait loop, instead of a fade_out function???

using fade_out($oldnoteid, 33000,1) should basically do the same thing and avoid the re-entrance..

Oh and if you want to wait 33000 before playing the next note, you can add a wait(33000) after the fade_out function. It still should avoid clashing between non-poly variables.
 
. . . But, quickly looking at your posted example, I think at least one of your problems is with re-entrance. During the wait of your old fade-out note what if a 3rd note comes in? Since you didn't include your declarations I don't know which vars might or might not be polyphonic so I can't be too precise in discussing this.
When I first read this, I got excited because I thought you nailed it. Indeed, $tick (and $interval, although I see now in this stripped down script, $interval is a superfluous variable) were *not* polyphonic. But alas, making them polyphonic didn't help. It needed to be done anyway, though, so I'm glad you brought it to my attention. 8)

I left out the ICB because I didn't want my example to appear too long. But here it is (before fixing the polyphonic declaration of $tick and $interval):

Code:
on init
    declare $doo
    $doo := find_group("Doo")
    declare $dooleg
    $dooleg := find_group("DooLeg")
    declare $poly := 0
    declare polyphonic $NEWEVENT_ID
    declare polyphonic $NEWEVENT_NOTE
    declare polyphonic $NEWEVENT_VELOCITY
    declare $oldnoteid
    declare $oldnote
    declare $oldnoteactual
    declare $actualnote
    declare $interval
    declare $tick
    declare $shift := 5
end on

As I said, making $tick and $interval polyphonic didn't help. Just for the heck of it, I made $oldnoteid polyphonic in a separate test, but as expected, that just made it so it didn't play monophonically. (The "old note" would no longer be an "old" note.)

What's weird is that the script, even in this simple form (exactly as I've posted it here,) will give me the errant poly count. Nothing is left out. So there just aren't that many variables to change to or from polyphonic.

Luckily, it's only occasionally that the poly count glitches, and it's only when the playing is especially sloppy and fast. So if worse comes to worst and I can't fix it, it's not that big of a deal. Especially because I have a couple $poly self-correction devices built into the real version of the script, so a user would likely never even know when a glitch occurs.

But it's still "bugging" me. :mrgreen:
 
Mike, sorry for steering you down that complex route.
Well, I no longer get stuck notes, which was my real problem, so I'm a grateful boy. 8)

Plus, I needed to do this anyway. My code is actually cleaner now, because this made some other elements a little more streamlined. (That's the main reason it took a whole day is that I did a lot of rewriting. I probably trimmed about 100 lines of code in the process.)

Funny, the whole reason for doing this "shift" thing in the first place was that I thought it would be a ridiculously easy feature to add. A simple way to double or triple the voice by simply stealing adjacent samples and retuning them. Heck, Kontakt even has a change_note command, so what could be easier? :mrgreen:

I like your $REAL_NOTE idea. Thank you for that. Simple, logical, . . . and in the story of my coding life, it introduces a new problem. :lol: If an old note gets released during that while loop fade out, two notes get played at once, since the if ($poly = 1) conditional will get called, even though the ($poly > 1) already got called as well.

I can easily fix that by using "else" instead of two separate "if" conditionals. In fact, I'm pretty sure my real script uses "else" anyway. But it was a funny unexpected consequence.
 
But, if your scheme is so simple why do you use that while, wait loop, instead of a fade_out function???
Because in the real version, there's more going on in that while loop, including change_tune and some other little tricks to make the legato transition smoother. In fact, it's really two overlapping while loops, because I do some fancy footwork on the fade-in as well. (Even sampled legato can use a little extra help to sound more natural.)

My example was just a bare bones stripped-down version that would still make the $poly count glitch happen. The volume change element was the simplest to include for demonstration purposes.

You make an interesting point about order of note ons and note offs. Mine is pretty basic and only cares about the order of note ons, but I can see a lot of advantage to your method.
 
Reviving an old thread here I see(lots of great stuff in it though!).


I'm just now experiencing this stuck note on the virtual keyboard phenomena in the latest version of Kontakt 5 - cant exactly pinpoint when it started to happen but is/has anyone else experienced this recently? Would love a quick fix if it's available - This only happens when I play legato notes on legato instruments for some strange reason...



Thanks!

Ryan :D
 
That's a known issue and it's in NI's bug tracker. Who knows when it's going to be fixed, though.
 
It's more than a decade later and I'm experiencing this on the latest version of Kontakt 7.9.0. Like Ryan Scully I too noticed that it only happens with legato patches. If anyone knows a fix, I'm all ears.
 
Legato patches of which library or libraries?

This issue can be remedied with proper use of set_key_pressed_state() which was introduced in K5.4. So it's down to the scripting.
 
set_key_pressed_state()

If anyone knows a fix, I'm all ears.

I had this issue two weeks ago (but not on legato patches, instead of it on polyphony between zones and groups - specifically chords), and it didn't work with set_key_pressed_state() (Kontakt 6.7). I ended changing ignore_event($ALL_EVENTS) thru ignore_event($EVENT_ID) on NCB and all worked right like expected.
 
Please ELI5 (explain like I'm 5) how to do this?
This isn't a fix. But this worked for me for my specifically case:

On the NOTE CALLBACK, if you are using ignore_event($ALL_EVENTS) you may track your release notes (notes off) and do your own RELEASE CALLBACK (a custom function). I didn't get to that; I've changed the ignore_event($ALL_EVENTS) to ignore_event($EVENT_ID) and it worked as expected.

Code:
on note
 ignore_event($ALL_EVENTS) {change this to --> ignore_event($EVENT_ID)}
end on
 
Top Bottom