/ proof-of-concept / instrawave-compose
Sign in
proof-of-concept / instrawave-compose
instrawave-compose / src / numbl / snippets.ts
61 lines · 2.3 KBCodeBlameHistory
2 * Turning the voices' training clips into the snippet matrices a numbl
3 * classifier takes. Each clip is reduced to the classifier rate once,
4 * each included onset is refined to the foot of its energy rise, and a
5 * fixed window is cut, exactly as the audio-spike-sorting benchmark cuts
6 * its snippets. Voices whose clips have no usable audio contribute
7 * nothing and are reported, since the classifier cannot land hits on
8 * them; hits learned from takes (which store features, not audio) do not
9 * reach this path.
10 */
11import type { Voice } from '../store.ts';
12import { downsampleBox, refineOnset } from './dsp.ts';
13import { CLF_FS, CLF_PRE, CLF_POST, type TrainingArrays } from './types.ts';
15export interface VoiceTraining {
16 training: TrainingArrays;
17 /** Voice id per training type index (label k maps to voiceIds[k-1]). */
18 voiceIds: string[];
19 /** Names of voices that contributed no snippets. */
20 skipped: string[];
23export async function buildVoiceTraining(
24 voices: Voice[],
25 getClipAudio: (id: string) => Promise<{ samples: Float32Array; sr: number } | null>,
26): Promise<VoiceTraining> {
27 const snipLen = CLF_PRE + CLF_POST;
28 const perVoice: { voice: Voice; snips: Float32Array[] }[] = [];
29 for (const voice of voices) {
30 const snips: Float32Array[] = [];
31 for (const clip of voice.clips) {
32 const audio = await getClipAudio(clip.id);
33 if (!audio) continue;
34 const x = downsampleBox(audio.samples, audio.sr, CLF_FS);
35 const excluded = new Set(clip.excluded);
36 clip.onsets.forEach((t, i) => {
37 if (excluded.has(i)) return;
38 const s = refineOnset(x, CLF_FS, t);
39 if (s - CLF_PRE < 0 || s + CLF_POST > x.length) return;
40 snips.push(x.slice(s - CLF_PRE, s + CLF_POST));
41 });
42 }
43 perVoice.push({ voice, snips });
44 }
46 const contributing = perVoice.filter((p) => p.snips.length > 0);
47 const training: TrainingArrays = {
48 names: contributing.map((p) => p.voice.name),
49 pre: CLF_PRE,
50 snips: contributing.map((p) => {
51 const data = new Float32Array(snipLen * p.snips.length);
52 p.snips.forEach((s, j) => data.set(s, j * snipLen));
53 return { data, snipLen, count: p.snips.length };
54 }),
55 };
56 return {
57 training,
58 voiceIds: contributing.map((p) => p.voice.id),
59 skipped: perVoice.filter((p) => p.snips.length === 0).map((p) => p.voice.name),
60 };
↑↓ move↵openescclose