MIDI scriptlets: Play lowest or highest note only (but remember held notes)

Sometimes you may want to have the lowest note you play to go a separate plugin/device, for example a bass plugin (of course :stuck_out_tongue:). You could use the filter-option in the MIDI-in block, but that one filters the other notes out and do not remember the ones that are held.

The same thing goes for playing only the highest note.

Just for fun and out of curiosity I wrote two scriptlets for playing the lowest c.q. playing the highest note, while remembering the held notes, so these can be played when the current lowest (or highest) note is released. These scriptlets are named ‘PlayHighestNote’ and ‘PlayLowestNote’

An example using PlayLowestNote:

  1. You play C3-E3-G3, the C3 will pass, but E3 and G3 are remembered
  2. You release C3. A Note Off will be sent for C3 and a Note On will be sent for E3 (the next lowest)

Another example using PlayLowestNote:

  1. You play E3-G3, the E3 will pass, but G3 is remembered
  2. You play C3 in addition. A Note Off will be sent for E3 and a Note On will be sent for C3 (the new) lowest), but E3 is being remembered
  3. You release C3. A Note Off will be sent for C3 and a Note On will be sent for E3 (the next lowest)
  4. You release E3. A Note Off will be sent for E3 and a Note On will be sent for G3 (the next lowest)

General guidelines:

  • The scriptlets are channel aware, so a Note On entering the scriptlet at channel 2 will leave the scriptlet (when appropriate) at channel 2.
  • The scriptlets are not multichannel: For example when you a Note On E3 at channel 2 (and it it only remembered) and after that a Note On E3 at channel 8, if the E3 becomes the lowest (or highest) note it will be sent at channel 8. The E3 at channel 2 will be lost. As this also goes for the Note Off messages, things might become a real mess. Use separate MIDI-in blocks or filters to circumvent this
  • AfterTouch and PolyTouch are respected: AfterTouch just passes, PolyTouch passes when it is the current playing note, otherwise the velocity of the remembered note is adjusted to have the velocity of the PolyTouch message. I’m not sure whether this is the desired behavior. I do not have equipment to really test this

I created a demo gig file. Screenshots:

afbeelding

(The buttons operates like a radio switch, but the implementation is rather rudimentary. If you try hard enough you might succeed to activate two buttons at a time. But after all, that’s not what this gig file is about.)

For people (only) wanting to see the script, this is ‘PlayLowestNote’:

var nt : NoteTracker

var NoteVelocities : integer[128]
var NoteChannels : integer[128]
var CurrentNote : int = 128
var CurrentChannel : int = 0

/*
 * This scriptlet filters out all notes except the lowest. All the notes, however, are
 * tracked and when the current lowest note is released the next lowest note from
 * the tracker starts. If all notes are released, of course no note is send
 *
 * The midi channels are recorded, but this scriptlet is not multichannel capable:
 * If first a D3 on channel 1 is tracked and after that a D3 on channel 3 is coming in,
 * the D3 on channel 1 is lost.
 * To solve this, use a separate scriptlet for each channel
 */


Initialization
    SetInfoMessage("MIDI Filter for only playing the lowest note")
    SetDisplayMessage("MIDI Filter for only playing the lowest note")
End

On NoteOnEvent(m : NoteMessage)
var heldNotes : integer array
var newLowestNote : int
var noteOff : NoteMessage

    NoteVelocities[GetNoteNumber(m)] = GetVelocity(m)
    NoteChannels[GetNoteNumber(m)] = GetChannel(m)

    NoteTracker_GotNote(nt, m)
    
    heldNotes = NoteTracker_GetHeldNotes(nt)
    newLowestNote = SmallestInt(heldNotes)
    
    if newLowestNote != CurrentNote
    then
        if CurrentNote < 128
        then
            noteOff = MakeNoteMessageEx(CurrentNote, 127, CurrentChannel)
            SendNow(ReinterpretAsNoteOffMessage(noteOff))
        end
        SendNow(MakeNoteMessageEx(newLowestNote, NoteVelocities[newLowestNote], NoteChannels[newLowestNote]))
        CurrentNote = newLowestNote
        CurrentChannel = NoteChannels[newLowestNote]
        SetDisplayMessage("Current: " + NoteNumberToNoteName(newLowestNote))
    end
End

On NoteOffEvent(m : NoteMessage)
var heldNotes : integer array
var newLowestNote : int
var noteOff : NoteMessage

    NoteTracker_GotNote(nt, m)
    heldNotes = NoteTracker_GetHeldNotes(nt)
    
    if Size(heldNotes) > 0 
    then
        newLowestNote = SmallestInt(heldNotes)
        if newLowestNote != CurrentNote
        then
            noteOff = MakeNoteMessageEx(CurrentNote, 127, CurrentChannel)
            SendNow(ReinterpretAsNoteOffMessage(noteOff))
            SendNow(MakeNoteMessageEx(newLowestNote, NoteVelocities[newLowestNote], NoteChannels[newLowestNote]))
            CurrentNote = newLowestNote
            CurrentChannel = NoteChannels[newLowestNote]
            SetDisplayMessage("Current: " + NoteNumberToNoteName(newLowestNote))
        end
    else
        SendNow(m)
        CurrentNote = 128
        SetDisplayMessage("MIDI Filter for only playing the lowest note")
    end
end

On ControlChangeEvent(m : ControlChangeMessage)
    SendNow(m)
End

On PolyTouchEvent(m : PolyTouchMessage)
    NoteVelocities[GetPolyTouchNoteNumber(m)] = GetByte(m, 2)    
    if GetPolyTouchNoteNumber(m) == CurrentNote
    then
        SendNow(m)
    end
End

On AfterTouchEvent(m : AfterTouchMessage)
    SendNow(m)
End

The scriptlet ‘PlayHighestNote’ is almost the same.

The gig file itself:
MonoPhonicMidi.gig (83.2 KB)

Have fun with it. :beers:

7 Likes

Really good idea and effort! I set this up as a bass enhancement with keys using two midi controller inputs. Specified a limited key range for the bass midi controller. BTW: the midi controller specified in the Frank’s gig file is transposed to +36, just to let anyone know. Again, great job.

1 Like

Ah. Yes. Good catch. My midi-controller that I usually use controls drumpatterns in MDrummer. Typically c-1 thru g-1, so to make the Hammond somewhat sounding right I transposed +36.

In the end the user is supposed to save the scriptlet plugins to the favorites and load it from there in his/her gigfile

1 Like

Thanks so much for this script! Quick question - I occasionally get hanged notes on the lowest note, which doesn’t resolve unless I restart Gig Performer. Happened today live, of which I had to turn off the plugin which my lowest note was assigned to - during the middle of the session (which resulted in a slight panic moment). Any thoughts on why?

Is that while switching rackspaces or variations?

No. Just playing the notes.

All notes are using the same channel? If you’re not sure, there’s a MIDI filter for that.

That’s a good suggestion. Will try it and get back. Thanks!