UNPKG

@evan/wasm

Version:
321 lines (278 loc) 11 kB
let u8; let wasm; let pptr; let bptr; let pptrs; let bptrs; { const module = new WebAssembly.Module(await (await fetch('https://unpkg.com/@evan/wasm@0.0.95/src/opus/opus.wasm')).arrayBuffer()); const instance = new WebAssembly.Instance(module, { wasi_snapshot_preview1: { fd_seek() { }, fd_write() { }, fd_close() { }, proc_exit() { } }, env: { emscripten_notify_memory_growth() { u8 = new Uint8Array(wasm.memory.buffer); pptrs = u8.subarray(pptr, pptr + 2 ** 13); bptrs = u8.subarray(bptr, bptr + 2 ** 15); }, }, }); wasm = instance.exports; pptr = wasm.malloc(2 ** 13); bptr = wasm.malloc(2 ** 15); u8 = new Uint8Array(wasm.memory.buffer); pptrs = u8.subarray(pptr, pptr + 2 ** 13); bptrs = u8.subarray(bptr, bptr + 2 ** 15); } function clamp(min, int, max) { const t = int < min ? min : int; return t > max ? max : t; } function err(code) { if (0 > code) throw new Error(`opus: ${load_static_string(u8, wasm.opus_strerror(code))}`); else return code; } function cgc(f) { return !('FinalizationRegistry' in globalThis) ? { delete(_) { }, add(_, __) { } } : { r: new FinalizationRegistry(f), delete(k) { this.r.unregister(k); }, add(k, v) { this.r.register(k, v, k); } }; } function load_static_string(u8, ptr) { let s = ''; const l = u8.length | 0; for (let o = ptr | 0; o < l; o++) { const x = u8[o]; if (0 === x) break; s += String.fromCharCode(x); } return s; } export const ctl = { auto: -1000, bitrate_max: -1, reset_state: 4028, signal: { auto: -1000, // default voice: 3001, // best for voice music: 3002, // best for audio/music }, application: { voip: 2048, // best for voice audio: 2049, // best for audio/music restricted_lowdelay: 2051, // best for low coding delay }, bandwidth: { auto: -1000, wideband: 1103, // 8 KHz fullband: 1105, // 20 KHz narrowband: 1101, // 4 KHz mediumband: 1102, // 6 KHz superwideband: 1104, // 12 KHz }, framesize: { 5: 5002, // 5ms frames 10: 5003, // 10ms frames 20: 5004, // 20ms frames 40: 5005, // 40ms frames 60: 5006, // 60ms frames 80: 5007, // 80ms frames 100: 5008, // 100ms frames 120: 5009, // 120ms frames [2.5]: 5001, // 2.5ms frames arg: 5000, // encode argument }, set: { vbr: 4006, dtx: 4016, gain: 4034, signal: 4024, bitrate: 4002, bandwidth: 4008, lsb_depth: 4036, complexity: 4010, inband_fec: 4012, application: 4000, max_bandwidth: 4004, vbr_constraint: 4020, force_channels: 4022, packet_loss_perc: 4014, prediction_disabled: 4042, expert_frame_duration: 4040, phase_inversion_disabled: 4046, }, get: { vbr: 4007, dtx: 4017, gain: 4045, pitch: 4033, signal: 4025, in_dtx: 4049, bitrate: 4003, lsb_depth: 4037, bandwidth: 4009, lookahead: 4027, complexity: 4011, inband_fec: 4013, application: 4001, sample_rate: 4029, final_range: 4031, max_bandwidth: 4005, vbr_constraint: 4021, force_channels: 4023, packet_loss_perc: 4015, prediction_disabled: 4043, last_packet_duration: 4039, expert_frame_duration: 4041, phase_inversion_disabled: 4047, }, }; const convert = { fc: { 1: 1, 2: 2, 'auto': [ctl.auto], [ctl.auto]: 'auto', }, s: { [ctl.auto]: 'auto', [ctl.signal.voice]: 'voice', [ctl.signal.music]: 'music', }, a: { [ctl.application.voip]: 'voip', [ctl.application.audio]: 'audio', [ctl.application.restricted_lowdelay]: 'restricted_lowdelay', }, b: { [ctl.auto]: 'auto', [ctl.bandwidth.wideband]: 'wideband', [ctl.bandwidth.fullband]: 'fullband', [ctl.bandwidth.narrowband]: 'narrowband', [ctl.bandwidth.mediumband]: 'mediumband', [ctl.bandwidth.superwideband]: 'superwideband', }, efd: { [ctl.framesize[5]]: 5, [ctl.framesize[10]]: 10, [ctl.framesize[20]]: 20, [ctl.framesize[40]]: 40, [ctl.framesize[60]]: 60, [ctl.framesize[80]]: 80, [ctl.framesize[2.5]]: 2.5, [ctl.framesize[100]]: 100, [ctl.framesize[120]]: 120, [ctl.framesize.arg]: 'arg', }, } const pptrl = 2 ** 13; const gc = cgc(ptr => wasm.free(ptr)); class RawDecoder { #ptr = 0; constructor({ channels, sample_rate }) { gc.add(this, this.#ptr = wasm.malloc(wasm.opus_decoder_get_size(this.channels = channels || 2))); try { err(wasm.opus_decoder_init(this.#ptr, sample_rate || 48000, this.channels)); } catch (e) { throw (this.drop(), e); } } drop() { if (this.#ptr) (gc.delete(this), wasm.free(this.#ptr), this.#ptr = 0); } ctl(cmd, arg) { if (arg == null) return wasm.opus_decoder_ctl_get(this.#ptr, cmd); else return err(wasm.opus_decoder_ctl_set(this.#ptr, cmd, arg)); } decode(buffer) { pptrs.set(buffer = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)); return bptrs.slice(0, 2 * this.channels * err(wasm.opus_decode(this.#ptr, pptr, buffer.length, bptr, 5760, 0))); } } class RawEncoder { #ptr = 0; constructor({ channels, application, sample_rate }) { this.channels = channels || 2; gc.add(this, this.#ptr = wasm.malloc(wasm.opus_encoder_get_size(this.channels))); application = ({ voip: 2048, audio: 2049, restricted_lowdelay: 2051 })[application || 'audio']; try { err(wasm.opus_encoder_init(this.#ptr, sample_rate || 48000, this.channels, application)); } catch (e) { throw (this.drop(), e); } } drop() { if (this.#ptr) (gc.delete(this), wasm.free(this.#ptr), this.#ptr = 0); } ctl(cmd, arg) { if (arg == null) return wasm.opus_encoder_ctl_get(this.#ptr, cmd); else return err(wasm.opus_encoder_ctl_set(this.#ptr, cmd, arg)); } encode(buffer) { bptrs.set(buffer = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)); return pptrs.slice(0, err(wasm.opus_encode(this.#ptr, bptr, buffer.length / 2 / this.channels, pptr, pptrl))); } } export class Decoder extends RawDecoder { constructor({ channels = 2, sample_rate = 48000 } = {}) { super({ channels, sample_rate }); this.channels = channels; } // https://www.opus-codec.org/docs/opus_api-1.3.1/group__opus__decoderctls.html get gain() { return this.ctl(ctl.get.gain); } set gain(int) { this.ctl(ctl.set.gain, clamp(-32768, int, 32767)); } get pitch() { const x = this.ctl(ctl.get.pitch); return 0 === x ? null : x } get last_packet_duration() { return this.ctl(ctl.get.last_packet_duration); } // https://www.opus-codec.org/docs/opus_api-1.3.1/group__opus__genericctls.html reset() { this.ctl(ctl.reset_state); } get in_dtx() { return !!this.ctl(ctl.get.in_dtx); } get sample_rate() { return this.ctl(ctl.get.sample_rate); } get bandwidth() { return convert.b[this.ctl(ctl.get.bandwidth)]; } get phase_inversion_disabled() { return !!this.ctl(ctl.get.phase_inversion_disabled); } set phase_inversion_disabled(bool) { this.ctl(ctl.set.phase_inversion_disabled, bool ? 1 : 0); } } export class Encoder extends RawEncoder { constructor({ channels = 2, application, sample_rate = 48000 } = {}) { super({ channels, sample_rate, application: application || 'audio' }); this.channels = channels; } async *encode_pcm_stream(size, iter) { let needle = 0; const self = this; const r = 2 * size * this.channels; const t8 = new Uint8Array(r); function* consume(buf) { let offset = 0; while (offset < buf.length) { const s = buf.subarray(offset, offset += r); if (r === s.length) yield self.encode(s); else { t8.set(s, needle); needle += s.length; break; }; } }; for await (const buffer of iter) { const b8 = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); if (0 === needle) for (const x of consume(b8)) yield x; else { const rr = r - needle; if (rr > b8.length) (t8.set(b8, needle), needle += b8.length); else { t8.set(b8.subarray(0, rr), needle); yield (needle = 0, self.encode(t8)); for (const x of consume(b8.subarray(rr))) yield x; } } } } // https://www.opus-codec.org/docs/opus_api-1.3.1/group__opus__genericctls.html reset() { this.ctl(ctl.reset_state); } get in_dtx() { return !!this.ctl(ctl.get.in_dtx); } get sample_rate() { return this.ctl(ctl.get.sample_rate); } get bandwidth() { return convert.b[this.ctl(ctl.get.bandwidth)]; } get phase_inversion_disabled() { return !!this.ctl(ctl.get.phase_inversion_disabled); } set phase_inversion_disabled(bool) { this.ctl(ctl.set.phase_inversion_disabled, bool ? 1 : 0); } // https://www.opus-codec.org/docs/opus_api-1.3.1/group__opus__encoderctls.html get vbr() { return !!this.ctl(ctl.get.vbr); } get dtx() { return !!this.ctl(ctl.get.dtx); } get bitrate() { return this.ctl(ctl.get.bitrate); } get lookahead() { return this.ctl(ctl.get.lookahead); } get lsb_depth() { return this.ctl(ctl.get.lsb_depth); } get complexity() { return this.ctl(ctl.get.complexity); } get inband_fec() { return !!this.ctl(ctl.get.inband_fec); } get signal() { return convert.s[this.ctl(ctl.get.signal)]; } get packet_loss() { return this.ctl(ctl.get.packet_loss_perc); } get vbr_constraint() { return !!this.ctl(ctl.get.vbr_constraint); } get application() { return convert.a[this.ctl(ctl.get.application)]; } get max_bandwidth() { return convert.b[this.ctl(ctl.get.max_bandwidth)]; } get force_channels() { return convert.fc[this.ctl(ctl.get.force_channels)]; } get prediction_disabled() { return !!this.ctl(ctl.get.prediction_disabled); } get expert_frame_duration() { return covert.efd[this.ctl(ctl.get.expert_frame_duration)]; } set vbr(bool) { this.ctl(ctl.set.vbr, bool ? 1 : 0); } set dtx(bool) { this.ctl(ctl.set.dtx, bool ? 1 : 0); } set signal(arg) { this.ctl(ctl.set.signal, ctl.signal[arg]); } set inband_fec(bool) { this.ctl(ctl.set.inband_fec, bool ? 1 : 0); } set lsb_depth(int) { this.ctl(ctl.set.lsb_depth, clamp(8, int, 24)); } set bandwidth(arg) { this.ctl(ctl.set.bandwidth, ctl.bandwidth[arg]); } set complexity(int) { this.ctl(ctl.set.complexity, clamp(0, int, 10)); } set vbr_constraint(bool) { this.ctl(ctl.set.vbr_constraint, bool ? 1 : 0); } set application(arg) { this.ctl(ctl.set.application, ctl.application[arg]); } set force_channels(arg) { this.ctl(ctl.set.force_channels, convert.fc[arg]); } set packet_loss(int) { this.ctl(ctl.set.packet_loss_perc, clamp(0, int, 100)); } set prediction_disabled(bool) { this.ctl(ctl.set.prediction_disabled, bool ? 1 : 0); } set expert_frame_duration(arg) { this.ctl(ctl.set.expert_frame_duration, ctl.framesize[arg]) }; set max_bandwidth(arg) { this.ctl(ctl.set.max_bandwidth, ctl.bandwidth[arg === 'auto' ? 'fullband' : arg]); } set bitrate(arg) { this.ctl(ctl.set.bitrate, arg === 'max' ? ctl.bitrate_max : (arg === 'auto' ? ctl.auto : clamp(500, arg | 0, 512000))); } }