Live loop is missing cues

Hi there, I’m trying to get MIDI chords working, but there’s a strange behavior about cues missing / ignored. I have a live_loop for event handling:

set :synth_notes, {}
set :synth_amp, 0

# process single event on each cue
#
live_loop :state_manager do
  use_real_time
  event = sync :update

  nodes = get(:synth_notes, {}).to_h.clone

  case event[:action]

  # midi keys on
  when :add
    puts “[Add] #{event[:key]}”
    nodes[event[:key]] = event[:value]

  # midi keys off
  when :remove
    puts “[Rm] #{event[:key]}”
    node = nodes[event[:key]]
    if node
      # amp slide to 0 and kill node
      stop_synth_id(node)
      nodes.delete(event[:key])
    end
  end

  set :synth_notes, nodes
end

And I have midi keys listening:


live_loop :midi_note_on do
  use_real_time
  note, vel = sync “/midi:keys/note_on”

  s = synth_fx(note)
  control s, amp: get(:synth_amp)

  cue :update, action: :add, key: note, value: s
end

live_loop :midi_note_off do
  use_real_time
  note, vel = sync “/midi:keys/note_off”

  cue :update, action: :remove, key: note
end

From log I could see, when I hit two keys then release, they were both cued -

 /midi:minilab3_midi:1/note_off  [65, 0]
 /cue/update  (map action: :remove, key: 65)
 /live_loop/midi_note_off        []
 /midi:minilab3_midi:1/note_off  [69, 0]
 /cue/update  (map action: :remove, key: 69)
 /live_loop/midi_note_off        []
 /set/theremin_synth_notes       (map 69 => #<SonicPi::SynthNode @id=125, @name=sonic-pi-beep @state=running>)
 /live_loop/state_manager        []

But only one note_off was handled by event_manager! Log be like:

{run: 4, time: 1.4929, thread: :live_loop_state_manager}
 └─ "[Add] 69"
 
{run: 4, time: 1.5017, thread: :live_loop_state_manager}
 └─ "[Add] 65"
 
{run: 4, time: 1.7231, thread: :live_loop_state_manager}
 ├─ "[Rm] 65"
 ├─ control node 135, {amp: 0, amp_slide: 0.5}
 └─ "kill node: #<SonicPi::SynthNode @id=135, @name=sonic-pi-beep @state=running>"
 
{run: 4, time: 2.2231, thread: :live_loop_state_manager}
 └─ killing sound 135
 
=> Stopping all runs...

Why did it ignore a second cue?

Alright, my solution for that is creating 127 threads to listen on different cues. I couldn’t make a thread-safe queue work. e.g.

live_loop :midi_note_on do
  use_real_time
  note, vel = sync “/midi:minilab3_midi:1/note_on”
  cue “piano_#{note + 12}_on”
end

live_loop :midi_note_off do
  use_real_time
  note, vel = sync “/midi:minilab3_midi:1/note_off”
  cue “piano_#{note + 12}_off”
end

Then use a method to create a thread on a single key, handling key on and off - then repeat for 127 keys.

define :create_note_handler do |fx, i|
  live_loop “#{fx}_key_#{i}” do
    use_real_time
    sync "#{fx}_#{i}_on"

    s = synth :piano, i
    control s, amp: 1
    set "#{fx}_note_#{i}", s
  
    sync "#{fx}_#{i}_off"
  
    stop_synth_id(s)
    set "#{fx}_note_#{i}", nil
  end
end

(1..127).each do |i|
  create_note_handler(“piano”, i)
end

Only in this way it doesn’t skip cues when two of them arrive at nearly the same time.