Triggering from midi - is this example robust?

Could I get a quick thumbs up or nudge in the right direction?

The use case is: A remote computer running loops on a DAW. It broadcasts a midi note over the network using rtpMidi, at the start of a phrase, and want Sonic Pi loops to start up in time. My code below works fine - my question is if this is a robust way to do it. I don’t want to be relying on a fudge. In use, the loops start up a bit out of time (understandably) and settle down after just a couple of beats.

This is a crucial thing for me, if it’s good then all the doors are open to use Sonic Pi in a combo.

I’m using time_warp to manage the total latency. Trial and error gave me 530ms which seems very long. I’ve got a bit a of laggy setup here but normally around 50ms with other software.

Thanks in advance for any advice.

live_loop "miditrig" do
  use_real_time
  #Wait for the midi note from the DAW
  sync "/midi:rptmidi_remote:0:1/note_on"
  time_warp rt(-0.53) do
    cue "start"
  end
end

live_loop "main", sync: "/cue/start" do
  play :E6, release: 0.1
  #...Cue all the other loops...
  sleep 4
end

Extra bit of info. This works for midi channels 1 to 9 but not 10 to 16. I see the cues arrive in the cue window but they don’t trigger the sync.

Hi there,

if you’re trying to synchronise Sonic Pi’s internal synths with an external sound source (either from another app on the same computer or from an external sound source) you need to be aware that Sonic Pi, by default adds 500ms of latency to all synth and MIDI events. This is likely what you’re hearing. Of course, this is in addition to the latency of your sound device which will vary depending on your hardware and settings.

This extra 500ms latency enables Sonic Pi to work correctly on very low powered machines. You can reduce it (this is essentially what use_real_time does) but that’s not always advisable as it can cause Sonic Pi to “run out of time” much more easily. It’s also important to point out that use_real_time only affects the current thread, so some threads can not have this latency applied and others have the default.

In your example the miditrig live loop has no latency applied, but that isn’t affecting it as it’s not sending any sound events (play / synth) or MIDI events midi). So it’s not needed here.

When you receive the note_on event, you rewind the clock 530ms and send an event. This obviously is physically impossible as you can’t actually go back in time. What it instead does is write an event into the Time State with an old time.

The main live loop waits for the start event. After it is written by the miditrig live loop, it is immediately available - but time still hasn’t gone backwards as it can’t. However, the main live loop has its clock set back in the past to match the time that the start was sent which is in the past. This means that the main live loop is now late as its clock points to a time that is behind the actual time in the real world.

The main loop continues to spin round, attempting to catch up with the real time but still adding the 500ms of latency as it is not in real time mode. This is why you initially hear things late but then slowly catch up.

To fix this you ideally want to send an event from your external DAW before you want things to happen. This is similar to a band counting in. The leader of the band doesn’t just say “PLAY!”, they typically say, “Ready? 3, 2, 1, Play!”.

If you can send an event one bar ahead of time, then this will all be much easier.

Either way, you don’t actually need the miditrig live loop unless you want to do multiple cues during a performance. This would suffice:

num_beats_in_bar = 4
sync "/midi:rptmidi_remote:0:1/note_on"
time_warp rt(-0.53) + num_beats_in_bar do
  cue :start
end

live_loop :main, sync: :start do
  play :E6, release: 0.1
  #...Cue all the other loops...
  sleep 4
end

Perfect, thank you, I can work with that.

I found an interesting effect using your example of the cue not being in a loop. When I make some live changes in the following loops on that page, they don’t get played unless/until the midi note comes to cue it again. I think that makes sense - it’s about tuning into your model.

I want to optionally cue from broadcast midi or keyboard or start right away so I might put the control code on page 0 (if they are called pages, panels, whatever) and the musical code on other pages. That seems to avoid the problem, as the other pages seems to refresh/reload independently.

1 Like

Great, thanks, I’m taking ‘like’ to mean ‘this is correct’ :smile:

Ok, I’m all good to go. I had another epiphany while doing this bit - I can do away with the DAW and do everything in Sonic Pi. The DAW is just running pre-composed phrases collected in ‘scenes’ in session mode. Its clever thing is to trigger the phrases in tempo - but Sonic Pi does that and with much more flexibility and control than any GUI can ever provide.

All systems go, many thanks for all the help.