PHP Classes

File: public_html/js/mod-player/player.js

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   public_html/js/mod-player/player.js   Download  
File: public_html/js/mod-player/player.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 5 days ago
Size: 4,256 bytes
 

Contents

Class file image Download
import { loadMod } from './loader.js'; const WORKLET_URL = '/js/mod-player/mod-player-worklet.js'; const AUDIO = Symbol('audio'); const GAIN = Symbol('gain'); const WORKLET = Symbol('worklet'); const ROW_CALLBACKS = Symbol('rowCallbacks'); const SINGLE_CALLBACKS = Symbol('singleCallbacks'); const STOP_CALLBACKS = Symbol('stopCallbacks'); const ALL_NOTES_CALLBACKS = Symbol('allNotesCallbacks'); const range = function* (min, max) { for (let i = min; i <= max; ++i) yield i; }; const map = function* (iterator, func) { for (let i of iterator) yield func(i); }; const notePerPeriod = [...map(range(0, 65535), p => p < 124 ? null : 24 + Math.round(12 * Math.log2(428 / p)) )]; export class ModPlayer { constructor(audioContext) { this.mod = null; this.playing = false; this[AUDIO] = audioContext || null; this[GAIN] = null; this[WORKLET] = null; this[ROW_CALLBACKS] = []; this[SINGLE_CALLBACKS] = {}; this[STOP_CALLBACKS] = []; this[ALL_NOTES_CALLBACKS] = []; } async load(url) { if (this[WORKLET]) this.unload(); this.mod = await loadMod(url); if (!this[AUDIO]) this[AUDIO] = new AudioContext(); this[GAIN] = this[AUDIO].createGain(); this[GAIN].gain.value = 0.3; await this[AUDIO].audioWorklet.addModule(WORKLET_URL); this[WORKLET] = new AudioWorkletNode(this[AUDIO], 'mod-player-worklet'); this[WORKLET].connect(this[GAIN]).connect(this[AUDIO].destination); this[WORKLET].port.onmessage = this.onmessage.bind(this); } onmessage(event) { const { data } = event; switch (data.type) { case 'row': for (let cb of this[ROW_CALLBACKS]) cb(data.position, data.rowIndex); const key = data.position + ':' + data.rowIndex; if (key in this[SINGLE_CALLBACKS]) { for (let cb of this[SINGLE_CALLBACKS][key]) cb(data.position, data.rowIndex); } break; case 'stop': for (let cb of this[STOP_CALLBACKS]) cb(); break; case 'note': for (let cb of this[ALL_NOTES_CALLBACKS]) { cb({ channel: data.channel, sample: data.sample, volume: data.volume, note: notePerPeriod[data.period] }); } break; } } watchRows(callback) { this[WORKLET].port.postMessage({ type: 'enableRowSubscription' }); this[ROW_CALLBACKS].push(callback); } watch(position, row, callback) { this[WORKLET].port.postMessage({ type: 'enableRowSubscription' }); const key = position + ':' + row; if (!(key in this[SINGLE_CALLBACKS])) this[SINGLE_CALLBACKS][key] = []; this[SINGLE_CALLBACKS][key].push(callback); } watchStop(callback) { this[WORKLET].port.postMessage({ type: 'enableStopSubscription' }); this[STOP_CALLBACKS].push(callback); } watchNotes(callback) { this[WORKLET].port.postMessage({ type: 'enableNoteSubscription' }); this[ALL_NOTES_CALLBACKS].push(callback); } unload() { if (this.playing) this.stop(); if (!this[WORKLET]) return; this[WORKLET].disconnect(); this[AUDIO].close(); this.mod = null; this[AUDIO] = null; this[WORKLET] = null; this[ROW_CALLBACKS] = []; this[SINGLE_CALLBACKS] = {}; this[ALL_NOTES_CALLBACKS] = []; } play() { if (this.playing || !this[WORKLET]) return; this[AUDIO].resume(); this[WORKLET].port.postMessage({ type: 'play', mod: this.mod, sampleRate: this[AUDIO].sampleRate }); this.playing = true; } stop() { if (!this.playing) return; this[WORKLET].port.postMessage({ type: 'stop' }); this.playing = false; } resume() { if (this.playing) return; this[WORKLET].port.postMessage({ type: 'resume' }); this.playing = true; } setRow(position, row) { this[WORKLET].port.postMessage({ type: 'setRow', position, row }); } setVolume(volume) { this[GAIN].gain.value = volume; } }