aimeat 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/public/lib/samples/LICENSE.md +29 -0
  2. package/dist/public/lib/samples/bass/.gitkeep +0 -0
  3. package/dist/public/lib/samples/drums/.gitkeep +0 -0
  4. package/dist/public/lib/samples/flute/.gitkeep +0 -0
  5. package/dist/public/lib/samples/guitar/.gitkeep +0 -0
  6. package/dist/public/lib/samples/piano/A2.mp3 +0 -0
  7. package/dist/public/lib/samples/piano/A3.mp3 +0 -0
  8. package/dist/public/lib/samples/piano/A4.mp3 +0 -0
  9. package/dist/public/lib/samples/piano/C3.mp3 +0 -0
  10. package/dist/public/lib/samples/piano/C4.mp3 +0 -0
  11. package/dist/public/lib/samples/piano/C5.mp3 +0 -0
  12. package/dist/public/lib/samples/piano/C6.mp3 +0 -0
  13. package/dist/public/lib/samples/piano/C7.mp3 +0 -0
  14. package/dist/public/lib/samples/piano/Ds3.mp3 +0 -0
  15. package/dist/public/lib/samples/piano/Ds4.mp3 +0 -0
  16. package/dist/public/lib/samples/piano/Ds5.mp3 +0 -0
  17. package/dist/public/llms-template.txt +669 -34
  18. package/dist/public/spa.html +1 -34
  19. package/dist/src/routes/bootstrap.d.ts +9 -0
  20. package/dist/src/routes/bootstrap.d.ts.map +1 -1
  21. package/dist/src/routes/bootstrap.js +240 -0
  22. package/dist/src/routes/bootstrap.js.map +1 -1
  23. package/dist/src/routes/lib-audio.d.ts +15 -0
  24. package/dist/src/routes/lib-audio.d.ts.map +1 -0
  25. package/dist/src/routes/lib-audio.js +848 -0
  26. package/dist/src/routes/lib-audio.js.map +1 -0
  27. package/dist/src/routes/lib-speech.d.ts +13 -0
  28. package/dist/src/routes/lib-speech.d.ts.map +1 -0
  29. package/dist/src/routes/lib-speech.js +309 -0
  30. package/dist/src/routes/lib-speech.js.map +1 -0
  31. package/dist/src/routes/libs.d.ts.map +1 -1
  32. package/dist/src/routes/libs.js +27 -1
  33. package/dist/src/routes/libs.js.map +1 -1
  34. package/package.json +1 -1
@@ -0,0 +1,848 @@
1
+ export function aimeatAudioLib(config) {
2
+ return `// aimeat-audio.js — AIMEAT Audio Library
3
+ // Node: ${config.nodeId} | Generated: ${new Date().toISOString()}
4
+ // Include: <script src="${config.baseUrl}/v1/libs/aimeat-audio.js"><\\/script>
5
+ // Usage: AIMEAT.audio.play('piano', 'C4');
6
+ (function(global) {
7
+ 'use strict';
8
+
9
+ var NODE_URL = (function() {
10
+ var meta = document.querySelector('meta[name="aimeat-node"]');
11
+ if (meta) return meta.getAttribute('content').replace(/\\/$/, '');
12
+ if (location.protocol === 'http:' || location.protocol === 'https:') return location.origin;
13
+ return '${config.baseUrl}';
14
+ })();
15
+
16
+ // ══════════════════════════════════════════════════════
17
+ // AudioContext singleton
18
+ // ══════════════════════════════════════════════════════
19
+
20
+ var _ctx = null;
21
+
22
+ function ctx() {
23
+ if (!_ctx) {
24
+ _ctx = new (window.AudioContext || window.webkitAudioContext)();
25
+ if (_ctx.state === 'suspended') {
26
+ var resume = function() {
27
+ if (_ctx && _ctx.state === 'suspended') _ctx.resume();
28
+ };
29
+ ['click', 'touchstart', 'keydown'].forEach(function(e) {
30
+ document.addEventListener(e, resume, { once: false, passive: true });
31
+ });
32
+ }
33
+ }
34
+ return _ctx;
35
+ }
36
+
37
+ // ══════════════════════════════════════════════════════
38
+ // Note frequency table (A0-C8, scientific pitch)
39
+ // ══════════════════════════════════════════════════════
40
+
41
+ var NOTE_NAMES = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B'];
42
+ var FLAT_MAP = { 'Db':'C#','Eb':'D#','Gb':'F#','Ab':'G#','Bb':'A#','Cs':'C#','Ds':'D#','Fs':'F#','Gs':'G#','As':'A#' };
43
+ var _freqCache = {};
44
+
45
+ function noteToFreq(note) {
46
+ if (_freqCache[note]) return _freqCache[note];
47
+ var n = note.trim();
48
+ var flatKey = n.slice(0, 2);
49
+ if (FLAT_MAP[flatKey]) n = FLAT_MAP[flatKey] + n.slice(2);
50
+ var match = n.match(/^([A-G]#?)(\\d)$/);
51
+ if (!match) return null;
52
+ var name = match[1];
53
+ var octave = parseInt(match[2]);
54
+ var semitone = NOTE_NAMES.indexOf(name);
55
+ if (semitone < 0) return null;
56
+ var midi = (octave + 1) * 12 + semitone;
57
+ var freq = 440 * Math.pow(2, (midi - 69) / 12);
58
+ _freqCache[note] = freq;
59
+ return freq;
60
+ }
61
+
62
+ // ══════════════════════════════════════════════════════
63
+ // Master output
64
+ // ══════════════════════════════════════════════════════
65
+
66
+ var _masterGain = null;
67
+
68
+ function masterNode() {
69
+ if (!_masterGain) {
70
+ _masterGain = ctx().createGain();
71
+ _masterGain.connect(ctx().destination);
72
+ }
73
+ return _masterGain;
74
+ }
75
+
76
+ var master = {
77
+ get volume() { return masterNode().gain.value; },
78
+ set volume(v) { masterNode().gain.value = Math.max(0, Math.min(1, v)); },
79
+ _muted: false,
80
+ _premuteVol: 1,
81
+ get mute() { return master._muted; },
82
+ set mute(v) {
83
+ if (v && !master._muted) {
84
+ master._premuteVol = masterNode().gain.value;
85
+ masterNode().gain.value = 0;
86
+ master._muted = true;
87
+ } else if (!v && master._muted) {
88
+ masterNode().gain.value = master._premuteVol;
89
+ master._muted = false;
90
+ }
91
+ }
92
+ };
93
+
94
+ // ══════════════════════════════════════════════════════
95
+ // Active notes registry (for stop)
96
+ // ══════════════════════════════════════════════════════
97
+
98
+ var _active = {};
99
+
100
+ function registerActive(instrument, note, nodes, stopFn) {
101
+ var key = instrument + ':' + (note || '*');
102
+ if (_active[key]) _active[key].stop();
103
+ _active[key] = { nodes: nodes, stop: stopFn };
104
+ }
105
+
106
+ function stopActive(instrument, note) {
107
+ if (!instrument) {
108
+ Object.keys(_active).forEach(function(k) { _active[k].stop(); delete _active[k]; });
109
+ return;
110
+ }
111
+ if (note) {
112
+ var key = instrument + ':' + note;
113
+ if (_active[key]) { _active[key].stop(); delete _active[key]; }
114
+ } else {
115
+ Object.keys(_active).forEach(function(k) {
116
+ if (k.startsWith(instrument + ':')) { _active[k].stop(); delete _active[k]; }
117
+ });
118
+ }
119
+ }
120
+
121
+ // ══════════════════════════════════════════════════════
122
+ // Instruments
123
+ // ══════════════════════════════════════════════════════
124
+
125
+ var _instruments = {};
126
+ var _sampleBuffers = {};
127
+ var _customSynths = {};
128
+
129
+ // ── Piano: 2-operator FM synthesis ──
130
+
131
+ _instruments.piano = {
132
+ play: function(note, opts) {
133
+ var freq = noteToFreq(note);
134
+ if (!freq) return;
135
+ var t = ctx().currentTime;
136
+ var vel = (opts && opts.velocity !== undefined) ? opts.velocity : 0.7;
137
+ var dur = (opts && opts.duration) || 2.0;
138
+
139
+ var mod = ctx().createOscillator(); mod.type = 'sine';
140
+ mod.frequency.value = freq * 2;
141
+ var modGain = ctx().createGain();
142
+ modGain.gain.setValueAtTime(freq * 1.5 * vel, t);
143
+ modGain.gain.exponentialRampToValueAtTime(freq * 0.01, t + dur * 0.8);
144
+ mod.connect(modGain);
145
+
146
+ var car = ctx().createOscillator(); car.type = 'sine';
147
+ car.frequency.value = freq;
148
+ modGain.connect(car.frequency);
149
+
150
+ var env = ctx().createGain();
151
+ env.gain.setValueAtTime(0, t);
152
+ env.gain.linearRampToValueAtTime(vel * 0.4, t + 0.005);
153
+ env.gain.exponentialRampToValueAtTime(vel * 0.15, t + 0.1);
154
+ env.gain.exponentialRampToValueAtTime(0.001, t + dur);
155
+
156
+ car.connect(env); env.connect(masterNode());
157
+ mod.start(t); car.start(t);
158
+ mod.stop(t + dur + 0.1); car.stop(t + dur + 0.1);
159
+
160
+ registerActive('piano', note, [mod, car], function() {
161
+ try { env.gain.cancelScheduledValues(ctx().currentTime);
162
+ env.gain.setValueAtTime(env.gain.value, ctx().currentTime);
163
+ env.gain.linearRampToValueAtTime(0, ctx().currentTime + 0.05);
164
+ mod.stop(ctx().currentTime + 0.1); car.stop(ctx().currentTime + 0.1); } catch(e){}
165
+ });
166
+ }
167
+ };
168
+
169
+ // ── Guitar: Karplus-Strong plucked string ──
170
+
171
+ _instruments.guitar = {
172
+ play: function(note, opts) {
173
+ var freq = noteToFreq(note);
174
+ if (!freq) return;
175
+ var t = ctx().currentTime;
176
+ var vel = (opts && opts.velocity !== undefined) ? opts.velocity : 0.7;
177
+ var dur = (opts && opts.duration) || 1.5;
178
+
179
+ var bufSize = Math.round(ctx().sampleRate / freq);
180
+ var noiseBuffer = ctx().createBuffer(1, bufSize, ctx().sampleRate);
181
+ var data = noiseBuffer.getChannelData(0);
182
+ for (var i = 0; i < bufSize; i++) data[i] = (Math.random() * 2 - 1) * vel;
183
+
184
+ var noise = ctx().createBufferSource();
185
+ noise.buffer = noiseBuffer;
186
+ noise.loop = true;
187
+
188
+ var filter = ctx().createBiquadFilter();
189
+ filter.type = 'lowpass';
190
+ filter.frequency.value = freq * 4;
191
+ filter.Q.value = 0.5;
192
+
193
+ var env = ctx().createGain();
194
+ env.gain.setValueAtTime(vel * 0.5, t);
195
+ env.gain.exponentialRampToValueAtTime(0.001, t + dur);
196
+
197
+ noise.connect(filter); filter.connect(env); env.connect(masterNode());
198
+ noise.start(t); noise.stop(t + dur + 0.05);
199
+
200
+ registerActive('guitar', note, [noise], function() {
201
+ try { env.gain.cancelScheduledValues(ctx().currentTime);
202
+ env.gain.linearRampToValueAtTime(0, ctx().currentTime + 0.05);
203
+ noise.stop(ctx().currentTime + 0.1); } catch(e){}
204
+ });
205
+ }
206
+ };
207
+
208
+ // ── Bass: sawtooth + lowpass + sub-oscillator ──
209
+
210
+ _instruments.bass = {
211
+ play: function(note, opts) {
212
+ var freq = noteToFreq(note);
213
+ if (!freq) return;
214
+ var t = ctx().currentTime;
215
+ var vel = (opts && opts.velocity !== undefined) ? opts.velocity : 0.7;
216
+ var dur = (opts && opts.duration) || 0.8;
217
+
218
+ var osc = ctx().createOscillator(); osc.type = 'sawtooth'; osc.frequency.value = freq;
219
+ var sub = ctx().createOscillator(); sub.type = 'sine'; sub.frequency.value = freq / 2;
220
+ var subGain = ctx().createGain(); subGain.gain.value = 0.5;
221
+ sub.connect(subGain);
222
+
223
+ var mix = ctx().createGain();
224
+ osc.connect(mix); subGain.connect(mix);
225
+
226
+ var filter = ctx().createBiquadFilter(); filter.type = 'lowpass';
227
+ filter.frequency.setValueAtTime(freq * 3, t);
228
+ filter.frequency.exponentialRampToValueAtTime(freq * 1.2, t + 0.15);
229
+ filter.Q.value = 2;
230
+
231
+ var env = ctx().createGain();
232
+ env.gain.setValueAtTime(0, t);
233
+ env.gain.linearRampToValueAtTime(vel * 0.5, t + 0.01);
234
+ env.gain.setValueAtTime(vel * 0.5, t + dur * 0.7);
235
+ env.gain.exponentialRampToValueAtTime(0.001, t + dur);
236
+
237
+ mix.connect(filter); filter.connect(env); env.connect(masterNode());
238
+ osc.start(t); sub.start(t);
239
+ osc.stop(t + dur + 0.05); sub.stop(t + dur + 0.05);
240
+
241
+ registerActive('bass', note, [osc, sub], function() {
242
+ try { env.gain.cancelScheduledValues(ctx().currentTime);
243
+ env.gain.linearRampToValueAtTime(0, ctx().currentTime + 0.05);
244
+ osc.stop(ctx().currentTime + 0.1); sub.stop(ctx().currentTime + 0.1); } catch(e){}
245
+ });
246
+ }
247
+ };
248
+
249
+ // ── Drums: noise + sine bursts (per-hit) ──
250
+
251
+ _instruments.drums = {
252
+ play: function(hit, opts) {
253
+ var t = ctx().currentTime;
254
+ var vel = (opts && opts.velocity !== undefined) ? opts.velocity : 0.7;
255
+
256
+ function makeTom(freq) {
257
+ var osc = ctx().createOscillator(); osc.type = 'sine';
258
+ osc.frequency.setValueAtTime(freq, t);
259
+ osc.frequency.exponentialRampToValueAtTime(freq * 0.6, t + 0.15);
260
+ var g = ctx().createGain();
261
+ g.gain.setValueAtTime(vel * 0.5, t);
262
+ g.gain.exponentialRampToValueAtTime(0.001, t + 0.3);
263
+ osc.connect(g); g.connect(masterNode());
264
+ osc.start(t); osc.stop(t + 0.4);
265
+ return [osc];
266
+ }
267
+
268
+ var synths = {
269
+ 'kick': function() {
270
+ var osc = ctx().createOscillator(); osc.type = 'sine';
271
+ osc.frequency.setValueAtTime(150, t);
272
+ osc.frequency.exponentialRampToValueAtTime(50, t + 0.1);
273
+ var g = ctx().createGain();
274
+ g.gain.setValueAtTime(vel, t);
275
+ g.gain.exponentialRampToValueAtTime(0.001, t + 0.4);
276
+ osc.connect(g); g.connect(masterNode());
277
+ var click = ctx().createOscillator(); click.type = 'square'; click.frequency.value = 800;
278
+ var cg = ctx().createGain();
279
+ cg.gain.setValueAtTime(vel * 0.3, t);
280
+ cg.gain.exponentialRampToValueAtTime(0.001, t + 0.02);
281
+ click.connect(cg); cg.connect(masterNode());
282
+ osc.start(t); osc.stop(t + 0.5);
283
+ click.start(t); click.stop(t + 0.05);
284
+ return [osc, click];
285
+ },
286
+ 'snare': function() {
287
+ var bufLen = Math.round(ctx().sampleRate * 0.15);
288
+ var buf = ctx().createBuffer(1, bufLen, ctx().sampleRate);
289
+ var d = buf.getChannelData(0);
290
+ for (var i = 0; i < bufLen; i++) d[i] = (Math.random() * 2 - 1);
291
+ var noise = ctx().createBufferSource(); noise.buffer = buf;
292
+ var nf = ctx().createBiquadFilter(); nf.type = 'highpass'; nf.frequency.value = 1000;
293
+ var ng = ctx().createGain();
294
+ ng.gain.setValueAtTime(vel * 0.6, t);
295
+ ng.gain.exponentialRampToValueAtTime(0.001, t + 0.15);
296
+ noise.connect(nf); nf.connect(ng); ng.connect(masterNode());
297
+ var osc = ctx().createOscillator(); osc.type = 'sine'; osc.frequency.value = 200;
298
+ var og = ctx().createGain();
299
+ og.gain.setValueAtTime(vel * 0.5, t);
300
+ og.gain.exponentialRampToValueAtTime(0.001, t + 0.08);
301
+ osc.connect(og); og.connect(masterNode());
302
+ noise.start(t); osc.start(t); osc.stop(t + 0.2);
303
+ return [noise, osc];
304
+ },
305
+ 'hihat': function() {
306
+ var bufLen = Math.round(ctx().sampleRate * 0.05);
307
+ var buf = ctx().createBuffer(1, bufLen, ctx().sampleRate);
308
+ var d = buf.getChannelData(0);
309
+ for (var i = 0; i < bufLen; i++) d[i] = (Math.random() * 2 - 1);
310
+ var noise = ctx().createBufferSource(); noise.buffer = buf;
311
+ var f = ctx().createBiquadFilter(); f.type = 'highpass'; f.frequency.value = 7000;
312
+ var g = ctx().createGain();
313
+ g.gain.setValueAtTime(vel * 0.3, t);
314
+ g.gain.exponentialRampToValueAtTime(0.001, t + 0.05);
315
+ noise.connect(f); f.connect(g); g.connect(masterNode());
316
+ noise.start(t);
317
+ return [noise];
318
+ },
319
+ 'hihat-open': function() {
320
+ var bufLen = Math.round(ctx().sampleRate * 0.3);
321
+ var buf = ctx().createBuffer(1, bufLen, ctx().sampleRate);
322
+ var d = buf.getChannelData(0);
323
+ for (var i = 0; i < bufLen; i++) d[i] = (Math.random() * 2 - 1);
324
+ var noise = ctx().createBufferSource(); noise.buffer = buf;
325
+ var f = ctx().createBiquadFilter(); f.type = 'highpass'; f.frequency.value = 6000;
326
+ var g = ctx().createGain();
327
+ g.gain.setValueAtTime(vel * 0.3, t);
328
+ g.gain.exponentialRampToValueAtTime(0.001, t + 0.3);
329
+ noise.connect(f); f.connect(g); g.connect(masterNode());
330
+ noise.start(t);
331
+ return [noise];
332
+ },
333
+ 'crash': function() {
334
+ var bufLen = Math.round(ctx().sampleRate * 1.0);
335
+ var buf = ctx().createBuffer(1, bufLen, ctx().sampleRate);
336
+ var d = buf.getChannelData(0);
337
+ for (var i = 0; i < bufLen; i++) d[i] = (Math.random() * 2 - 1);
338
+ var noise = ctx().createBufferSource(); noise.buffer = buf;
339
+ var f = ctx().createBiquadFilter(); f.type = 'highpass'; f.frequency.value = 4000;
340
+ var g = ctx().createGain();
341
+ g.gain.setValueAtTime(vel * 0.5, t);
342
+ g.gain.exponentialRampToValueAtTime(0.001, t + 1.0);
343
+ noise.connect(f); f.connect(g); g.connect(masterNode());
344
+ noise.start(t);
345
+ return [noise];
346
+ },
347
+ 'ride': function() {
348
+ var bufLen = Math.round(ctx().sampleRate * 0.8);
349
+ var buf = ctx().createBuffer(1, bufLen, ctx().sampleRate);
350
+ var d = buf.getChannelData(0);
351
+ for (var i = 0; i < bufLen; i++) d[i] = (Math.random() * 2 - 1);
352
+ var noise = ctx().createBufferSource(); noise.buffer = buf;
353
+ var f = ctx().createBiquadFilter(); f.type = 'bandpass'; f.frequency.value = 8000; f.Q.value = 1;
354
+ var g = ctx().createGain();
355
+ g.gain.setValueAtTime(vel * 0.3, t);
356
+ g.gain.exponentialRampToValueAtTime(0.001, t + 0.8);
357
+ noise.connect(f); f.connect(g); g.connect(masterNode());
358
+ noise.start(t);
359
+ return [noise];
360
+ },
361
+ 'tom-high': function() { return makeTom(300); },
362
+ 'tom-mid': function() { return makeTom(220); },
363
+ 'tom-low': function() { return makeTom(150); },
364
+ 'clap': function() {
365
+ var nodes = [];
366
+ for (var ci = 0; ci < 3; ci++) {
367
+ var delay = ci * 0.01;
368
+ var bufLen = Math.round(ctx().sampleRate * 0.02);
369
+ var buf = ctx().createBuffer(1, bufLen, ctx().sampleRate);
370
+ var d = buf.getChannelData(0);
371
+ for (var j = 0; j < bufLen; j++) d[j] = (Math.random() * 2 - 1);
372
+ var noise = ctx().createBufferSource(); noise.buffer = buf;
373
+ var f = ctx().createBiquadFilter(); f.type = 'bandpass'; f.frequency.value = 2500; f.Q.value = 3;
374
+ var g = ctx().createGain();
375
+ g.gain.setValueAtTime(vel * 0.4, t + delay);
376
+ g.gain.exponentialRampToValueAtTime(0.001, t + delay + 0.08);
377
+ noise.connect(f); f.connect(g); g.connect(masterNode());
378
+ noise.start(t + delay);
379
+ nodes.push(noise);
380
+ }
381
+ return nodes;
382
+ },
383
+ 'cowbell': function() {
384
+ var osc1 = ctx().createOscillator(); osc1.type = 'square'; osc1.frequency.value = 587;
385
+ var osc2 = ctx().createOscillator(); osc2.type = 'square'; osc2.frequency.value = 845;
386
+ var g = ctx().createGain();
387
+ g.gain.setValueAtTime(vel * 0.3, t);
388
+ g.gain.exponentialRampToValueAtTime(0.001, t + 0.4);
389
+ var f = ctx().createBiquadFilter(); f.type = 'bandpass'; f.frequency.value = 700; f.Q.value = 3;
390
+ osc1.connect(g); osc2.connect(g); g.connect(f); f.connect(masterNode());
391
+ osc1.start(t); osc2.start(t);
392
+ osc1.stop(t + 0.5); osc2.stop(t + 0.5);
393
+ return [osc1, osc2];
394
+ }
395
+ };
396
+
397
+ var fn = synths[hit];
398
+ if (!fn) { console.warn('[aimeat-audio] Unknown drum hit:', hit); return; }
399
+ var nodes = fn();
400
+ registerActive('drums', hit, nodes, function() {
401
+ nodes.forEach(function(n) { try { n.stop(ctx().currentTime + 0.01); } catch(e){} });
402
+ });
403
+ }
404
+ };
405
+
406
+ // ── Flute: sine + breath noise + vibrato LFO ──
407
+
408
+ _instruments.flute = {
409
+ play: function(note, opts) {
410
+ var freq = noteToFreq(note);
411
+ if (!freq) return;
412
+ var t = ctx().currentTime;
413
+ var vel = (opts && opts.velocity !== undefined) ? opts.velocity : 0.6;
414
+ var dur = (opts && opts.duration) || 1.5;
415
+ var vibDepth = (opts && opts.vibrato !== undefined) ? opts.vibrato : 0.15;
416
+
417
+ var osc = ctx().createOscillator(); osc.type = 'sine'; osc.frequency.value = freq;
418
+
419
+ var lfo = ctx().createOscillator(); lfo.type = 'sine'; lfo.frequency.value = 5.5;
420
+ var lfoGain = ctx().createGain(); lfoGain.gain.value = freq * vibDepth * 0.02;
421
+ lfo.connect(lfoGain); lfoGain.connect(osc.frequency);
422
+
423
+ var noiseBufLen = Math.round(ctx().sampleRate * dur);
424
+ var noiseBuf = ctx().createBuffer(1, noiseBufLen, ctx().sampleRate);
425
+ var nd = noiseBuf.getChannelData(0);
426
+ for (var i = 0; i < noiseBufLen; i++) nd[i] = (Math.random() * 2 - 1);
427
+ var noiseNode = ctx().createBufferSource(); noiseNode.buffer = noiseBuf;
428
+ var noiseFilt = ctx().createBiquadFilter(); noiseFilt.type = 'bandpass';
429
+ noiseFilt.frequency.value = freq; noiseFilt.Q.value = 2;
430
+ var noiseGain = ctx().createGain(); noiseGain.gain.value = vel * 0.06;
431
+ noiseNode.connect(noiseFilt); noiseFilt.connect(noiseGain);
432
+
433
+ var env = ctx().createGain();
434
+ env.gain.setValueAtTime(0, t);
435
+ env.gain.linearRampToValueAtTime(vel * 0.3, t + 0.08);
436
+ env.gain.setValueAtTime(vel * 0.3, t + dur - 0.1);
437
+ env.gain.linearRampToValueAtTime(0, t + dur);
438
+
439
+ osc.connect(env); noiseGain.connect(env); env.connect(masterNode());
440
+ lfo.start(t); osc.start(t); noiseNode.start(t);
441
+ lfo.stop(t + dur + 0.1); osc.stop(t + dur + 0.1);
442
+
443
+ registerActive('flute', note, [osc, lfo, noiseNode], function() {
444
+ try { env.gain.cancelScheduledValues(ctx().currentTime);
445
+ env.gain.linearRampToValueAtTime(0, ctx().currentTime + 0.05);
446
+ osc.stop(ctx().currentTime + 0.1); lfo.stop(ctx().currentTime + 0.1); } catch(e){}
447
+ });
448
+ }
449
+ };
450
+
451
+ // ── Synth: configurable oscillator ──
452
+
453
+ _instruments.synth = {
454
+ play: function(note, opts) {
455
+ var freq = noteToFreq(note);
456
+ if (!freq) return;
457
+ var t = ctx().currentTime;
458
+ var vel = (opts && opts.velocity !== undefined) ? opts.velocity : 0.7;
459
+ var dur = (opts && opts.duration) || 1.0;
460
+ var wave = (opts && opts.wave) || 'sawtooth';
461
+ var filterFreq = (opts && opts.filter) || 2000;
462
+
463
+ var osc = ctx().createOscillator(); osc.type = wave; osc.frequency.value = freq;
464
+ var osc2 = ctx().createOscillator(); osc2.type = wave; osc2.frequency.value = freq; osc2.detune.value = 7;
465
+ var osc2Gain = ctx().createGain(); osc2Gain.gain.value = 0.5;
466
+ osc2.connect(osc2Gain);
467
+
468
+ var filter = ctx().createBiquadFilter(); filter.type = 'lowpass';
469
+ filter.frequency.value = filterFreq; filter.Q.value = 1;
470
+
471
+ var env = ctx().createGain();
472
+ env.gain.setValueAtTime(0, t);
473
+ env.gain.linearRampToValueAtTime(vel * 0.3, t + 0.02);
474
+ env.gain.setValueAtTime(vel * 0.3, t + dur * 0.7);
475
+ env.gain.exponentialRampToValueAtTime(0.001, t + dur);
476
+
477
+ osc.connect(filter); osc2Gain.connect(filter);
478
+ filter.connect(env); env.connect(masterNode());
479
+ osc.start(t); osc2.start(t);
480
+ osc.stop(t + dur + 0.05); osc2.stop(t + dur + 0.05);
481
+
482
+ registerActive('synth', note, [osc, osc2], function() {
483
+ try { env.gain.cancelScheduledValues(ctx().currentTime);
484
+ env.gain.linearRampToValueAtTime(0, ctx().currentTime + 0.05);
485
+ osc.stop(ctx().currentTime + 0.1); osc2.stop(ctx().currentTime + 0.1); } catch(e){}
486
+ });
487
+ }
488
+ };
489
+
490
+ // ══════════════════════════════════════════════════════
491
+ // Soundboard: load and play audio files
492
+ // ══════════════════════════════════════════════════════
493
+
494
+ var _soundboardBuffers = {};
495
+ var _soundboardSources = {};
496
+
497
+ var soundboard = {
498
+ load: function(name, url) {
499
+ return fetch(url).then(function(resp) {
500
+ if (!resp.ok) throw new Error('Failed to load sound: ' + url);
501
+ return resp.arrayBuffer();
502
+ }).then(function(arrayBuf) {
503
+ return ctx().decodeAudioData(arrayBuf);
504
+ }).then(function(decoded) {
505
+ _soundboardBuffers[name] = decoded;
506
+ });
507
+ },
508
+
509
+ loadAll: function(map) {
510
+ var entries = Object.keys(map);
511
+ return Promise.all(entries.map(function(k) { return soundboard.load(k, map[k]); }));
512
+ },
513
+
514
+ play: function(name, opts) {
515
+ var buf = _soundboardBuffers[name];
516
+ if (!buf) { console.warn('[aimeat-audio] Sound not loaded:', name); return; }
517
+ var source = ctx().createBufferSource();
518
+ source.buffer = buf;
519
+ source.loop = (opts && opts.loop) || false;
520
+ var gain = ctx().createGain();
521
+ gain.gain.value = (opts && opts.volume !== undefined) ? opts.volume : 1;
522
+ source.connect(gain); gain.connect(masterNode());
523
+ source.start();
524
+ _soundboardSources[name] = source;
525
+ source.onended = function() { if (_soundboardSources[name] === source) delete _soundboardSources[name]; };
526
+ },
527
+
528
+ stop: function(name) {
529
+ if (_soundboardSources[name]) {
530
+ try { _soundboardSources[name].stop(); } catch(e){}
531
+ delete _soundboardSources[name];
532
+ }
533
+ }
534
+ };
535
+
536
+ // ══════════════════════════════════════════════════════
537
+ // Sample Loader
538
+ // ══════════════════════════════════════════════════════
539
+
540
+ var SAMPLE_NOTES = {
541
+ piano: ['A2','C3','Ds3','A3','C4','Ds4','A4','C5','Ds5','C6','C7'],
542
+ guitar: ['E2','A2','D3','G3','B3','E4','A4','E5'],
543
+ bass: ['E1','A1','D2','G2','B2','E3'],
544
+ flute: ['C4','E4','A4','C5','E5','A5','C6'],
545
+ drums: ['kick','snare','hihat','hihat-open','crash','ride','tom-high','tom-mid','tom-low','clap','cowbell']
546
+ };
547
+
548
+ function loadSamples(instrument, opts) {
549
+ var source = (opts && opts.source) || (NODE_URL + '/lib/samples/' + instrument + '/');
550
+ var notes = SAMPLE_NOTES[instrument];
551
+ if (!notes) { console.warn('[aimeat-audio] No sample map for:', instrument); return Promise.resolve(); }
552
+ if (!_sampleBuffers[instrument]) _sampleBuffers[instrument] = {};
553
+
554
+ return Promise.all(notes.map(function(note) {
555
+ var url = source + note + '.mp3';
556
+ return fetch(url).then(function(resp) {
557
+ if (!resp.ok) return null;
558
+ return resp.arrayBuffer();
559
+ }).then(function(buf) {
560
+ if (!buf) return;
561
+ return ctx().decodeAudioData(buf);
562
+ }).then(function(decoded) {
563
+ if (decoded) _sampleBuffers[instrument][note] = decoded;
564
+ }).catch(function() {
565
+ console.warn('[aimeat-audio] Failed to load sample:', url);
566
+ });
567
+ }));
568
+ }
569
+
570
+ function hasSamples(instrument) {
571
+ return !!_sampleBuffers[instrument] && Object.keys(_sampleBuffers[instrument]).length > 0;
572
+ }
573
+
574
+ function findNearestSample(instrument, note) {
575
+ var samples = _sampleBuffers[instrument];
576
+ if (!samples) return null;
577
+ if (instrument === 'drums') return samples[note] ? { buffer: samples[note], rate: 1 } : null;
578
+ var targetFreq = noteToFreq(note);
579
+ if (!targetFreq) return null;
580
+ var nearest = null;
581
+ var nearestDist = Infinity;
582
+ Object.keys(samples).forEach(function(sn) {
583
+ var sf = noteToFreq(sn);
584
+ if (!sf) return;
585
+ var dist = Math.abs(Math.log2(targetFreq / sf));
586
+ if (dist < nearestDist) { nearestDist = dist; nearest = { buffer: samples[sn], rate: targetFreq / sf }; }
587
+ });
588
+ return nearest;
589
+ }
590
+
591
+ function playSample(instrument, note, opts) {
592
+ var s = findNearestSample(instrument, note);
593
+ if (!s) return false;
594
+ var vel = (opts && opts.velocity !== undefined) ? opts.velocity : 0.7;
595
+ var source = ctx().createBufferSource();
596
+ source.buffer = s.buffer;
597
+ source.playbackRate.value = s.rate;
598
+ var gain = ctx().createGain(); gain.gain.value = vel;
599
+ source.connect(gain); gain.connect(masterNode());
600
+ source.start();
601
+ registerActive(instrument, note, [source], function() {
602
+ try { source.stop(); } catch(e){}
603
+ });
604
+ return true;
605
+ }
606
+
607
+ // ══════════════════════════════════════════════════════
608
+ // Custom Synth Builder
609
+ // ══════════════════════════════════════════════════════
610
+
611
+ function createCustomSynth(config) {
612
+ var name = config.name || ('custom-' + Date.now());
613
+ var oscConfigs = config.oscillators || [{ wave: 'sawtooth', detune: 0 }];
614
+ var envConfig = config.envelope || { attack: 0.01, decay: 0.1, sustain: 0.7, release: 0.1 };
615
+ var filterConfig = config.filter || null;
616
+ var pitchEnvConfig = config.pitchEnvelope || null;
617
+ var effectsConfig = config.effects || [];
618
+
619
+ function buildEffectChain(input) {
620
+ var current = input;
621
+ effectsConfig.forEach(function(fx) {
622
+ if (fx.type === 'distortion') {
623
+ var ws = ctx().createWaveShaper();
624
+ var amount = fx.amount || 0.5;
625
+ var k = amount * 400;
626
+ var samples = 44100;
627
+ var curve = new Float32Array(samples);
628
+ for (var i = 0; i < samples; i++) {
629
+ var x = i * 2 / samples - 1;
630
+ curve[i] = (3 + k) * x * 20 * (Math.PI / 180) / (Math.PI + k * Math.abs(x));
631
+ }
632
+ ws.curve = curve;
633
+ current.connect(ws); current = ws;
634
+ } else if (fx.type === 'delay') {
635
+ var dryG = ctx().createGain(); dryG.gain.value = 1 - (fx.mix || 0.3);
636
+ var wetG = ctx().createGain(); wetG.gain.value = fx.mix || 0.3;
637
+ var dly = ctx().createDelay(5.0); dly.delayTime.value = fx.time || 0.3;
638
+ var fb = ctx().createGain(); fb.gain.value = fx.feedback || 0.4;
639
+ current.connect(dryG); current.connect(dly);
640
+ dly.connect(fb); fb.connect(dly); dly.connect(wetG);
641
+ var merge = ctx().createGain();
642
+ dryG.connect(merge); wetG.connect(merge);
643
+ current = merge;
644
+ } else if (fx.type === 'chorus') {
645
+ var cDry = ctx().createGain(); cDry.gain.value = 1 - (fx.mix || 0.5);
646
+ var cWet = ctx().createGain(); cWet.gain.value = fx.mix || 0.5;
647
+ var cDelay = ctx().createDelay(); cDelay.delayTime.value = 0.02;
648
+ var cLfo = ctx().createOscillator(); cLfo.type = 'sine'; cLfo.frequency.value = fx.rate || 1.5;
649
+ var cDepth = ctx().createGain(); cDepth.gain.value = (fx.depth || 0.7) * 0.01;
650
+ cLfo.connect(cDepth); cDepth.connect(cDelay.delayTime); cLfo.start();
651
+ current.connect(cDry); current.connect(cDelay); cDelay.connect(cWet);
652
+ var cMerge = ctx().createGain();
653
+ cDry.connect(cMerge); cWet.connect(cMerge);
654
+ current = cMerge;
655
+ } else if (fx.type === 'tremolo') {
656
+ var trem = ctx().createGain();
657
+ var tLfo = ctx().createOscillator(); tLfo.type = 'sine'; tLfo.frequency.value = fx.rate || 4;
658
+ var tDepth = ctx().createGain(); tDepth.gain.value = fx.depth || 0.5;
659
+ tLfo.connect(tDepth); tDepth.connect(trem.gain); tLfo.start();
660
+ trem.gain.value = 1 - (fx.depth || 0.5) / 2;
661
+ current.connect(trem); current = trem;
662
+ } else if (fx.type === 'reverb') {
663
+ var rDry = ctx().createGain(); rDry.gain.value = 1 - (fx.mix || 0.5);
664
+ var rWet = ctx().createGain(); rWet.gain.value = fx.mix || 0.5;
665
+ var d1 = ctx().createDelay(); d1.delayTime.value = 0.037;
666
+ var d2 = ctx().createDelay(); d2.delayTime.value = 0.053;
667
+ var d3 = ctx().createDelay(); d3.delayTime.value = 0.071;
668
+ var rFb = Math.min(0.85, (fx.decay || 2) / 5);
669
+ var fg1 = ctx().createGain(); fg1.gain.value = rFb;
670
+ var fg2 = ctx().createGain(); fg2.gain.value = rFb * 0.9;
671
+ var fg3 = ctx().createGain(); fg3.gain.value = rFb * 0.8;
672
+ d1.connect(fg1); fg1.connect(d1); d2.connect(fg2); fg2.connect(d2); d3.connect(fg3); fg3.connect(d3);
673
+ current.connect(rDry); current.connect(d1); current.connect(d2); current.connect(d3);
674
+ var rMerge = ctx().createGain();
675
+ rDry.connect(rMerge); d1.connect(rWet); d2.connect(rWet); d3.connect(rWet); rWet.connect(rMerge);
676
+ current = rMerge;
677
+ } else if (fx.type === 'filter') {
678
+ var ff = ctx().createBiquadFilter();
679
+ ff.type = fx.filterType || 'lowpass';
680
+ ff.frequency.value = fx.frequency || 1000;
681
+ ff.Q.value = fx.Q || 1;
682
+ current.connect(ff); current = ff;
683
+ }
684
+ });
685
+ return current;
686
+ }
687
+
688
+ var synth = {
689
+ name: name,
690
+ play: function(note, opts) {
691
+ var freq = noteToFreq(note);
692
+ if (!freq) return;
693
+ var t = ctx().currentTime;
694
+ var vel = (opts && opts.velocity !== undefined) ? opts.velocity : 0.7;
695
+ var dur = (opts && opts.duration) || 1.0;
696
+ var allOscs = [];
697
+ var mix = ctx().createGain();
698
+ mix.gain.value = vel * 0.4 / oscConfigs.length;
699
+
700
+ oscConfigs.forEach(function(oc) {
701
+ var o = ctx().createOscillator();
702
+ o.type = oc.wave || 'sawtooth';
703
+ o.frequency.value = freq;
704
+ o.detune.value = oc.detune || 0;
705
+ if (oc.gain !== undefined) {
706
+ var og = ctx().createGain(); og.gain.value = oc.gain;
707
+ o.connect(og); og.connect(mix);
708
+ } else {
709
+ o.connect(mix);
710
+ }
711
+ allOscs.push(o);
712
+ });
713
+
714
+ var chain = mix;
715
+ if (filterConfig) {
716
+ var cf = ctx().createBiquadFilter();
717
+ cf.type = filterConfig.type || 'lowpass';
718
+ cf.frequency.value = filterConfig.frequency || 1000;
719
+ cf.Q.value = filterConfig.Q || 1;
720
+ mix.connect(cf); chain = cf;
721
+ }
722
+
723
+ chain = buildEffectChain(chain);
724
+
725
+ var env = ctx().createGain();
726
+ var a = envConfig.attack || 0.01;
727
+ var d = envConfig.decay || 0.1;
728
+ var s = envConfig.sustain !== undefined ? envConfig.sustain : 0.7;
729
+ var r = envConfig.release || 0.1;
730
+ env.gain.setValueAtTime(0, t);
731
+ env.gain.linearRampToValueAtTime(1, t + a);
732
+ env.gain.linearRampToValueAtTime(s, t + a + d);
733
+ if (dur) {
734
+ env.gain.setValueAtTime(s, t + dur - r);
735
+ env.gain.linearRampToValueAtTime(0, t + dur);
736
+ }
737
+
738
+ chain.connect(env); env.connect(masterNode());
739
+
740
+ if (pitchEnvConfig) {
741
+ allOscs.forEach(function(o) {
742
+ o.frequency.setValueAtTime(pitchEnvConfig.start || freq, t);
743
+ o.frequency.exponentialRampToValueAtTime(pitchEnvConfig.end || freq, t + (pitchEnvConfig.time || 0.1));
744
+ });
745
+ }
746
+
747
+ allOscs.forEach(function(o) { o.start(t); if (dur) o.stop(t + dur + 0.1); });
748
+
749
+ registerActive(name, note, allOscs, function() {
750
+ try { env.gain.cancelScheduledValues(ctx().currentTime);
751
+ env.gain.linearRampToValueAtTime(0, ctx().currentTime + 0.05);
752
+ allOscs.forEach(function(o) { try{o.stop(ctx().currentTime+0.1);}catch(e){} }); } catch(e){}
753
+ });
754
+ },
755
+ stop: function(note) { stopActive(name, note); }
756
+ };
757
+
758
+ _instruments[name] = synth;
759
+ _customSynths[name] = synth;
760
+ return synth;
761
+ }
762
+
763
+ // ══════════════════════════════════════════════════════
764
+ // Realtime Bridge
765
+ // ══════════════════════════════════════════════════════
766
+
767
+ var _rtInstance = null;
768
+ var _rtHandlerBroadcast = null;
769
+ var _rtHandlerPeerData = null;
770
+
771
+ function connectRealtime(rt) {
772
+ if (_rtInstance) disconnectRealtime();
773
+ _rtInstance = rt;
774
+
775
+ _rtHandlerBroadcast = function(msg) {
776
+ var d = msg.data || msg.payload || msg;
777
+ if (d && d.instrument && (d.note || d.hit)) {
778
+ audio.play(d.instrument, d.note || d.hit, d);
779
+ }
780
+ };
781
+ _rtHandlerPeerData = function(msg) {
782
+ var d = msg.data;
783
+ if (d && d.instrument && (d.note || d.hit)) {
784
+ audio.play(d.instrument, d.note || d.hit, d);
785
+ }
786
+ };
787
+
788
+ rt.on('broadcast', _rtHandlerBroadcast);
789
+ rt.on('peer-data', _rtHandlerPeerData);
790
+ }
791
+
792
+ function disconnectRealtime() {
793
+ if (_rtInstance) {
794
+ if (_rtHandlerBroadcast) _rtInstance.off('broadcast', _rtHandlerBroadcast);
795
+ if (_rtHandlerPeerData) _rtInstance.off('peer-data', _rtHandlerPeerData);
796
+ _rtInstance = null;
797
+ _rtHandlerBroadcast = null;
798
+ _rtHandlerPeerData = null;
799
+ }
800
+ }
801
+
802
+ // ══════════════════════════════════════════════════════
803
+ // Public API
804
+ // ══════════════════════════════════════════════════════
805
+
806
+ var audio = {
807
+ play: function(instrument, note, opts) {
808
+ if (_sampleBuffers[instrument] && Object.keys(_sampleBuffers[instrument]).length > 0) {
809
+ if (playSample(instrument, note, opts)) return;
810
+ }
811
+ if (_customSynths[instrument]) {
812
+ _customSynths[instrument].play(note, opts);
813
+ return;
814
+ }
815
+ var inst = _instruments[instrument];
816
+ if (!inst) { console.warn('[aimeat-audio] Unknown instrument:', instrument); return; }
817
+ inst.play(note, opts);
818
+ },
819
+
820
+ stop: function(instrument, note) {
821
+ stopActive(instrument, note);
822
+ },
823
+
824
+ get instruments() {
825
+ var list = Object.keys(_instruments);
826
+ Object.keys(_customSynths).forEach(function(k) {
827
+ if (list.indexOf(k) < 0) list.push(k);
828
+ });
829
+ return list;
830
+ },
831
+
832
+ loadSamples: loadSamples,
833
+ hasSamples: hasSamples,
834
+ synth: createCustomSynth,
835
+ soundboard: soundboard,
836
+ master: master,
837
+ connectRealtime: connectRealtime,
838
+ disconnectRealtime: disconnectRealtime,
839
+ };
840
+
841
+ // ── Expose globally ──
842
+ if (!global.AIMEAT) global.AIMEAT = {};
843
+ global.AIMEAT.audio = audio;
844
+
845
+ })(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this);
846
+ `;
847
+ }
848
+ //# sourceMappingURL=lib-audio.js.map