croonjs
Version:
Toolkit for parsing and playing numbered musical notation
75 lines (74 loc) • 3.22 kB
JavaScript
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);
});
});
}