Playback must remain on this path:
TimelineEvent[]
-> EventScheduler
-> TimelineRouter
-> ToneAdapter
-> BusGraph
The concrete event type may be TimelineEventV2[] in current runtime modules, but the architectural boundary is the same: timeline events are scheduled, routed, rendered through the Tone adapter, and connected through the bus graph.
- No direct Tone.js scheduling outside
ToneAdapter. - No rogue
AudioContextcreation outside the approved audio boundary. - No side-channel playback from UI components, server handlers, macros, WubPad routes, preview helpers, or diagnostics.
- No direct destination output that bypasses
BusGraph. - No hidden playback timers that compete with
EventSchedulerandToneAdapter. - Metering may observe existing bus output, but it must remain read-only and cannot create new playback paths.
- No raw-audio upload to Gemini or any remote AI provider.
- No API keys in source, logs, docs examples, or client-exposed state.
- Clip edit processing must use Float32Array operations only. No AudioContext manipulation in edit renderers.
- Clip edit parameters must flow into the canonical pipeline via event payload — never via out-of-band Player instantiation or store subscriptions.
Timeline events are the deterministic playback contract. Higher-level producer, remix, arrangement, and project systems may produce or transform events, but they should not render sound directly.
EventScheduler owns validation, ordering, deduplication, schedule clearing, and handoff to the rendering path. Scheduler behavior must remain deterministic.
TimelineRouter maps timeline events into typed playback actions. It is the isolation layer between musical event semantics and rendering implementation details.
ToneAdapter is the Tone.js scheduling boundary. Tone transport callbacks, Tone players, and render primitive scheduling belong here. If a feature needs Tone scheduling, route the request through timeline events and the adapter.
BusGraph owns the physical audio graph, named buses, effects chain, master output path, lifecycle reset/dispose behavior, and modulation parameter binding.
The meter registry is a visual consumer of existing playback state. It may read bus levels or deterministic project/playhead state, but it must not route audio, schedule playback, or replace the canonical playback path.
Clip edits (gain, reverse, fade, normalize) are applied as follows:
- Live playback:
projectToTimelineEventsincludesclipEditandnormalizedGainin the event payload.ToneAdapter.triggerEventPlaybackreads these and sets Tone.js Player properties (reverse,fadeIn,fadeOut,volume.value). No AudioContext operations. No new audio nodes. - WAV export:
OfflineRenderService.mixClipIntocallsrenderClipEdits(peaks, edit, duration). This is a pure Float32Array operation with no AudioContext dependency.
The edit renderer (src/audio/rendering/clipEditRenderer.ts) is a pure function module. It must not import Tone.js, create AudioContext, or schedule playback.
Do not add code like:
Tone.Transport.scheduleOnce(...)outside ToneAdapter.
Do not add code like:
new AudioContext()inside UI components, server handlers, store actions, or feature helpers.
Do not route audio directly to destination:
player.toDestination()when the sound should be part of public playback. Public playback must flow through BusGraph.
UI components may:
- Display project, transport, mixer, browser, and piano-roll state.
- Dispatch store/controller actions.
- Provide editing affordances.
- Show diagnostics and status.
UI components must not:
- Schedule audio.
- Instantiate Tone/WebAudio nodes.
- Start independent playback timers for audio.
- Mutate persistence contracts to satisfy visual needs.
WubLabz remains local-first:
- Local audio stays local.
- Gemini and other AI providers must remain optional.
- Raw audio must not be uploaded to Gemini.
- AI arrangement and producer memory should come after core DAW stability.
Core remix and arrangement generation must remain deterministic:
- Do not use
Math.randomfor remix generation. - Use seeded randomness when variation is needed.
- Add tests for every core logic change.
Before merging runtime or architecture changes, run:
NODE_OPTIONS=--max-old-space-size=2048 npm run typecheck
NODE_OPTIONS=--max-old-space-size=2048 npm run lint
NODE_OPTIONS=--max-old-space-size=2048 npm test
NODE_OPTIONS=--max-old-space-size=2048 npm run build