fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 1# instrawave-compose
3Compose rhythms with your voice, or with anything else you can do near a
4microphone. You teach the app a handful of named *voices* by example (say
5"doo" five times, tap the desk five times), then record a *take* in one
6pass: "doo baa doo-doo doo". The take is segmented into hits, each hit is
7classified back onto the voices, and the result is a timeline of timed,
8weighted events that renders to audio through an *instrument* bound to
9each voice.
11A limitation of most rhythm entry tools is the keyboard: tapping a pad or
12clicking a grid discards the timing and dynamics a person can produce
13vocally without effort. Here the performed take is the source of truth.
14Times are kept exactly as performed (there is no quantization grid), and
15each hit's measured loudness becomes the note's strength, taken relative
16to the loudest hit in the take and compressed so soft hits stay audible
17while the ordering is preserved.
19## Voices, takes, and the timeline
21A voice is a named class of sound plus its training examples. Recording
22into a voice runs an onset detector (spectral flux against a local mean,
23with a sensitivity control, since a soft syllable and a sharp tap sit at
bd6f329Editable persistent trainings, multi-select exclusion, playback pulses, instrawave bindingsJeremy Magland 24very different flux levels) and opens the clip in an editor: the detected
25hits are markers on the waveform, auditioned by click, selected in groups
26by dragging or shift-clicking, and excluded with a button or the X key, so
27a cough or a stray detection is visible rather than silently poisoning the
28label. Clips persist (audio in IndexedDB, detections and features in
29localStorage) and reopen in the same editor later; deleting a clip removes
30its hits from the voice. Each committed hit is stored as a small feature
fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 31vector: spectral centroid and rolloff, band energy ratios, zero-crossing
32rate, attack and decay times. Loudness is deliberately kept out of this
33vector; it is expression, not identity, so playing softly does not change
34which voice a hit lands on.
36A take goes through the same detector, and each hit is classified by
37distance-weighted nearest neighbours over the pooled training set,
38z-scored per dimension. The timeline shows one lane per voice with a
39marker per hit, bar height showing strength. The classifier will sometimes
40be wrong, and the fix is one gesture: drag the marker to the right lane.
41"Learn" folds the corrected hit back into that voice's training set, so
42the labelling improves with use; there is no model to refit.
9f20d5bTuning audition, instrument geometry editor with 3D view, full browser persistenceJeremy Magland 44Everything persists in the browser: voices and their training, the take
45(audio and edited events), per-instrument geometry, slider positions, and
46the bakes themselves, which round-trip through IndexedDB so a reload finds
47them stored and loads one the first time it is needed rather than paying
48the precompute again. During playback each hit flashes as the playhead
bd6f329Editable persistent trainings, multi-select exclusion, playback pulses, instrawave bindingsJeremy Magland 49crosses it, ring size following the hit's strength, so the dynamics are
50visible in motion as well as in the bar heights.
fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 51
52## Rendering
bd6f329Editable persistent trainings, multi-select exclusion, playback pulses, instrawave bindingsJeremy Magland 54The seam is `src/render/types.ts`: events in, audio out. Each voice is
55bound either to a placeholder WebAudio synth preset (kick, snare, hat,
56tom) or to an [instrawave](../instrawave) instrument (drum, dulcimer,
57chime) with per-voice tuning overrides, edited as sliders under the voice.
58Playback touches the GPU only when at least one bound instrument is
59instrawave; a purely synth arrangement never asks for a device.
9f20d5bTuning audition, instrument geometry editor with 3D view, full browser persistenceJeremy Magland 61Each instrument carries one set of geometry and accuracy values, edited
30039cdMove instrument bake controls inline into the voice rowsJeremy Magland 62in the instrument panel that expands inline under a voice row bound to it:
63instrawave's own raymarched scene view alongside the geometry and accuracy
64sliders, so the staircased sound hole or the microphone position is seen
65exactly as the solver sees it. The bake state, the Precompute button, and
66its progress sit inline on the same rows. Geometry belongs to the
67instrument rather than the voice because it is what the shared bake
68depends on; each instrument therefore has exactly one bake, computed on
69demand, and rows sharing an instrument show the same state. A Hear button
70in each
9f20d5bTuning audition, instrument geometry editor with 3D view, full browser persistenceJeremy Magland 71voice's tuning panel renders a single hit at the current tuning from that
72bake, so tuning is auditioned without recording anything. All per-voice
bd6f329Editable persistent trainings, multi-select exclusion, playback pulses, instrawave bindingsJeremy Magland 73character travels as tuning, which is free: every voice bound to the same
74instrument shares its bake, and one render call carries all their events,
75each hit an event `{t, set: {...tuning, vel}}` with the voice's overrides
76and its strength scaling the excitation (`vel`, or `amp` for the plucked
77dulcimer; the equations are linear, so this is a pure scale). Traces come
78back at the solver rate, are resampled by linear interpolation, levelled
79per instrument (a pressure trace carries no absolute scale worth keeping
80against a synth's), and mixed; relative volumes within one instrument stay
2d56c5ePer-voice playback volume applied at mix timeJeremy Magland 83Rendered tracks are cached per voice, in memory and in IndexedDB, each
84stamped with exactly what it was rendered from: its events, tuning, gains,
85instrument, and the bake key. Editing one voice therefore re-renders only
86that voice's track, an unchanged take replays for free across reloads, and
87a stored track plays without its bake even being loaded. Each track runs
88to its own last event plus its longest decay, so its signature cannot be
89perturbed by hits on other lanes. Each voice also carries a playback
90volume slider; volume is deliberately absent from the track signature and
91scales the finished track at mix time, so moving it never renders
92anything. Save .wav downloads the current mix as 16-bit PCM at 44.1 kHz,
93rendered through the same caches.
bd6f329Editable persistent trainings, multi-select exclusion, playback pulses, instrawave bindingsJeremy Magland 94
95Loudness to excitation strength is the only feature mapping for now.
fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 96Duration to decay, vocal pitch to drum pitch, and brightness to strike
97position are natural next steps and the event structure leaves room for
98them.
100## Honest limitations
102Onset detection is offline and frame-based; timing is good to a few
103milliseconds on sharp attacks and worse on breathy ones. The classifier is
104nearest-neighbour over hand-picked features, which separates a dozen
105distinct sounds well but has no notion of a hit it has never seen; an
106unfamiliar sound lands on whatever is closest. Overlapping hits closer
107than 50 ms merge into one. The browser's microphone processing is asked to
108stand down (automatic gain control would rewrite the very dynamics being
109tracked), but a system-level AGC outside the browser will still flatten
9f20d5bTuning audition, instrument geometry editor with 3D view, full browser persistenceJeremy Magland 110them. A stored bake is a couple of hundred MB in IndexedDB for the drum;
111browsers may evict it under storage pressure, in which case the precompute
112(about 8 s for the drum on the development machine) simply runs again. A
113hit learned from a take stores
bd6f329Editable persistent trainings, multi-select exclusion, playback pulses, instrawave bindingsJeremy Magland 114only its features, so it cannot be re-auditioned later; clips can. A
115multi-event instrawave render costs one synthesis pass per event over the
116whole trace, so a very dense take renders slowly.
fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 117
118## Running it
120```
121npm install # expects ../instrawave alongside, per the vault layout
122npm run dev # the page: voices, take, timeline, playback
123npm test # onset, feature, classifier checks on synthetic audio
124npm run build
125```