Closed robbielyman closed 1 year ago
raaaaad, omg you just casually rewrote a fundamental component of the system. wiiiiiild, thank you for all of your work!!!
m = midi.connect(1)
function init()
params:set('clock_source',2) -- '2' is midi, change to '1' for internal, '3' for link
end
function start_clock()
step = 0
print("started", clock.get_beats())
test_clock = clock.run(function()
while true do
step = util.wrap(step + 1,1,16)
print("step", clock.get_beats(), step)
m:note_on(60, 127, 1)
clock.sync(1)
m:note_off(60, nil, 1)
end
end)
print("running clock: "..test_clock)
end
function clock.transport.start()
start_clock()
end
function clock.transport.stop()
print("stopped, cancelling clock: "..test_clock)
clock.cancel(test_clock)
test_clock = nil -- cleans up the variable
end
in Live, i'm using a Global Launch Quantization of 1 Bar
.
there seems to be a double-tap at transport start, where the clock action is called just before the beat i'd expect it to sync to and then on the beat i'd expect it to sync to.
so if i'm running a step sequencer, i'd get steps 1 and 2 blasted out on the first beat:
> are we playing: true
started 11.995096
step 11.995336 1 -- unexpected premature sync
running clock: 3
step 12.000256 2 -- expected sync
step 13.000252 3
step 14.000394 4
the MIDI out stream looks like this, where the first step gets two triggers:
re: link being behind the beat, i'm only seeing 1/256th distance in terms of the tick-to-MIDI-note journey, which feels totally workable imo!
everything looks good at transport start! there's only one, expected clock-sync fire at start, which is perf! the starting note is just barely 1/256th late, but that seems workable.
as the sequence continues, it looks like each clock.sync(1)
is firing pretty on-schedule in seamstress:
started 0.016608001062912
step 0.03802406643354 1
running clock: 3
step 1.0005250579021 2
step 2.0003966156531 3
step 3.0002447873308 4
step 4.0004181672096 5
step 5.0003806542426 6
step 6.0003360181437 7
step 7.0003239396711 8
step 8.0003593726645 9
step 9.0002515749693 10
step 10.000345627056 11
step 11.000401361572 12
step 12.000330216396 13
step 13.000372982103 14
step 14.000367824291 15
step 15.000355609877 16
but in Live, I'm seeing each of those notes pop in around 1/128th early:
on the first first ever ever time seamstress receives a midi start message, if it hasn't been receiving clock beforehand, the midi clock is in a sad and sorry state, so takes a couple beats to figure itself out. notably this isn't a problem if one stops and restarts quickly?
i'm not seeing this, i don't think? unless, is that the 0.03802406643354
-stamped first beat in my MIDI results above?
reset
if i load the test script on internal clock, i'd expect that hitting the reset
action in the params would kickstart the sequence, but i'm only getting two steps before the clock seemingly stops executing its action:
started 0.3130976277506
step 0.31325825225076 1
running clock: 3
step 1.0003098784378 2
...
if i keep the script running, a minute later two additional notes occur, beat-stamped as if no time had passed!!
step 1.010734698167 3
step 2.0003111981566 4
this 'wait 30-60 seconds and another step or two will eventually happen' is consistent.
small potatoes, but if i execute quit
after this sketch runs, i get a seq fault:
Segmentation fault at address 0x1020cc000
Panicked during a panic. Aborting.
zsh: abort seamstress scratch.lua
shweeee!! thank youuuuuu, please lmk if i can test anything else!
hiiiii, thank you very much for this thorough feedback! i think i managed to fix up the major outstanding issues; notably the internal clock should be working, having seamstress hooked up to midi should no longer cause start
s and stop
s to propagate to lua when they oughtn't, etc.
huh, i guess if the midi clock is stopped then seamstress just maths it out as though it is running at the tempo it was previously running at—that's just an accidental side effect of the current design. love it. it doesn't even cause anything more than a potential blip in the displayed tempo when the clock restarts. neato.
I don't really know what to do about midi coming in early with certain settings of latency in Live: since MIDI runs at 24 PPQN, i can only really tune by 1/96ths? the fact that it changes when i change the buffer size in Live leads me to suspect that it might have more to do with how Live handles MIDI than anything else.
I believe I corrected the segfault issue, but i'm not 100% sure. let me know if it pops back up.
Anyway, I'd love to get your eyes and ears on this revision // please let me know if there are more things I should take care of before merging!
rad rad rad, happy to help however i can!! you're cruising thru everything so impressively, thank you!! sorry for raising new issues, but hopefully these'll be helpful ? :grimacing: ❤️
i'm seeing some double-steps with the internal + MIDI clocks (not Link, though!), which i've not seen before.
if i run the script and execute start_clock()
in the REPL, the whole-beat steps will spin up fine, but eventually there's a double-step, eg:
step 59.000270428996 12 -- fine
step 60.000275741339 13 -- fine
step 61.000268744495 14 -- fine
step 61.99158680683 15 -- early sync, unexpected hit
step 62.000176556839 16 -- expected hit, but is technically a double-step
step 63.000286621495 1 -- should be step 16
step 64.000283997994 2 -- should be step 1
all starts well, but if i let the script run while receiving external clock, i run into a weird bottleneck where the seamstress clock process stops for a while, and then picks back and prints a bunch of (what were otherwise missed) steps when it resumes:
step 326.00026992385 7 -- fine
step 327.00033956826 8 -- this is the last step i hear for a while
-- all of a sudden, these print:
step 328.1524936761 9
step 329.11717279802 10
step 330.10820007178 11
step 331.12215861847 12
step 332.10995063415 13
step 333.10845499743 14
step 334.13481299618 15
step 335.11052394333 16
step 336.11650430202 1
step 337.11592005183 2
step 338.10485102715 3
step 339.10463220686 4
step 340.10176894328 5
step 341.10711144081 6
step 342.11687629652 7
step 343.11059396582 8
step 344.11452288424 9
step 345.10559611026 10
step 346.19267783526 11
-- and we're back to a regular tick!
step 347.00034684346 12
step 348.0003867306 13
step 349.00048527183 14
step 350.00050683949 15
what it looks like on Live's side:
this was reproducible pretty consistently with 128
sample latency.
after running a few bars of the example script with Link clocking, I'm getting full-on crashes with 128 sample latency:
> thread 4277119 panic: integer cast truncated bits
<filepath>/seamstress/src/clock.zig:178:39: 0x102ff59bf in loop (seamstress)
self.beat.beat_duration = @intCast(@as(i128, micros - now) * std.time.ns_per_us);
^
/opt/homebrew/Cellar/zig/0.11.0/lib/zig/std/Thread.zig:412:13: 0x102fcbc9b in callFn__anon_35753 (seamstress)
@call(.auto, f, args);
^
/opt/homebrew/Cellar/zig/0.11.0/lib/zig/std/Thread.zig:680:34: 0x102f939f3 in entryFn (seamstress)
return callFn(f, @as(Args, undefined));
^
???:?:?: 0x180a57fa7 in ??? (libsystem_pthread.dylib)
???:?:?: 0xef70000180a52d9f in ??? (???)
zsh: abort seamstress scratch.lua
doesn't seem to happen with 512 sample latency!
i'm seeing that if i run a session at 120bpm (with 512 sample latency), the Link session tempo will automatically change (not just momentary fluctuations beat-to-beat, but just permanent changes for many bars) without my input -- eg. dropping down to 117bpm or speeding up to 123bpm. this reflects both in seamstress's clock menu and in Live. in my most recent run, it took 250 bars to get back to 120bpm, but then around 400 bars the clock jumped up to 134bpm.
i wasn't able to reproduce the double trigger... are you still seeing it consistently?
i think some instability and certainly the crash was caused by time somehow not being monotonic; i added a pair of guards to avoid this.
are kind of stumping me; they're even happening when the clock is set to link. maybe it's my impatience, but it often seems like the clock just "dies" and only restarts if i stop and start Live's transport. aha, i added the following to my init()
clock.run(function()
local last = clock.get_beats()
while true do
clock.sleep(1)
local beat = clock.get_beats()
if beat < last then
print("hey, did we just reset to " .. beat .. "???")
end
last = beat
end
end)
and the next time the dropout happened i see that lo and behold, we have somehow gotten hey, did we reset to -1034.522281???
which is pretttttttttttty extreme and also very confusing. i added some code to the Link thread to hopefully gracefully handle this situation. can you add this snippet to your test script and see if you can reproduce the issue you were having with MIDI?
i noticed that if i have the seamstress windows semi-minimized with macOS stage manager, and then i go and interact with them while the clock is running, i get beat inconsistencies. i think this is because the "yo, let's check for screen events" function is somewhat heavy. probably the correct way to deal with this is to shunt off a lot of the actual application code (including the main event loop) to a separate thread and let the main thread just be in charge of carefully tending to the poor needy lil screen... lol. anyway, that's certainly outside the scope of this PR.
oh rightrightright I forgot about this: fundamentally i think the issue is that there's a feedback loop going on where seamstress has a clock that's periodically saying "hey, set the tempo to the current tempo" so if link's tempo fluctuates for some reason, seamstress will then broadcast that fluctuation back through link to live, and the process can repeat... would love your thoughts on how to short-circuit this.
oh jkjk let me just use silent
if source == "link"
that's easy enough
ayyyy, rad rad rad, hope you had a good AM :)
196.9912682608
AND at beat 197.0002
) are now not double-triggering, so calling this fixed! raaaaad.clock.sync
s also stayed consistent!! siiiiiick, amazing solve!!!thank youuuuuuu!!!!
fwiw, i'm not seeing these with Link or internal -- only MIDI clocking. ran 1k steps with all three and MIDI gunked up around step 325.
i popped that snippet into my init()
, but it only fired off at the start of the script:
started 0.000163916
step 0.000360166 1
running clock: 4
step 1.0002078388763 2
hey, did we just reset to 1.2722905156914???
step 2.0002797185872 3
step 3.000283573243 4
when i experienced the bottleneck, nothing reported:
step 323.00026225344 4
step 324.00023200723 5
-- hiccup //
step 325.09089484215 6
step 326.14295016612 7
step 327.12255481728 8
step 328.12094019934 9
step 329.10857364342 10
step 330.10801993356 11
step 331.11125027686 12
step 332.11120376524 13
step 333.11461794021 14
step 334.09952934663 15
step 335.15794573644 16
step 336.1057131783 1
step 337.12080177188 2
step 338.12440088594 3
step 339.10705094131 4
step 340.11992469547 5
step 341.10146733113 6
step 342.10428239203 7
step 343.09920708749 8
step 344.10289811739 9
step 345.09754595793 10
-- // hiccup
step 346.00022238732 11
step 347.00030195148 12
step 348.00023270855 13
step 349.00023397989 14
step 350.00026268006 15
step 351.00031435078 16
step 352.00028832297 1
step 353.00046158341 2
step 354.00037167337 3
step 355.00029624143 4
on a hunch, i recompiled seamstress in release mode (-Doptimize=ReleaseFast
) and was suddenly unable to reproduce this issue; i let the MIDI clock run up to 2000+ steps with no hiccups. i'm suspecting although completely unsure of the influence of lua's garbage collection here? i'm gonna go ahead and merge! thank you so so much for your diligent help with this PR.
the purpose of this PR is to really nail down
clock.sync
with MIDI and Link. i've hit upon a semi-workable solution, but could use feedback@dndrks when you get a chance, would you mind testing this out? if you're on the homebrew build of seamstress, doing so should be as easy as cloning the repo, pulling down this branch, and doing
zig build run -- test-midi
in the repo root. the--
at the end lets you pass arguments toseamstress
once it's built, so in this case i'm asking it to run a script calledtest-midi
(probably in~/seamstress
).known issues:
quantum
is not exposed to luai'll keep poking at it too.