Decision recorded, not scheduled. Brad: "build the core so that the product is possible later. We don't have to do it now, but we don't paint ourselves into a corner and build something that can't be built upon."
The duplication
Two scripts do the same three things — drive something that emits audio blocks, deliver timed events into it, stream the result to a WAV with a digest:
audiocomponents/tools/render_component.py — targets audioinstruments components by name; modes oneshots, kit, phrase, transitions. Runs under CPython and MicroPython, and is careful about memory because a whole render in RAM is a MemoryError on a board.
micropython-vst3/tools/harness.py — targets micropython-vst3 scripts through the vstaudio shim on the audioif CPython wheel. CPython only.
They differ only in what they drive. Every number the accuracy program has produced came from the first one.
The three seams, and what "not painting into a corner" means for each
Source. The core must not import audioinstruments. A source is anything that yields blocks — an audioinstruments component, a vst3 script, an effects chain — plugged in from outside. If the core knows about instruments, a MIDI-file front end or an effects-only render needs the core changed to exist.
Timeline — the one that actually decides whether the product is possible. Events must be data, not closures. render_component currently schedules lambda: inst.note_on(36, 100). A closure cannot be parsed from a MIDI file, serialised, inspected, diffed, or generated by a DAW export. Declarative records (time, kind, args) can be all of those, and executing them is trivial. This is the difference between a harness and something a musician could ever drive.
The timeline is also where note-offs, macro moves and program changes get modelled properly rather than being a list of note-ons — see #16.
Sink. Streaming, so length is not bounded by RAM, and the digest separable from the file so tests and users want the same code path.
Constraints to carry
- MicroPython compatibility is not optional.
render_component has it and harness.py does not; folding toward the latter's shape loses the ability to render on a board and breaks the cross-interpreter parity work, which is how the audioif envelope bug was found.
- Dependency direction: the core depends on audioif's
audiocore/synthio only. audiocomponents and micropython-vst3 consume it.
- Blast radius:
render_component is used by the tests, measure_hits, shared_circuits, the overlap gate and every listening capture. Changing its interface is not free.
What this is NOT, today
Not a musician-facing product. That would additionally need MIDI-file input, patches addressable by name, and CLI/docs/packaging — the fluidsynth -F out.wav font.sf2 in.mid shape, which is the obvious precedent. Deliberately deferred. The point of the seams above is that adding a MIDI-file source later is another source, not a rewrite.
Related
Decision recorded, not scheduled. Brad: "build the core so that the product is possible later. We don't have to do it now, but we don't paint ourselves into a corner and build something that can't be built upon."
The duplication
Two scripts do the same three things — drive something that emits audio blocks, deliver timed events into it, stream the result to a WAV with a digest:
audiocomponents/tools/render_component.py— targetsaudioinstrumentscomponents by name; modesoneshots,kit,phrase,transitions. Runs under CPython and MicroPython, and is careful about memory because a whole render in RAM is a MemoryError on a board.micropython-vst3/tools/harness.py— targets micropython-vst3 scripts through thevstaudioshim on the audioif CPython wheel. CPython only.They differ only in what they drive. Every number the accuracy program has produced came from the first one.
The three seams, and what "not painting into a corner" means for each
Source. The core must not import
audioinstruments. A source is anything that yields blocks — an audioinstruments component, a vst3 script, an effects chain — plugged in from outside. If the core knows about instruments, a MIDI-file front end or an effects-only render needs the core changed to exist.Timeline — the one that actually decides whether the product is possible. Events must be data, not closures.
render_componentcurrently scheduleslambda: inst.note_on(36, 100). A closure cannot be parsed from a MIDI file, serialised, inspected, diffed, or generated by a DAW export. Declarative records (time, kind, args) can be all of those, and executing them is trivial. This is the difference between a harness and something a musician could ever drive.The timeline is also where note-offs, macro moves and program changes get modelled properly rather than being a list of note-ons — see #16.
Sink. Streaming, so length is not bounded by RAM, and the digest separable from the file so tests and users want the same code path.
Constraints to carry
render_componenthas it andharness.pydoes not; folding toward the latter's shape loses the ability to render on a board and breaks the cross-interpreter parity work, which is how the audioif envelope bug was found.audiocore/synthioonly. audiocomponents and micropython-vst3 consume it.render_componentis used by the tests,measure_hits,shared_circuits, the overlap gate and every listening capture. Changing its interface is not free.What this is NOT, today
Not a musician-facing product. That would additionally need MIDI-file input, patches addressable by name, and CLI/docs/packaging — the
fluidsynth -F out.wav font.sf2 in.midshape, which is the obvious precedent. Deliberately deferred. The point of the seams above is that adding a MIDI-file source later is another source, not a rewrite.Related
harness.pyalready documents that "the REAPER render through the real plug-in stays authoritative", but nothing checks it.