Worker mode

By default process() runs on Max's audio thread, so whatever else holds Python's interpreter — a reload, a message, another instance — holds up the audio while it does. With @mode worker it runs on a thread of its own instead, @latency milliseconds behind the audio (30 by default, rounded up to whole signal vectors): the audio thread then only copies vectors to and from that thread, and never waits for Python. Max computes a whole I/O vector's worth of signal vectors at once, so @latency must be longer than the I/O vector (Options > Audio Status: 512 samples is 11.6 ms at 44.1 kHz) — what is left over is the time Python has to keep up; raise it with a larger I/O vector. If Python falls further behind, the vectors it is late for are output as silence and the console says so, once per load; the delay stays the same. The read-only @latencysamples gives the delay in samples, for aligning other signal paths (a delay~, say); in direct mode it is 0. The worker thread has the real-time scheduling of an audio thread. @mode and @latency take effect as soon as they are set, by rebuilding the signal chain. Stopping the worker (DSP off, a chain rebuild, the object deleted) waits up to 100 ms for the vector in progress; a process() that has not returned by then is interrupted with a WorkerStopped exception (a BaseException, so except Exception: does not swallow it), that vector is silence, the console says so once, and the class stays bound; one that still has not returned after a further 250 ms is blocked in a call Python cannot interrupt (time.sleep, a long C call) and is abandoned: audio continues on a new worker, while the abandoned thread keeps costing a core until Max quits.