fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 1
2<html lang="en">
3 <head>
4 <meta charset="utf-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1" />
6 <title>instrawave-compose</title>
7 <style>
8 :root {
9 color-scheme: light dark;
10 font-family: system-ui, sans-serif;
11 }
12 body {
13 margin: 0 auto;
14 max-width: 72rem;
15 padding: 1rem 1.5rem 4rem;
16 line-height: 1.45;
17 }
18 h1 { font-size: 1.3rem; margin: 0.3rem 0 0; }
19 h1 + p { margin-top: 0.2rem; opacity: 0.75; }
20 h2 { font-size: 0.95rem; margin: 1.4rem 0 0.4rem; }
21 .cost { font-weight: normal; opacity: 0.6; font-size: 0.8rem; }
22 .bar { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; margin: 0.5rem 0; }
23 button { font: inherit; padding: 0.3rem 0.8rem; }
24 button:disabled { opacity: 0.5; }
25 select, input[type='text'] { font: inherit; }
26 input[type='range'] { width: 9rem; }
27 .voice-row {
28 display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap;
29 padding: 0.3rem 0.4rem; border-radius: 6px; font-size: 0.9rem;
30 }
31 .voice-row:nth-child(odd) { background: rgba(128, 128, 128, 0.09); }
32 .voice-row .swatch { width: 0.8rem; height: 0.8rem; border-radius: 3px; flex: none; }
33 .voice-row input.name { width: 8rem; border: none; background: transparent; font-weight: 600; }
bd6f329Editable persistent trainings, multi-select exclusion, playback pulses, instrawave bindingsJeremy Magland 34 .voice-row .count { opacity: 0.65; font-size: 0.8rem; min-width: 7.5rem; }
35 .tuning-panel {
36 display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
37 gap: 0.2rem 1.5rem; padding: 0.3rem 0.6rem 0.5rem 1.8rem; font-size: 0.82rem;
38 }
39 .tuning-panel .param { display: grid; }
40 .tuning-panel label { display: flex; justify-content: space-between; }
41 .tuning-panel label span:last-child { opacity: 0.7; font-variant-numeric: tabular-nums; }
42 .tuning-panel input[type='range'] { width: 100%; }
30039cdMove instrument bake controls inline into the voice rowsJeremy Magland 43 .voice-row progress { width: 6rem; }
2d56c5ePer-voice playback volume applied at mix timeJeremy Magland 44 .voice-row input.vol { width: 5rem; }
9f20d5bTuning audition, instrument geometry editor with 3D view, full browser persistenceJeremy Magland 45 #iwedit { border: 1px solid rgba(128,128,128,0.35); border-radius: 8px; padding: 0.6rem 0.8rem; margin: 0.5rem 0; }
b11b069Classify takes with numbl algorithms by default (silence_gap_knn from audio-spike-sorting, noise-floor adapted), with a custom-algorithm editor and the feature kNN kept as an optionJeremy Magland 46 #clf-editor { border: 1px solid rgba(128,128,128,0.35); border-radius: 8px; padding: 0.6rem 0.8rem; margin: 0.5rem 0; }
47 #clf-editor textarea {
48 width: 100%; min-height: 20rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
49 font-size: 0.8rem; line-height: 1.4; tab-size: 4; box-sizing: border-box;
50 }
9f20d5bTuning audition, instrument geometry editor with 3D view, full browser persistenceJeremy Magland 51 .iwedit-grid { display: grid; grid-template-columns: minmax(0, 1fr) 24rem; gap: 0 1.5rem; align-items: start; }
52 @media (max-width: 56rem) { .iwedit-grid { grid-template-columns: 1fr; } }
53 .params { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: 0.3rem 1.5rem; }
54 .params .param { display: grid; font-size: 0.82rem; }
55 .params label { display: flex; justify-content: space-between; }
56 .params label span:last-child { opacity: 0.7; font-variant-numeric: tabular-nums; }
57 .params input[type='range'] { width: 100%; }
58 .scene { position: relative; width: 24rem; max-width: 100%; aspect-ratio: 4 / 3; }
59 .scene canvas { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: 6px; }
60 .scene .scene-hint { position: absolute; right: 0.5rem; bottom: 0.3rem; font-size: 0.7rem; color: rgba(255,255,255,0.45); }
fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 61 .voice-row .spacer { flex: 1; }
62 .rec { color: #d33; }
63 canvas.wave { width: 100%; height: 110px; border-radius: 6px; display: block; touch-action: none; }
64 canvas.tl { width: 100%; border-radius: 6px; display: block; touch-action: none; }
65 .meter { width: 7rem; height: 0.55rem; border-radius: 3px; background: rgba(128,128,128,0.25); overflow: hidden; }
66 .meter div { height: 100%; width: 0; background: #3dbb6e; }
67 #capture { border: 1px solid rgba(128,128,128,0.35); border-radius: 8px; padding: 0.6rem 0.8rem; margin-top: 0.8rem; }
68 #status { font-size: 0.85rem; opacity: 0.8; min-height: 1.3em; }
69 .hint { font-size: 0.8rem; opacity: 0.6; }
70 label { display: inline-flex; gap: 0.35rem; align-items: center; }
71 </style>
72 </head>
73 <body>
74 <h1>instrawave-compose</h1>
75 <p>
76 Teach it your sounds as named voices, then perform a rhythm in one take;
77 hits are classified back onto the voices and rendered on instruments.
78 </p>
bd6f329Editable persistent trainings, multi-select exclusion, playback pulses, instrawave bindingsJeremy Magland 80 <h2>Voices <span class="cost">record several examples of each sound; recordings stay editable</span></h2>
fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 81 <div id="voice-list"></div>
82 <div class="bar">
83 <input id="voice-name" type="text" placeholder="new voice name (e.g. doo)" />
84 <button id="add-voice">Add voice</button>
85 <span class="spacer" style="flex:1"></span>
86 <button id="reset-all">Forget everything</button>
87 </div>
89 <div id="capture" hidden>
90 <div class="bar">
91 <strong id="capture-title"></strong>
bd6f329Editable persistent trainings, multi-select exclusion, playback pulses, instrawave bindingsJeremy Magland 92 <select id="capture-clip"></select>
93 <span class="hint">click a hit to audition · drag or shift-click to select several · X toggles exclusion</span>
fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 94 </div>
95 <canvas id="capture-wave" class="wave"></canvas>
96 <div class="bar">
97 <label>sensitivity <input id="capture-sens" type="range" min="0" max="1" step="0.01" value="0.6" /></label>
98 <button id="capture-exclude" disabled>Exclude</button>
bd6f329Editable persistent trainings, multi-select exclusion, playback pulses, instrawave bindingsJeremy Magland 99 <span id="capture-count" class="hint"></span>
fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 100 <span style="flex:1"></span>
bd6f329Editable persistent trainings, multi-select exclusion, playback pulses, instrawave bindingsJeremy Magland 101 <button id="capture-delete">Delete clip</button>
102 <button id="capture-done">Done</button>
fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 103 </div>
104 </div>
106 <h2>Take <span class="cost">one pass, exactly as performed; no grid</span></h2>
107 <div class="bar">
108 <button id="take-record">● Record take</button>
109 <div class="meter" id="meter" hidden><div></div></div>
b11b069Classify takes with numbl algorithms by default (silence_gap_knn from audio-spike-sorting, noise-floor adapted), with a custom-algorithm editor and the feature kNN kept as an optionJeremy Magland 110 <label>classifier
111 <select id="clf-method">
112 <option value="builtin">silence_gap_knn (numbl)</option>
113 <option value="custom">custom (numbl)</option>
114 <option value="legacy">feature kNN (built-in)</option>
115 </select>
116 </label>
117 <button id="clf-edit">algorithm…</button>
118 <span id="take-sens-wrap"><label>sensitivity <input id="take-sens" type="range" min="0" max="1" step="0.01" value="0.6" /></label></span>
fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 119 <button id="take-redetect" disabled>Re-detect & classify</button>
120 <span style="flex:1"></span>
121 <button id="take-play" disabled>Play</button>
838af43Per-instrument render cache in IndexedDB and .wav downloadJeremy Magland 122 <button id="take-save" disabled>Save .wav</button>
fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 123 </div>
b11b069Classify takes with numbl algorithms by default (silence_gap_knn from audio-spike-sorting, noise-floor adapted), with a custom-algorithm editor and the feature kNN kept as an optionJeremy Magland 124 <div id="clf-editor" hidden>
125 <div class="bar">
126 <strong id="clf-title"></strong>
127 <span class="hint">function [times, labels] = name(x, fs, training) · the audio-spike-sorting interface, so an algorithm developed there drops in unchanged</span>
128 <span style="flex:1"></span>
129 <button id="clf-reset" hidden>Reset to default</button>
130 <button id="clf-close">Close</button>
131 </div>
132 <textarea id="clf-source" spellcheck="false"></textarea>
133 </div>
9f20d5bTuning audition, instrument geometry editor with 3D view, full browser persistenceJeremy Magland 135 <div id="iwedit" hidden>
136 <div class="bar">
137 <strong id="iwedit-title"></strong>
138 <span class="hint">geometry and accuracy feed the bake; changing one makes it stale · shared by every voice bound to this instrument</span>
139 <span style="flex:1"></span>
140 <button id="iwedit-done">Done</button>
141 </div>
142 <div class="iwedit-grid">
143 <div class="params" id="iwedit-params"></div>
144 <div class="scene">
145 <canvas id="iw-volume"></canvas>
146 <canvas id="iw-overlay"></canvas>
147 <span class="hint scene-hint">drag to orbit · wheel to zoom</span>
148 </div>
149 </div>
150 </div>
fef783aVoice-driven rhythm capture: voices trained by example, takes classified onto them, synth playbackJeremy Magland 151 <canvas id="timeline" class="tl"></canvas>
152 <div class="bar">
153 <span class="hint">drag a hit to another lane to reassign · double-click a lane to add a hit · delete key removes</span>
154 <span style="flex:1"></span>
155 <button id="hit-learn" disabled>Learn selected into its voice</button>
156 <button id="hit-delete" disabled>Delete selected</button>
157 </div>
158 <p id="status"></p>
159 <script type="module" src="./src/main.ts"></script>
160 </body>
161</html>