UNPKG

croonjs

Version:

Toolkit for parsing and playing numbered musical notation

75 lines (74 loc) 3.22 kB
import { digitize } from './digitizer.js'; function generateAudioNodes(context, notation, options) { const oscillator = context.createOscillator(); const gain = context.createGain(); const simulation = options === null || options === void 0 ? void 0 : options.simulation; if (options === null || options === void 0 ? void 0 : options.waveform) { if (typeof options.waveform === 'string') { oscillator.type = options.waveform; } else { const periodicWave = context.createPeriodicWave(options.waveform.real, options.waveform.imag); oscillator.setPeriodicWave(periodicWave); } } const baseGain = options === null || options === void 0 ? void 0 : options.gain; if (baseGain) { gain.gain.value = baseGain; } const initialTime = context.currentTime; for (const node of notation.nodes) { switch (node.type) { case 'FrequencyNode': oscillator.frequency.setValueAtTime(node.value, initialTime + node.time); break; case 'BreakNode': if (simulation === 'idiophone') { gain.gain.exponentialRampToValueAtTime(0.01, initialTime + node.time); } else if (simulation === 'aerophone') { gain.gain.exponentialRampToValueAtTime(baseGain !== null && baseGain !== void 0 ? baseGain : 1, initialTime + node.time - node.before / 4); gain.gain.exponentialRampToValueAtTime(0.01, initialTime + node.time); } else { gain.gain.setValueAtTime(baseGain !== null && baseGain !== void 0 ? baseGain : 1, initialTime + node.time); gain.gain.exponentialRampToValueAtTime(0.01, initialTime + node.time + node.base / 128); } gain.gain.exponentialRampToValueAtTime(baseGain !== null && baseGain !== void 0 ? baseGain : 1, initialTime + node.time + node.base / 64); break; } } oscillator.connect(gain); return { source: oscillator, destination: gain, duration: notation.duration, }; } class AbortError extends Error { constructor() { super('The operation was aborted.'); } } export function play(notation, options) { var _a; if (typeof notation === 'string' || notation.type === 'ParsedNotation') { notation = digitize(notation); } const context = (_a = options === null || options === void 0 ? void 0 : options.context) !== null && _a !== void 0 ? _a : new AudioContext(); const { source, destination, duration } = generateAudioNodes(context, notation, options); return new Promise((resolve, reject) => { if (options === null || options === void 0 ? void 0 : options.signal) { options.signal.addEventListener('abort', () => { source.stop(); reject(new AbortError()); }); } destination.connect(context.destination); source.start(); source.stop(duration); source.addEventListener('ended', () => { resolve(undefined); }); }); }