@al8b/audio 0.1.12 → 0.1.14
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.
- package/dist/core/audio-core.js +24 -9
- package/dist/core/audio-core.js.map +1 -1
- package/dist/core/audio-core.mjs +24 -11
- package/dist/core/audio-core.mjs.map +1 -1
- package/dist/core/index.js +24 -11
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +24 -11
- package/dist/core/index.mjs.map +1 -1
- package/dist/devices/index.js +12 -5
- package/dist/devices/index.js.map +1 -1
- package/dist/devices/index.mjs +12 -5
- package/dist/devices/index.mjs.map +1 -1
- package/dist/devices/sound.d.mts +4 -0
- package/dist/devices/sound.d.ts +4 -0
- package/dist/devices/sound.js +12 -5
- package/dist/devices/sound.js.map +1 -1
- package/dist/devices/sound.mjs +12 -5
- package/dist/devices/sound.mjs.map +1 -1
- package/dist/index.js +36 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +36 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +33 -35
package/dist/core/audio-core.js
CHANGED
|
@@ -24,7 +24,6 @@ __export(audio_core_exports, {
|
|
|
24
24
|
AudioCore: () => AudioCore
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(audio_core_exports);
|
|
27
|
-
var import_diagnostics = require("@al8b/diagnostics");
|
|
28
27
|
|
|
29
28
|
// src/constants.ts
|
|
30
29
|
var A4_FREQUENCY = 440;
|
|
@@ -474,8 +473,12 @@ var AudioCore = class {
|
|
|
474
473
|
const soundName = sound.replace(/\//g, "-");
|
|
475
474
|
const s = this.runtime.sounds[soundName];
|
|
476
475
|
if (!s) {
|
|
477
|
-
|
|
478
|
-
|
|
476
|
+
this.runtime?.listener?.reportError?.({
|
|
477
|
+
code: "E7013",
|
|
478
|
+
message: "Sound not found",
|
|
479
|
+
data: {
|
|
480
|
+
soundName
|
|
481
|
+
}
|
|
479
482
|
});
|
|
480
483
|
return 0;
|
|
481
484
|
}
|
|
@@ -491,8 +494,12 @@ var AudioCore = class {
|
|
|
491
494
|
const musicName = music.replace(/\//g, "-");
|
|
492
495
|
const m = this.runtime.music[musicName];
|
|
493
496
|
if (!m) {
|
|
494
|
-
|
|
495
|
-
|
|
497
|
+
this.runtime?.listener?.reportError?.({
|
|
498
|
+
code: "E7014",
|
|
499
|
+
message: "Music not found",
|
|
500
|
+
data: {
|
|
501
|
+
musicName
|
|
502
|
+
}
|
|
496
503
|
});
|
|
497
504
|
return 0;
|
|
498
505
|
}
|
|
@@ -559,8 +566,12 @@ var AudioCore = class {
|
|
|
559
566
|
this.workletNode.connect(this.context.destination);
|
|
560
567
|
this.flushBuffer();
|
|
561
568
|
} catch (e) {
|
|
562
|
-
|
|
563
|
-
|
|
569
|
+
this.runtime?.listener?.reportError?.({
|
|
570
|
+
code: "E7012",
|
|
571
|
+
message: "Audio worklet error",
|
|
572
|
+
data: {
|
|
573
|
+
error: String(e)
|
|
574
|
+
}
|
|
564
575
|
});
|
|
565
576
|
}
|
|
566
577
|
}
|
|
@@ -649,8 +660,12 @@ var AudioCore = class {
|
|
|
649
660
|
try {
|
|
650
661
|
p.stop();
|
|
651
662
|
} catch (err) {
|
|
652
|
-
|
|
653
|
-
|
|
663
|
+
this.runtime?.listener?.reportError?.({
|
|
664
|
+
code: "E7016",
|
|
665
|
+
message: "Audio error",
|
|
666
|
+
data: {
|
|
667
|
+
error: String(err)
|
|
668
|
+
}
|
|
654
669
|
});
|
|
655
670
|
}
|
|
656
671
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/audio-core.ts","../../src/constants.ts","../../src/devices/beeper.ts","../../src/core/audio-worklet.ts"],"sourcesContent":["/**\n * AudioCore - Web Audio API wrapper\n * Manages audio context, beeper, and sound/music playback\n */\n\nimport { APIErrorCode, reportRuntimeError } from \"@al8b/diagnostics\";\nimport { Beeper } from \"../devices/beeper\";\nimport { AUDIO_WORKLET_CODE } from \"./audio-worklet\";\n\n/** An actively playing sound/music that can be stopped */\ninterface PlayingHandle {\n\tstop: () => void;\n}\n\n/** Item that can be woken up on audio context activation */\ninterface WakeUpItem {\n\twakeUp: () => void;\n}\n\nexport class AudioCore {\n\tpublic context!: AudioContext;\n\tprivate buffer: string[] = [];\n\tprivate playing: PlayingHandle[] = [];\n\tprivate wakeupList: WakeUpItem[] = [];\n\tprivate workletNode?: AudioWorkletNode;\n\tprivate beeper?: Beeper;\n\tprivate runtime: any;\n\tprivate masterVolume: number = 1;\n\n\tconstructor(runtime: any) {\n\t\tthis.runtime = runtime;\n\t\tthis.getContext();\n\t}\n\n\t/**\n\t * Check if audio context is running\n\t */\n\tpublic isStarted(): boolean {\n\t\treturn this.context.state === \"running\";\n\t}\n\n\t/**\n\t * Add item to wakeup list (for mobile audio activation)\n\t */\n\tpublic addToWakeUpList(item: WakeUpItem): void {\n\t\tthis.wakeupList.push(item);\n\t}\n\n\tprivate interfaceCache: Record<string, any> | null = null;\n\n\t/**\n\t * Set master volume (0-1). Applied as a multiplier to all sound/music playback.\n\t */\n\tpublic setVolume(volume: number): void {\n\t\tthis.masterVolume = Math.max(0, Math.min(1, volume));\n\t}\n\n\t/**\n\t * Get current master volume (0-1)\n\t */\n\tpublic getVolume(): number {\n\t\treturn this.masterVolume;\n\t}\n\n\t/**\n\t * Get interface for game code\n\t */\n\tpublic getInterface() {\n\t\tif (this.interfaceCache) {\n\t\t\treturn this.interfaceCache;\n\t\t}\n\t\tthis.interfaceCache = {\n\t\t\tbeep: (sequence: string) => this.beep(sequence),\n\t\t\tcancelBeeps: () => this.cancelBeeps(),\n\t\t\tplaySound: (sound: any, volume?: number, pitch?: number, pan?: number, loopit?: boolean) =>\n\t\t\t\tthis.playSound(sound, volume, pitch, pan, loopit),\n\t\t\tplayMusic: (music: any, volume?: number, loopit?: boolean) => this.playMusic(music, volume, loopit),\n\t\t\tsetVolume: (volume: number) => this.setVolume(volume),\n\t\t\tgetVolume: () => this.getVolume(),\n\t\t\tstopAll: () => this.stopAll(),\n\t\t};\n\t\treturn this.interfaceCache;\n\t}\n\n\t/**\n\t * Play sound effect\n\t */\n\tpublic playSound(sound: any, volume: number = 1, pitch: number = 1, pan: number = 0, loopit: boolean = false): number {\n\t\tif (typeof sound === \"string\") {\n\t\t\tconst soundName = sound.replace(/\\//g, \"-\");\n\t\t\tconst s = this.runtime.sounds[soundName];\n\t\t\tif (!s) {\n\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7013, { soundName });\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\treturn s.play(volume * this.masterVolume, pitch, pan, loopit);\n\t\t}\n\t\treturn 0;\n\t}\n\n\t/**\n\t * Play music\n\t */\n\tpublic playMusic(music: any, volume: number = 1, loopit: boolean = false): number {\n\t\tif (typeof music === \"string\") {\n\t\t\tconst musicName = music.replace(/\\//g, \"-\");\n\t\t\tconst m = this.runtime.music[musicName];\n\t\t\tif (!m) {\n\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7014, { musicName });\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\treturn m.play(volume * this.masterVolume, loopit);\n\t\t}\n\t\treturn 0;\n\t}\n\n\t/**\n\t * Get or create audio context (lazy initialization - created on first use)\n\t * Note: Browser may suspend context until user interaction, which is handled automatically\n\t */\n\tpublic getContext(): AudioContext {\n\t\tif (!this.context) {\n\t\t\tconst AudioContextClass = (window as any).AudioContext || (window as any).webkitAudioContext;\n\t\t\t// Create context - browser may suspend until user interaction\n\t\t\tthis.context = new AudioContextClass();\n\n\t\t\t// If context is suspended, set up activation listeners\n\t\t\tif (this.context.state !== \"running\") {\n\t\t\t\tconst activate = () => {\n\t\t\t\t\tif (this.context && this.context.state !== \"running\") {\n\t\t\t\t\t\tthis.context.resume();\n\t\t\t\t\t\tif (this.beeper) {\n\t\t\t\t\t\t\tthis.start();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const item of this.wakeupList) {\n\t\t\t\t\t\t\titem.wakeUp();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Clean up listeners\n\t\t\t\t\t\tdocument.body.removeEventListener(\"touchend\", activate);\n\t\t\t\t\t\tdocument.body.removeEventListener(\"mouseup\", activate);\n\t\t\t\t\t\tdocument.body.removeEventListener(\"click\", activate);\n\t\t\t\t\t\tdocument.body.removeEventListener(\"keydown\", activate);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// Add multiple event listeners for better compatibility\n\t\t\t\tdocument.body.addEventListener(\"touchend\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t\tdocument.body.addEventListener(\"mouseup\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t\tdocument.body.addEventListener(\"click\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t\tdocument.body.addEventListener(\"keydown\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t} else if (this.beeper) {\n\t\t\t\tthis.start();\n\t\t\t}\n\t\t}\n\n\t\treturn this.context;\n\t}\n\n\t/**\n\t * Start audio processor\n\t */\n\tpublic async start(): Promise<void> {\n\t\tif (this.workletNode) return;\n\n\t\ttry {\n\t\t\tconst blob = new Blob([AUDIO_WORKLET_CODE], {\n\t\t\t\ttype: \"application/javascript\",\n\t\t\t});\n\t\t\tconst url = URL.createObjectURL(blob);\n\n\t\t\tawait this.context.audioWorklet.addModule(url);\n\n\t\t\tthis.workletNode = new AudioWorkletNode(this.context, \"l8b-audio-processor\");\n\t\t\tthis.workletNode.connect(this.context.destination);\n\n\t\t\tthis.flushBuffer();\n\t\t} catch (e) {\n\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7012, { error: String(e) });\n\t\t}\n\t}\n\n\t/**\n\t * Flush buffered messages\n\t */\n\tprivate flushBuffer(): void {\n\t\tif (!this.workletNode) return;\n\n\t\twhile (this.buffer.length > 0) {\n\t\t\tthis.workletNode.port.postMessage(this.buffer.splice(0, 1)[0]);\n\t\t}\n\t}\n\n\t/**\n\t * Get or create beeper\n\t */\n\tpublic getBeeper(): Beeper {\n\t\tif (!this.beeper) {\n\t\t\t// Create Beeper instance\n\t\t\tthis.beeper = new Beeper(this);\n\n\t\t\tif (this.context.state === \"running\") {\n\t\t\t\tthis.start();\n\t\t\t}\n\t\t}\n\t\treturn this.beeper;\n\t}\n\n\t/**\n\t * Play beep sequence\n\t */\n\tpublic beep(sequence: string): void {\n\t\tthis.getBeeper().beep(sequence);\n\t}\n\n\t/**\n\t * Add beeps to audio processor\n\t */\n\tpublic addBeeps(beeps: any[]): void {\n\t\tfor (const b of beeps) {\n\t\t\tb.duration *= this.context.sampleRate;\n\t\t\tb.increment = b.frequency / this.context.sampleRate;\n\t\t}\n\n\t\tif (this.workletNode) {\n\t\t\tthis.workletNode.port.postMessage(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"beep\",\n\t\t\t\t\tsequence: beeps,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.buffer.push(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"beep\",\n\t\t\t\t\tsequence: beeps,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel all beeps\n\t */\n\tpublic cancelBeeps(): void {\n\t\tif (this.workletNode) {\n\t\t\tthis.workletNode.port.postMessage(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"cancel_beeps\",\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.buffer.push(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"cancel_beeps\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tthis.stopAll();\n\t}\n\n\t/**\n\t * Add playing sound/music to list\n\t */\n\tpublic addPlaying(item: PlayingHandle): void {\n\t\tthis.playing.push(item);\n\t}\n\n\t/**\n\t * Remove playing sound/music from list\n\t */\n\tpublic removePlaying(item: PlayingHandle): void {\n\t\tconst index = this.playing.indexOf(item);\n\t\tif (index >= 0) {\n\t\t\tthis.playing.splice(index, 1);\n\t\t}\n\t}\n\n\t/**\n\t * Stop all playing sounds/music\n\t */\n\tpublic stopAll(): void {\n\t\tfor (const p of this.playing) {\n\t\t\ttry {\n\t\t\t\tp.stop();\n\t\t\t} catch (err) {\n\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7016, { error: String(err) });\n\t\t\t}\n\t\t}\n\t\tthis.playing = [];\n\t}\n}\n","/** A4 reference frequency in Hz (concert pitch) */\nexport const A4_FREQUENCY = 440;\n\n/** Ratio between adjacent semitones in equal temperament */\nexport const SEMITONE_RATIO = 2 ** (1 / 12);\n\n/** MIDI note number for A4 */\nexport const A4_MIDI_NOTE = 69;\n","/**\n * Beeper - Procedural sound generation from text sequences\n * Converts music notation strings into beep sequences\n * Example: \"square tempo 120 C4 D4 E4 F4\"\n */\nimport { A4_FREQUENCY, A4_MIDI_NOTE, SEMITONE_RATIO } from \"../constants\";\nexport class Beeper {\n\tprivate audio: any;\n\tprivate notes: Record<string | number, number> = {};\n\tprivate plainNotes: Record<string, number> = {};\n\tprivate currentOctave = 5;\n\tprivate currentDuration = 0.5;\n\tprivate currentVolume = 0.5;\n\tprivate currentSpan = 1;\n\tprivate currentWaveform = \"square\";\n\n\tconstructor(audio: any) {\n\t\tthis.audio = audio;\n\t\tthis.initializeNotes();\n\t}\n\n\t/**\n\t * Initialize note mappings\n\t */\n\tprivate initializeNotes(): void {\n\t\tconst noteNames = [\n\t\t\t[\"C\", \"DO\"],\n\t\t\t[\"C#\", \"DO#\", \"Db\", \"REb\"],\n\t\t\t[\"D\", \"RE\"],\n\t\t\t[\"D#\", \"RE#\", \"Eb\", \"MIb\"],\n\t\t\t[\"E\", \"MI\"],\n\t\t\t[\"F\", \"FA\"],\n\t\t\t[\"F#\", \"FA#\", \"Gb\", \"SOLb\"],\n\t\t\t[\"G\", \"SOL\"],\n\t\t\t[\"G#\", \"SOL#\", \"Ab\", \"LAb\"],\n\t\t\t[\"A\", \"LA\"],\n\t\t\t[\"A#\", \"LA#\", \"Bb\", \"SIb\"],\n\t\t\t[\"B\", \"SI\"],\n\t\t];\n\n\t\tfor (let i = 0; i <= 127; i++) {\n\t\t\tthis.notes[i] = i;\n\t\t\tconst oct = Math.floor(i / 12) - 1;\n\n\t\t\tfor (const n of noteNames[i % 12]) {\n\t\t\t\tthis.notes[n + oct] = i;\n\t\t\t}\n\n\t\t\tif (oct === -1) {\n\t\t\t\tfor (const n of noteNames[i % 12]) {\n\t\t\t\t\tthis.plainNotes[n] = i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Parse and play beep sequence\n\t */\n\tpublic beep(input: string): void {\n\t\tlet status: string = \"normal\";\n\t\tconst sequence: any[] = [];\n\t\tconst loops: any[] = [];\n\t\tlet note: number | undefined;\n\n\t\tconst parsed = input.split(\" \");\n\n\t\tfor (const t of parsed) {\n\t\t\tif (t === \"\") continue;\n\n\t\t\tswitch (status) {\n\t\t\t\tcase \"normal\":\n\t\t\t\t\tif (this.notes[t] !== undefined) {\n\t\t\t\t\t\t// Full note with octave (e.g., \"C4\")\n\t\t\t\t\t\tnote = this.notes[t];\n\t\t\t\t\t\tthis.currentOctave = Math.floor(note / 12);\n\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\tfrequency: A4_FREQUENCY * SEMITONE_RATIO ** (note - A4_MIDI_NOTE),\n\t\t\t\t\t\t\tvolume: this.currentVolume,\n\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (this.plainNotes[t] !== undefined) {\n\t\t\t\t\t\t// Note without octave (e.g., \"C\")\n\t\t\t\t\t\tnote = this.plainNotes[t] + this.currentOctave * 12;\n\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\tfrequency: A4_FREQUENCY * SEMITONE_RATIO ** (note - A4_MIDI_NOTE),\n\t\t\t\t\t\t\tvolume: this.currentVolume,\n\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if ([\"square\", \"sine\", \"saw\", \"noise\"].includes(t)) {\n\t\t\t\t\t\t// Waveform\n\t\t\t\t\t\tthis.currentWaveform = t;\n\t\t\t\t\t} else if ([\"tempo\", \"duration\", \"volume\", \"span\", \"loop\", \"to\"].includes(t)) {\n\t\t\t\t\t\t// Commands\n\t\t\t\t\t\tstatus = t;\n\t\t\t\t\t} else if (t === \"-\") {\n\t\t\t\t\t\t// Rest/silence\n\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\tfrequency: A4_FREQUENCY,\n\t\t\t\t\t\t\tvolume: 0,\n\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (t === \"end\") {\n\t\t\t\t\t\t// End loop\n\t\t\t\t\t\tif (loops.length > 0 && sequence.length > 0) {\n\t\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\t\tfrequency: A4_FREQUENCY,\n\t\t\t\t\t\t\t\tvolume: 0,\n\t\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\t\tduration: 0,\n\t\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst lop = loops.splice(loops.length - 1, 1)[0];\n\t\t\t\t\t\t\tsequence[sequence.length - 1].loopto = lop.start;\n\t\t\t\t\t\t\tsequence[sequence.length - 1].repeats = lop.repeats;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"tempo\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst tempo = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(tempo) && tempo > 0) {\n\t\t\t\t\t\tthis.currentDuration = 60 / tempo;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"duration\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst duration = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(duration) && duration > 0) {\n\t\t\t\t\t\tthis.currentDuration = duration / 1000;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"volume\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst volume = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(volume)) {\n\t\t\t\t\t\tthis.currentVolume = volume / 100;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"span\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst span = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(span)) {\n\t\t\t\t\t\tthis.currentSpan = span / 100;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"loop\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tloops.push({\n\t\t\t\t\t\tstart: sequence.length,\n\t\t\t\t\t});\n\t\t\t\t\tconst repeats = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(repeats)) {\n\t\t\t\t\t\tloops[loops.length - 1].repeats = repeats;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"to\":\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tif (note !== undefined) {\n\t\t\t\t\t\tlet n: number | undefined;\n\n\t\t\t\t\t\tif (this.notes[t] !== undefined) {\n\t\t\t\t\t\t\tn = this.notes[t];\n\t\t\t\t\t\t} else if (this.plainNotes[t] !== undefined) {\n\t\t\t\t\t\t\tn = this.plainNotes[t] + this.currentOctave * 12;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (n !== undefined && n !== note) {\n\t\t\t\t\t\t\t// Generate slide from note to n\n\t\t\t\t\t\t\tconst step = n > note ? 1 : -1;\n\t\t\t\t\t\t\tfor (let i = note + step; step > 0 ? i <= n : i >= n; i += step) {\n\t\t\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\t\t\tfrequency: A4_FREQUENCY * SEMITONE_RATIO ** (i - A4_MIDI_NOTE),\n\t\t\t\t\t\t\t\t\tvolume: this.currentVolume,\n\t\t\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnote = n;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Close any remaining loops\n\t\tif (loops.length > 0 && sequence.length > 0) {\n\t\t\tconst lop = loops.splice(loops.length - 1, 1)[0];\n\t\t\tsequence.push({\n\t\t\t\tfrequency: A4_FREQUENCY,\n\t\t\t\tvolume: 0,\n\t\t\t\tspan: this.currentSpan,\n\t\t\t\tduration: 0,\n\t\t\t\twaveform: this.currentWaveform,\n\t\t\t});\n\n\t\t\tsequence[sequence.length - 1].loopto = lop.start;\n\t\t\tsequence[sequence.length - 1].repeats = lop.repeats;\n\t\t}\n\n\t\tthis.audio.addBeeps(sequence);\n\t}\n}\n","export const AUDIO_WORKLET_CODE = `\nclass L8bAudioProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.beeps = [];\n this.last = 0;\n this.port.onmessage = (event) => {\n const data = JSON.parse(event.data);\n if (data.name === \"cancel_beeps\") {\n this.beeps = [];\n } else if (data.name === \"beep\") {\n const seq = data.sequence;\n // Link sequence notes together\n for (let i = 0; i < seq.length; i++) {\n const note = seq[i];\n if (i > 0) {\n seq[i - 1].next = note;\n }\n // Resolve loopto index to actual note reference\n if (note.loopto != null) {\n note.loopto = seq[note.loopto];\n }\n // Initialize phase and time\n note.phase = 0;\n note.time = 0;\n }\n // Add first note to beeps queue\n if (seq.length > 0) {\n this.beeps.push(seq[0]);\n }\n }\n };\n }\n\n process(inputs, outputs, parameters) {\n const output = outputs[0];\n \n for (let i = 0; i < output.length; i++) {\n const channel = output[i];\n \n if (i > 0) {\n // Copy first channel to other channels\n for (let j = 0; j < channel.length; j++) {\n channel[j] = output[0][j];\n }\n } else {\n // Generate audio for first channel\n for (let j = 0; j < channel.length; j++) {\n let sig = 0;\n \n for (let k = this.beeps.length - 1; k >= 0; k--) {\n const b = this.beeps[k];\n let volume = b.volume;\n \n if (b.time / b.duration > b.span) {\n volume = 0;\n }\n \n // Generate waveform\n switch (b.waveform) {\n case \"square\":\n sig += b.phase > 0.5 ? volume : -volume;\n break;\n case \"saw\":\n sig += (b.phase * 2 - 1) * volume;\n break;\n case \"noise\":\n sig += (Math.random() * 2 - 1) * volume;\n break;\n default: // sine\n sig += Math.sin(b.phase * Math.PI * 2) * volume;\n }\n \n b.phase = (b.phase + b.increment) % 1;\n b.time += 1;\n \n if (b.time >= b.duration) {\n b.time = 0;\n \n if (b.loopto != null) {\n if (b.repeats != null && b.repeats > 0) {\n if (b.loopcount == null) {\n b.loopcount = 0;\n }\n b.loopcount++;\n \n if (b.loopcount >= b.repeats) {\n b.loopcount = 0;\n if (b.next != null) {\n b.next.phase = b.phase;\n this.beeps[k] = b.next;\n } else {\n this.beeps.splice(k, 1);\n }\n } else {\n b.loopto.phase = b.phase;\n this.beeps[k] = b.loopto;\n }\n } else {\n b.loopto.phase = b.phase;\n this.beeps[k] = b.loopto;\n }\n } else if (b.next != null) {\n b.next.phase = b.phase;\n this.beeps[k] = b.next;\n } else {\n this.beeps.splice(k, 1);\n }\n }\n }\n \n this.last = this.last * 0.9 + sig * 0.1;\n channel[j] = this.last;\n }\n }\n }\n \n return true;\n }\n}\n\nregisterProcessor(\"l8b-audio-processor\", L8bAudioProcessor);\n`;\n"],"mappings":"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","names":["A4_FREQUENCY","SEMITONE_RATIO","A4_MIDI_NOTE","Beeper","audio","notes","plainNotes","currentOctave","currentDuration","currentVolume","currentSpan","currentWaveform","initializeNotes","noteNames","i","oct","Math","floor","n","beep","input","status","sequence","loops","note","parsed","split","t","undefined","push","frequency","A4_FREQUENCY","SEMITONE_RATIO","A4_MIDI_NOTE","volume","span","duration","waveform","includes","length","lop","splice","loopto","start","repeats","tempo","Number","parseFloat","isNaN","step","addBeeps","AUDIO_WORKLET_CODE","AudioCore","context","buffer","playing","wakeupList","workletNode","beeper","runtime","masterVolume","getContext","isStarted","state","addToWakeUpList","item","push","interfaceCache","setVolume","volume","Math","max","min","getVolume","getInterface","beep","sequence","cancelBeeps","playSound","sound","pitch","pan","loopit","playMusic","music","stopAll","soundName","replace","s","sounds","reportRuntimeError","listener","APIErrorCode","E7013","play","musicName","m","E7014","AudioContextClass","window","AudioContext","webkitAudioContext","activate","resume","start","wakeUp","document","body","removeEventListener","addEventListener","once","blob","Blob","AUDIO_WORKLET_CODE","type","url","URL","createObjectURL","audioWorklet","addModule","AudioWorkletNode","connect","destination","flushBuffer","e","E7012","error","String","length","port","postMessage","splice","getBeeper","Beeper","addBeeps","beeps","b","duration","sampleRate","increment","frequency","JSON","stringify","name","addPlaying","removePlaying","index","indexOf","p","stop","err","E7016"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/audio-core.ts","../../src/constants.ts","../../src/devices/beeper.ts","../../src/core/audio-worklet.ts"],"sourcesContent":["/**\n * AudioCore - Web Audio API wrapper\n * Manages audio context, beeper, and sound/music playback\n */\n\nimport { Beeper } from \"../devices/beeper\";\nimport { AUDIO_WORKLET_CODE } from \"./audio-worklet\";\n\n/** An actively playing sound/music that can be stopped */\ninterface PlayingHandle {\n\tstop: () => void;\n}\n\n/** Item that can be woken up on audio context activation */\ninterface WakeUpItem {\n\twakeUp: () => void;\n}\n\nexport class AudioCore {\n\tpublic context!: AudioContext;\n\tprivate buffer: string[] = [];\n\tprivate playing: PlayingHandle[] = [];\n\tprivate wakeupList: WakeUpItem[] = [];\n\tprivate workletNode?: AudioWorkletNode;\n\tprivate beeper?: Beeper;\n\tprivate runtime: any;\n\tprivate masterVolume: number = 1;\n\n\tconstructor(runtime: any) {\n\t\tthis.runtime = runtime;\n\t\tthis.getContext();\n\t}\n\n\t/**\n\t * Check if audio context is running\n\t */\n\tpublic isStarted(): boolean {\n\t\treturn this.context.state === \"running\";\n\t}\n\n\t/**\n\t * Add item to wakeup list (for mobile audio activation)\n\t */\n\tpublic addToWakeUpList(item: WakeUpItem): void {\n\t\tthis.wakeupList.push(item);\n\t}\n\n\tprivate interfaceCache: Record<string, any> | null = null;\n\n\t/**\n\t * Set master volume (0-1). Applied as a multiplier to all sound/music playback.\n\t */\n\tpublic setVolume(volume: number): void {\n\t\tthis.masterVolume = Math.max(0, Math.min(1, volume));\n\t}\n\n\t/**\n\t * Get current master volume (0-1)\n\t */\n\tpublic getVolume(): number {\n\t\treturn this.masterVolume;\n\t}\n\n\t/**\n\t * Get interface for game code\n\t */\n\tpublic getInterface() {\n\t\tif (this.interfaceCache) {\n\t\t\treturn this.interfaceCache;\n\t\t}\n\t\tthis.interfaceCache = {\n\t\t\tbeep: (sequence: string) => this.beep(sequence),\n\t\t\tcancelBeeps: () => this.cancelBeeps(),\n\t\t\tplaySound: (sound: any, volume?: number, pitch?: number, pan?: number, loopit?: boolean) =>\n\t\t\t\tthis.playSound(sound, volume, pitch, pan, loopit),\n\t\t\tplayMusic: (music: any, volume?: number, loopit?: boolean) => this.playMusic(music, volume, loopit),\n\t\t\tsetVolume: (volume: number) => this.setVolume(volume),\n\t\t\tgetVolume: () => this.getVolume(),\n\t\t\tstopAll: () => this.stopAll(),\n\t\t};\n\t\treturn this.interfaceCache;\n\t}\n\n\t/**\n\t * Play sound effect\n\t */\n\tpublic playSound(sound: any, volume: number = 1, pitch: number = 1, pan: number = 0, loopit: boolean = false): number {\n\t\tif (typeof sound === \"string\") {\n\t\t\tconst soundName = sound.replace(/\\//g, \"-\");\n\t\t\tconst s = this.runtime.sounds[soundName];\n\t\t\tif (!s) {\n\t\t\t\tthis.runtime?.listener?.reportError?.({ code: \"E7013\", message: \"Sound not found\", data: { soundName } });\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\treturn s.play(volume * this.masterVolume, pitch, pan, loopit);\n\t\t}\n\t\treturn 0;\n\t}\n\n\t/**\n\t * Play music\n\t */\n\tpublic playMusic(music: any, volume: number = 1, loopit: boolean = false): number {\n\t\tif (typeof music === \"string\") {\n\t\t\tconst musicName = music.replace(/\\//g, \"-\");\n\t\t\tconst m = this.runtime.music[musicName];\n\t\t\tif (!m) {\n\t\t\t\tthis.runtime?.listener?.reportError?.({ code: \"E7014\", message: \"Music not found\", data: { musicName } });\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\treturn m.play(volume * this.masterVolume, loopit);\n\t\t}\n\t\treturn 0;\n\t}\n\n\t/**\n\t * Get or create audio context (lazy initialization - created on first use)\n\t * Note: Browser may suspend context until user interaction, which is handled automatically\n\t */\n\tpublic getContext(): AudioContext {\n\t\tif (!this.context) {\n\t\t\tconst AudioContextClass = (window as any).AudioContext || (window as any).webkitAudioContext;\n\t\t\t// Create context - browser may suspend until user interaction\n\t\t\tthis.context = new AudioContextClass();\n\n\t\t\t// If context is suspended, set up activation listeners\n\t\t\tif (this.context.state !== \"running\") {\n\t\t\t\tconst activate = () => {\n\t\t\t\t\tif (this.context && this.context.state !== \"running\") {\n\t\t\t\t\t\tthis.context.resume();\n\t\t\t\t\t\tif (this.beeper) {\n\t\t\t\t\t\t\tthis.start();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const item of this.wakeupList) {\n\t\t\t\t\t\t\titem.wakeUp();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Clean up listeners\n\t\t\t\t\t\tdocument.body.removeEventListener(\"touchend\", activate);\n\t\t\t\t\t\tdocument.body.removeEventListener(\"mouseup\", activate);\n\t\t\t\t\t\tdocument.body.removeEventListener(\"click\", activate);\n\t\t\t\t\t\tdocument.body.removeEventListener(\"keydown\", activate);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// Add multiple event listeners for better compatibility\n\t\t\t\tdocument.body.addEventListener(\"touchend\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t\tdocument.body.addEventListener(\"mouseup\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t\tdocument.body.addEventListener(\"click\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t\tdocument.body.addEventListener(\"keydown\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t} else if (this.beeper) {\n\t\t\t\tthis.start();\n\t\t\t}\n\t\t}\n\n\t\treturn this.context;\n\t}\n\n\t/**\n\t * Start audio processor\n\t */\n\tpublic async start(): Promise<void> {\n\t\tif (this.workletNode) return;\n\n\t\ttry {\n\t\t\tconst blob = new Blob([AUDIO_WORKLET_CODE], {\n\t\t\t\ttype: \"application/javascript\",\n\t\t\t});\n\t\t\tconst url = URL.createObjectURL(blob);\n\n\t\t\tawait this.context.audioWorklet.addModule(url);\n\n\t\t\tthis.workletNode = new AudioWorkletNode(this.context, \"l8b-audio-processor\");\n\t\t\tthis.workletNode.connect(this.context.destination);\n\n\t\t\tthis.flushBuffer();\n\t\t} catch (e) {\n\t\t\tthis.runtime?.listener?.reportError?.({ code: \"E7012\", message: \"Audio worklet error\", data: { error: String(e) } });\n\t\t}\n\t}\n\n\t/**\n\t * Flush buffered messages\n\t */\n\tprivate flushBuffer(): void {\n\t\tif (!this.workletNode) return;\n\n\t\twhile (this.buffer.length > 0) {\n\t\t\tthis.workletNode.port.postMessage(this.buffer.splice(0, 1)[0]);\n\t\t}\n\t}\n\n\t/**\n\t * Get or create beeper\n\t */\n\tpublic getBeeper(): Beeper {\n\t\tif (!this.beeper) {\n\t\t\t// Create Beeper instance\n\t\t\tthis.beeper = new Beeper(this);\n\n\t\t\tif (this.context.state === \"running\") {\n\t\t\t\tthis.start();\n\t\t\t}\n\t\t}\n\t\treturn this.beeper;\n\t}\n\n\t/**\n\t * Play beep sequence\n\t */\n\tpublic beep(sequence: string): void {\n\t\tthis.getBeeper().beep(sequence);\n\t}\n\n\t/**\n\t * Add beeps to audio processor\n\t */\n\tpublic addBeeps(beeps: any[]): void {\n\t\tfor (const b of beeps) {\n\t\t\tb.duration *= this.context.sampleRate;\n\t\t\tb.increment = b.frequency / this.context.sampleRate;\n\t\t}\n\n\t\tif (this.workletNode) {\n\t\t\tthis.workletNode.port.postMessage(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"beep\",\n\t\t\t\t\tsequence: beeps,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.buffer.push(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"beep\",\n\t\t\t\t\tsequence: beeps,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel all beeps\n\t */\n\tpublic cancelBeeps(): void {\n\t\tif (this.workletNode) {\n\t\t\tthis.workletNode.port.postMessage(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"cancel_beeps\",\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.buffer.push(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"cancel_beeps\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tthis.stopAll();\n\t}\n\n\t/**\n\t * Add playing sound/music to list\n\t */\n\tpublic addPlaying(item: PlayingHandle): void {\n\t\tthis.playing.push(item);\n\t}\n\n\t/**\n\t * Remove playing sound/music from list\n\t */\n\tpublic removePlaying(item: PlayingHandle): void {\n\t\tconst index = this.playing.indexOf(item);\n\t\tif (index >= 0) {\n\t\t\tthis.playing.splice(index, 1);\n\t\t}\n\t}\n\n\t/**\n\t * Stop all playing sounds/music\n\t */\n\tpublic stopAll(): void {\n\t\tfor (const p of this.playing) {\n\t\t\ttry {\n\t\t\t\tp.stop();\n\t\t\t} catch (err) {\n\t\t\t\tthis.runtime?.listener?.reportError?.({ code: \"E7016\", message: \"Audio error\", data: { error: String(err) } });\n\t\t\t}\n\t\t}\n\t\tthis.playing = [];\n\t}\n}\n","/** A4 reference frequency in Hz (concert pitch) */\nexport const A4_FREQUENCY = 440;\n\n/** Ratio between adjacent semitones in equal temperament */\nexport const SEMITONE_RATIO = 2 ** (1 / 12);\n\n/** MIDI note number for A4 */\nexport const A4_MIDI_NOTE = 69;\n","/**\n * Beeper - Procedural sound generation from text sequences\n * Converts music notation strings into beep sequences\n * Example: \"square tempo 120 C4 D4 E4 F4\"\n */\nimport { A4_FREQUENCY, A4_MIDI_NOTE, SEMITONE_RATIO } from \"../constants\";\nexport class Beeper {\n\tprivate audio: any;\n\tprivate notes: Record<string | number, number> = {};\n\tprivate plainNotes: Record<string, number> = {};\n\tprivate currentOctave = 5;\n\tprivate currentDuration = 0.5;\n\tprivate currentVolume = 0.5;\n\tprivate currentSpan = 1;\n\tprivate currentWaveform = \"square\";\n\n\tconstructor(audio: any) {\n\t\tthis.audio = audio;\n\t\tthis.initializeNotes();\n\t}\n\n\t/**\n\t * Initialize note mappings\n\t */\n\tprivate initializeNotes(): void {\n\t\tconst noteNames = [\n\t\t\t[\"C\", \"DO\"],\n\t\t\t[\"C#\", \"DO#\", \"Db\", \"REb\"],\n\t\t\t[\"D\", \"RE\"],\n\t\t\t[\"D#\", \"RE#\", \"Eb\", \"MIb\"],\n\t\t\t[\"E\", \"MI\"],\n\t\t\t[\"F\", \"FA\"],\n\t\t\t[\"F#\", \"FA#\", \"Gb\", \"SOLb\"],\n\t\t\t[\"G\", \"SOL\"],\n\t\t\t[\"G#\", \"SOL#\", \"Ab\", \"LAb\"],\n\t\t\t[\"A\", \"LA\"],\n\t\t\t[\"A#\", \"LA#\", \"Bb\", \"SIb\"],\n\t\t\t[\"B\", \"SI\"],\n\t\t];\n\n\t\tfor (let i = 0; i <= 127; i++) {\n\t\t\tthis.notes[i] = i;\n\t\t\tconst oct = Math.floor(i / 12) - 1;\n\n\t\t\tfor (const n of noteNames[i % 12]) {\n\t\t\t\tthis.notes[n + oct] = i;\n\t\t\t}\n\n\t\t\tif (oct === -1) {\n\t\t\t\tfor (const n of noteNames[i % 12]) {\n\t\t\t\t\tthis.plainNotes[n] = i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Parse and play beep sequence\n\t */\n\tpublic beep(input: string): void {\n\t\tlet status: string = \"normal\";\n\t\tconst sequence: any[] = [];\n\t\tconst loops: any[] = [];\n\t\tlet note: number | undefined;\n\n\t\tconst parsed = input.split(\" \");\n\n\t\tfor (const t of parsed) {\n\t\t\tif (t === \"\") continue;\n\n\t\t\tswitch (status) {\n\t\t\t\tcase \"normal\":\n\t\t\t\t\tif (this.notes[t] !== undefined) {\n\t\t\t\t\t\t// Full note with octave (e.g., \"C4\")\n\t\t\t\t\t\tnote = this.notes[t];\n\t\t\t\t\t\tthis.currentOctave = Math.floor(note / 12);\n\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\tfrequency: A4_FREQUENCY * SEMITONE_RATIO ** (note - A4_MIDI_NOTE),\n\t\t\t\t\t\t\tvolume: this.currentVolume,\n\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (this.plainNotes[t] !== undefined) {\n\t\t\t\t\t\t// Note without octave (e.g., \"C\")\n\t\t\t\t\t\tnote = this.plainNotes[t] + this.currentOctave * 12;\n\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\tfrequency: A4_FREQUENCY * SEMITONE_RATIO ** (note - A4_MIDI_NOTE),\n\t\t\t\t\t\t\tvolume: this.currentVolume,\n\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if ([\"square\", \"sine\", \"saw\", \"noise\"].includes(t)) {\n\t\t\t\t\t\t// Waveform\n\t\t\t\t\t\tthis.currentWaveform = t;\n\t\t\t\t\t} else if ([\"tempo\", \"duration\", \"volume\", \"span\", \"loop\", \"to\"].includes(t)) {\n\t\t\t\t\t\t// Commands\n\t\t\t\t\t\tstatus = t;\n\t\t\t\t\t} else if (t === \"-\") {\n\t\t\t\t\t\t// Rest/silence\n\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\tfrequency: A4_FREQUENCY,\n\t\t\t\t\t\t\tvolume: 0,\n\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (t === \"end\") {\n\t\t\t\t\t\t// End loop\n\t\t\t\t\t\tif (loops.length > 0 && sequence.length > 0) {\n\t\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\t\tfrequency: A4_FREQUENCY,\n\t\t\t\t\t\t\t\tvolume: 0,\n\t\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\t\tduration: 0,\n\t\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst lop = loops.splice(loops.length - 1, 1)[0];\n\t\t\t\t\t\t\tsequence[sequence.length - 1].loopto = lop.start;\n\t\t\t\t\t\t\tsequence[sequence.length - 1].repeats = lop.repeats;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"tempo\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst tempo = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(tempo) && tempo > 0) {\n\t\t\t\t\t\tthis.currentDuration = 60 / tempo;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"duration\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst duration = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(duration) && duration > 0) {\n\t\t\t\t\t\tthis.currentDuration = duration / 1000;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"volume\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst volume = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(volume)) {\n\t\t\t\t\t\tthis.currentVolume = volume / 100;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"span\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst span = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(span)) {\n\t\t\t\t\t\tthis.currentSpan = span / 100;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"loop\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tloops.push({\n\t\t\t\t\t\tstart: sequence.length,\n\t\t\t\t\t});\n\t\t\t\t\tconst repeats = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(repeats)) {\n\t\t\t\t\t\tloops[loops.length - 1].repeats = repeats;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"to\":\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tif (note !== undefined) {\n\t\t\t\t\t\tlet n: number | undefined;\n\n\t\t\t\t\t\tif (this.notes[t] !== undefined) {\n\t\t\t\t\t\t\tn = this.notes[t];\n\t\t\t\t\t\t} else if (this.plainNotes[t] !== undefined) {\n\t\t\t\t\t\t\tn = this.plainNotes[t] + this.currentOctave * 12;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (n !== undefined && n !== note) {\n\t\t\t\t\t\t\t// Generate slide from note to n\n\t\t\t\t\t\t\tconst step = n > note ? 1 : -1;\n\t\t\t\t\t\t\tfor (let i = note + step; step > 0 ? i <= n : i >= n; i += step) {\n\t\t\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\t\t\tfrequency: A4_FREQUENCY * SEMITONE_RATIO ** (i - A4_MIDI_NOTE),\n\t\t\t\t\t\t\t\t\tvolume: this.currentVolume,\n\t\t\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnote = n;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Close any remaining loops\n\t\tif (loops.length > 0 && sequence.length > 0) {\n\t\t\tconst lop = loops.splice(loops.length - 1, 1)[0];\n\t\t\tsequence.push({\n\t\t\t\tfrequency: A4_FREQUENCY,\n\t\t\t\tvolume: 0,\n\t\t\t\tspan: this.currentSpan,\n\t\t\t\tduration: 0,\n\t\t\t\twaveform: this.currentWaveform,\n\t\t\t});\n\n\t\t\tsequence[sequence.length - 1].loopto = lop.start;\n\t\t\tsequence[sequence.length - 1].repeats = lop.repeats;\n\t\t}\n\n\t\tthis.audio.addBeeps(sequence);\n\t}\n}\n","export const AUDIO_WORKLET_CODE = `\nclass L8bAudioProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.beeps = [];\n this.last = 0;\n this.port.onmessage = (event) => {\n const data = JSON.parse(event.data);\n if (data.name === \"cancel_beeps\") {\n this.beeps = [];\n } else if (data.name === \"beep\") {\n const seq = data.sequence;\n // Link sequence notes together\n for (let i = 0; i < seq.length; i++) {\n const note = seq[i];\n if (i > 0) {\n seq[i - 1].next = note;\n }\n // Resolve loopto index to actual note reference\n if (note.loopto != null) {\n note.loopto = seq[note.loopto];\n }\n // Initialize phase and time\n note.phase = 0;\n note.time = 0;\n }\n // Add first note to beeps queue\n if (seq.length > 0) {\n this.beeps.push(seq[0]);\n }\n }\n };\n }\n\n process(inputs, outputs, parameters) {\n const output = outputs[0];\n \n for (let i = 0; i < output.length; i++) {\n const channel = output[i];\n \n if (i > 0) {\n // Copy first channel to other channels\n for (let j = 0; j < channel.length; j++) {\n channel[j] = output[0][j];\n }\n } else {\n // Generate audio for first channel\n for (let j = 0; j < channel.length; j++) {\n let sig = 0;\n \n for (let k = this.beeps.length - 1; k >= 0; k--) {\n const b = this.beeps[k];\n let volume = b.volume;\n \n if (b.time / b.duration > b.span) {\n volume = 0;\n }\n \n // Generate waveform\n switch (b.waveform) {\n case \"square\":\n sig += b.phase > 0.5 ? volume : -volume;\n break;\n case \"saw\":\n sig += (b.phase * 2 - 1) * volume;\n break;\n case \"noise\":\n sig += (Math.random() * 2 - 1) * volume;\n break;\n default: // sine\n sig += Math.sin(b.phase * Math.PI * 2) * volume;\n }\n \n b.phase = (b.phase + b.increment) % 1;\n b.time += 1;\n \n if (b.time >= b.duration) {\n b.time = 0;\n \n if (b.loopto != null) {\n if (b.repeats != null && b.repeats > 0) {\n if (b.loopcount == null) {\n b.loopcount = 0;\n }\n b.loopcount++;\n \n if (b.loopcount >= b.repeats) {\n b.loopcount = 0;\n if (b.next != null) {\n b.next.phase = b.phase;\n this.beeps[k] = b.next;\n } else {\n this.beeps.splice(k, 1);\n }\n } else {\n b.loopto.phase = b.phase;\n this.beeps[k] = b.loopto;\n }\n } else {\n b.loopto.phase = b.phase;\n this.beeps[k] = b.loopto;\n }\n } else if (b.next != null) {\n b.next.phase = b.phase;\n this.beeps[k] = b.next;\n } else {\n this.beeps.splice(k, 1);\n }\n }\n }\n \n this.last = this.last * 0.9 + sig * 0.1;\n channel[j] = this.last;\n }\n }\n }\n \n return true;\n }\n}\n\nregisterProcessor(\"l8b-audio-processor\", L8bAudioProcessor);\n`;\n"],"mappings":"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","names":["A4_FREQUENCY","SEMITONE_RATIO","A4_MIDI_NOTE","Beeper","audio","notes","plainNotes","currentOctave","currentDuration","currentVolume","currentSpan","currentWaveform","initializeNotes","noteNames","i","oct","Math","floor","n","beep","input","status","sequence","loops","note","parsed","split","t","undefined","push","frequency","A4_FREQUENCY","SEMITONE_RATIO","A4_MIDI_NOTE","volume","span","duration","waveform","includes","length","lop","splice","loopto","start","repeats","tempo","Number","parseFloat","isNaN","step","addBeeps","AUDIO_WORKLET_CODE","AudioCore","context","buffer","playing","wakeupList","workletNode","beeper","runtime","masterVolume","getContext","isStarted","state","addToWakeUpList","item","push","interfaceCache","setVolume","volume","Math","max","min","getVolume","getInterface","beep","sequence","cancelBeeps","playSound","sound","pitch","pan","loopit","playMusic","music","stopAll","soundName","replace","s","sounds","listener","reportError","code","message","data","play","musicName","m","AudioContextClass","window","AudioContext","webkitAudioContext","activate","resume","start","wakeUp","document","body","removeEventListener","addEventListener","once","blob","Blob","AUDIO_WORKLET_CODE","type","url","URL","createObjectURL","audioWorklet","addModule","AudioWorkletNode","connect","destination","flushBuffer","e","error","String","length","port","postMessage","splice","getBeeper","Beeper","addBeeps","beeps","b","duration","sampleRate","increment","frequency","JSON","stringify","name","addPlaying","removePlaying","index","indexOf","p","stop","err"]}
|
package/dist/core/audio-core.mjs
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
|
-
// src/core/audio-core.ts
|
|
5
|
-
import { APIErrorCode, reportRuntimeError } from "@al8b/diagnostics";
|
|
6
|
-
|
|
7
4
|
// src/constants.ts
|
|
8
5
|
var A4_FREQUENCY = 440;
|
|
9
6
|
var SEMITONE_RATIO = 2 ** (1 / 12);
|
|
@@ -452,8 +449,12 @@ var AudioCore = class {
|
|
|
452
449
|
const soundName = sound.replace(/\//g, "-");
|
|
453
450
|
const s = this.runtime.sounds[soundName];
|
|
454
451
|
if (!s) {
|
|
455
|
-
|
|
456
|
-
|
|
452
|
+
this.runtime?.listener?.reportError?.({
|
|
453
|
+
code: "E7013",
|
|
454
|
+
message: "Sound not found",
|
|
455
|
+
data: {
|
|
456
|
+
soundName
|
|
457
|
+
}
|
|
457
458
|
});
|
|
458
459
|
return 0;
|
|
459
460
|
}
|
|
@@ -469,8 +470,12 @@ var AudioCore = class {
|
|
|
469
470
|
const musicName = music.replace(/\//g, "-");
|
|
470
471
|
const m = this.runtime.music[musicName];
|
|
471
472
|
if (!m) {
|
|
472
|
-
|
|
473
|
-
|
|
473
|
+
this.runtime?.listener?.reportError?.({
|
|
474
|
+
code: "E7014",
|
|
475
|
+
message: "Music not found",
|
|
476
|
+
data: {
|
|
477
|
+
musicName
|
|
478
|
+
}
|
|
474
479
|
});
|
|
475
480
|
return 0;
|
|
476
481
|
}
|
|
@@ -537,8 +542,12 @@ var AudioCore = class {
|
|
|
537
542
|
this.workletNode.connect(this.context.destination);
|
|
538
543
|
this.flushBuffer();
|
|
539
544
|
} catch (e) {
|
|
540
|
-
|
|
541
|
-
|
|
545
|
+
this.runtime?.listener?.reportError?.({
|
|
546
|
+
code: "E7012",
|
|
547
|
+
message: "Audio worklet error",
|
|
548
|
+
data: {
|
|
549
|
+
error: String(e)
|
|
550
|
+
}
|
|
542
551
|
});
|
|
543
552
|
}
|
|
544
553
|
}
|
|
@@ -627,8 +636,12 @@ var AudioCore = class {
|
|
|
627
636
|
try {
|
|
628
637
|
p.stop();
|
|
629
638
|
} catch (err) {
|
|
630
|
-
|
|
631
|
-
|
|
639
|
+
this.runtime?.listener?.reportError?.({
|
|
640
|
+
code: "E7016",
|
|
641
|
+
message: "Audio error",
|
|
642
|
+
data: {
|
|
643
|
+
error: String(err)
|
|
644
|
+
}
|
|
632
645
|
});
|
|
633
646
|
}
|
|
634
647
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/audio-core.ts","../../src/constants.ts","../../src/devices/beeper.ts","../../src/core/audio-worklet.ts"],"sourcesContent":["/**\n * AudioCore - Web Audio API wrapper\n * Manages audio context, beeper, and sound/music playback\n */\n\nimport { APIErrorCode, reportRuntimeError } from \"@al8b/diagnostics\";\nimport { Beeper } from \"../devices/beeper\";\nimport { AUDIO_WORKLET_CODE } from \"./audio-worklet\";\n\n/** An actively playing sound/music that can be stopped */\ninterface PlayingHandle {\n\tstop: () => void;\n}\n\n/** Item that can be woken up on audio context activation */\ninterface WakeUpItem {\n\twakeUp: () => void;\n}\n\nexport class AudioCore {\n\tpublic context!: AudioContext;\n\tprivate buffer: string[] = [];\n\tprivate playing: PlayingHandle[] = [];\n\tprivate wakeupList: WakeUpItem[] = [];\n\tprivate workletNode?: AudioWorkletNode;\n\tprivate beeper?: Beeper;\n\tprivate runtime: any;\n\tprivate masterVolume: number = 1;\n\n\tconstructor(runtime: any) {\n\t\tthis.runtime = runtime;\n\t\tthis.getContext();\n\t}\n\n\t/**\n\t * Check if audio context is running\n\t */\n\tpublic isStarted(): boolean {\n\t\treturn this.context.state === \"running\";\n\t}\n\n\t/**\n\t * Add item to wakeup list (for mobile audio activation)\n\t */\n\tpublic addToWakeUpList(item: WakeUpItem): void {\n\t\tthis.wakeupList.push(item);\n\t}\n\n\tprivate interfaceCache: Record<string, any> | null = null;\n\n\t/**\n\t * Set master volume (0-1). Applied as a multiplier to all sound/music playback.\n\t */\n\tpublic setVolume(volume: number): void {\n\t\tthis.masterVolume = Math.max(0, Math.min(1, volume));\n\t}\n\n\t/**\n\t * Get current master volume (0-1)\n\t */\n\tpublic getVolume(): number {\n\t\treturn this.masterVolume;\n\t}\n\n\t/**\n\t * Get interface for game code\n\t */\n\tpublic getInterface() {\n\t\tif (this.interfaceCache) {\n\t\t\treturn this.interfaceCache;\n\t\t}\n\t\tthis.interfaceCache = {\n\t\t\tbeep: (sequence: string) => this.beep(sequence),\n\t\t\tcancelBeeps: () => this.cancelBeeps(),\n\t\t\tplaySound: (sound: any, volume?: number, pitch?: number, pan?: number, loopit?: boolean) =>\n\t\t\t\tthis.playSound(sound, volume, pitch, pan, loopit),\n\t\t\tplayMusic: (music: any, volume?: number, loopit?: boolean) => this.playMusic(music, volume, loopit),\n\t\t\tsetVolume: (volume: number) => this.setVolume(volume),\n\t\t\tgetVolume: () => this.getVolume(),\n\t\t\tstopAll: () => this.stopAll(),\n\t\t};\n\t\treturn this.interfaceCache;\n\t}\n\n\t/**\n\t * Play sound effect\n\t */\n\tpublic playSound(sound: any, volume: number = 1, pitch: number = 1, pan: number = 0, loopit: boolean = false): number {\n\t\tif (typeof sound === \"string\") {\n\t\t\tconst soundName = sound.replace(/\\//g, \"-\");\n\t\t\tconst s = this.runtime.sounds[soundName];\n\t\t\tif (!s) {\n\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7013, { soundName });\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\treturn s.play(volume * this.masterVolume, pitch, pan, loopit);\n\t\t}\n\t\treturn 0;\n\t}\n\n\t/**\n\t * Play music\n\t */\n\tpublic playMusic(music: any, volume: number = 1, loopit: boolean = false): number {\n\t\tif (typeof music === \"string\") {\n\t\t\tconst musicName = music.replace(/\\//g, \"-\");\n\t\t\tconst m = this.runtime.music[musicName];\n\t\t\tif (!m) {\n\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7014, { musicName });\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\treturn m.play(volume * this.masterVolume, loopit);\n\t\t}\n\t\treturn 0;\n\t}\n\n\t/**\n\t * Get or create audio context (lazy initialization - created on first use)\n\t * Note: Browser may suspend context until user interaction, which is handled automatically\n\t */\n\tpublic getContext(): AudioContext {\n\t\tif (!this.context) {\n\t\t\tconst AudioContextClass = (window as any).AudioContext || (window as any).webkitAudioContext;\n\t\t\t// Create context - browser may suspend until user interaction\n\t\t\tthis.context = new AudioContextClass();\n\n\t\t\t// If context is suspended, set up activation listeners\n\t\t\tif (this.context.state !== \"running\") {\n\t\t\t\tconst activate = () => {\n\t\t\t\t\tif (this.context && this.context.state !== \"running\") {\n\t\t\t\t\t\tthis.context.resume();\n\t\t\t\t\t\tif (this.beeper) {\n\t\t\t\t\t\t\tthis.start();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const item of this.wakeupList) {\n\t\t\t\t\t\t\titem.wakeUp();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Clean up listeners\n\t\t\t\t\t\tdocument.body.removeEventListener(\"touchend\", activate);\n\t\t\t\t\t\tdocument.body.removeEventListener(\"mouseup\", activate);\n\t\t\t\t\t\tdocument.body.removeEventListener(\"click\", activate);\n\t\t\t\t\t\tdocument.body.removeEventListener(\"keydown\", activate);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// Add multiple event listeners for better compatibility\n\t\t\t\tdocument.body.addEventListener(\"touchend\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t\tdocument.body.addEventListener(\"mouseup\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t\tdocument.body.addEventListener(\"click\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t\tdocument.body.addEventListener(\"keydown\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t} else if (this.beeper) {\n\t\t\t\tthis.start();\n\t\t\t}\n\t\t}\n\n\t\treturn this.context;\n\t}\n\n\t/**\n\t * Start audio processor\n\t */\n\tpublic async start(): Promise<void> {\n\t\tif (this.workletNode) return;\n\n\t\ttry {\n\t\t\tconst blob = new Blob([AUDIO_WORKLET_CODE], {\n\t\t\t\ttype: \"application/javascript\",\n\t\t\t});\n\t\t\tconst url = URL.createObjectURL(blob);\n\n\t\t\tawait this.context.audioWorklet.addModule(url);\n\n\t\t\tthis.workletNode = new AudioWorkletNode(this.context, \"l8b-audio-processor\");\n\t\t\tthis.workletNode.connect(this.context.destination);\n\n\t\t\tthis.flushBuffer();\n\t\t} catch (e) {\n\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7012, { error: String(e) });\n\t\t}\n\t}\n\n\t/**\n\t * Flush buffered messages\n\t */\n\tprivate flushBuffer(): void {\n\t\tif (!this.workletNode) return;\n\n\t\twhile (this.buffer.length > 0) {\n\t\t\tthis.workletNode.port.postMessage(this.buffer.splice(0, 1)[0]);\n\t\t}\n\t}\n\n\t/**\n\t * Get or create beeper\n\t */\n\tpublic getBeeper(): Beeper {\n\t\tif (!this.beeper) {\n\t\t\t// Create Beeper instance\n\t\t\tthis.beeper = new Beeper(this);\n\n\t\t\tif (this.context.state === \"running\") {\n\t\t\t\tthis.start();\n\t\t\t}\n\t\t}\n\t\treturn this.beeper;\n\t}\n\n\t/**\n\t * Play beep sequence\n\t */\n\tpublic beep(sequence: string): void {\n\t\tthis.getBeeper().beep(sequence);\n\t}\n\n\t/**\n\t * Add beeps to audio processor\n\t */\n\tpublic addBeeps(beeps: any[]): void {\n\t\tfor (const b of beeps) {\n\t\t\tb.duration *= this.context.sampleRate;\n\t\t\tb.increment = b.frequency / this.context.sampleRate;\n\t\t}\n\n\t\tif (this.workletNode) {\n\t\t\tthis.workletNode.port.postMessage(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"beep\",\n\t\t\t\t\tsequence: beeps,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.buffer.push(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"beep\",\n\t\t\t\t\tsequence: beeps,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel all beeps\n\t */\n\tpublic cancelBeeps(): void {\n\t\tif (this.workletNode) {\n\t\t\tthis.workletNode.port.postMessage(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"cancel_beeps\",\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.buffer.push(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"cancel_beeps\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tthis.stopAll();\n\t}\n\n\t/**\n\t * Add playing sound/music to list\n\t */\n\tpublic addPlaying(item: PlayingHandle): void {\n\t\tthis.playing.push(item);\n\t}\n\n\t/**\n\t * Remove playing sound/music from list\n\t */\n\tpublic removePlaying(item: PlayingHandle): void {\n\t\tconst index = this.playing.indexOf(item);\n\t\tif (index >= 0) {\n\t\t\tthis.playing.splice(index, 1);\n\t\t}\n\t}\n\n\t/**\n\t * Stop all playing sounds/music\n\t */\n\tpublic stopAll(): void {\n\t\tfor (const p of this.playing) {\n\t\t\ttry {\n\t\t\t\tp.stop();\n\t\t\t} catch (err) {\n\t\t\t\treportRuntimeError(this.runtime?.listener, APIErrorCode.E7016, { error: String(err) });\n\t\t\t}\n\t\t}\n\t\tthis.playing = [];\n\t}\n}\n","/** A4 reference frequency in Hz (concert pitch) */\nexport const A4_FREQUENCY = 440;\n\n/** Ratio between adjacent semitones in equal temperament */\nexport const SEMITONE_RATIO = 2 ** (1 / 12);\n\n/** MIDI note number for A4 */\nexport const A4_MIDI_NOTE = 69;\n","/**\n * Beeper - Procedural sound generation from text sequences\n * Converts music notation strings into beep sequences\n * Example: \"square tempo 120 C4 D4 E4 F4\"\n */\nimport { A4_FREQUENCY, A4_MIDI_NOTE, SEMITONE_RATIO } from \"../constants\";\nexport class Beeper {\n\tprivate audio: any;\n\tprivate notes: Record<string | number, number> = {};\n\tprivate plainNotes: Record<string, number> = {};\n\tprivate currentOctave = 5;\n\tprivate currentDuration = 0.5;\n\tprivate currentVolume = 0.5;\n\tprivate currentSpan = 1;\n\tprivate currentWaveform = \"square\";\n\n\tconstructor(audio: any) {\n\t\tthis.audio = audio;\n\t\tthis.initializeNotes();\n\t}\n\n\t/**\n\t * Initialize note mappings\n\t */\n\tprivate initializeNotes(): void {\n\t\tconst noteNames = [\n\t\t\t[\"C\", \"DO\"],\n\t\t\t[\"C#\", \"DO#\", \"Db\", \"REb\"],\n\t\t\t[\"D\", \"RE\"],\n\t\t\t[\"D#\", \"RE#\", \"Eb\", \"MIb\"],\n\t\t\t[\"E\", \"MI\"],\n\t\t\t[\"F\", \"FA\"],\n\t\t\t[\"F#\", \"FA#\", \"Gb\", \"SOLb\"],\n\t\t\t[\"G\", \"SOL\"],\n\t\t\t[\"G#\", \"SOL#\", \"Ab\", \"LAb\"],\n\t\t\t[\"A\", \"LA\"],\n\t\t\t[\"A#\", \"LA#\", \"Bb\", \"SIb\"],\n\t\t\t[\"B\", \"SI\"],\n\t\t];\n\n\t\tfor (let i = 0; i <= 127; i++) {\n\t\t\tthis.notes[i] = i;\n\t\t\tconst oct = Math.floor(i / 12) - 1;\n\n\t\t\tfor (const n of noteNames[i % 12]) {\n\t\t\t\tthis.notes[n + oct] = i;\n\t\t\t}\n\n\t\t\tif (oct === -1) {\n\t\t\t\tfor (const n of noteNames[i % 12]) {\n\t\t\t\t\tthis.plainNotes[n] = i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Parse and play beep sequence\n\t */\n\tpublic beep(input: string): void {\n\t\tlet status: string = \"normal\";\n\t\tconst sequence: any[] = [];\n\t\tconst loops: any[] = [];\n\t\tlet note: number | undefined;\n\n\t\tconst parsed = input.split(\" \");\n\n\t\tfor (const t of parsed) {\n\t\t\tif (t === \"\") continue;\n\n\t\t\tswitch (status) {\n\t\t\t\tcase \"normal\":\n\t\t\t\t\tif (this.notes[t] !== undefined) {\n\t\t\t\t\t\t// Full note with octave (e.g., \"C4\")\n\t\t\t\t\t\tnote = this.notes[t];\n\t\t\t\t\t\tthis.currentOctave = Math.floor(note / 12);\n\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\tfrequency: A4_FREQUENCY * SEMITONE_RATIO ** (note - A4_MIDI_NOTE),\n\t\t\t\t\t\t\tvolume: this.currentVolume,\n\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (this.plainNotes[t] !== undefined) {\n\t\t\t\t\t\t// Note without octave (e.g., \"C\")\n\t\t\t\t\t\tnote = this.plainNotes[t] + this.currentOctave * 12;\n\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\tfrequency: A4_FREQUENCY * SEMITONE_RATIO ** (note - A4_MIDI_NOTE),\n\t\t\t\t\t\t\tvolume: this.currentVolume,\n\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if ([\"square\", \"sine\", \"saw\", \"noise\"].includes(t)) {\n\t\t\t\t\t\t// Waveform\n\t\t\t\t\t\tthis.currentWaveform = t;\n\t\t\t\t\t} else if ([\"tempo\", \"duration\", \"volume\", \"span\", \"loop\", \"to\"].includes(t)) {\n\t\t\t\t\t\t// Commands\n\t\t\t\t\t\tstatus = t;\n\t\t\t\t\t} else if (t === \"-\") {\n\t\t\t\t\t\t// Rest/silence\n\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\tfrequency: A4_FREQUENCY,\n\t\t\t\t\t\t\tvolume: 0,\n\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (t === \"end\") {\n\t\t\t\t\t\t// End loop\n\t\t\t\t\t\tif (loops.length > 0 && sequence.length > 0) {\n\t\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\t\tfrequency: A4_FREQUENCY,\n\t\t\t\t\t\t\t\tvolume: 0,\n\t\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\t\tduration: 0,\n\t\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst lop = loops.splice(loops.length - 1, 1)[0];\n\t\t\t\t\t\t\tsequence[sequence.length - 1].loopto = lop.start;\n\t\t\t\t\t\t\tsequence[sequence.length - 1].repeats = lop.repeats;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"tempo\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst tempo = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(tempo) && tempo > 0) {\n\t\t\t\t\t\tthis.currentDuration = 60 / tempo;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"duration\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst duration = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(duration) && duration > 0) {\n\t\t\t\t\t\tthis.currentDuration = duration / 1000;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"volume\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst volume = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(volume)) {\n\t\t\t\t\t\tthis.currentVolume = volume / 100;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"span\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst span = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(span)) {\n\t\t\t\t\t\tthis.currentSpan = span / 100;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"loop\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tloops.push({\n\t\t\t\t\t\tstart: sequence.length,\n\t\t\t\t\t});\n\t\t\t\t\tconst repeats = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(repeats)) {\n\t\t\t\t\t\tloops[loops.length - 1].repeats = repeats;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"to\":\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tif (note !== undefined) {\n\t\t\t\t\t\tlet n: number | undefined;\n\n\t\t\t\t\t\tif (this.notes[t] !== undefined) {\n\t\t\t\t\t\t\tn = this.notes[t];\n\t\t\t\t\t\t} else if (this.plainNotes[t] !== undefined) {\n\t\t\t\t\t\t\tn = this.plainNotes[t] + this.currentOctave * 12;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (n !== undefined && n !== note) {\n\t\t\t\t\t\t\t// Generate slide from note to n\n\t\t\t\t\t\t\tconst step = n > note ? 1 : -1;\n\t\t\t\t\t\t\tfor (let i = note + step; step > 0 ? i <= n : i >= n; i += step) {\n\t\t\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\t\t\tfrequency: A4_FREQUENCY * SEMITONE_RATIO ** (i - A4_MIDI_NOTE),\n\t\t\t\t\t\t\t\t\tvolume: this.currentVolume,\n\t\t\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnote = n;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Close any remaining loops\n\t\tif (loops.length > 0 && sequence.length > 0) {\n\t\t\tconst lop = loops.splice(loops.length - 1, 1)[0];\n\t\t\tsequence.push({\n\t\t\t\tfrequency: A4_FREQUENCY,\n\t\t\t\tvolume: 0,\n\t\t\t\tspan: this.currentSpan,\n\t\t\t\tduration: 0,\n\t\t\t\twaveform: this.currentWaveform,\n\t\t\t});\n\n\t\t\tsequence[sequence.length - 1].loopto = lop.start;\n\t\t\tsequence[sequence.length - 1].repeats = lop.repeats;\n\t\t}\n\n\t\tthis.audio.addBeeps(sequence);\n\t}\n}\n","export const AUDIO_WORKLET_CODE = `\nclass L8bAudioProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.beeps = [];\n this.last = 0;\n this.port.onmessage = (event) => {\n const data = JSON.parse(event.data);\n if (data.name === \"cancel_beeps\") {\n this.beeps = [];\n } else if (data.name === \"beep\") {\n const seq = data.sequence;\n // Link sequence notes together\n for (let i = 0; i < seq.length; i++) {\n const note = seq[i];\n if (i > 0) {\n seq[i - 1].next = note;\n }\n // Resolve loopto index to actual note reference\n if (note.loopto != null) {\n note.loopto = seq[note.loopto];\n }\n // Initialize phase and time\n note.phase = 0;\n note.time = 0;\n }\n // Add first note to beeps queue\n if (seq.length > 0) {\n this.beeps.push(seq[0]);\n }\n }\n };\n }\n\n process(inputs, outputs, parameters) {\n const output = outputs[0];\n \n for (let i = 0; i < output.length; i++) {\n const channel = output[i];\n \n if (i > 0) {\n // Copy first channel to other channels\n for (let j = 0; j < channel.length; j++) {\n channel[j] = output[0][j];\n }\n } else {\n // Generate audio for first channel\n for (let j = 0; j < channel.length; j++) {\n let sig = 0;\n \n for (let k = this.beeps.length - 1; k >= 0; k--) {\n const b = this.beeps[k];\n let volume = b.volume;\n \n if (b.time / b.duration > b.span) {\n volume = 0;\n }\n \n // Generate waveform\n switch (b.waveform) {\n case \"square\":\n sig += b.phase > 0.5 ? volume : -volume;\n break;\n case \"saw\":\n sig += (b.phase * 2 - 1) * volume;\n break;\n case \"noise\":\n sig += (Math.random() * 2 - 1) * volume;\n break;\n default: // sine\n sig += Math.sin(b.phase * Math.PI * 2) * volume;\n }\n \n b.phase = (b.phase + b.increment) % 1;\n b.time += 1;\n \n if (b.time >= b.duration) {\n b.time = 0;\n \n if (b.loopto != null) {\n if (b.repeats != null && b.repeats > 0) {\n if (b.loopcount == null) {\n b.loopcount = 0;\n }\n b.loopcount++;\n \n if (b.loopcount >= b.repeats) {\n b.loopcount = 0;\n if (b.next != null) {\n b.next.phase = b.phase;\n this.beeps[k] = b.next;\n } else {\n this.beeps.splice(k, 1);\n }\n } else {\n b.loopto.phase = b.phase;\n this.beeps[k] = b.loopto;\n }\n } else {\n b.loopto.phase = b.phase;\n this.beeps[k] = b.loopto;\n }\n } else if (b.next != null) {\n b.next.phase = b.phase;\n this.beeps[k] = b.next;\n } else {\n this.beeps.splice(k, 1);\n }\n }\n }\n \n this.last = this.last * 0.9 + sig * 0.1;\n channel[j] = this.last;\n }\n }\n }\n \n return true;\n }\n}\n\nregisterProcessor(\"l8b-audio-processor\", L8bAudioProcessor);\n`;\n"],"mappings":"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","names":["APIErrorCode","reportRuntimeError","A4_FREQUENCY","SEMITONE_RATIO","A4_MIDI_NOTE","Beeper","audio","notes","plainNotes","currentOctave","currentDuration","currentVolume","currentSpan","currentWaveform","initializeNotes","noteNames","i","oct","Math","floor","n","beep","input","status","sequence","loops","note","parsed","split","t","undefined","push","frequency","A4_FREQUENCY","SEMITONE_RATIO","A4_MIDI_NOTE","volume","span","duration","waveform","includes","length","lop","splice","loopto","start","repeats","tempo","Number","parseFloat","isNaN","step","addBeeps","AUDIO_WORKLET_CODE","AudioCore","context","buffer","playing","wakeupList","workletNode","beeper","runtime","masterVolume","getContext","isStarted","state","addToWakeUpList","item","push","interfaceCache","setVolume","volume","Math","max","min","getVolume","getInterface","beep","sequence","cancelBeeps","playSound","sound","pitch","pan","loopit","playMusic","music","stopAll","soundName","replace","s","sounds","reportRuntimeError","listener","APIErrorCode","E7013","play","musicName","m","E7014","AudioContextClass","window","AudioContext","webkitAudioContext","activate","resume","start","wakeUp","document","body","removeEventListener","addEventListener","once","blob","Blob","AUDIO_WORKLET_CODE","type","url","URL","createObjectURL","audioWorklet","addModule","AudioWorkletNode","connect","destination","flushBuffer","e","E7012","error","String","length","port","postMessage","splice","getBeeper","Beeper","addBeeps","beeps","b","duration","sampleRate","increment","frequency","JSON","stringify","name","addPlaying","removePlaying","index","indexOf","p","stop","err","E7016"]}
|
|
1
|
+
{"version":3,"sources":["../../src/constants.ts","../../src/devices/beeper.ts","../../src/core/audio-worklet.ts","../../src/core/audio-core.ts"],"sourcesContent":["/** A4 reference frequency in Hz (concert pitch) */\nexport const A4_FREQUENCY = 440;\n\n/** Ratio between adjacent semitones in equal temperament */\nexport const SEMITONE_RATIO = 2 ** (1 / 12);\n\n/** MIDI note number for A4 */\nexport const A4_MIDI_NOTE = 69;\n","/**\n * Beeper - Procedural sound generation from text sequences\n * Converts music notation strings into beep sequences\n * Example: \"square tempo 120 C4 D4 E4 F4\"\n */\nimport { A4_FREQUENCY, A4_MIDI_NOTE, SEMITONE_RATIO } from \"../constants\";\nexport class Beeper {\n\tprivate audio: any;\n\tprivate notes: Record<string | number, number> = {};\n\tprivate plainNotes: Record<string, number> = {};\n\tprivate currentOctave = 5;\n\tprivate currentDuration = 0.5;\n\tprivate currentVolume = 0.5;\n\tprivate currentSpan = 1;\n\tprivate currentWaveform = \"square\";\n\n\tconstructor(audio: any) {\n\t\tthis.audio = audio;\n\t\tthis.initializeNotes();\n\t}\n\n\t/**\n\t * Initialize note mappings\n\t */\n\tprivate initializeNotes(): void {\n\t\tconst noteNames = [\n\t\t\t[\"C\", \"DO\"],\n\t\t\t[\"C#\", \"DO#\", \"Db\", \"REb\"],\n\t\t\t[\"D\", \"RE\"],\n\t\t\t[\"D#\", \"RE#\", \"Eb\", \"MIb\"],\n\t\t\t[\"E\", \"MI\"],\n\t\t\t[\"F\", \"FA\"],\n\t\t\t[\"F#\", \"FA#\", \"Gb\", \"SOLb\"],\n\t\t\t[\"G\", \"SOL\"],\n\t\t\t[\"G#\", \"SOL#\", \"Ab\", \"LAb\"],\n\t\t\t[\"A\", \"LA\"],\n\t\t\t[\"A#\", \"LA#\", \"Bb\", \"SIb\"],\n\t\t\t[\"B\", \"SI\"],\n\t\t];\n\n\t\tfor (let i = 0; i <= 127; i++) {\n\t\t\tthis.notes[i] = i;\n\t\t\tconst oct = Math.floor(i / 12) - 1;\n\n\t\t\tfor (const n of noteNames[i % 12]) {\n\t\t\t\tthis.notes[n + oct] = i;\n\t\t\t}\n\n\t\t\tif (oct === -1) {\n\t\t\t\tfor (const n of noteNames[i % 12]) {\n\t\t\t\t\tthis.plainNotes[n] = i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Parse and play beep sequence\n\t */\n\tpublic beep(input: string): void {\n\t\tlet status: string = \"normal\";\n\t\tconst sequence: any[] = [];\n\t\tconst loops: any[] = [];\n\t\tlet note: number | undefined;\n\n\t\tconst parsed = input.split(\" \");\n\n\t\tfor (const t of parsed) {\n\t\t\tif (t === \"\") continue;\n\n\t\t\tswitch (status) {\n\t\t\t\tcase \"normal\":\n\t\t\t\t\tif (this.notes[t] !== undefined) {\n\t\t\t\t\t\t// Full note with octave (e.g., \"C4\")\n\t\t\t\t\t\tnote = this.notes[t];\n\t\t\t\t\t\tthis.currentOctave = Math.floor(note / 12);\n\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\tfrequency: A4_FREQUENCY * SEMITONE_RATIO ** (note - A4_MIDI_NOTE),\n\t\t\t\t\t\t\tvolume: this.currentVolume,\n\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (this.plainNotes[t] !== undefined) {\n\t\t\t\t\t\t// Note without octave (e.g., \"C\")\n\t\t\t\t\t\tnote = this.plainNotes[t] + this.currentOctave * 12;\n\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\tfrequency: A4_FREQUENCY * SEMITONE_RATIO ** (note - A4_MIDI_NOTE),\n\t\t\t\t\t\t\tvolume: this.currentVolume,\n\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if ([\"square\", \"sine\", \"saw\", \"noise\"].includes(t)) {\n\t\t\t\t\t\t// Waveform\n\t\t\t\t\t\tthis.currentWaveform = t;\n\t\t\t\t\t} else if ([\"tempo\", \"duration\", \"volume\", \"span\", \"loop\", \"to\"].includes(t)) {\n\t\t\t\t\t\t// Commands\n\t\t\t\t\t\tstatus = t;\n\t\t\t\t\t} else if (t === \"-\") {\n\t\t\t\t\t\t// Rest/silence\n\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\tfrequency: A4_FREQUENCY,\n\t\t\t\t\t\t\tvolume: 0,\n\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (t === \"end\") {\n\t\t\t\t\t\t// End loop\n\t\t\t\t\t\tif (loops.length > 0 && sequence.length > 0) {\n\t\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\t\tfrequency: A4_FREQUENCY,\n\t\t\t\t\t\t\t\tvolume: 0,\n\t\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\t\tduration: 0,\n\t\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst lop = loops.splice(loops.length - 1, 1)[0];\n\t\t\t\t\t\t\tsequence[sequence.length - 1].loopto = lop.start;\n\t\t\t\t\t\t\tsequence[sequence.length - 1].repeats = lop.repeats;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"tempo\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst tempo = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(tempo) && tempo > 0) {\n\t\t\t\t\t\tthis.currentDuration = 60 / tempo;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"duration\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst duration = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(duration) && duration > 0) {\n\t\t\t\t\t\tthis.currentDuration = duration / 1000;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"volume\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst volume = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(volume)) {\n\t\t\t\t\t\tthis.currentVolume = volume / 100;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"span\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tconst span = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(span)) {\n\t\t\t\t\t\tthis.currentSpan = span / 100;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"loop\": {\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tloops.push({\n\t\t\t\t\t\tstart: sequence.length,\n\t\t\t\t\t});\n\t\t\t\t\tconst repeats = Number.parseFloat(t);\n\t\t\t\t\tif (!Number.isNaN(repeats)) {\n\t\t\t\t\t\tloops[loops.length - 1].repeats = repeats;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"to\":\n\t\t\t\t\tstatus = \"normal\";\n\t\t\t\t\tif (note !== undefined) {\n\t\t\t\t\t\tlet n: number | undefined;\n\n\t\t\t\t\t\tif (this.notes[t] !== undefined) {\n\t\t\t\t\t\t\tn = this.notes[t];\n\t\t\t\t\t\t} else if (this.plainNotes[t] !== undefined) {\n\t\t\t\t\t\t\tn = this.plainNotes[t] + this.currentOctave * 12;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (n !== undefined && n !== note) {\n\t\t\t\t\t\t\t// Generate slide from note to n\n\t\t\t\t\t\t\tconst step = n > note ? 1 : -1;\n\t\t\t\t\t\t\tfor (let i = note + step; step > 0 ? i <= n : i >= n; i += step) {\n\t\t\t\t\t\t\t\tsequence.push({\n\t\t\t\t\t\t\t\t\tfrequency: A4_FREQUENCY * SEMITONE_RATIO ** (i - A4_MIDI_NOTE),\n\t\t\t\t\t\t\t\t\tvolume: this.currentVolume,\n\t\t\t\t\t\t\t\t\tspan: this.currentSpan,\n\t\t\t\t\t\t\t\t\tduration: this.currentDuration,\n\t\t\t\t\t\t\t\t\twaveform: this.currentWaveform,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnote = n;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Close any remaining loops\n\t\tif (loops.length > 0 && sequence.length > 0) {\n\t\t\tconst lop = loops.splice(loops.length - 1, 1)[0];\n\t\t\tsequence.push({\n\t\t\t\tfrequency: A4_FREQUENCY,\n\t\t\t\tvolume: 0,\n\t\t\t\tspan: this.currentSpan,\n\t\t\t\tduration: 0,\n\t\t\t\twaveform: this.currentWaveform,\n\t\t\t});\n\n\t\t\tsequence[sequence.length - 1].loopto = lop.start;\n\t\t\tsequence[sequence.length - 1].repeats = lop.repeats;\n\t\t}\n\n\t\tthis.audio.addBeeps(sequence);\n\t}\n}\n","export const AUDIO_WORKLET_CODE = `\nclass L8bAudioProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.beeps = [];\n this.last = 0;\n this.port.onmessage = (event) => {\n const data = JSON.parse(event.data);\n if (data.name === \"cancel_beeps\") {\n this.beeps = [];\n } else if (data.name === \"beep\") {\n const seq = data.sequence;\n // Link sequence notes together\n for (let i = 0; i < seq.length; i++) {\n const note = seq[i];\n if (i > 0) {\n seq[i - 1].next = note;\n }\n // Resolve loopto index to actual note reference\n if (note.loopto != null) {\n note.loopto = seq[note.loopto];\n }\n // Initialize phase and time\n note.phase = 0;\n note.time = 0;\n }\n // Add first note to beeps queue\n if (seq.length > 0) {\n this.beeps.push(seq[0]);\n }\n }\n };\n }\n\n process(inputs, outputs, parameters) {\n const output = outputs[0];\n \n for (let i = 0; i < output.length; i++) {\n const channel = output[i];\n \n if (i > 0) {\n // Copy first channel to other channels\n for (let j = 0; j < channel.length; j++) {\n channel[j] = output[0][j];\n }\n } else {\n // Generate audio for first channel\n for (let j = 0; j < channel.length; j++) {\n let sig = 0;\n \n for (let k = this.beeps.length - 1; k >= 0; k--) {\n const b = this.beeps[k];\n let volume = b.volume;\n \n if (b.time / b.duration > b.span) {\n volume = 0;\n }\n \n // Generate waveform\n switch (b.waveform) {\n case \"square\":\n sig += b.phase > 0.5 ? volume : -volume;\n break;\n case \"saw\":\n sig += (b.phase * 2 - 1) * volume;\n break;\n case \"noise\":\n sig += (Math.random() * 2 - 1) * volume;\n break;\n default: // sine\n sig += Math.sin(b.phase * Math.PI * 2) * volume;\n }\n \n b.phase = (b.phase + b.increment) % 1;\n b.time += 1;\n \n if (b.time >= b.duration) {\n b.time = 0;\n \n if (b.loopto != null) {\n if (b.repeats != null && b.repeats > 0) {\n if (b.loopcount == null) {\n b.loopcount = 0;\n }\n b.loopcount++;\n \n if (b.loopcount >= b.repeats) {\n b.loopcount = 0;\n if (b.next != null) {\n b.next.phase = b.phase;\n this.beeps[k] = b.next;\n } else {\n this.beeps.splice(k, 1);\n }\n } else {\n b.loopto.phase = b.phase;\n this.beeps[k] = b.loopto;\n }\n } else {\n b.loopto.phase = b.phase;\n this.beeps[k] = b.loopto;\n }\n } else if (b.next != null) {\n b.next.phase = b.phase;\n this.beeps[k] = b.next;\n } else {\n this.beeps.splice(k, 1);\n }\n }\n }\n \n this.last = this.last * 0.9 + sig * 0.1;\n channel[j] = this.last;\n }\n }\n }\n \n return true;\n }\n}\n\nregisterProcessor(\"l8b-audio-processor\", L8bAudioProcessor);\n`;\n","/**\n * AudioCore - Web Audio API wrapper\n * Manages audio context, beeper, and sound/music playback\n */\n\nimport { Beeper } from \"../devices/beeper\";\nimport { AUDIO_WORKLET_CODE } from \"./audio-worklet\";\n\n/** An actively playing sound/music that can be stopped */\ninterface PlayingHandle {\n\tstop: () => void;\n}\n\n/** Item that can be woken up on audio context activation */\ninterface WakeUpItem {\n\twakeUp: () => void;\n}\n\nexport class AudioCore {\n\tpublic context!: AudioContext;\n\tprivate buffer: string[] = [];\n\tprivate playing: PlayingHandle[] = [];\n\tprivate wakeupList: WakeUpItem[] = [];\n\tprivate workletNode?: AudioWorkletNode;\n\tprivate beeper?: Beeper;\n\tprivate runtime: any;\n\tprivate masterVolume: number = 1;\n\n\tconstructor(runtime: any) {\n\t\tthis.runtime = runtime;\n\t\tthis.getContext();\n\t}\n\n\t/**\n\t * Check if audio context is running\n\t */\n\tpublic isStarted(): boolean {\n\t\treturn this.context.state === \"running\";\n\t}\n\n\t/**\n\t * Add item to wakeup list (for mobile audio activation)\n\t */\n\tpublic addToWakeUpList(item: WakeUpItem): void {\n\t\tthis.wakeupList.push(item);\n\t}\n\n\tprivate interfaceCache: Record<string, any> | null = null;\n\n\t/**\n\t * Set master volume (0-1). Applied as a multiplier to all sound/music playback.\n\t */\n\tpublic setVolume(volume: number): void {\n\t\tthis.masterVolume = Math.max(0, Math.min(1, volume));\n\t}\n\n\t/**\n\t * Get current master volume (0-1)\n\t */\n\tpublic getVolume(): number {\n\t\treturn this.masterVolume;\n\t}\n\n\t/**\n\t * Get interface for game code\n\t */\n\tpublic getInterface() {\n\t\tif (this.interfaceCache) {\n\t\t\treturn this.interfaceCache;\n\t\t}\n\t\tthis.interfaceCache = {\n\t\t\tbeep: (sequence: string) => this.beep(sequence),\n\t\t\tcancelBeeps: () => this.cancelBeeps(),\n\t\t\tplaySound: (sound: any, volume?: number, pitch?: number, pan?: number, loopit?: boolean) =>\n\t\t\t\tthis.playSound(sound, volume, pitch, pan, loopit),\n\t\t\tplayMusic: (music: any, volume?: number, loopit?: boolean) => this.playMusic(music, volume, loopit),\n\t\t\tsetVolume: (volume: number) => this.setVolume(volume),\n\t\t\tgetVolume: () => this.getVolume(),\n\t\t\tstopAll: () => this.stopAll(),\n\t\t};\n\t\treturn this.interfaceCache;\n\t}\n\n\t/**\n\t * Play sound effect\n\t */\n\tpublic playSound(sound: any, volume: number = 1, pitch: number = 1, pan: number = 0, loopit: boolean = false): number {\n\t\tif (typeof sound === \"string\") {\n\t\t\tconst soundName = sound.replace(/\\//g, \"-\");\n\t\t\tconst s = this.runtime.sounds[soundName];\n\t\t\tif (!s) {\n\t\t\t\tthis.runtime?.listener?.reportError?.({ code: \"E7013\", message: \"Sound not found\", data: { soundName } });\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\treturn s.play(volume * this.masterVolume, pitch, pan, loopit);\n\t\t}\n\t\treturn 0;\n\t}\n\n\t/**\n\t * Play music\n\t */\n\tpublic playMusic(music: any, volume: number = 1, loopit: boolean = false): number {\n\t\tif (typeof music === \"string\") {\n\t\t\tconst musicName = music.replace(/\\//g, \"-\");\n\t\t\tconst m = this.runtime.music[musicName];\n\t\t\tif (!m) {\n\t\t\t\tthis.runtime?.listener?.reportError?.({ code: \"E7014\", message: \"Music not found\", data: { musicName } });\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\treturn m.play(volume * this.masterVolume, loopit);\n\t\t}\n\t\treturn 0;\n\t}\n\n\t/**\n\t * Get or create audio context (lazy initialization - created on first use)\n\t * Note: Browser may suspend context until user interaction, which is handled automatically\n\t */\n\tpublic getContext(): AudioContext {\n\t\tif (!this.context) {\n\t\t\tconst AudioContextClass = (window as any).AudioContext || (window as any).webkitAudioContext;\n\t\t\t// Create context - browser may suspend until user interaction\n\t\t\tthis.context = new AudioContextClass();\n\n\t\t\t// If context is suspended, set up activation listeners\n\t\t\tif (this.context.state !== \"running\") {\n\t\t\t\tconst activate = () => {\n\t\t\t\t\tif (this.context && this.context.state !== \"running\") {\n\t\t\t\t\t\tthis.context.resume();\n\t\t\t\t\t\tif (this.beeper) {\n\t\t\t\t\t\t\tthis.start();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (const item of this.wakeupList) {\n\t\t\t\t\t\t\titem.wakeUp();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Clean up listeners\n\t\t\t\t\t\tdocument.body.removeEventListener(\"touchend\", activate);\n\t\t\t\t\t\tdocument.body.removeEventListener(\"mouseup\", activate);\n\t\t\t\t\t\tdocument.body.removeEventListener(\"click\", activate);\n\t\t\t\t\t\tdocument.body.removeEventListener(\"keydown\", activate);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// Add multiple event listeners for better compatibility\n\t\t\t\tdocument.body.addEventListener(\"touchend\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t\tdocument.body.addEventListener(\"mouseup\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t\tdocument.body.addEventListener(\"click\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t\tdocument.body.addEventListener(\"keydown\", activate, {\n\t\t\t\t\tonce: true,\n\t\t\t\t});\n\t\t\t} else if (this.beeper) {\n\t\t\t\tthis.start();\n\t\t\t}\n\t\t}\n\n\t\treturn this.context;\n\t}\n\n\t/**\n\t * Start audio processor\n\t */\n\tpublic async start(): Promise<void> {\n\t\tif (this.workletNode) return;\n\n\t\ttry {\n\t\t\tconst blob = new Blob([AUDIO_WORKLET_CODE], {\n\t\t\t\ttype: \"application/javascript\",\n\t\t\t});\n\t\t\tconst url = URL.createObjectURL(blob);\n\n\t\t\tawait this.context.audioWorklet.addModule(url);\n\n\t\t\tthis.workletNode = new AudioWorkletNode(this.context, \"l8b-audio-processor\");\n\t\t\tthis.workletNode.connect(this.context.destination);\n\n\t\t\tthis.flushBuffer();\n\t\t} catch (e) {\n\t\t\tthis.runtime?.listener?.reportError?.({ code: \"E7012\", message: \"Audio worklet error\", data: { error: String(e) } });\n\t\t}\n\t}\n\n\t/**\n\t * Flush buffered messages\n\t */\n\tprivate flushBuffer(): void {\n\t\tif (!this.workletNode) return;\n\n\t\twhile (this.buffer.length > 0) {\n\t\t\tthis.workletNode.port.postMessage(this.buffer.splice(0, 1)[0]);\n\t\t}\n\t}\n\n\t/**\n\t * Get or create beeper\n\t */\n\tpublic getBeeper(): Beeper {\n\t\tif (!this.beeper) {\n\t\t\t// Create Beeper instance\n\t\t\tthis.beeper = new Beeper(this);\n\n\t\t\tif (this.context.state === \"running\") {\n\t\t\t\tthis.start();\n\t\t\t}\n\t\t}\n\t\treturn this.beeper;\n\t}\n\n\t/**\n\t * Play beep sequence\n\t */\n\tpublic beep(sequence: string): void {\n\t\tthis.getBeeper().beep(sequence);\n\t}\n\n\t/**\n\t * Add beeps to audio processor\n\t */\n\tpublic addBeeps(beeps: any[]): void {\n\t\tfor (const b of beeps) {\n\t\t\tb.duration *= this.context.sampleRate;\n\t\t\tb.increment = b.frequency / this.context.sampleRate;\n\t\t}\n\n\t\tif (this.workletNode) {\n\t\t\tthis.workletNode.port.postMessage(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"beep\",\n\t\t\t\t\tsequence: beeps,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.buffer.push(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"beep\",\n\t\t\t\t\tsequence: beeps,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Cancel all beeps\n\t */\n\tpublic cancelBeeps(): void {\n\t\tif (this.workletNode) {\n\t\t\tthis.workletNode.port.postMessage(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"cancel_beeps\",\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.buffer.push(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tname: \"cancel_beeps\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tthis.stopAll();\n\t}\n\n\t/**\n\t * Add playing sound/music to list\n\t */\n\tpublic addPlaying(item: PlayingHandle): void {\n\t\tthis.playing.push(item);\n\t}\n\n\t/**\n\t * Remove playing sound/music from list\n\t */\n\tpublic removePlaying(item: PlayingHandle): void {\n\t\tconst index = this.playing.indexOf(item);\n\t\tif (index >= 0) {\n\t\t\tthis.playing.splice(index, 1);\n\t\t}\n\t}\n\n\t/**\n\t * Stop all playing sounds/music\n\t */\n\tpublic stopAll(): void {\n\t\tfor (const p of this.playing) {\n\t\t\ttry {\n\t\t\t\tp.stop();\n\t\t\t} catch (err) {\n\t\t\t\tthis.runtime?.listener?.reportError?.({ code: \"E7016\", message: \"Audio error\", data: { error: String(err) } });\n\t\t\t}\n\t\t}\n\t\tthis.playing = [];\n\t}\n}\n"],"mappings":"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","names":["A4_FREQUENCY","SEMITONE_RATIO","A4_MIDI_NOTE","Beeper","audio","notes","plainNotes","currentOctave","currentDuration","currentVolume","currentSpan","currentWaveform","initializeNotes","noteNames","i","oct","Math","floor","n","beep","input","status","sequence","loops","note","parsed","split","t","undefined","push","frequency","A4_FREQUENCY","SEMITONE_RATIO","A4_MIDI_NOTE","volume","span","duration","waveform","includes","length","lop","splice","loopto","start","repeats","tempo","Number","parseFloat","isNaN","step","addBeeps","AUDIO_WORKLET_CODE","AudioCore","context","buffer","playing","wakeupList","workletNode","beeper","runtime","masterVolume","getContext","isStarted","state","addToWakeUpList","item","push","interfaceCache","setVolume","volume","Math","max","min","getVolume","getInterface","beep","sequence","cancelBeeps","playSound","sound","pitch","pan","loopit","playMusic","music","stopAll","soundName","replace","s","sounds","listener","reportError","code","message","data","play","musicName","m","AudioContextClass","window","AudioContext","webkitAudioContext","activate","resume","start","wakeUp","document","body","removeEventListener","addEventListener","once","blob","Blob","AUDIO_WORKLET_CODE","type","url","URL","createObjectURL","audioWorklet","addModule","AudioWorkletNode","connect","destination","flushBuffer","e","error","String","length","port","postMessage","splice","getBeeper","Beeper","addBeeps","beeps","b","duration","sampleRate","increment","frequency","JSON","stringify","name","addPlaying","removePlaying","index","indexOf","p","stop","err"]}
|
package/dist/core/index.js
CHANGED
|
@@ -25,9 +25,6 @@ __export(core_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(core_exports);
|
|
27
27
|
|
|
28
|
-
// src/core/audio-core.ts
|
|
29
|
-
var import_diagnostics = require("@al8b/diagnostics");
|
|
30
|
-
|
|
31
28
|
// src/constants.ts
|
|
32
29
|
var A4_FREQUENCY = 440;
|
|
33
30
|
var SEMITONE_RATIO = 2 ** (1 / 12);
|
|
@@ -476,8 +473,12 @@ var AudioCore = class {
|
|
|
476
473
|
const soundName = sound.replace(/\//g, "-");
|
|
477
474
|
const s = this.runtime.sounds[soundName];
|
|
478
475
|
if (!s) {
|
|
479
|
-
|
|
480
|
-
|
|
476
|
+
this.runtime?.listener?.reportError?.({
|
|
477
|
+
code: "E7013",
|
|
478
|
+
message: "Sound not found",
|
|
479
|
+
data: {
|
|
480
|
+
soundName
|
|
481
|
+
}
|
|
481
482
|
});
|
|
482
483
|
return 0;
|
|
483
484
|
}
|
|
@@ -493,8 +494,12 @@ var AudioCore = class {
|
|
|
493
494
|
const musicName = music.replace(/\//g, "-");
|
|
494
495
|
const m = this.runtime.music[musicName];
|
|
495
496
|
if (!m) {
|
|
496
|
-
|
|
497
|
-
|
|
497
|
+
this.runtime?.listener?.reportError?.({
|
|
498
|
+
code: "E7014",
|
|
499
|
+
message: "Music not found",
|
|
500
|
+
data: {
|
|
501
|
+
musicName
|
|
502
|
+
}
|
|
498
503
|
});
|
|
499
504
|
return 0;
|
|
500
505
|
}
|
|
@@ -561,8 +566,12 @@ var AudioCore = class {
|
|
|
561
566
|
this.workletNode.connect(this.context.destination);
|
|
562
567
|
this.flushBuffer();
|
|
563
568
|
} catch (e) {
|
|
564
|
-
|
|
565
|
-
|
|
569
|
+
this.runtime?.listener?.reportError?.({
|
|
570
|
+
code: "E7012",
|
|
571
|
+
message: "Audio worklet error",
|
|
572
|
+
data: {
|
|
573
|
+
error: String(e)
|
|
574
|
+
}
|
|
566
575
|
});
|
|
567
576
|
}
|
|
568
577
|
}
|
|
@@ -651,8 +660,12 @@ var AudioCore = class {
|
|
|
651
660
|
try {
|
|
652
661
|
p.stop();
|
|
653
662
|
} catch (err) {
|
|
654
|
-
|
|
655
|
-
|
|
663
|
+
this.runtime?.listener?.reportError?.({
|
|
664
|
+
code: "E7016",
|
|
665
|
+
message: "Audio error",
|
|
666
|
+
data: {
|
|
667
|
+
error: String(err)
|
|
668
|
+
}
|
|
656
669
|
});
|
|
657
670
|
}
|
|
658
671
|
}
|