Documentation
¶
Index ¶
- Constants
- func VoiceName(track int) string
- type BassDrum
- func (v *BassDrum) IsActive() bool
- func (b *BassDrum) Param(index int) float64
- func (b *BassDrum) ParamSpecs() []ParamSpec
- func (v *BassDrum) SetDecay(amount float64)
- func (v *BassDrum) SetParam(index int, value01 float64)
- func (v *BassDrum) Tick() float64
- func (v *BassDrum) Trigger(velocity float64)
- type Cymbal
- type Engine
- func (e *Engine) ActiveBank() int
- func (e *Engine) BeginStart()
- func (e *Engine) ChainPosition() int
- func (e *Engine) CopyPattern(dst *[PatternSize]float32)
- func (e *Engine) CurrentStep() int
- func (e *Engine) IsIdle() bool
- func (e *Engine) Muted(track int) bool
- func (e *Engine) Pause()
- func (e *Engine) QueuedBank() int
- func (e *Engine) Render(buf []float32)
- func (e *Engine) ReplacePatternBank(bank int, state PatternBankState) error
- func (e *Engine) ReplaceState(state EngineState) error
- func (e *Engine) RequestBank(bank int)
- func (e *Engine) SetCell(bank, track, step int, velocity float64)
- func (e *Engine) SetCellCondition(bank, track, step int, condition TriggerCondition)
- func (e *Engine) SetCellHumanize(bank, track, step int, amount float64)
- func (e *Engine) SetCellProbability(bank, track, step int, probability float64)
- func (e *Engine) SetCellRepeats(bank, track, step, repeats int)
- func (e *Engine) SetChain(chain []int)
- func (e *Engine) SetChainEnabled(enabled bool)
- func (e *Engine) SetCountInBars(bars int)
- func (e *Engine) SetDecay(track int, amount float64)
- func (e *Engine) SetFillMode(enabled bool)
- func (e *Engine) SetHumanize(h float64)
- func (e *Engine) SetMetronomeEnabled(enabled bool)
- func (e *Engine) SetMuted(track int, muted bool)
- func (e *Engine) SetPattern(bank int, velocities []float64)
- func (e *Engine) SetPhysicalTomParam(track, index int, value01 float64)
- func (e *Engine) SetProbability(p float64)
- func (e *Engine) SetReverb(amount float64)
- func (e *Engine) SetRunning(running bool)
- func (e *Engine) SetStepCount(bank, count int)
- func (e *Engine) SetSwing(swing float64)
- func (e *Engine) SetTempo(bpm float64)
- func (e *Engine) SetTomModel(track int, model TomModel)
- func (e *Engine) SetTrackLength(bank, track, count int)
- func (e *Engine) SetVoiceParam(track, index int, value01 float64)
- func (e *Engine) SetVolume(track int, vol float64)
- func (e *Engine) State() EngineState
- func (e *Engine) TomModel(track int) TomModel
- func (e *Engine) TransportSnapshot() TransportSnapshot
- func (e *Engine) TriggerVoice(track int, velocity float64)
- func (e *Engine) Validate() error
- type EngineState
- type HiHat
- type ParamSpec
- type PatternBankState
- type Percussion
- func (v *Percussion) IsActive() bool
- func (b *Percussion) Param(index int) float64
- func (b *Percussion) ParamSpecs() []ParamSpec
- func (v *Percussion) SetDecay(amount float64)
- func (v *Percussion) SetParam(index int, value01 float64)
- func (v *Percussion) Tick() float64
- func (v *Percussion) Trigger(velocity float64)
- type Snare
- type Tom
- type TomModel
- type TomState
- type TrackState
- type TransportSnapshot
- type TransportState
- type TriggerCondition
- type Voice
Constants ¶
const ( TrackCount = 7 PatternBankCount = 4 MaxChainLength = 16 MaxCountInBars = 2 NoBank = -1 // MaxSteps is the pattern capacity. SetStepCount sets the master/displayed // loop and initializes every track to that length; SetTrackLength then lets // tracks diverge for polymeter. Steps are 16th notes, so 16 steps span one // 4/4 bar. MaxSteps = 16 )
const PatternSize = TrackCount * MaxSteps
const ProtocolVersion = 6
ProtocolVersion identifies the semantics of the AlgoDrum JS API and the worker/worklet transport snapshots built on top of it. audioWorker.ts pins the same number and refuses to run against a mismatching engine, while wasmEngine.ts uses it to cache-bust the independently cached worklet.
Bump it whenever an existing entry point changes meaning rather than existing: argument order or count, the unit or range of an argument, the shape of EngineState, or what a call does. Purely *adding* a method needs no bump — the worker's REQUIRED_METHODS already rejects an engine that lacks one.
This is unrelated to persistence.ts's FORMAT_VERSION, which versions saved patterns rather than the live API.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type BassDrum ¶
type BassDrum struct {
// contains filtered or unexported fields
}
func NewBassDrum ¶
func (*BassDrum) Param ¶
Param returns the normalized position of one parameter, or 0 for an out-of-range index.
func (*BassDrum) ParamSpecs ¶
func (b *BassDrum) ParamSpecs() []ParamSpec
ParamSpecs returns the voice's parameter descriptors, in index order.
type Cymbal ¶
type Cymbal struct {
// contains filtered or unexported fields
}
func (*Cymbal) Param ¶
Param returns the normalized position of one parameter, or 0 for an out-of-range index.
func (*Cymbal) ParamSpecs ¶
func (b *Cymbal) ParamSpecs() []ParamSpec
ParamSpecs returns the voice's parameter descriptors, in index order.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is the drum machine sequencer and mixer.
func NewEngine ¶
NewEngine creates a drum engine at the given sample rate. A non-finite or non-positive rate falls back to defaultSampleRate; any other value is clamped to [minSampleRate, maxSampleRate].
func (*Engine) ActiveBank ¶
ActiveBank reports the bank currently driving the sequencer.
func (*Engine) BeginStart ¶
func (e *Engine) BeginStart()
BeginStart records a requested start before the browser's asynchronous audio graph is ready. Render does not advance the sequencer in this state; SetRunning(true) commits the transition once audio output has resumed.
func (*Engine) ChainPosition ¶
ChainPosition reports the active entry within Chain. It is runtime state and resets to zero on Stop.
func (*Engine) CopyPattern ¶
func (e *Engine) CopyPattern(dst *[PatternSize]float32)
CopyPattern writes the full pattern into caller-owned storage without allocating. float32 matches the WASM wire format.
func (*Engine) CurrentStep ¶
func (*Engine) IsIdle ¶
IsIdle reports whether Render is producing nothing but silence and can be stopped being called: the output has stayed below engineSilence for idleConfirmS while the transport was not playing and no humanize-delayed hit was armed. It is the engine's half of the "stop the audio graph when there is nothing to hear" contract — the worklet/worker side decides what to do with it.
This truncates a decaying tail rather than rendering it to the last denormal: the reverb and the voice envelopes are exponential and never reach exactly zero, so waiting for a bit-exact zero would mean never idling at all. What is discarded is everything below −120 dBFS, which is over two orders of magnitude under the quantisation step of the 16-bit output it eventually reaches and far under the noise floor of any playback chain, so the cut is inaudible by construction. The consequence to be aware of is that renders are no longer bit-identical to a build without idling once a tail crosses the threshold — hence the tests hold idling to "nothing above engineSilence was lost" rather than to sample equality.
func (*Engine) Pause ¶
func (e *Engine) Pause()
Pause freezes sequencer time and delayed humanized hits while allowing already-triggered voices and effects to ring out. SetRunning(true) resumes from the held fractional position; SetRunning(false) performs a full stop.
func (*Engine) QueuedBank ¶
QueuedBank reports the standalone bank requested for a future master wrap, or NoBank when there is no outstanding request.
func (*Engine) Render ¶
Render fills buf with mono audio samples.
The invariants the loop relies on — a positive duration for every step, the playhead inside the loop, no pending trigger past its deadline — are checked per buffer, on entry and on exit, only in builds tagged `drumassert`; the shipped build compiles assertValid away to nothing (see assert.go).
func (*Engine) ReplacePatternBank ¶
func (e *Engine) ReplacePatternBank(bank int, state PatternBankState) error
ReplacePatternBank atomically validates and replaces one complete rhythmic bank. Invalid indexes are silent no-ops, matching the indexed setter contract; malformed state is reported without mutating any bank.
func (*Engine) ReplaceState ¶
func (e *Engine) ReplaceState(state EngineState) error
ReplaceState atomically validates and normalizes a full user-state snapshot, then replaces the engine's corresponding values. Structural mistakes and every NaN/Inf reject the whole snapshot before mutation. Finite numeric input follows the setters' existing contract and is clamped into its valid range. Runtime state (transport position, active tails and smoothing positions) is preserved so applying a preset during playback does not restart the machine.
func (*Engine) RequestBank ¶
RequestBank selects a standalone bank. Stopped requests take effect immediately; playing and paused requests are last-write-wins at a master wrap. Chain mode owns selection and therefore ignores manual requests.
func (*Engine) SetCell ¶
SetCell sets a cell's velocity, clamped to [0, 1] (0 = off). Steps are addressable up to MaxSteps regardless of the active step count, so shrinking and re-growing the pattern is lossless.
Out-of-range contract: an invalid track or step index is a silent no-op, as is a non-finite velocity — the cell keeps its previous value. Every indexed setter behaves this way (SetVolume, SetDecay), because the JS bridge feeds unvalidated arguments straight through and must never take the engine down.
func (*Engine) SetCellCondition ¶
func (e *Engine) SetCellCondition(bank, track, step int, condition TriggerCondition)
SetCellCondition sets one cell's loop/fill condition. Unknown numeric codes are rejected so persisted state cannot silently acquire different semantics.
func (*Engine) SetCellHumanize ¶
SetCellHumanize sets one cell's multiplier for the global Humanize amount. Defaults are 1, so older patterns retain their existing global response.
func (*Engine) SetCellProbability ¶
SetCellProbability sets one cell's probability multiplier. It is combined with the global Probability control at trigger time. Defaults are 1, so old patterns and the mechanical render path remain sample-exact.
func (*Engine) SetCellRepeats ¶
SetCellRepeats sets the number of evenly spaced hits emitted by an eligible cell. Invalid indexes no-op and counts clamp to [1, 4].
func (*Engine) SetChain ¶
SetChain atomically replaces the 1..MaxChainLength bank sequence. It is a stopped-only edit so the cursor and already-rendered lookahead cannot drift from the persisted configuration.
func (*Engine) SetChainEnabled ¶
SetChainEnabled toggles chain playback while stopped. Enabling selects the first chain entry; disabling returns to the standalone selection.
func (*Engine) SetCountInBars ¶
SetCountInBars selects a stopped-start count-in of zero, one or two fixed 4/4 bars. A count already in progress keeps the length latched at its start.
func (*Engine) SetDecay ¶
SetDecay sets per-track decay amount, clamped to [0, 1]. An out-of-range track or a non-finite amount is a silent no-op (see SetCell).
func (*Engine) SetFillMode ¶
SetFillMode enables or disables cells marked TriggerFillOnly. It is semantic configuration state rather than transport state so snapshots and shares can reproduce what the engine will play.
func (*Engine) SetHumanize ¶
SetHumanize sets the humanize amount, clamped to [0, 1]. It jitters each hit's timing (delayed up to humanize·15 ms) and scales its velocity by up to ±humanize·20%. 0 = mechanical (default). A non-finite value is rejected and leaves the current amount unchanged.
func (*Engine) SetMetronomeEnabled ¶
SetMetronomeEnabled controls quarter-note clicks during ordinary playback. Count-in clicks are always audible regardless of this setting.
func (*Engine) SetMuted ¶
SetMuted changes one track's mute state without changing its stored volume. Render ramps toward zero while muted and back toward the stored volume when unmuted, so both transitions inherit the zipper-noise protection of the volume control.
func (*Engine) SetPattern ¶
SetPattern atomically replaces the full flat track-major pattern (index = track*MaxSteps + step). A wrong-sized snapshot or any non-finite entry is rejected as a whole; finite velocities are clamped to [0, 1].
func (*Engine) SetPhysicalTomParam ¶
SetPhysicalTomParam updates one Tom's independent physical parameter bank. It is valid while either model is selected so A/B edits survive a model switch. Invalid tracks, indices, and non-finite values are ignored.
func (*Engine) SetProbability ¶
SetProbability sets the global probability multiplier, clamped to [0, 1]. Each hit's effective chance is this value times its CellProbability. A global value of 1 leaves cell probabilities unscaled (the default), while 0 silences every sequenced hit. A non-finite value is rejected and leaves the current probability unchanged.
func (*Engine) SetReverb ¶
SetReverb sets the target reverb amount in [0, 1]. Render smooths the wet gain to that target; 0 = fully dry, 1 = maximum (wet=reverbMaxWet, RT60=4 s). A non-finite amount is rejected and leaves the current setting unchanged.
func (*Engine) SetRunning ¶
func (*Engine) SetStepCount ¶
SetStepCount sets the master/displayed loop length, clamped to [1, MaxSteps], and applies it to every track as the backwards-compatible global-length operation. Call SetTrackLength afterward to create a polymeter. Cells beyond a new length keep their contents (see SetCell). Step durations are recomputed because swing pairs steps within the master loop.
func (*Engine) SetSwing ¶
SetSwing sets the swing amount, clamped to [0, maxSwing]. A non-finite value is rejected and leaves the current swing unchanged.
func (*Engine) SetTempo ¶
SetTempo sets the tempo, clamped to [minTempoBPM, maxTempoBPM]. A non-finite value is rejected and leaves the current tempo unchanged.
func (*Engine) SetTomModel ¶
SetTomModel explicitly selects one Tom track's implementation. Procedural remains the default, and invalid tracks or values are ignored. Switching resets both sides so a dormant tail cannot resume later.
func (*Engine) SetTrackLength ¶
SetTrackLength sets one track's independent loop length, clamped to [1, MaxSteps]. The master StepCount continues to define the displayed playhead and swing loop; track playheads advance continuously across master wraps, which is what makes non-dividing lengths a true polymeter.
func (*Engine) SetVoiceParam ¶
SetVoiceParam sets one of a voice's synthesis parameters from a normalized [0, 1] position; see params.go for the per-voice tables. An out-of-range track or index, or a non-finite value, is a silent no-op (see SetCell).
Unlike SetVolume/SetDecay the engine keeps no mirror of the value — it lives in the voice, reachable via Voice.Param.
func (*Engine) SetVolume ¶
SetVolume sets per-track volume, clamped to [0, 1]. The change ramps in over ~volSmoothTauS inside Render to avoid zipper noise. An out-of-range track or a non-finite volume is a silent no-op (see SetCell).
func (*Engine) State ¶
func (e *Engine) State() EngineState
State returns a deep snapshot of every user-controlled value. Callers own all returned slices and may modify them without aliasing the live engine.
func (*Engine) TomModel ¶
TomModel reports one Tom track's selected implementation. Invalid tracks report the procedural default.
func (*Engine) TransportSnapshot ¶
func (e *Engine) TransportSnapshot() TransportSnapshot
TransportSnapshot returns the engine-owned transport state, its logical playhead and a revision that identifies the current transport epoch.
func (*Engine) TriggerVoice ¶
TriggerVoice fires one voice immediately, independent of the sequencer, so the UI can audition a voice while the transport is stopped. An out-of-range track, a non-finite velocity, or a velocity of 0 is a silent no-op.
Triggering advances a noise voice's RNG stream, so an audition shifts the noise a later rendered hit will draw — see docs/voices.md.
func (*Engine) Validate ¶
Validate reports every engine invariant that is currently broken, joined into one error (nil when the state is sound). The setters already refuse out-of-contract input (see validFloat), so a violation here means the engine corrupted itself — the C11 swing bug rewrote step lengths from a valid SetStepCount call — and that class of defect is otherwise silent: Render's final clamp mutes a stray NaN and a wrong step length only sounds off.
It is a pure read of the engine's own state and allocates nothing while the state is sound, so it is cheap enough for tests, the fuzz target and the per-render assertion built under the `drumassert` tag (see assert.go).
type EngineState ¶
type EngineState struct {
TempoBPM float64
Swing float64
Reverb float64
Probability float64
Humanize float64
FillMode bool
MetronomeEnabled bool
CountInBars int
Banks []PatternBankState
StandaloneBank int
ChainEnabled bool
Chain []int
Tracks []TrackState
}
EngineState is the complete user-controlled engine state. Each bank owns its rhythmic configuration; mixer, voice and global performance controls remain shared. Active/queued bank and chain cursor are runtime-only. The transport/playheads, conditional pass history, live smoothing values, active tails and RNG position are deliberately runtime state rather than preset state.
type HiHat ¶
type HiHat struct {
// contains filtered or unexported fields
}
func (*HiHat) Param ¶
Param returns the normalized position of one parameter, or 0 for an out-of-range index.
func (*HiHat) ParamSpecs ¶
func (b *HiHat) ParamSpecs() []ParamSpec
ParamSpecs returns the voice's parameter descriptors, in index order.
type ParamSpec ¶
The spec type and its normalized→engineering mapping live in github.com/cwbudde/algo-tom/tomparams, because the physical Tom's mapping had to move there — an offline fitter that reused a copy would measure a different instrument than the one that ships — and the five procedural voices are described by the very same curve machinery.
These are deliberately *aliases*, not defined types. A defined type would give this package its own Map, its own byte-step snap and its own Default derivation, and a drift between the two copies would silently retune the shipped sound with nothing but ears to catch it. As aliases there is exactly one of each in existence, cmd/gen-voiceparams needs no source change, and web/src/engine/voiceParams.generated.ts comes out byte-identical.
func PhysicalTomSpecs ¶
func PhysicalTomSpecs() []ParamSpec
PhysicalTomSpecs returns the generated descriptor source for the physical Tom editor. Its indices are stable persistence and WASM command addresses.
func SpecsForTrack ¶
SpecsForTrack returns the parameter descriptors of one voice, or nil for an out-of-range track. Used by cmd/gen-voiceparams to generate the TypeScript mirror the UI renders from.
type PatternBankState ¶
type PatternBankState struct {
StepCount int
Pattern []float64
CellProbabilities []float64
CellHumanize []float64
CellConditions []TriggerCondition
CellRepeats []uint8
TrackLengths []int
}
PatternBankState is one owned rhythmic snapshot. Cell arrays are flat track-major with PatternSize entries; TrackLengths is engine-major.
type Percussion ¶
type Percussion struct {
// contains filtered or unexported fields
}
func NewPercussion ¶
func NewPercussion(sr float64) *Percussion
func (*Percussion) IsActive ¶
func (v *Percussion) IsActive() bool
func (*Percussion) Param ¶
Param returns the normalized position of one parameter, or 0 for an out-of-range index.
func (*Percussion) ParamSpecs ¶
func (b *Percussion) ParamSpecs() []ParamSpec
ParamSpecs returns the voice's parameter descriptors, in index order.
func (*Percussion) SetDecay ¶
func (v *Percussion) SetDecay(amount float64)
func (*Percussion) SetParam ¶
func (v *Percussion) SetParam(index int, value01 float64)
func (*Percussion) Tick ¶
func (v *Percussion) Tick() float64
func (*Percussion) Trigger ¶
func (v *Percussion) Trigger(velocity float64)
type Snare ¶
type Snare struct {
// contains filtered or unexported fields
}
func (*Snare) Param ¶
Param returns the normalized position of one parameter, or 0 for an out-of-range index.
func (*Snare) ParamSpecs ¶
func (b *Snare) ParamSpecs() []ParamSpec
ParamSpecs returns the voice's parameter descriptors, in index order.
type Tom ¶
type Tom struct {
// contains filtered or unexported fields
}
func (*Tom) Param ¶
Param returns the normalized position of one parameter, or 0 for an out-of-range index.
func (*Tom) ParamSpecs ¶
func (b *Tom) ParamSpecs() []ParamSpec
ParamSpecs returns the voice's parameter descriptors, in index order.
type TomState ¶
TomState carries the state unique to the two Tom tracks. PhysicalParams is present even for a procedural Tom, so switching models never discards the inactive bank and a snapshot does not need to instantiate the physical model.
type TrackState ¶
type TrackState struct {
Volume float64
Decay float64
Muted bool
VoiceParams []float64
Tom *TomState
}
TrackState is one engine-major track. Volume remains meaningful while Muted is true; muting changes only the smoothed render target. VoiceParams always describes the procedural/ordinary voice bank, including on a Tom whose physical model is currently selected.
type TransportSnapshot ¶
type TransportSnapshot struct {
State TransportState
Step int
Revision uint64
}
TransportSnapshot identifies one transport epoch and its playhead. Revision changes at every state transition, which lets the main thread reject chunks rendered before a Stop, Pause or restart even when they reach the speakers later from the worklet's queue.
type TransportState ¶
type TransportState string
TransportState is the semantic transport state exposed at the WASM boundary. The sequencer remains the authority for this value; the worker and UI only mirror snapshots returned by TransportSnapshot.
const ( TransportStopped TransportState = "stopped" TransportStarting TransportState = "starting" TransportCountingIn TransportState = "counting-in" TransportPlaying TransportState = "playing" TransportPaused TransportState = "paused" )
type TriggerCondition ¶
type TriggerCondition uint8
TriggerCondition controls whether an active cell is eligible on a given pass through its track's independent loop. The numeric values are part of the EngineState/WASM wire contract; append new values rather than reordering these constants.
const ( TriggerAlways TriggerCondition = iota TriggerEvery2 TriggerEvery3 TriggerEvery4 TriggerFirstLoop TriggerFillOnly TriggerNotPreviousFired )
type Voice ¶
type Voice interface {
// Trigger starts (or restarts) the voice; velocity in [0, 1] scales
// the level of the whole hit.
Trigger(velocity float64)
Tick() float64
// SetDecay trims the voice's base decay time by decayScaleMin + amount.
// The base itself is a synthesis parameter (see params.go), so the
// effective decay is base × (decayScaleMin + amount).
SetDecay(amount float64)
// SetParam sets one synthesis parameter from a normalized [0, 1]
// position; an out-of-range index or non-finite value is a no-op.
SetParam(index int, value01 float64)
Param(index int) float64
ParamSpecs() []ParamSpec
}
Voice is a single-shot drum synthesizer voice.