@al8b/audio 0.1.0
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/README.md +23 -0
- package/dist/constants.d.mts +8 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +37 -0
- package/dist/constants.js.map +1 -0
- package/dist/constants.mjs +10 -0
- package/dist/constants.mjs.map +1 -0
- package/dist/core/audio-core.d.mts +98 -0
- package/dist/core/audio-core.d.ts +98 -0
- package/dist/core/audio-core.js +664 -0
- package/dist/core/audio-core.js.map +1 -0
- package/dist/core/audio-core.mjs +641 -0
- package/dist/core/audio-core.mjs.map +1 -0
- package/dist/core/audio-worklet.d.mts +3 -0
- package/dist/core/audio-worklet.d.ts +3 -0
- package/dist/core/audio-worklet.js +153 -0
- package/dist/core/audio-worklet.js.map +1 -0
- package/dist/core/audio-worklet.mjs +128 -0
- package/dist/core/audio-worklet.mjs.map +1 -0
- package/dist/core/index.d.mts +2 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +666 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +641 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/devices/beeper.d.mts +21 -0
- package/dist/devices/beeper.d.ts +21 -0
- package/dist/devices/beeper.js +286 -0
- package/dist/devices/beeper.js.map +1 -0
- package/dist/devices/beeper.mjs +261 -0
- package/dist/devices/beeper.mjs.map +1 -0
- package/dist/devices/index.d.mts +3 -0
- package/dist/devices/index.d.ts +3 -0
- package/dist/devices/index.js +534 -0
- package/dist/devices/index.js.map +1 -0
- package/dist/devices/index.mjs +507 -0
- package/dist/devices/index.mjs.map +1 -0
- package/dist/devices/music.d.mts +27 -0
- package/dist/devices/music.d.ts +27 -0
- package/dist/devices/music.js +104 -0
- package/dist/devices/music.js.map +1 -0
- package/dist/devices/music.mjs +81 -0
- package/dist/devices/music.mjs.map +1 -0
- package/dist/devices/sound.d.mts +22 -0
- package/dist/devices/sound.d.ts +22 -0
- package/dist/devices/sound.js +198 -0
- package/dist/devices/sound.js.map +1 -0
- package/dist/devices/sound.mjs +175 -0
- package/dist/devices/sound.mjs.map +1 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +916 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +888 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +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"]}
|