The sounds tab's sounds as data: which of its sub-tabs a sound is under,
how many variants it has, and how a song plays it. The sounds tab
(repl/components/panel/SoundsTab.jsx) filters with inCategory, and the
MCP's list_sounds and ask_jev_sound (tabTools.mjs) read the same, so
what an AI is told is what the listener sees.
A sound is an entry of superdough's soundMap: [name, { data }], where
data says its type (sample, synth, soundfont, wavetable), whether the
REPL loaded it before any song (prebake), a tag ('drum-machines') and
its files (samples, fonts, tables).
The sub-tabs, as the MCP names them, and the settings value each one is
(settings.mjs's soundFilterType); imported is the part of user the
import-sounds button added from disk (idbutils.mjs), whose files are
blob: URLs of this browser.
19const firstUrl = (samples) => { 20 if (Array.isArray(samples)) return samples[0]; 21 if (samples && typeof samples === 'object') { 22 const first = Object.values(samples)[0]; 23 return Array.isArray(first) ? first[0] : first; 24 } 25 return typeof samples === 'string' ? samples : undefined; 26}; 27const imported = (data) => !data?.prebake && data?.type === 'sample' && String(firstUrl(data.samples) ?? '').startsWith('blob:');
Whether a sound is under category (a sub-tab's filter value from
soundFilterType, or an MCP category name); 'all' and undefined take all.
31export function inCategory(data, category) { 32 switch (category) { 33 case 'user': 34 return !data?.prebake; 35 case 'imported': 36 return imported(data); 37 case 'drums': 38 case 'drum-machines': 39 return data?.type === 'sample' && data.tag === 'drum-machines'; 40 case 'samples': 41 return data?.type === 'sample' && data.tag !== 'drum-machines'; 42 case 'synths': 43 return ['synth', 'soundfont'].includes(data?.type); 44 case 'wavetables': 45 return data?.type === 'wavetable'; 46 default: 47 return true; 48 } 49}
The one sub-tab a sound shows under, besides user (which cuts across).
58const count = (x) => (Array.isArray(x) ? x.length : x && typeof x === 'object' ? Object.values(x).length : x ? 1 : 0);
How many variants a sound has, as the tab shows in brackets: a sample's files (chosen with .n()), a wavetable's tables, a soundfont's fonts.
69const quote = (s) => JSON.stringify(s);
How a song plays it (its variants are .n(0), .n(1), …). A drum
machine's sounds are named <machine>_<sound>, played as the sound with
the machine as its bank.
74export function useOf(name, data) { 75 if (data?.type === 'synth' || data?.type === 'soundfont' || data?.type === 'wavetable') { 76 return `note("c3 e3 g3").s(${quote(name)})`; 77 } 78 const bank = data?.tag === 'drum-machines' ? /^(.+)_([^_]+)$/.exec(name) : null; 79 if (bank) return `s(${quote(bank[2])}).bank(${quote(bank[1])})`; 80 return `s(${quote(name)})`; 81}
The sounds a list shows: named, not internal (_…), in category,
whose name contains filter (case aside), in name order.
85export function soundList(map, { category, filter = '' } = {}) { 86 const f = String(filter).toLowerCase(); 87 return Object.entries(map ?? {}) 88 .filter(([name, sound]) => !name.startsWith('_') && inCategory(sound?.data, category)) 89 .filter(([name]) => name.toLowerCase().includes(f)) 90 .sort((a, b) => a[0].localeCompare(b[0])); 91}