2.5 (b): worker mode in the Max object — @mode, @latency, @latencysamples - #30
Merged
Merged
Conversation
…ples @mode worker runs process() on the core's worker, @Latency milliseconds behind the audio (30 by default, rounded up to whole signal vectors); the read-only @latencysamples gives the delay in samples, 0 in direct mode. Both settings take effect at once, by rebuilding the signal chain, and the worker is started for each chain in dspsetup with the object's inlets and outlets. What measuring in Max changed: - The worker thread gets the scheduling of an audio thread — Mach's time-constraint policy on macOS, THREAD_PRIORITY_TIME_CRITICAL on Windows — through a hook the core calls on the thread as it starts: on a busy machine an ordinary thread was late even with 21 ms of latency. The core also makes the thread's Python thread state before the audio thread can give it work. - Max computes an I/O vector's worth of signal vectors back to back (here 512 samples: 8 at once), so the worker has only the latency beyond that burst: 2 vectors (the first default) left a late vector in every second, 10 ms in 2 of 11. @Latency is therefore milliseconds, the whole delay, default 30, documented as having to exceed the I/O vector. Fields are now checked against the host's reserved names as methods are, and the object reserves its new attributes. @latencysamples is set directly (Max refuses to set a readonly attribute through its own setter, which the mock kernel does not model). The runner's stale-build check ignores the mock test's source. Mock test: worker mode delays by @Latency, reports @latencysamples, and returns to direct mode. Runtime test in Max (worker, at the default): the output matches delay~ by @latencysamples sample for sample, through a 0.2 s stall (reported once) and a reload; @mode direct takes the delay away. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019rVV9SkjkWmiBXFMT4whkz
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019rVV9SkjkWmiBXFMT4whkz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step (b) of plan 2.5, the last part of worker mode. It's based on development/v2 now that #29 has merged.
What changes
@mode workerrunsprocess()on the core's worker thread. The audio thread only copies vectors to and from it and never takes the GIL.@mode direct, the default, behaves as before.@latencyis the whole delay in milliseconds, rounded up to whole signal vectors. The default is 30, and the value must exceed Max's I/O vector duration (see below).@latencysamplesis read-only and gives the delay in samples, so a patch can line up other signal paths (with adelay~, say). It's 0 in direct mode.@modeor@latencytakes effect at once: it rebuilds the signal chain.dspsetupstarts the worker for each chain with the object's inlet and outlet counts.mode,latencyandlatencysamples. Fields are now checked against reserved names as methods already were, with a core test.What measuring in Max changed (this machine: 96 kHz, I/O vector 512, signal vector 64)
The worker thread needs audio-thread scheduling. As an ordinary thread on a busy machine, it was late in 2 of 5 quiet seconds even with 21 ms of latency. The fix:
thread_setuphook on the worker thread as it starts.THREAD_PRIORITY_TIME_CRITICALon Windows. Both are documented OS calls.The latency must exceed the I/O vector. Max computes an I/O vector's worth of signal vectors back to back (8 at a time here), so the worker only has the latency beyond that burst. Seconds with a late vector:
So
@latencyis in milliseconds, and the default is 30. That leaves about 18 ms beyond a 512-sample I/O vector at 44.1 kHz. The ReadMe tells users to raise it if they use a larger I/O vector. The plan records both revisions of the default you approved.@latencysamplesalways read 0 in Max. Max refuses to set a read-only attribute through its own setter, so it's now set directly. The mock kernel doesn't model that refusal, which is why the mock test passed anyway.Tests
@latencyand reports@latencysamples. It also checks returning to direct mode, an invalid mode becomingdirect, and a negative latency being held at 0 (one vector).worker, in Max, at the default:delay~by@latencysamples, sample for sample.mode directtakes the delay away.*_test.cpp, which isn't part of the external.This completes 2.5. The plan ticks it, and the help patcher gains a note on
@mode workerand@latency, which I checked open in Max (its window is 50 px taller to fit).🤖 Generated with Claude Code
https://claude.ai/code/session_019rVV9SkjkWmiBXFMT4whkz