You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracking issue for the engine refactor. Individual issues are linked below and are meant to be done in order — each one is provable on its own before the next starts.
The goal in one sentence
Move the C boundary from the middle of the signal path to its edge, so audio becomes a []float32 we own.
flowchart LR
subgraph NOW
A1[TOML] --> B1["Go<br/>config only"] --> C1["ma_engine<br/>clips + mix + FX"] --> D1[device]
end
subgraph AFTER
A2[TOML] --> B2["Go<br/>Session"] --> C2["mixBlock()<br/>clips + mix + FX"] --> R2[ring buffer] --> D2["malgo<br/>device"]
end
style C1 fill:#ffe0dc,stroke:#c33
style C2 fill:#dff0e6,stroke:#2a7
Loading
Today ma_engine owns the mix and Go only configures it from outside. That is why the samples are unreachable, why the compressor has no DSP, why master.limiter = true only logs a warning, and why a plugin would need a cgo callback to run at all.
What gets deleted
Path
Lines
internal/audio/miniaudio.h
95,864
internal/audio/bridge.c
14
internal/audio/{audio,sound,effect,offline}.go
672
total
96,550
Replaced by roughly 1,000 lines of Go across sample/, mix/, dsp/ and a much smaller audio/.
What does NOT change
internal/project, internal/state, internal/watcher, internal/serve, cmd/codaw, the whole vscode-extension/, and every TOML file anyone has on disk. This is an engine-internals refactor, not a product change.
Order of work
flowchart LR
I1["#1 sample/<br/>decode WAV"] --> I2["#2 mix/<br/>mixBlock()"]
I2 --> I3["#3 dsp/ + plugin/<br/>effects as Go funcs"]
I3 --> I4["#4 audio/<br/>malgo, delete the C"]
I4 -.-> I5["#5 cleanup<br/>(optional)"]
Loading
The order matters for one specific reason: #3 comes before #4 so that nothing regresses at the moment the C is deleted. If the device swap happened first, there'd be a window with no EQ and no reverb.
MVP = #1 through #4. At that point CodaW does everything it does today, with no C in the repository. #5 is cleanup that can wait indefinitely.
Two things that turned out easier than expected
The store is already copy-on-write.Store.Get() returns a pointer to a project that is never mutated again — see the comment at the top of internal/project/clone.go. The "immutable snapshot" architecture is essentially already built. Nothing in this refactor needs to change it.
The mixer will not run on the audio thread. With a ring buffer between the mixer and malgo's callback, the callback does nothing but copy bytes out of the ring. The mixer runs on an ordinary goroutine, where a GC pause costs latency we've already buffered for rather than a dropout. This removes most of the scary parts of writing audio code in Go.
Guiding principle
Prefer the version you can explain to someone else over the version that is 5% faster. Every file in this refactor should be readable top-to-bottom without a diagram. Where this issue set proposes something clever, it also says what the boring alternative was and why it lost.
Background reading
docs/architecture.md — the three mixer shapes considered, and why the flat one won
docs/plugins.md — plugins as Go functions
docs/ipc.md — the two-plane rule (files carry what you'd commit; the channel carries what you'd never commit)
Tracking issue for the engine refactor. Individual issues are linked below and are meant to be done in order — each one is provable on its own before the next starts.
The goal in one sentence
Move the C boundary from the middle of the signal path to its edge, so audio becomes a
[]float32we own.flowchart LR subgraph NOW A1[TOML] --> B1["Go<br/>config only"] --> C1["ma_engine<br/>clips + mix + FX"] --> D1[device] end subgraph AFTER A2[TOML] --> B2["Go<br/>Session"] --> C2["mixBlock()<br/>clips + mix + FX"] --> R2[ring buffer] --> D2["malgo<br/>device"] end style C1 fill:#ffe0dc,stroke:#c33 style C2 fill:#dff0e6,stroke:#2a7Today
ma_engineowns the mix and Go only configures it from outside. That is why the samples are unreachable, why the compressor has no DSP, whymaster.limiter = trueonly logs a warning, and why a plugin would need a cgo callback to run at all.What gets deleted
internal/audio/miniaudio.hinternal/audio/bridge.cinternal/audio/{audio,sound,effect,offline}.goReplaced by roughly 1,000 lines of Go across
sample/,mix/,dsp/and a much smalleraudio/.What does NOT change
internal/project,internal/state,internal/watcher,internal/serve,cmd/codaw, the wholevscode-extension/, and every TOML file anyone has on disk. This is an engine-internals refactor, not a product change.Order of work
The order matters for one specific reason: #3 comes before #4 so that nothing regresses at the moment the C is deleted. If the device swap happened first, there'd be a window with no EQ and no reverb.
MVP = #1 through #4. At that point CodaW does everything it does today, with no C in the repository. #5 is cleanup that can wait indefinitely.
Two things that turned out easier than expected
The store is already copy-on-write.
Store.Get()returns a pointer to a project that is never mutated again — see the comment at the top ofinternal/project/clone.go. The "immutable snapshot" architecture is essentially already built. Nothing in this refactor needs to change it.The mixer will not run on the audio thread. With a ring buffer between the mixer and malgo's callback, the callback does nothing but copy bytes out of the ring. The mixer runs on an ordinary goroutine, where a GC pause costs latency we've already buffered for rather than a dropout. This removes most of the scary parts of writing audio code in Go.
Guiding principle
Prefer the version you can explain to someone else over the version that is 5% faster. Every file in this refactor should be readable top-to-bottom without a diagram. Where this issue set proposes something clever, it also says what the boring alternative was and why it lost.
Background reading
docs/architecture.md— the three mixer shapes considered, and why the flat one wondocs/plugins.md— plugins as Go functionsdocs/ipc.md— the two-plane rule (files carry what you'd commit; the channel carries what you'd never commit)