smplr
Version:
A Sampled collection of instruments
1,475 lines (1,451 loc) • 84.9 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/player/connect.ts
function connectSerial(nodes) {
const _nodes = nodes.filter((x) => !!x);
_nodes.reduce((a, b) => {
const left = "output" in a ? a.output : a;
const right = "input" in b ? b.input : b;
left.connect(right);
return b;
});
return () => {
_nodes.reduce((a, b) => {
const left = "output" in a ? a.output : a;
const right = "input" in b ? b.input : b;
left.disconnect(right);
return b;
});
};
}
// src/player/signals.ts
function createControl(initialValue) {
let current = initialValue;
const listeners = /* @__PURE__ */ new Set();
function subscribe(listener) {
listeners.add(listener);
listener(current);
return () => {
listeners.delete(listener);
};
}
function set(value) {
current = value;
listeners.forEach((listener) => listener(current));
}
function get() {
return current;
}
return { subscribe, set, get };
}
function createTrigger() {
const listeners = /* @__PURE__ */ new Set();
function subscribe(listener) {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
}
function trigger(value) {
listeners.forEach((listener) => listener(value));
}
return { subscribe, trigger };
}
function unsubscribeAll(unsubscribe) {
let done = false;
return () => {
if (done) return;
done = true;
unsubscribe.forEach((cb) => cb == null ? void 0 : cb());
};
}
// src/player/volume.ts
function midiVelToGain(vel) {
return vel * vel / 16129;
}
function dbToGain(decibels) {
return Math.pow(10, decibels / 20);
}
// src/player/channel.ts
var _volume, _sends, _inserts, _disconnect, _unsubscribe, _config, _disconnected;
var Channel = class {
constructor(context, options) {
this.context = context;
__privateAdd(this, _volume);
__privateAdd(this, _sends);
__privateAdd(this, _inserts);
__privateAdd(this, _disconnect);
__privateAdd(this, _unsubscribe);
__privateAdd(this, _config);
__privateAdd(this, _disconnected, false);
var _a, _b, _c;
__privateSet(this, _config, {
destination: (_a = options == null ? void 0 : options.destination) != null ? _a : context.destination,
volume: (_b = options == null ? void 0 : options.volume) != null ? _b : 100,
volumeToGain: (_c = options == null ? void 0 : options.volumeToGain) != null ? _c : midiVelToGain
});
this.input = context.createGain();
__privateSet(this, _volume, context.createGain());
__privateSet(this, _disconnect, connectSerial([
this.input,
__privateGet(this, _volume),
__privateGet(this, _config).destination
]));
const volume = createControl(__privateGet(this, _config).volume);
this.setVolume = volume.set;
__privateSet(this, _unsubscribe, volume.subscribe((volume2) => {
__privateGet(this, _volume).gain.value = __privateGet(this, _config).volumeToGain(volume2);
}));
}
addInsert(effect) {
var _a;
if (__privateGet(this, _disconnected)) {
throw Error("Can't add insert to disconnected channel");
}
(_a = __privateGet(this, _inserts)) != null ? _a : __privateSet(this, _inserts, []);
__privateGet(this, _inserts).push(effect);
__privateGet(this, _disconnect).call(this);
__privateSet(this, _disconnect, connectSerial([
this.input,
...__privateGet(this, _inserts),
__privateGet(this, _volume),
__privateGet(this, _config).destination
]));
}
addEffect(name, effect, mixValue) {
var _a;
if (__privateGet(this, _disconnected)) {
throw Error("Can't add effect to disconnected channel");
}
const mix = this.context.createGain();
mix.gain.value = mixValue;
const input = "input" in effect ? effect.input : effect;
const disconnect = connectSerial([__privateGet(this, _volume), mix, input]);
(_a = __privateGet(this, _sends)) != null ? _a : __privateSet(this, _sends, []);
__privateGet(this, _sends).push({ name, mix, disconnect });
}
sendEffect(name, mix) {
var _a;
if (__privateGet(this, _disconnected)) {
throw Error("Can't send effect to disconnected channel");
}
const send = (_a = __privateGet(this, _sends)) == null ? void 0 : _a.find((send2) => send2.name === name);
if (send) {
send.mix.gain.value = mix;
} else {
console.warn("Send bus not found: " + name);
}
}
disconnect() {
var _a;
if (__privateGet(this, _disconnected)) return;
__privateSet(this, _disconnected, true);
__privateGet(this, _disconnect).call(this);
__privateGet(this, _unsubscribe).call(this);
(_a = __privateGet(this, _sends)) == null ? void 0 : _a.forEach((send) => send.disconnect());
__privateSet(this, _sends, void 0);
}
};
_volume = new WeakMap();
_sends = new WeakMap();
_inserts = new WeakMap();
_disconnect = new WeakMap();
_unsubscribe = new WeakMap();
_config = new WeakMap();
_disconnected = new WeakMap();
// src/player/sorted-queue.ts
var _items;
var SortedQueue = class {
constructor(compare) {
this.compare = compare;
__privateAdd(this, _items, []);
}
push(item) {
const len = __privateGet(this, _items).length;
let left = 0;
let right = len - 1;
let index = len;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (this.compare(item, __privateGet(this, _items)[mid]) < 0) {
index = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
__privateGet(this, _items).splice(index, 0, item);
}
pop() {
return __privateGet(this, _items).shift();
}
peek() {
return __privateGet(this, _items)[0];
}
removeAll(predicate) {
const len = __privateGet(this, _items).length;
__privateSet(this, _items, __privateGet(this, _items).filter((item) => !predicate(item)));
return __privateGet(this, _items).length !== len;
}
clear() {
__privateSet(this, _items, []);
}
size() {
return __privateGet(this, _items).length;
}
};
_items = new WeakMap();
// src/player/queued-player.ts
function compose(a, b) {
return a && b ? (x) => {
a(x);
b(x);
} : a != null ? a : b;
}
function getConfig(options) {
var _a, _b, _c;
const config = {
disableScheduler: (_a = options.disableScheduler) != null ? _a : false,
scheduleLookaheadMs: (_b = options.scheduleLookaheadMs) != null ? _b : 200,
scheduleIntervalMs: (_c = options.scheduleIntervalMs) != null ? _c : 50,
onStart: options.onStart,
onEnded: options.onEnded
};
if (config.scheduleLookaheadMs < 1) {
throw Error("scheduleLookaheadMs must be greater than 0");
}
if (config.scheduleIntervalMs < 1) {
throw Error("scheduleIntervalMs must be greater than 0");
}
if (config.scheduleLookaheadMs < config.scheduleIntervalMs) {
throw Error("scheduleLookaheadMs must be greater than scheduleIntervalMs");
}
return config;
}
var _config2, _queue, _intervalId;
var QueuedPlayer = class {
constructor(player, options = {}) {
__privateAdd(this, _config2);
__privateAdd(this, _queue);
__privateAdd(this, _intervalId);
__privateSet(this, _config2, getConfig(options));
__privateSet(this, _queue, new SortedQueue(
(a, b) => a.time - b.time
));
this.player = player;
}
get context() {
return this.player.context;
}
get buffers() {
return this.player.buffers;
}
get isRunning() {
return __privateGet(this, _intervalId) !== void 0;
}
start(sample) {
var _a;
if (__privateGet(this, _config2).disableScheduler) {
return this.player.start(sample);
}
const context = this.player.context;
const now = context.currentTime;
const startAt = (_a = sample.time) != null ? _a : now;
const lookAhead = __privateGet(this, _config2).scheduleLookaheadMs / 1e3;
sample.onStart = compose(sample.onStart, __privateGet(this, _config2).onStart);
sample.onEnded = compose(sample.onEnded, __privateGet(this, _config2).onEnded);
if (startAt < now + lookAhead) {
return this.player.start(sample);
}
__privateGet(this, _queue).push(__spreadProps(__spreadValues({}, sample), { time: startAt }));
if (!__privateGet(this, _intervalId)) {
__privateSet(this, _intervalId, setInterval(() => {
const nextTick = context.currentTime + lookAhead;
while (__privateGet(this, _queue).size() && __privateGet(this, _queue).peek().time <= nextTick) {
const sample2 = __privateGet(this, _queue).pop();
if (sample2) {
this.player.start(sample2);
}
}
if (!__privateGet(this, _queue).size()) {
clearInterval(__privateGet(this, _intervalId));
__privateSet(this, _intervalId, void 0);
}
}, __privateGet(this, _config2).scheduleIntervalMs));
}
return (time) => {
if (!time || time < startAt) {
if (!__privateGet(this, _queue).removeAll((item) => item === sample)) {
this.player.stop(__spreadProps(__spreadValues({}, sample), { time }));
}
} else {
this.player.stop(__spreadProps(__spreadValues({}, sample), { time }));
}
};
}
stop(sample) {
var _a;
if (__privateGet(this, _config2).disableScheduler) {
return this.player.stop(sample);
}
this.player.stop(sample);
if (!sample) {
__privateGet(this, _queue).clear();
return;
}
const time = (_a = sample == null ? void 0 : sample.time) != null ? _a : 0;
const stopId = sample == null ? void 0 : sample.stopId;
if (stopId) {
__privateGet(this, _queue).removeAll(
(item) => item.time >= time && item.stopId ? item.stopId === stopId : item.note === stopId
);
} else {
__privateGet(this, _queue).removeAll((item) => item.time >= time);
}
}
disconnect() {
this.player.disconnect();
}
};
_config2 = new WeakMap();
_queue = new WeakMap();
_intervalId = new WeakMap();
// src/player/sample-player.ts
var _config3, _disconnected2, _stop;
var SamplePlayer = class {
constructor(context, options) {
this.context = context;
this.options = options;
__privateAdd(this, _config3);
__privateAdd(this, _disconnected2, false);
__privateAdd(this, _stop);
var _a, _b;
__privateSet(this, _config3, {
velocityToGain: (_a = options.velocityToGain) != null ? _a : midiVelToGain,
destination: (_b = options.destination) != null ? _b : context.destination
});
this.buffers = {};
__privateSet(this, _stop, createTrigger());
}
start(sample) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
if (__privateGet(this, _disconnected2)) {
throw new Error("Can't start a sample on disconnected player");
}
const context = this.context;
const buffer = sample.name && this.buffers[sample.name] || this.buffers[sample.note];
if (!buffer) {
console.warn(`Sample not found: '${sample.note}'`);
return () => void 0;
}
const source = context.createBufferSource();
source.buffer = buffer;
const cents = (_b = (_a = sample.detune) != null ? _a : this.options.detune) != null ? _b : 0;
if (source.detune) {
source.detune.value = cents;
} else if (source.playbackRate) {
source.playbackRate.value = Math.pow(2, cents / 1200);
}
const lpfCutoffHz = (_c = sample.lpfCutoffHz) != null ? _c : this.options.lpfCutoffHz;
const lpf = lpfCutoffHz ? context.createBiquadFilter() : void 0;
if (lpfCutoffHz && lpf) {
lpf.type = "lowpass";
lpf.frequency.value = lpfCutoffHz;
}
const volume = context.createGain();
const velocity = (_e = (_d = sample.velocity) != null ? _d : this.options.velocity) != null ? _e : 100;
volume.gain.value = __privateGet(this, _config3).velocityToGain(velocity);
const loop = (_f = sample.loop) != null ? _f : this.options.loop;
if (loop) {
source.loop = true;
source.loopStart = (_g = sample.loopStart) != null ? _g : 0;
source.loopEnd = (_h = sample.loopEnd) != null ? _h : buffer.duration;
}
const decayTime = (_i = sample.decayTime) != null ? _i : this.options.decayTime;
const [decay, startDecay] = createDecayEnvelope(context, decayTime);
function stop(time) {
time != null ? time : time = context.currentTime;
if (time <= startAt) {
source.stop(time);
} else {
const stopAt = startDecay(time);
source.stop(stopAt);
}
}
const gainCompensation = sample.gainOffset ? context.createGain() : void 0;
if (gainCompensation && sample.gainOffset) {
gainCompensation.gain.value = sample.gainOffset;
}
const stopId = (_j = sample.stopId) != null ? _j : sample.note;
const cleanup = unsubscribeAll([
connectSerial([
source,
lpf,
volume,
decay,
gainCompensation,
__privateGet(this, _config3).destination
]),
(_k = sample.stop) == null ? void 0 : _k.call(sample, stop),
__privateGet(this, _stop).subscribe((event) => {
if (!event || event.stopId === void 0 || event.stopId === stopId) {
stop(event == null ? void 0 : event.time);
}
})
]);
source.onended = () => {
var _a2;
cleanup();
(_a2 = sample.onEnded) == null ? void 0 : _a2.call(sample, sample);
};
(_l = sample.onStart) == null ? void 0 : _l.call(sample, sample);
const startAt = Math.max((_m = sample.time) != null ? _m : 0, context.currentTime);
source.start(sample.time);
let duration = (_n = sample.duration) != null ? _n : buffer.duration;
if (typeof sample.duration == "number") {
stop(startAt + duration);
}
return stop;
}
stop(sample) {
__privateGet(this, _stop).trigger(sample);
}
disconnect() {
if (__privateGet(this, _disconnected2)) return;
__privateSet(this, _disconnected2, true);
this.stop();
Object.keys(this.buffers).forEach((key) => {
delete this.buffers[key];
});
}
get connected() {
return !__privateGet(this, _disconnected2);
}
};
_config3 = new WeakMap();
_disconnected2 = new WeakMap();
_stop = new WeakMap();
function createDecayEnvelope(context, envelopeTime = 0.2) {
let stopAt = 0;
const envelope = context.createGain();
envelope.gain.value = 1;
function start(time) {
if (stopAt) return stopAt;
envelope.gain.cancelScheduledValues(time);
const envelopeAt = time || context.currentTime;
stopAt = envelopeAt + envelopeTime;
envelope.gain.setValueAtTime(1, envelopeAt);
envelope.gain.linearRampToValueAtTime(0, stopAt);
return stopAt;
}
return [envelope, start];
}
// src/player/default-player.ts
var DefaultPlayer = class {
constructor(context, options) {
this.context = context;
const channel = new Channel(context, options);
this.player = new QueuedPlayer(
new SamplePlayer(context, __spreadProps(__spreadValues({}, options), { destination: channel.input })),
options
);
this.output = channel;
}
get buffers() {
return this.player.buffers;
}
start(sample) {
return this.player.start(sample);
}
stop(sample) {
this.player.stop(
typeof sample === "object" ? sample : sample !== void 0 ? { stopId: sample } : void 0
);
}
disconnect() {
this.output.disconnect();
this.player.disconnect();
}
};
// src/player/load-audio.ts
function loadAudioBuffer(context, url, storage) {
return __async(this, null, function* () {
url = url.replace(/#/g, "%23").replace(/([^:]\/)\/+/g, "$1");
const response = yield storage.fetch(url);
if (response.status !== 200) {
console.warn(
"Error loading buffer. Invalid status: ",
response.status,
url
);
return;
}
try {
const audioData = yield response.arrayBuffer();
const buffer = yield context.decodeAudioData(audioData);
return buffer;
} catch (error) {
console.warn("Error loading buffer", error, url);
}
});
}
function findFirstSupportedFormat(formats) {
if (typeof document === "undefined") return null;
const audio = document.createElement("audio");
for (let i = 0; i < formats.length; i++) {
const format = formats[i];
const canPlay = audio.canPlayType(`audio/${format}`);
if (canPlay === "probably" || canPlay === "maybe") {
return format;
}
if (format === "m4a") {
const canPlay2 = audio.canPlayType(`audio/aac`);
if (canPlay2 === "probably" || canPlay2 === "maybe") {
return format;
}
}
}
return null;
}
function getPreferredAudioExtension() {
var _a;
const format = (_a = findFirstSupportedFormat(["ogg", "m4a"])) != null ? _a : "ogg";
return "." + format;
}
// src/storage.ts
var HttpStorage = {
fetch(url) {
return fetch(url);
}
};
var _cache, _CacheStorage_instances, tryFromCache_fn, saveResponse_fn;
var CacheStorage = class {
constructor(name = "smplr") {
__privateAdd(this, _CacheStorage_instances);
__privateAdd(this, _cache);
if (typeof window === "undefined" || !("caches" in window)) {
__privateSet(this, _cache, Promise.reject("CacheStorage not supported"));
} else {
__privateSet(this, _cache, caches.open(name));
}
}
fetch(url) {
return __async(this, null, function* () {
const request = new Request(url);
try {
return yield __privateMethod(this, _CacheStorage_instances, tryFromCache_fn).call(this, request);
} catch (err) {
const response = yield fetch(request);
yield __privateMethod(this, _CacheStorage_instances, saveResponse_fn).call(this, request, response);
return response;
}
});
}
};
_cache = new WeakMap();
_CacheStorage_instances = new WeakSet();
tryFromCache_fn = function(request) {
return __async(this, null, function* () {
const cache = yield __privateGet(this, _cache);
const response = yield cache.match(request);
if (response) return response;
else throw Error("Not found");
});
};
saveResponse_fn = function(request, response) {
return __async(this, null, function* () {
try {
const cache = yield __privateGet(this, _cache);
yield cache.put(request, response.clone());
} catch (err) {
}
});
};
// src/drum-machine/dm-instrument.ts
function isDrumMachineInstrument(instrument) {
return typeof instrument === "object" && typeof instrument.baseUrl === "string" && typeof instrument.name === "string" && Array.isArray(instrument.samples) && Array.isArray(instrument.groupNames) && typeof instrument.nameToSampleName === "object" && typeof instrument.sampleGroupVariations === "object";
}
var EMPTY_INSTRUMENT = {
baseUrl: "",
name: "",
samples: [],
groupNames: [],
nameToSampleName: {},
sampleGroupVariations: {}
};
function fetchDrumMachineInstrument(url, storage) {
return __async(this, null, function* () {
var _a, _b, _c, _d;
const res = yield storage.fetch(url);
const json = yield res.json();
json.baseUrl = url.replace("/dm.json", "");
json.groupNames = [];
json.nameToSampleName = {};
json.sampleGroupVariations = {};
for (const sample of json.samples) {
json.nameToSampleName[sample] = sample;
const separator = sample.indexOf("/") !== -1 ? "/" : "-";
const [base, variation] = sample.split(separator);
if (!json.groupNames.includes(base)) {
json.groupNames.push(base);
}
(_b = (_a = json.nameToSampleName)[base]) != null ? _b : _a[base] = sample;
(_d = (_c = json.sampleGroupVariations)[base]) != null ? _d : _c[base] = [];
if (variation) {
json.sampleGroupVariations[base].push(`${base}${separator}${variation}`);
}
}
return json;
});
}
// src/drum-machine/drum-machine.ts
function getDrumMachineNames() {
return Object.keys(INSTRUMENTS);
}
var INSTRUMENTS = {
"TR-808": "https://smpldsnds.github.io/drum-machines/TR-808/dm.json",
"Casio-RZ1": "https://smpldsnds.github.io/drum-machines/Casio-RZ1/dm.json",
"LM-2": "https://smpldsnds.github.io/drum-machines/LM-2/dm.json",
"MFB-512": "https://smpldsnds.github.io/drum-machines/MFB-512/dm.json",
"Roland CR-8000": "https://smpldsnds.github.io/drum-machines/Roland-CR-8000/dm.json"
};
function getConfig2(options) {
var _a, _b, _c;
const config = {
instrument: (_a = options == null ? void 0 : options.instrument) != null ? _a : "TR-808",
storage: (_b = options == null ? void 0 : options.storage) != null ? _b : HttpStorage,
url: (_c = options == null ? void 0 : options.url) != null ? _c : ""
};
if (typeof config.instrument === "string") {
config.url || (config.url = INSTRUMENTS[config.instrument]);
if (!config.url)
throw new Error("Invalid instrument: " + config.instrument);
} else if (!isDrumMachineInstrument(config.instrument)) {
throw new Error("Invalid instrument: " + config.instrument);
}
return config;
}
var _instrument;
var DrumMachine = class {
constructor(context, options) {
__privateAdd(this, _instrument, EMPTY_INSTRUMENT);
const config = getConfig2(options);
const instrument = isDrumMachineInstrument(config.instrument) ? Promise.resolve(config.instrument) : fetchDrumMachineInstrument(config.url, config.storage);
this.player = new DefaultPlayer(context, options);
this.output = this.player.output;
this.load = drumMachineLoader(
context,
this.player.buffers,
instrument,
config.storage
).then(() => this);
instrument.then((instrument2) => {
__privateSet(this, _instrument, instrument2);
});
}
getSampleNames() {
return __privateGet(this, _instrument).samples.slice();
}
getGroupNames() {
return __privateGet(this, _instrument).groupNames.slice();
}
getSampleNamesForGroup(groupName) {
var _a;
return (_a = __privateGet(this, _instrument).sampleGroupVariations[groupName]) != null ? _a : [];
}
start(sample) {
var _a;
const sampleName = __privateGet(this, _instrument).nameToSampleName[sample.note];
return this.player.start(__spreadProps(__spreadValues({}, sample), {
note: sampleName ? sampleName : sample.note,
stopId: (_a = sample.stopId) != null ? _a : sample.note
}));
}
stop(sample) {
return this.player.stop(sample);
}
/** @deprecated */
loaded() {
return __async(this, null, function* () {
console.warn("deprecated: use load instead");
return this.load;
});
}
/** @deprecated */
get sampleNames() {
console.log("deprecated: Use getGroupNames instead");
return __privateGet(this, _instrument).groupNames.slice();
}
/** @deprecated */
getVariations(groupName) {
var _a;
console.warn("deprecated: use getSampleNamesForGroup");
return (_a = __privateGet(this, _instrument).sampleGroupVariations[groupName]) != null ? _a : [];
}
};
_instrument = new WeakMap();
function drumMachineLoader(context, buffers, instrument, storage) {
var _a;
const format = (_a = findFirstSupportedFormat(["ogg", "m4a"])) != null ? _a : "ogg";
return instrument.then(
(data) => Promise.all(
data.samples.map((sampleName) => __async(this, null, function* () {
const url = `${data.baseUrl}/${sampleName}.${format}`;
const buffer = yield loadAudioBuffer(context, url, storage);
if (buffer) buffers[sampleName] = buffer;
}))
)
);
}
// src/player/midi.ts
function noteNameToMidi(note) {
const REGEX = /^([a-gA-G]?)(#{1,}|b{1,}|)(-?\d+)$/;
const m = REGEX.exec(note);
if (!m) return;
const letter = m[1].toUpperCase();
if (!letter) return;
const acc = m[2];
const alt = acc[0] === "b" ? -acc.length : acc.length;
const oct = m[3] ? +m[3] : 4;
const step = (letter.charCodeAt(0) + 3) % 7;
return [0, 2, 4, 5, 7, 9, 11][step] + alt + 12 * (oct + 1);
}
function toMidi(note) {
return note === void 0 ? void 0 : typeof note === "number" ? note : noteNameToMidi(note);
}
// src/sfz/sfz-load.ts
function loadSfzBuffers(context, buffers, websfz, storage) {
return __async(this, null, function* () {
websfz.groups.forEach((group) => {
const urls = getWebsfzGroupUrls(websfz, group);
return loadAudioBuffers(context, buffers, urls, storage);
});
});
}
function SfzInstrumentLoader(instrument, storage) {
return __async(this, null, function* () {
var _a;
const isWebsfz = (inst) => "global" in inst;
const isSfzInstrument = (inst) => "websfzUrl" in inst;
if (typeof instrument === "string") {
return fetchWebSfz(instrument, storage);
} else if (isWebsfz(instrument)) {
return instrument;
} else if (isSfzInstrument(instrument)) {
const websfz = yield fetchWebSfz(instrument.websfzUrl, storage);
(_a = websfz.meta) != null ? _a : websfz.meta = {};
if (instrument.name) websfz.meta.name = instrument.name;
if (instrument.baseUrl) websfz.meta.baseUrl = instrument.baseUrl;
if (instrument.formats) websfz.meta.formats = instrument.formats;
return websfz;
} else {
throw new Error("Invalid instrument: " + JSON.stringify(instrument));
}
});
}
function loadAudioBuffers(context, buffers, urls, storage) {
return __async(this, null, function* () {
yield Promise.all(
Object.keys(urls).map((sampleId) => __async(this, null, function* () {
if (buffers[sampleId]) return;
const buffer = yield loadAudioBuffer(context, urls[sampleId], storage);
if (buffer) buffers[sampleId] = buffer;
return buffers;
}))
);
});
}
function fetchWebSfz(url, storage) {
return __async(this, null, function* () {
try {
const response = yield fetch(url);
const json = yield response.json();
return json;
} catch (error) {
console.warn(`Can't load SFZ file ${url}`, error);
throw new Error(`Can't load SFZ file ${url}`);
}
});
}
function getWebsfzGroupUrls(websfz, group) {
var _a, _b, _c, _d;
const urls = {};
const baseUrl = (_a = websfz.meta.baseUrl) != null ? _a : "";
const format = (_c = findFirstSupportedFormat((_b = websfz.meta.formats) != null ? _b : [])) != null ? _c : "ogg";
const prefix = (_d = websfz.global["default_path"]) != null ? _d : "";
if (!group) return urls;
return group.regions.reduce((urls2, region) => {
if (region.sample) {
urls2[region.sample] = `${baseUrl}/${prefix}${region.sample}.${format}`;
}
return urls2;
}, urls);
}
// src/sfz/sfz-regions.ts
function checkRange(value, low, hi) {
const isAboveLow = low === void 0 || value !== void 0 && value >= low;
const isBelowHi = hi === void 0 || value !== void 0 && value <= hi;
return isAboveLow && isBelowHi;
}
function findRegions(websfz, note) {
const regions = [];
for (const group of websfz.groups) {
if (checkRange(note.midi, group.lokey, group.hikey) && checkRange(note.velocity, group.lovel, group.hivel) && checkRange(note.cc64, group.locc64, group.hicc64)) {
for (const region of group.regions) {
if (checkRange(note.midi, region.lokey, region.hikey) && checkRange(note.velocity, region.lovel, region.hivel) && checkRange(note.cc64, group.locc64, group.hicc64)) {
regions.push([group, region]);
}
}
}
}
return regions;
}
// src/sfz/sfz-sampler.ts
var EMPTY_WEBSFZ = Object.freeze({
meta: {},
global: {},
groups: []
});
var _websfz, _SfzSampler_instances, startLayers_fn;
var SfzSampler = class {
constructor(context, options) {
this.context = context;
__privateAdd(this, _SfzSampler_instances);
__privateAdd(this, _websfz);
this.options = Object.freeze(
Object.assign(
{
volume: 100,
velocity: 100,
storage: HttpStorage,
detune: 0,
destination: context.destination
},
options
)
);
this.player = new DefaultPlayer(context, options);
__privateSet(this, _websfz, EMPTY_WEBSFZ);
this.load = SfzInstrumentLoader(options.instrument, this.options.storage).then((result) => {
__privateSet(this, _websfz, Object.freeze(result));
return loadSfzBuffers(
context,
this.player.buffers,
__privateGet(this, _websfz),
this.options.storage
);
}).then(() => this);
}
get output() {
return this.player.output;
}
loaded() {
return __async(this, null, function* () {
console.warn("deprecated: use load instead");
return this.load;
});
}
start(sample) {
__privateMethod(this, _SfzSampler_instances, startLayers_fn).call(this, typeof sample === "object" ? sample : { note: sample });
}
stop(sample) {
this.player.stop(sample);
}
disconnect() {
this.player.disconnect();
}
};
_websfz = new WeakMap();
_SfzSampler_instances = new WeakSet();
startLayers_fn = function(sample) {
var _a;
const midi = toMidi(sample.note);
if (midi === void 0) return () => void 0;
const velocity = (_a = sample.velocity) != null ? _a : this.options.velocity;
const regions = findRegions(__privateGet(this, _websfz), { midi, velocity });
const onEnded = () => {
var _a2;
(_a2 = sample.onEnded) == null ? void 0 : _a2.call(sample, sample);
};
const stopAll = regions.map(([group, region]) => {
var _a2, _b, _c;
let target = (_b = (_a2 = region.pitch_keycenter) != null ? _a2 : region.key) != null ? _b : midi;
const detune = (midi - target) * 100;
return this.player.start(__spreadProps(__spreadValues({}, sample), {
note: region.sample,
decayTime: sample.decayTime,
detune: detune + ((_c = sample.detune) != null ? _c : this.options.detune),
onEnded,
stopId: midi
}));
});
switch (stopAll.length) {
case 0:
return () => void 0;
case 1:
return stopAll[0];
default:
return (time) => stopAll.forEach((stop) => stop(time));
}
};
// src/tremolo.ts
function createTremolo(context, depth) {
const input = context.createGain();
const output = context.createGain();
input.channelCount = 2;
input.channelCountMode = "explicit";
const splitter = context.createChannelSplitter(2);
const ampL = context.createGain();
const ampR = context.createGain();
const merger = context.createChannelMerger(2);
const lfoL = context.createOscillator();
lfoL.type = "sine";
lfoL.frequency.value = 1;
lfoL.start();
const lfoLAmp = context.createGain();
const lfoR = context.createOscillator();
lfoR.type = "sine";
lfoR.frequency.value = 1.1;
lfoR.start();
const lfoRAmp = context.createGain();
input.connect(splitter);
splitter.connect(ampL, 0);
splitter.connect(ampR, 1);
ampL.connect(merger, 0, 0);
ampR.connect(merger, 0, 1);
lfoL.connect(lfoLAmp);
lfoLAmp.connect(ampL.gain);
lfoR.connect(lfoRAmp);
lfoRAmp.connect(ampR.gain);
merger.connect(output);
const unsubscribe = depth((depth2) => {
lfoLAmp.gain.value = depth2;
lfoRAmp.gain.value = depth2;
});
input.disconnect = () => {
unsubscribe();
lfoL.stop();
lfoR.stop();
input.disconnect(splitter);
splitter.disconnect(ampL, 0);
splitter.disconnect(ampR, 1);
ampL.disconnect(merger, 0, 0);
ampR.disconnect(merger, 0, 1);
lfoL.disconnect(ampL);
lfoR.disconnect(ampR);
merger.disconnect(output);
};
return { input, output };
}
// src/electric-piano.ts
function getElectricPianoNames() {
return Object.keys(INSTRUMENTS2);
}
var INSTRUMENTS2 = {
CP80: "https://danigb.github.io/samples/gs-e-pianos/CP80/cp80.websfz.json",
PianetT: "https://danigb.github.io/samples/gs-e-pianos/Pianet T/pianet-t.websfz.json",
WurlitzerEP200: "https://danigb.github.io/samples/gs-e-pianos/Wurlitzer EP200/wurlitzer-ep200.websfz.json",
TX81Z: "https://danigb.github.io/samples/vcsl/TX81Z/tx81z-fm-piano.websfz.json"
};
var ElectricPiano = class extends SfzSampler {
constructor(context, options) {
var _a;
super(context, __spreadProps(__spreadValues({}, options), {
instrument: (_a = INSTRUMENTS2[options.instrument]) != null ? _a : options.instrument
}));
const depth = createControl(0);
this.tremolo = {
level: (level) => depth.set(midiVelToGain(level))
};
const tremolo = createTremolo(context, depth.subscribe);
this.output.addInsert(tremolo);
}
};
// src/player/layers.ts
function createEmptySamplerInstrument(options = {}) {
return { groups: [], options };
}
function createEmptyRegionGroup() {
return { regions: [] };
}
function findSamplesInRegions(group, sample, seqNumber, options) {
const results = [];
const midi = toMidi(sample.note);
if (midi === void 0) return results;
for (const region of group.regions) {
const found = findSampleInRegion(
midi,
seqNumber != null ? seqNumber : 0,
sample,
region,
options
);
if (found) results.push(found);
}
return results;
}
function findFirstSampleInRegions(group, sample, seqNumber, options) {
const midi = toMidi(sample.note);
if (midi === void 0) return void 0;
for (const region of group.regions) {
const found = findSampleInRegion(
midi,
seqNumber != null ? seqNumber : 0,
sample,
region,
options
);
if (found) return found;
}
return void 0;
}
function findSampleInRegion(midi, seqNum, sample, region, defaults) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B;
const matchMidi = midi >= ((_a = region.midiLow) != null ? _a : 0) && midi < ((_b = region.midiHigh) != null ? _b : 127) + 1;
if (!matchMidi) return void 0;
const matchVelocity = sample.velocity === void 0 || sample.velocity >= ((_c = region.velLow) != null ? _c : 0) && sample.velocity <= ((_d = region.velHigh) != null ? _d : 127);
if (!matchVelocity) return void 0;
if (region.seqLength) {
const currentSeq = seqNum % region.seqLength;
const regionIndex = ((_e = region.seqPosition) != null ? _e : 1) - 1;
if (currentSeq !== regionIndex) return void 0;
}
const semitones = midi - region.midiPitch;
const velocity = (_f = sample.velocity) != null ? _f : defaults == null ? void 0 : defaults.velocity;
const regionGainOffset = region.volume ? dbToGain(region.volume) : 0;
const sampleGainOffset = (_h = (_g = sample.gainOffset) != null ? _g : defaults == null ? void 0 : defaults.gainOffset) != null ? _h : 0;
const sampleDetune = (_i = sample.detune) != null ? _i : 0;
return {
decayTime: (_l = (_k = sample == null ? void 0 : sample.decayTime) != null ? _k : (_j = region.sample) == null ? void 0 : _j.decayTime) != null ? _l : defaults == null ? void 0 : defaults.decayTime,
detune: 100 * (semitones + ((_m = region.tune) != null ? _m : 0)) + sampleDetune,
duration: (_p = (_o = sample == null ? void 0 : sample.duration) != null ? _o : (_n = region.sample) == null ? void 0 : _n.duration) != null ? _p : defaults == null ? void 0 : defaults.duration,
gainOffset: sampleGainOffset + regionGainOffset || void 0,
loop: (_s = (_r = sample == null ? void 0 : sample.loop) != null ? _r : (_q = region.sample) == null ? void 0 : _q.loop) != null ? _s : defaults == null ? void 0 : defaults.loop,
loopEnd: (_v = (_u = sample == null ? void 0 : sample.loopEnd) != null ? _u : (_t = region.sample) == null ? void 0 : _t.loopEnd) != null ? _v : defaults == null ? void 0 : defaults.loopEnd,
loopStart: (_y = (_x = sample == null ? void 0 : sample.loopStart) != null ? _x : (_w = region.sample) == null ? void 0 : _w.loopStart) != null ? _y : defaults == null ? void 0 : defaults.loopStart,
lpfCutoffHz: (_B = (_A = sample == null ? void 0 : sample.lpfCutoffHz) != null ? _A : (_z = region.sample) == null ? void 0 : _z.lpfCutoffHz) != null ? _B : defaults == null ? void 0 : defaults.lpfCutoffHz,
name: region.sampleName,
note: midi,
onEnded: sample.onEnded,
onStart: sample.onStart,
stopId: sample.name,
time: sample.time,
velocity: velocity == void 0 ? void 0 : velocity
};
}
function spreadRegions(regions) {
if (regions.length === 0) return [];
regions.sort((a, b) => a.midiPitch - b.midiPitch);
regions[0].midiLow = 0;
regions[0].midiHigh = 127;
if (regions.length === 1) return regions;
for (let i = 1; i < regions.length; i++) {
const prev = regions[i - 1];
const curr = regions[i];
const mid = Math.floor((prev.midiPitch + curr.midiPitch) / 2);
prev.midiHigh = mid;
curr.midiLow = mid + 1;
}
regions[regions.length - 1].midiHigh = 127;
return regions;
}
// src/player/region-player.ts
var RegionPlayer = class {
constructor(context, options) {
this.context = context;
this.seqNum = 0;
const channel = new Channel(context, options);
this.instrument = createEmptySamplerInstrument(options);
this.player = new QueuedPlayer(
new SamplePlayer(context, __spreadProps(__spreadValues({}, options), { destination: channel.input })),
options
);
this.output = channel;
}
get buffers() {
return this.player.buffers;
}
start(sample) {
const stopAll = [];
const sampleStart = typeof sample === "object" ? sample : { note: sample };
for (const group of this.instrument.groups) {
const found = findSamplesInRegions(group, sampleStart, this.seqNum);
this.seqNum++;
for (const sample2 of found) {
let stop = this.player.start(sample2);
stopAll.push(stop);
}
}
return (time) => stopAll.forEach((stop) => stop(time));
}
stop(sample) {
if (sample == void 0) {
this.player.stop();
return;
}
const toStop = typeof sample === "object" ? sample : { stopId: sample };
const midi = toMidi(toStop.stopId);
if (!midi) return;
toStop.stopId = midi;
this.player.stop(toStop);
}
disconnect() {
this.output.disconnect();
this.player.disconnect();
}
};
// src/sfz2.ts
function SfzInstrumentLoader2(url, config) {
const audioExt = getPreferredAudioExtension();
return (context, storage) => __async(this, null, function* () {
const sfz = yield fetch(url).then((res) => res.text());
const errors = sfzToLayer(sfz, config.group);
if (errors.length) {
console.warn("Problems converting sfz", errors);
}
const sampleNames = /* @__PURE__ */ new Set();
config.group.regions.forEach((r) => sampleNames.add(r.sampleName));
return Promise.all(
Array.from(sampleNames).map((sampleName) => __async(this, null, function* () {
const sampleUrl = config.urlFromSampleName(sampleName, audioExt);
const buffer = yield loadAudioBuffer(context, sampleUrl, storage);
config.buffers[sampleName] = buffer;
}))
).then(() => config.group);
});
}
function sfzToLayer(sfz, group) {
let mode = "global";
const tokens = sfz.split("\n").map(parseToken).filter((x) => !!x);
const scope = new Scope();
let errors = [];
for (const token of tokens) {
switch (token.type) {
case "mode":
errors.push(scope.closeScope(mode, group));
mode = token.value;
break;
case "prop:num":
case "prop:str":
case "prop:num_arr":
scope.push(token.key, token.value);
break;
case "unknown":
console.warn("Unknown SFZ token", token.value);
break;
}
}
closeScope(mode, scope, group);
return errors.filter((x) => !!x);
function closeScope(mode2, scope2, group2) {
}
}
var MODE_REGEX = /^<([^>]+)>$/;
var PROP_NUM_REGEX = /^([^=]+)=([-\.\d]+)$/;
var PROP_STR_REGEX = /^([^=]+)=(.+)$/;
var PROP_NUM_ARR_REGEX = /^([^=]+)_(\d+)=(\d+)$/;
function parseToken(line) {
line = line.trim();
if (line === "") return void 0;
if (line.startsWith("//")) return void 0;
const modeMatch = line.match(MODE_REGEX);
if (modeMatch) return { type: "mode", value: modeMatch[1] };
const propNumArrMatch = line.match(PROP_NUM_ARR_REGEX);
if (propNumArrMatch)
return {
type: "prop:num_arr",
key: propNumArrMatch[1],
value: [Number(propNumArrMatch[2]), Number(propNumArrMatch[3])]
};
const propNumMatch = line.match(PROP_NUM_REGEX);
if (propNumMatch)
return {
type: "prop:num",
key: propNumMatch[1],
value: Number(propNumMatch[2])
};
const propStrMatch = line.match(PROP_STR_REGEX);
if (propStrMatch)
return {
type: "prop:str",
key: propStrMatch[1],
value: propStrMatch[2]
};
return { type: "unknown", value: line };
}
var _Scope_instances, closeRegion_fn;
var Scope = class {
constructor() {
__privateAdd(this, _Scope_instances);
this.values = {};
this.global = {};
this.group = {};
}
closeScope(mode, group) {
if (mode === "global") {
__privateMethod(this, _Scope_instances, closeRegion_fn).call(this, this.global);
} else if (mode === "group") {
this.group = __privateMethod(this, _Scope_instances, closeRegion_fn).call(this, {});
} else if (mode === "region") {
const region = __privateMethod(this, _Scope_instances, closeRegion_fn).call(this, __spreadValues(__spreadValues({
sampleName: "",
midiPitch: -1
}, this.global), this.group));
if (region.sampleName === "") {
return "Missing sample name";
}
if (region.midiPitch === -1) {
if (region.midiLow !== void 0) {
region.midiPitch = region.midiLow;
} else {
return "Missing pitch_keycenter";
}
}
if (region.seqLength && region.seqPosition === void 0) {
region.seqPosition = 1;
}
if (region.ampRelease) {
region.sample = { decayTime: region.ampRelease };
delete region.ampRelease;
}
group.regions.push(region);
}
}
get empty() {
return Object.keys(this.values).length === 0;
}
get keys() {
return Object.keys(this.values);
}
push(key, value) {
this.values[key] = value;
}
popNum(key, dest, destKey) {
if (typeof this.values[key] !== "number") return false;
dest[destKey] = this.values[key];
delete this.values[key];
return true;
}
popStr(key, dest, destKey) {
if (typeof this.values[key] !== "string") return false;
dest[destKey] = this.values[key];
delete this.values[key];
return true;
}
popNumArr(key, dest, destKey) {
if (!Array.isArray(this.values[key])) return false;
dest[destKey] = this.values[key];
delete this.values[key];
return true;
}
};
_Scope_instances = new WeakSet();
closeRegion_fn = function(region) {
this.popStr("sample", region, "sampleName");
this.popNum("pitch_keycenter", region, "midiPitch");
this.popNum("ampeg_attack", region, "ampAttack");
this.popNum("ampeg_release", region, "ampRelease");
this.popNum("bend_down", region, "bendDown");
this.popNum("bend_up", region, "bendUp");
this.popNum("group", region, "group");
this.popNum("hikey", region, "midiHigh");
this.popNum("hivel", region, "velHigh");
this.popNum("lokey", region, "midiLow");
this.popNum("offset", region, "offset");
this.popNum("lovel", region, "velLow");
this.popNum("off_by", region, "groupOffBy");
this.popNum("pitch_keytrack", region, "ignore");
this.popNum("seq_length", region, "seqLength");
this.popNum("seq_position", region, "seqPosition");
this.popNum("tune", region, "tune");
this.popNum("volume", region, "volume");
this.popNumArr("amp_velcurve", region, "ampVelCurve");
this.values = {};
return region;
};
// src/versilian.ts
var instruments = [];
var BASE_URL = "https://smpldsnds.github.io/sgossner-vcsl/";
function getVersilianInstruments() {
return __async(this, null, function* () {
if (instruments.length) return instruments;
instruments = yield fetch(BASE_URL + "sfz_files.json").then(
(res) => res.json()
);
return instruments;
});
}
function getVcslInstrumentSfzUrl(instrument) {
return BASE_URL + instrument + ".sfz";
}
function VcslInstrumentLoader(instrument, buffers) {
const url = getVcslInstrumentSfzUrl(instrument);
const base = instrument.slice(0, instrument.lastIndexOf("/") + 1);
const sampleBase = `https://smpldsnds.github.io/sgossner-vcsl/${base}`;
const group = createEmptyRegionGroup();
return SfzInstrumentLoader2(url, {
buffers,
group,
urlFromSampleName: (sampleName, audioExt) => {
return sampleBase + "/" + sampleName.replace(".wav", audioExt);
}
});
}
var Versilian = class {
constructor(context, options = {}) {
var _a, _b;
this.config = {
instrument: (_a = options.instrument) != null ? _a : "Arco",
storage: (_b = options.storage) != null ? _b : HttpStorage
};
this.player = new RegionPlayer(context, options);
const loader = VcslInstrumentLoader(
this.config.instrument,
this.player.buffers
);
this.load = loader(context, this.config.storage).then((group) => {
this.player.instrument.groups.push(group);
return this;
});
}
get output() {
return this.player.output;
}
get buffers() {
return this.player.buffers;
}
get context() {
return this.player.context;
}
start(sample) {
return this.player.start(sample);
}
stop(sample) {
return this.player.stop(sample);
}
disconnect() {
this.player.disconnect();
}
};
// src/mallet.ts
function getMalletNames() {
return Object.keys(NAME_TO_PATH);
}
var Mallet = class extends Versilian {
constructor(context, options) {
var _a;
super(context, __spreadProps(__spreadValues({}, options), {
instrument: NAME_TO_PATH[(_a = options.instrument) != null ? _a : ""]
}));
}
};
var NAME_TO_PATH = {
"Balafon - Hard Mallet": "Idiophones/Struck Idiophones/Balafon - Hard Mallet",
"Balafon - Keyswitch": "Idiophones/Struck Idiophones/Balafon - Keyswitch",
"Balafon - Soft Mallet": "Idiophones/Struck Idiophones/Balafon - Soft Mallet",
"Balafon - Traditional Mallet": "Idiophones/Struck Idiophones/Balafon - Traditional Mallet",
"Tubular Bells 1": "Idiophones/Struck Idiophones/Tubular Bells 1",
"Tubular Bells 2": "Idiophones/Struck Idiophones/Tubular Bells 2",
"Vibraphone - Bowed": "Idiophones/Struck Idiophones/Vibraphone - Bowed",
"Vibraphone - Hard Mallets": "Idiophones/Struck Idiophones/Vibraphone - Hard Mallets",
"Vibraphone - Keyswitch": "Idiophones/Struck Idiophones/Vibraphone - Keyswitch",
"Vibraphone - Soft Mallets": "Idiophones/Struck Idiophones/Vibraphone - Soft Mallets",
"Xylophone - Hard Mallets": "Idiophones/Struck Idiophones/Xylophone - Hard Mallets",
"Xylophone - Keyswitch": "Idiophones/Struck Idiophones/Xylophone - Keyswitch",
"Xylophone - Medium Mallets": "Idiophones/Struck Idiophones/Xylophone - Medium Mallets",
"Xylophone - Soft Mallets": "Idiophones/Struck Idiophones/Xylophone - Soft Mallets"
};
// src/mellotron.ts
var INSTRUMENT_VARIATIONS = {
"300 STRINGS CELLO": ["300 STRINGS", "CELL"],
"300 STRINGS VIOLA": ["300 STRINGS", "VIOL"]
};
function getMellotronNames() {
return [
"300 STRINGS CELLO",
"300 STRINGS VIOLA",
"8VOICE CHOIR",
"BASSA+STRNGS",
"BOYS CHOIR",
"CHA CHA FLT",
"CHM CLARINET",
"CHMB 3 VLNS",
"CHMB ALTOSAX",
"CHMB FEMALE",
"CHMB MALE VC",
"CHMB TNR SAX",
"CHMB TRMBONE",
"CHMB TRUMPET",
"CHMBLN CELLO",
"CHMBLN FLUTE",
"CHMBLN OBOE",
"DIXIE+TRMBN",
"FOXTROT+SAX",
"HALFSP.BRASS",
"MIXED STRGS",
"MKII BRASS",
"MKII GUITAR",
"MKII ORGAN",
"MKII SAX",
"MKII VIBES",
"MKII VIOLINS",
"MOVE BS+STGS",
"STRGS+BRASS",
"TROMB+TRMPT",
"TRON 16VLNS",
"TRON CELLO",
"TRON FLUTE",
"TRON VIOLA"
];
}
var Mellotron = class {
constructor(context, options) {
this.context = context;
this.options = options;
this.config = getMellotronConfig(options);
this.player = new DefaultPlayer(contex