var tween = use('prosperon/tween') var io = use('cellfs') var res = use('resources') var doc = use('doc') var wav = use('audio/wav') var mp3 = use('audio/mp3') var flac = use('audio/flac') var dsp = use('audio/dsp') var audio = {} var pcms = {} // keep every live voice here so GC can’t collect it prematurely var voices = [] // load-and-cache WAVs/MP3s/FLACs audio.pcm = function pcm(file) { file = res.find_sound(file); if (!file) return if (pcms[file]) return pcms[file] var buf = io.slurp(file) if (!buf) return null var decoded = null if (file.endsWith('.wav')) { decoded = wav.decode(buf) } else if (file.endsWith('.mp3')) { decoded = mp3.decode(buf) } else if (file.endsWith('.flac')) { decoded = flac.decode(buf) } if (decoded && decoded.pcm) { // Store extra info needed for playback decoded.file = file return pcms[file] = decoded } return null } function cleanup() { // Remove voices that have finished playing var active_voices = [] for (var i = 0; i < voices.length; i++) { var v = voices[i] // Check if voice has finished (pos >= total_frames) // We can calculate total frames from pcm length and format var total_samples = v.pcm.length / 4 // f32 is 4 bytes var total_frames = total_samples / v.channels if (v.pos < total_frames && !v.stopped) { active_voices.push(v) } else { v.finish_hook?.() } } voices = active_voices } // Voice class function Voice(pcm_data) { this.pcm = pcm_data.pcm this.channels = pcm_data.channels || 1 this.sample_rate = pcm_data.sample_rate || 44100 this.pos = 0 this.vol = 1.0 this.stopped = false } Voice.prototype.stop = function() { this.stopped = true } Voice.prototype.set_volume = function(v) { this.vol = v } // play a one‑shot; returns the voice for volume/stop control audio.play = function play(file) { var pcm_data = audio.pcm(file); if (!pcm_data) return var voice = new Voice(pcm_data) voices.push(voice) return voice } // cry is just a play+stop closure audio.cry = function cry(file) { var v = audio.play(file); if (!v) return return function() { v.stop(); v = null } } // // pump + periodic cleanup // var ss = use('sdl/audio') var feeder = ss.open_stream("playback") // We output stereo float32 at 44100Hz feeder.set_format({format:"f32", channels:2, samplerate:44100}) feeder.resume() var FRAMES = 1024 var CHANNELS = 2 var BYTES_PER_F = 4 var CHUNK_BYTES = FRAMES * CHANNELS * BYTES_PER_F function pump() { // Keep buffer full if (feeder.queued() < CHUNK_BYTES*3) { // Mix voices var mixed_blob = dsp.mix(voices, FRAMES) if (mixed_blob) { feeder.put(mixed_blob) } else { // If mix failed or no voices, output silence? // dsp.mix should return silence if no voices, but let's be safe // Actually dsp.mix returns a zeroed buffer if no voices. } cleanup() } $_.delay(pump, 1/240) } pump() return audio