Norns - MIDI note on / note off management

Hi! I’m trying to figure out what best practices for managing MIDI note on/off pairs might look like in the land of norns.

One pattern I’ve noticed is to maintain a table of active notes, sending note offs for anything in that table when a new note on is about to be sent. I haven’t experimented with it enough to confirm, but on first inspection this approach seems best suited for monophonic sequencers. It’s also handy for handling events like stopping a sequencer or changing devices.

What I’m working on now is meant to work polyphonically - a new note on event shouldn’t trigger a note off event. Further to that, each note on event can be of a variable duration. Here’s what I’ve come up with that seems to work:

function play_midi(note, vel, duration, chan)  
  m_out:note_on(note, vel, chan)
  clock.run(midi_note_off, note, duration, chan)
end

function midi_note_off(note, duration, chan)
  clock.sync(duration)
  m_out:note_off(note, 0, chan)
end

(Note that I do still keep a running table of active notes for stopping the sequencer, but I stripped it out here to get the core of what’s happening)

Does something like this look familiar to you? Or are you going about it differently? Please do tell! I’m hoping to pack this stuff away in a lib once I sort out the best approach so I can never think about it again. :slight_smile:

1 Like

Hi! Hope you’re well :slight_smile: Just wondering where you landed with this and if your conclusions led you in a different direction at all? Thanks!

1 Like

I am well thanks, hopefully the same applies to you. :slight_smile:

This is pretty much where I landed, though that isn’t an endorsement from me in the sense of “yes…this is the way”. It just seems to work fine for my needs, and the lack of replies to my question had me wondering if maybe it was a silly question and I was missing something obvious (or there was no glaring issue with what I had done). :man_shrugging:

1 Like

Ah cool! I’ll give it a go! Thank you. Maybe our dialog here will now precipitate a great inundation of knowledge and guidance from the broader community…

1 Like

i missed this one last year!

something to note about the code in the OP: if duration = 1, clock.sync(1) doesn’t give you a pause of 1 beat – it holds execution of the rest of the code until the next specified beat division occurs. so at 120bpm + 4/4 where a beat is 500 ms:

  • if play_midi is executed 10ms before the next beat, you’d get a 10ms note duration
  • if play_midi is executed 500ms before the next beat, you’d get a 500ms note duration

which, depending on how play_midi is being executed and whether you want note-offs to be quantized to the clock, could totally work!

if i’m going after “i want the duration of any played note to be x beats in duration regardless of when i play it”, then I’d use clock.sleep instead of clock.sync. something like:

function play_midi(note, vel, duration, chan)  
  m_out:note_on(note, vel, chan)
  clock.run(midi_note_off, note, duration, chan)
end

function midi_note_off(note, duration, chan)
  local note_time = clock.get_beat_sec() * duration
  clock.sleep(note_time)
  m_out:note_off(note, 0, chan)
end

nb. clock.get_beat_sec() will return the value of a single beat at the current clock tempo

which could also be a single function:

function play_midi(note, vel, duration, chan)  
  m_out:note_on(note, vel, chan)
  local note_time = clock.get_beat_sec() * duration
  clock.run(
    function()
      clock.sleep(note_time)
      m_out:note_off(note, 0, chan)
    end
  )
end

curious to hear how others have also solved these unique cases (and how many more unique cases there might be)!

3 Likes

Oooohh yep, that makes sense! Thanks @dan_derks!

edit: took a look at my code for fourtunes (which prompted this question last year) and it turns out I did end up using clock.sleep in the end.

I was wondering why it seemed to work even though everything you said made sense. What I’m not doing though, is calculating note_time. Time to dig around a bit more.

1 Like