-
-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sending synchronized midi clock to midi port #67
Comments
Great idea! I need to think how to implement it... |
Btw, I ran some tests with Reaper, to see which messages it sends when clock is enabled: Reaper clock dump: // I press play with song pointer being at the start (tick 0 basically):
Realtime(Start) // Sent because song pointer is at tick 0
Realtime(TimingClock) // Sent at 24 PPQN
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(Stop) // I pressed pause
Common(SongPosition(u14(0))) // After pressing pause, Reaper's song pointer jumps back to where it was before I pressed play, and communicates this to the clock consumer via the `SongPosition` meta message (telling a connected device how many 16th notes have elapsed since the beginning of a song)
Common(SongPosition(u14(16))) // In Reaper I manually set the song pointer to one bar after start (16 16th notes = 1 bar)
// I press play: Reaper sends port reset messages
channel: u4(0), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(0), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(1), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(1), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(2), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(2), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(3), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(3), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(4), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(4), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(5), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(5), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(6), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(6), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(7), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(7), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(8), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(8), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(9), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(9), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(10), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(10), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(11), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(11), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(12), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(12), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(13), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(13), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(14), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(14), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(15), message: Controller { controller: u7(120), value: u7(0) }
channel: u4(15), message: Controller { controller: u7(123), value: u7(0) }
channel: u4(0), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(1), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(2), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(3), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(4), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(5), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(6), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(7), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(8), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(9), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(10), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(11), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(12), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(13), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(14), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(15), message: PitchBend { bend: PitchBend(u14(8192)) }
channel: u4(0), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(1), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(2), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(3), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(4), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(5), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(6), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(7), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(8), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(9), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(10), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(11), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(12), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(13), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(14), message: Controller { controller: u7(64), value: u7(0) }
channel: u4(15), message: Controller { controller: u7(64), value: u7(0) }
Realtime(Continue) // Then it sends `continue` instead of `start`, because it plays from SPP that's not at tick 0! (see below)
Realtime(TimingClock) // Playing...
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(Stop) // I paused again
Common(SongPosition(u14(16))) // Reaper moves the SPP moves back to where it was before starting playback
---
// Now testing looping behavior:
Realtime(TimingClock) // Playing, SPP is shortly before loop end
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(Stop) // At loop end, Reaper sends Stop
Common(SongPosition(u14(16))) // Reaper jumps to loop start
Realtime(Continue) // Reaper tells clock consumer to continue from given SPP
Realtime(TimingClock) // Playing...
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(TimingClock)
Realtime(Stop) // I paused playback
Common(SongPosition(u14(16))) // Reaper moves the SPP moves back to where it was before starting playback I found a good explanation here: http://midi.teragonaudio.com/tech/midispec/seq.htm
|
Hi, thanks for this great library! I'm using it to send midi to VCV Rack, and I need to send clock messages in sync with the midi playback at 24 clocks per beat, using the Impromptu clocked module as slave in VCV like this:
But JZZ Player currently doesn't send any clock. Is there a way to register a custom callback/hook for every time the midi player advances its state, so that I can pass a function that sends clock signals when each next clock frame is reached?
Before switching to JZZ, I was using my own code (Rust compiled to wasm) where I was successfully sending clock like this (called in my player's step function, which accounts for any number of ticks being passed since the last call):
(The initial value of
midi_beat_clock_frame
is-1
so that it already sends the first clock on tick 0.)That way, I can have my other modules (e.g. delay or rhythmic stuff) in VCV synced to my midi file's grid.
JZZ seems great, it even properly takes into account tempo change meta messages, which other JS midi players don't do, and it seems it also responds to RPNs for setting pitchbend range, which is great. Also I like that it supports all GM instruments, timidity doesn't.
Just the one thing missing is the midi clock :)
I'm not suggesting sending clock by default, but it could be either built-in/optional (with constructor option flag) or via a custom
onStep
callback (such that the callback can access the player instance and send a msg to it that will get sent still in this current frame!).Btw, I also like that JZZ Player supports jumping to a tick position, other js midi players don't.
In my use case, I set a custom loop (not looping the full song but custom section) so I need this jumping functionality.
Although I noticed if the loop len is not a beat/bar multiple, it messes up the clock sync in VCV.
So I need to figure out how to reset the clock properly on a jump. Maybe by sending a clock
stop
message when the playhead reaches the loop end and sending a clockstart
message when after jumping the playhead reaches a beat or bar boundary (in most cases, the jump target tick is such a boundary because loop len will be quantized to beats/bars).If you know anything about midi clock, I'd appreciate if you could tell me how to handle this situation properly :)
But this is only my use case, I don't expect JZZ to support non-full-song looping because I can implement it on top of its API.
I just mention this use case because it affects how the clock messages need to be sent.
The text was updated successfully, but these errors were encountered: