MIDI clock stability and usability improvements #240
Conversation
…put to correctly refresh MIDI message handler when no MIDI input was selected at startup
… we've reached the end of the `Midi.inputs` array. Now relying on `midi.selectInput` to do all the work, including setting `Midi.inputIndex` - this make sure an existing MIDI message handler is removed from the previous input device before we switch to the next one (or no input device in case of index -1)
|
Also tested these changes in the browser (Chrome), seems to work fine too |
|
Nice! But, are you able to get the frames to advance once the clock is running and playing? I see Orca reporting received start and stop messages, but the frames are not increasing. Looking at the code it never gets out of this.tap = function () {
pulse.last = performance.now()
if (!this.isPuppet) {
console.log('Clock', 'Puppeteering starts..')
this.isPuppet = true
...
}
if (this.isPaused) { return }
... // on every 6th pulse
client.run()But this.play = function (msg = false) {
console.log('Clock', 'Play', msg)
if (this.isPaused === false) { return }
if (this.isPuppet === true) { console.warn('Clock', 'External Midi control'); return }
this.isPaused = false |
|
BTW thank you for leaving fddfba6 in the history, I was looking for an example :) |
|
Yep, when I do this it works: --- a/desktop/sources/scripts/clock.js
+++ b/desktop/sources/scripts/clock.js
@@ -59,8 +59,8 @@ function Clock (client) {
this.play = function (msg = false) {
console.log('Clock', 'Play', msg)
if (this.isPaused === false) { return }
- if (this.isPuppet === true) { console.warn('Clock', 'External Midi control'); return }
this.isPaused = false
+ if (this.isPuppet === true) { console.warn('Clock', 'External Midi control'); return }
if (msg === true) { client.io.midi.sendClockStart() }
this.setSpeed(this.speed.target, this.speed.target, true)
}
@@ -68,8 +68,8 @@ function Clock (client) {
this.stop = function (msg = false) {
console.log('Clock', 'Stop')
if (this.isPaused === true) { return }
- if (this.isPuppet === true) { console.warn('Clock', 'External Midi control'); return }
this.isPaused = true
+ if (this.isPuppet === true) { console.warn('Clock', 'External Midi control'); return }
if (msg === true || client.io.midi.isClock) { client.io.midi.sendClockStop() }
client.io.midi.allNotesOff()
this.clearTimer() |
…in Puppeteering mode (thanks @unthingable!)
|
Hi @unthingable, for me the frames started advancing as soon as I sent Your changes in Thanks! |
|
Thanks! I think the behavior you were seeing initially could be because Orca starts up already running, and I was pausing mine. This PR is just in time for this discussion: #219 |
|
One minor annoyance is "clock start -> play" picks up the frame count where it left off and that is 6 times more likely to be out of sync with clock source. I think it makes more sense to reset the frame count. Giving it a try... |
|
Ok, it's a little wonky but it works: this.tap = function () {
pulse.last = performance.now()
if (!this.isPuppet) {
console.log('Clock', 'Puppeteering starts..')
this.isPuppet = true
this.clearTimer()
pulse.timer = setInterval(() => {
if (performance.now() - pulse.last < 2000) { return }
this.untap()
}, 2000)
}
if (this.isPaused) { return }
if (pulse.count === 0) {
client.run()
}
pulse.count = (pulse.count + 1) % 6
}
this.play = function (msg = false) {
console.log('Clock', 'Play', msg)
if (this.isPaused === false) { return }
this.isPaused = false
if (this.isPuppet === true) {
pulse.count = 0 // works in conjunction with `tap` advancing on 0
this.setFrame(0) // make sure frame aligns with pulse count for an accurate beat
console.warn('Clock', 'External Midi control')
return
}
if (msg === true) { client.io.midi.sendClockStart() }
this.setSpeed(this.speed.target, this.speed.target, true)
}This assumes clock start/continue is always quantized at the source (starting on a beat), if not then there is also SPP to get an offset from. Interesting note about Bitwig's SPP implementation: when looping with SPP enabled it will send "stop" then SPP then "start" again. Without SPP it sends no stop/start, the clock just keeps going. Don't know if that's standard, haven't checked other hosts yet. Once the clock is going steady the timing sounds internally consistent and solid. What's wonky is "start -> play" can be off by a random amount. Adjusting clock out offset at the source will bring it in sync and it will stay there until the next stop+start, then it will be off again. I wonder if this has something to do with Although... playing with it a bit more, Bitwig driving Orca at 110bpm I'm getting pretty consistent results with no SPP enabled and clock out offset at 35-40ms, even after stopping and restarting Bitwig playback — the first beat in Orca is off but the rest are fine. With SPP same is true with the same but negative offset, -35 - -40ms, and the first beat will be off every time it loops. With SPP off it's pretty usable! |
|
Happy to PR the above into this branch or master. |
@unthingable Sounds good! It would be great to have predictable MIDI clock syncing in Orca. I'm personally using TidalCycles to send the MIDI clock messages, and I also noticed the drifting of the beat alignment. I guess creating a PR on this branch instead of master would make testing easier. |
|
@njanssen done: njanssen#1 (with an additional improvement: stopping notes while in clock slave mode) |
- play() will reset both frame and pulse counts to 0, for alingment - stop() will silence - tap() will update immediately upon play(), not on the next frame
|
@njanssen I've been playing with my version for a couple of days now, no drifting beats. |
Looks good! I found an issue when the user pauses/resumes Orca using |
|
@njanssen thanks! I see your comments in my PR, will respond there. |
|
@njanssen my fixes are ready to merge, @neauoire will be testing and merging this this weekend. Summary of incoming changes:
This collaborative PR may need an updated title. |
MIDI clock slave mode: fix timing and note-off behavior
|
I can't wait to give this a try, you're amazing u know <3 |
|
For posterity, a fun mod for those wanting 16th notes without doubling the source bpm: diff --git a/desktop/sources/scripts/clock.js b/desktop/sources/scripts/clock.js
index 95086cf..3a29be5 100644
--- a/desktop/sources/scripts/clock.js
+++ b/desktop/sources/scripts/clock.js
@@ -5,6 +5,7 @@
function Clock (client) {
const workerScript = 'onmessage = (e) => { setInterval(() => { postMessage(true) }, e.data)}'
const worker = window.URL.createObjectURL(new Blob([workerScript], { type: 'text/javascript' }))
+ const pulsePerFrame = 3
this.isPaused = true
this.timer = null
@@ -65,7 +66,7 @@ function Clock (client) {
if (!pulse.frame || midiStart) { // no frames counted while paused (starting from no clock, unlikely) or triggered by MIDI clock START
this.setFrame(0) // make sure frame aligns with pulse count for an accurate beat
pulse.frame = 0
- pulse.count = 5 // by MIDI standard next pulse is the beat
+ pulse.count = pulsePerFrame - 1 // by MIDI standard next pulse is the beat
}
} else {
if (msg === true) { client.io.midi.sendClockStart() }
@@ -97,7 +98,7 @@ function Clock (client) {
}
this.tap = function () {
- pulse.count = (pulse.count + 1) % 6
+ pulse.count = (pulse.count + 1) % pulsePerFrame
pulse.last = performance.now()
if (!this.isPuppet) {
console.log('Clock', 'Puppeteering starts..') |
|
This works wonders! |


Hi!
I ran into an issue in the Electron version of Orca where MIDI clock messages were never processed when Orca started with no initial input MIDI device selected. After switching to the next MIDI input device with
CmdOrCtrl+,(in my case selectingIAC Driver Bus 1) the MIDI message handler (Midi.receive) was not attached resulting in Orca not being notified of the clock messages received from this device.I resolved this problem by relying on the logic in
MIDI.selectInput(which sets the new input device index and MIDI message handler) inMIDI.selectNextInputwhen switching between input devices. I also updated the wrapping logic so index -1 (no input device) is selected when the user reached the end of the array with available input devices (Midi.inputs).