sw-synth
Version:
Lightweight sound synthesizer designed for real-time user interaction using the Web Audio API
130 lines • 4.53 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BufferSynth = exports.AperiodicSynth = exports.UnisonSynth = exports.Synth = void 0;
const oscillator_1 = require("./voice/oscillator");
const buffer_1 = require("./voice/buffer");
__exportStar(require("./voice"), exports);
// Tracking numbers for voice stealing
// Technically we could run out of note identifiers,
// but who is going to play 9007199254740991 notes in one session?
let NOTE_ID = 1;
/**
* Simple web audio synth of finite polyphony.
*/
class Synth {
constructor(audioContext, destination, log) {
this.audioContext = audioContext;
this.destination = destination;
if (log === undefined) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
this.log = (msg) => { };
}
else {
this.log = log;
}
this.voices = [];
}
_newVoice() {
return new oscillator_1.OscillatorVoice(this.audioContext, this.destination, this.log);
}
setPolyphony(maxPolyphony) {
if (maxPolyphony < 0 || maxPolyphony === Infinity || isNaN(maxPolyphony)) {
throw new Error('Invalid max polyphony');
}
while (this.voices.length > maxPolyphony) {
this.voices.pop().dispose();
}
while (this.voices.length < maxPolyphony) {
this.voices.push(this._newVoice());
}
}
get maxPolyphony() {
return this.voices.length;
}
set maxPolyphony(value) {
this.setPolyphony(value);
}
_allocateVoice() {
// Allocate voices based on age.
// Boils down to:
// a) Pick the oldest released voice.
// b) If there are no released voices, replace the oldest currently playing voice.
let oldestVoice;
for (const voice of this.voices) {
voice.age++;
if (oldestVoice === undefined || voice.age > oldestVoice.age) {
oldestVoice = voice;
}
}
return oldestVoice;
}
/**
* Start playing a note at the specified frequency and velocity.
* @param frequency Frequency in Hertz.
* @param velocity Voice amplitude. Recomended range from 0 to 1.
* @returns A callback that stops playing the note.
*/
noteOn(frequency, velocity) {
const oldestVoice = this._allocateVoice();
if (oldestVoice === undefined) {
return () => { };
}
if (this.voiceParams === undefined) {
throw new Error('Synth.voiceParams must be set before calling Synth.noteOn');
}
return oldestVoice.noteOn(frequency, velocity, NOTE_ID++, this.voiceParams);
}
/**
* Trigger panic and release all notes.
*/
allNotesOff() {
for (const voice of this.voices) {
if (voice.lastNoteOff !== undefined) {
voice.lastNoteOff();
}
}
}
}
exports.Synth = Synth;
/**
* Web audio synth of finite polyphony where the voices are stacked in unison.
*/
class UnisonSynth extends Synth {
_newVoice() {
return new oscillator_1.UnisonVoice(this.audioContext, this.destination, this.log);
}
}
exports.UnisonSynth = UnisonSynth;
/**
* Web audio synth of finite polyphony that supports inharmonic timbres.
*/
class AperiodicSynth extends Synth {
_newVoice() {
return new oscillator_1.AperiodicVoice(this.audioContext, this.destination, this.log);
}
}
exports.AperiodicSynth = AperiodicSynth;
/**
* Web audio synth of finite polyphony with user-provided audio buffers.
*/
class BufferSynth extends Synth {
_newVoice() {
return new buffer_1.BufferVoice(this.audioContext, this.destination, this.log);
}
}
exports.BufferSynth = BufferSynth;
//# sourceMappingURL=index.js.map