scribbletune
Version:
Create music with JavaScript and Node.js!
1,523 lines (1,510 loc) • 63.5 kB
JavaScript
"use strict";
var scribble = (() => {
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
// src/browser-index.ts
var browser_index_exports = {};
__export(browser_index_exports, {
Session: () => Session,
arp: () => arp,
chord: () => chord,
chords: () => chords,
clip: () => clip,
getChordDegrees: () => getChordDegrees,
getChordsByProgression: () => getChordsByProgression,
midi: () => midi,
mode: () => scale,
modes: () => scales,
progression: () => progression,
scale: () => scale,
scales: () => scales
});
// node_modules/harmonics/dist/index.mjs
var scaleMaps_default = {
"major pentatonic": "101010010100",
"ionian pentatonic": "100011010001",
"mixolydian pentatonic": "100011010010",
ritusen: "101001010100",
egyptian: "101001010010",
"neopolitan major pentatonic": "100011100010",
"vietnamese 1": "100101011000",
pelog: "110100011000",
kumoijoshi: "110001011000",
hirajoshi: "101100011000",
iwato: "110001100010",
"in-sen": "110001010010",
"lydian pentatonic": "100010110001",
"malkos raga": "100101001010",
"locrian pentatonic": "100101100010",
"minor pentatonic": "100101010010",
"minor six pentatonic": "100101010100",
"flat three pentatonic": "101100010100",
"flat six pentatonic": "101010011000",
scriabin: "110010010100",
"whole tone pentatonic": "100010101010",
"lydian #5P pentatonic": "100010101001",
"lydian dominant pentatonic": "100010110010",
"minor #7M pentatonic": "100101010001",
"super locrian pentatonic": "100110100010",
"minor hexatonic": "101101010001",
augmented: "100110011001",
"major blues": "101110010100",
piongio: "101001010110",
"prometheus neopolitan": "110010100110",
prometheus: "101010100110",
"mystery #1": "110010101010",
"six tone symmetric": "110011001100",
"whole tone": "101010101010",
"messiaen's mode #5": "110001110001",
"minor blues": "100101110010",
"locrian major": "101011101010",
"double harmonic lydian": "110010111001",
"harmonic minor": "101101011001",
altered: "110110101010",
"locrian #2": "101101101010",
"mixolydian b6": "101011011010",
"lydian dominant": "101010110110",
lydian: "101010110101",
"lydian augmented": "101010101101",
"dorian b2": "110101010110",
"melodic minor": "101101010101",
locrian: "110101101010",
ultralocrian: "110110101100",
"locrian 6": "110101100110",
"augmented heptatonic": "100111011001",
"romanian minor": "101100110110",
"dorian #4": "101100110110",
"lydian diminished": "101100110101",
phrygian: "110101011010",
"leading whole tone": "101010101011",
"lydian minor": "101010111010",
"phrygian dominant": "110011011010",
balinese: "110101011001",
"neopolitan major": "110101010101",
aeolian: "101101011010",
"harmonic major": "101011011001",
"double harmonic major": "110011011001",
dorian: "101101010110",
"hungarian minor": "101100111001",
"hungarian major": "100110110110",
oriental: "110011100110",
flamenco: "110110110010",
"todi raga": "110100111001",
mixolydian: "101011010110",
persian: "110011101001",
major: "101011010101",
enigmatic: "110010101011",
"major augmented": "101011001101",
"lydian #9": "100110110101",
"messiaen's mode #4": "111001111001",
"purvi raga": "110011111001",
"spanish heptatonic": "110111011010",
bebop: "101011010111",
"bebop minor": "101111010110",
"bebop major": "101011011101",
"bebop locrian": "110101111010",
"minor bebop": "101101011011",
diminished: "101101101101",
ichikosucho: "101011110101",
"minor six diminished": "101101011101",
"half-whole diminished": "110110110110",
"kafi raga": "100111010111",
"messiaen's mode #6": "101011101011",
"composite blues": "101111110110",
"messiaen's mode #3": "101110111011",
"messiaen's mode #7": "111101111101",
chromatic: "111111111111",
ionian: "101011010101",
minor: "101101011010",
Kanakangi: "111001011100",
Ratnangi: "111001011010",
Ganamurti: "111001011001",
Vanaspati: "111001010110",
Manavati: "111001010101",
Tanarupi: "111001010011",
Senavati: "110101011100",
Hanumatodi: "110101011010",
Dhenuka: "110101011001",
Natakapriya: "110101010110",
Kokilapriya: "110101010101",
Rupavati: "110101010011",
Gayakapriya: "110011011100",
Vakulabharanam: "110011011010",
Mayamalavagowla: "110011011001",
Chakravakam: "110011010110",
Suryakantam: "110011010101",
Hatakambari: "110011010011",
Jhankaradhwani: "101101011100",
Natabhairavi: "101101011010",
Keeravani: "101101011001",
Kharaharapriya: "101101010110",
Gourimanohari: "101101010101",
Varunapriya: "101101010011",
Mararanjani: "101011011100",
Charukesi: "101011011010",
Sarasangi: "101011011001",
Harikambhoji: "101011010110",
Dheerasankarabaranam: "101011010101",
Naganandini: "101011010011",
Yagapriya: "100111011100",
Ragavardhini: "100111011010",
Gangeyabhushani: "100111011001",
Vagadheeswari: "100111010110",
Shulini: "100111010101",
Chalanata: "100111010011",
Salagam: "111000111100",
Jalarnavam: "111000111010",
Jhalavarali: "111000111001",
Navaneetam: "111000110110",
Pavani: "111000110101",
Raghupriya: "111000110011",
Gavambhodi: "110100111100",
Bhavapriya: "110100111010",
Shubhapantuvarali: "110100111001",
Shadvidamargini: "110100110110",
Suvarnangi: "110100110101",
Divyamani: "110100110011",
Dhavalambari: "110010111100",
Namanarayani: "110010111010",
Kamavardhini: "110010111001",
Ramapriya: "110010110110",
Gamanashrama: "110010110101",
Vishwambari: "110010110011",
Shamalangi: "101100111100",
Shanmukhapriya: "101100111010",
Simhendramadhyamam: "101100111001",
Hemavati: "101100110110",
Dharmavati: "101100110101",
Neetimati: "101100110011",
Kantamani: "101010111100",
Rishabhapriya: "101010111010",
Latangi: "101010111001",
Vachaspati: "101010110110",
Mechakalyani: "101010110101",
Chitrambari: "101010110011",
Sucharitra: "100110111100",
"Jyoti swarupini": "100110111010",
Dhatuvardani: "100110111001",
Nasikabhushini: "100110110110",
Kosalam: "100110110101",
Rasikapriya: "100110110011"
};
var chordMaps_default = {
"5th": "100000010000",
"M7#5sus4": "100001001001",
"7#5sus4": "100001001010",
sus4: "100001010000",
M7sus4: "100001010001",
"7sus4": "100001010010",
"7no5": "100010000010",
aug: "100010001000",
M7b6: "100010001001",
"maj7#5": "100010001001",
"7#5": "100010001010",
"7b13": "100010001010",
M: "100010010000",
maj7: "100010010001",
"7th": "100010010010",
"6th": "100010010100",
"7add6": "100010010110",
"7b6": "100010011010",
Mb5: "100010100000",
M7b5: "100010100001",
"7b5": "100010100010",
"maj#4": "100010110001",
"7#11": "100010110010",
"M6#11": "100010110100",
"7#11b13": "100010111010",
"m#5": "100100001000",
mb6M7: "100100001001",
"m7#5": "100100001010",
m: "100100010000",
"m/ma7": "100100010001",
m7: "100100010010",
m6: "100100010100",
mMaj7b6: "100100011001",
dim: "100100100000",
oM7: "100100100001",
m7b5: "100100100010",
dim7: "100100100100",
o7M7: "100100100101",
"4th": "100101000010",
madd4: "100101010000",
m7add11: "100101010010",
"+add#9": "100110001000",
"7#5#9": "100110001010",
"7#9": "100110010010",
"13#9": "100110010110",
"7#9b13": "100110011010",
"maj7#9#11": "100110110001",
"7#9#11": "100110110010",
"13#9#11": "100110110110",
"7#9#11b13": "100110111010",
sus2: "101000010000",
"M9#5sus4": "101001001001",
sus24: "101001010000",
M9sus4: "101001010001",
"11th": "101001010010",
"9sus4": "101001010010",
"13sus4": "101001010110",
"9no5": "101010000010",
"13no5": "101010000110",
"M#5add9": "101010001000",
"maj9#5": "101010001001",
"9#5": "101010001010",
"9b13": "101010001010",
Madd9: "101010010000",
maj9: "101010010001",
"9th": "101010010010",
"6/9": "101010010100",
maj13: "101010010101",
M7add13: "101010010101",
"13th": "101010010110",
M9b5: "101010100001",
"9b5": "101010100010",
"13b5": "101010100110",
"9#5#11": "101010101010",
"maj9#11": "101010110001",
"9#11": "101010110010",
"69#11": "101010110100",
"M13#11": "101010110101",
"13#11": "101010110110",
"9#11b13": "101010111010",
"m9#5": "101100001010",
madd9: "101100010000",
mM9: "101100010001",
m9: "101100010010",
m69: "101100010100",
m13: "101100010110",
mMaj9b6: "101100011001",
m9b5: "101100100010",
m11A: "101101001010",
m11: "101101010010",
b9sus: "110001010010",
"11b9": "110001010010",
"7sus4b9b13": "110001011010",
alt7: "110010000010",
"7#5b9": "110010001010",
Maddb9: "110010010000",
M7b9: "110010010001",
"7b9": "110010010010",
"13b9": "110010010110",
"7b9b13": "110010011010",
"7#5b9#11": "110010101010",
"7b9#11": "110010110010",
"13b9#11": "110010110110",
"7b9b13#11": "110010111010",
mb6b9: "110100001000",
"7b9#9": "110110010010"
};
var DEFAULT_OCTAVE = 4;
var sharpToFlatMap = {
"C#": "Db",
"D#": "Eb",
"F#": "Gb",
"G#": "Ab",
"A#": "Bb",
"CB": "B",
"FB": "E",
"E#": "F",
"B#": "C"
};
function sharpToFlat(root) {
return sharpToFlatMap[root.toUpperCase()] || root.charAt(0).toUpperCase() + root.slice(1);
}
var CHROMATIC = [
"C",
"Db",
"D",
"Eb",
"E",
"F",
"Gb",
"G",
"Ab",
"A",
"Bb",
"B"
];
function getChromatic(root, octave) {
const index = CHROMATIC.indexOf(root);
if (index === -1) {
throw new Error(`${root} is not a valid root note`);
}
const o1 = CHROMATIC.map((n) => n + octave);
const o2 = CHROMATIC.map((n) => n + (octave + 1));
const c = o1.concat(o2);
return c.slice(index);
}
var scaleMap = scaleMaps_default;
var chordMap = chordMaps_default;
function _getNotesForScaleOrChord({ scale: scale2, chord: chord2 }) {
const input = scale2 || chord2;
const SCALE_OR_CHORD = scale2 ? "scale" : "chord";
if (typeof input !== "string") {
throw new Error(`${input} is not a valid input for ${SCALE_OR_CHORD}`);
}
const rootOctaveScale = input.trim();
const indexOfFirstSpace = rootOctaveScale.indexOf(" ");
let scaleOrChord;
let rootOctave;
if (indexOfFirstSpace === -1) {
scaleOrChord = rootOctaveScale.slice(1);
rootOctave = rootOctaveScale[0];
if (rootOctaveScale[1] === "b" || rootOctaveScale[1] === "#") {
scaleOrChord = rootOctaveScale.slice(2);
rootOctave += rootOctaveScale[1];
}
} else {
scaleOrChord = rootOctaveScale.slice(indexOfFirstSpace === -1 ? 1 : indexOfFirstSpace + 1);
rootOctave = rootOctaveScale.slice(0, indexOfFirstSpace);
}
const root = sharpToFlat(rootOctave.replace(/\d/g, ""));
const octaveNumber = rootOctave.replace(/\D/g, "");
const octave = octaveNumber !== "" ? +rootOctave.replace(/\D/g, "") : DEFAULT_OCTAVE;
if (isNaN(octave)) {
throw new Error(`${rootOctave[0]} does not have a valid octave`);
}
if (!scaleMap[scaleOrChord] && !chordMap[scaleOrChord]) {
throw new Error(`${rootOctaveScale} is not a valid ${SCALE_OR_CHORD}`);
}
const chroma = getChromatic(root, octave);
const acc = [];
let p1 = 0, p2 = 0;
const map = scale2 ? scaleMap : chordMap;
while (p1 < map[scaleOrChord].length) {
if (map[scaleOrChord][p1] === "1") {
acc.push(chroma[p2]);
}
p1++;
p2++;
}
return acc;
}
function inlineChord(rootChord_Oct) {
const B9SUS = "b9sus";
let root, chord2, octave = DEFAULT_OCTAVE;
if (rootChord_Oct.includes(B9SUS)) {
chord2 = B9SUS;
root = rootChord_Oct.slice(0, rootChord_Oct.indexOf(B9SUS));
} else {
root = rootChord_Oct[0];
chord2 = rootChord_Oct.slice(1);
if (rootChord_Oct[1] === "b" || rootChord_Oct[1] === "#") {
root += rootChord_Oct[1];
chord2 = rootChord_Oct.slice(2);
}
}
if (rootChord_Oct.includes("_")) {
octave = +rootChord_Oct.split("_")[1];
chord2 = chord2.slice(0, chord2.indexOf("_"));
}
return _getNotesForScaleOrChord({ chord: root + octave + " " + chord2 });
}
function chords() {
return Object.keys(chordMap);
}
function scales() {
return Object.keys(scaleMap);
}
function chord(chord2) {
return _getNotesForScaleOrChord({ chord: chord2 });
}
function scale(scale2) {
return _getNotesForScaleOrChord({ scale: scale2 });
}
// src/utils.ts
var isNote = (str) => /^[a-gA-G](?:#|b)?\d$/.test(str);
var expandStr = (str) => {
str = JSON.stringify(str.split(""));
str = str.replace(/,"\[",/g, ", [");
str = str.replace(/"\[",/g, "[");
str = str.replace(/,"\]"/g, "]");
return JSON.parse(str);
};
var shuffle = (arr, fullShuffle = true) => {
const lastIndex = arr.length - 1;
arr.forEach((el, idx) => {
if (idx >= lastIndex) {
return;
}
const rnd = fullShuffle ? (
// Pick random number from idx+1 to lastIndex (Modified algorithm, (N-1)! combinations)
// Math.random -> [0, 1) -> [0, lastIndex-idx ) --floor-> [0, lastIndex-idx-1]
// rnd = [0, lastIndex-idx-1] + 1 + idx = [1 + idx, lastIndex]
// (Original algorithm would pick rnd = [idx, lastIndex], thus any element could arrive back into its slot)
Math.floor(Math.random() * (lastIndex - idx)) + 1 + idx
) : (
// Pick random number from idx to lastIndex (Unmodified Richard Durstenfeld, N! combinations)
Math.floor(Math.random() * (lastIndex + 1 - idx)) + idx
);
arr[idx] = arr[rnd];
arr[rnd] = el;
});
return arr;
};
var pickOne = (arr) => arr[Math.floor(Math.random() * arr.length)];
var dice = () => !!Math.round(Math.random());
var errorHasMessage = (x) => {
return typeof x === "object" && x !== null && "message" in x && typeof x.message === "string";
};
var convertChordToNotes = (el) => {
let c1;
let c2;
let e1;
let e2;
try {
c1 = inlineChord(el);
} catch (e) {
e1 = e;
}
try {
c2 = chord(el.replace(/_/g, " "));
} catch (e) {
e2 = e;
}
if (!e1 && !e2 && c1 && c2) {
if (c1.toString() !== c2.toString()) {
throw new Error(`Chord ${el} cannot decode, guessing ${c1} or ${c2}`);
}
return c1;
}
if (!e1 && c1) {
return c1;
}
if (!e2 && c2) {
return c2;
}
return chord(el);
};
var convertChordsToNotes = (el) => {
if (typeof el === "string" && isNote(el)) {
return [el];
}
if (Array.isArray(el)) {
el.forEach((n) => {
if (Array.isArray(n)) {
n.forEach((n1) => {
if (typeof n1 !== "string" || !isNote(n1)) {
throw new TypeError("array of arrays must comprise valid notes");
}
});
} else if (typeof n !== "string" || !isNote(n)) {
throw new TypeError("array must comprise valid notes");
}
});
return el;
}
if (!Array.isArray(el)) {
const c = convertChordToNotes(el);
if (c?.length) {
return c;
}
}
throw new Error(`Chord ${el} not found`);
};
var randomInt = (num = 1) => Math.round(Math.random() * num);
// src/arp.ts
var DEFAULT_OCTAVE2 = 4;
var fillArr = (arr, len) => {
const bumpOctave = (el) => {
if (!el) {
throw new Error("Empty element");
}
const note = el.replace(/\d/, "");
const oct = el.replace(/\D/g, "") || DEFAULT_OCTAVE2;
if (!note) {
throw new Error("Incorrect note");
}
return note + (+oct + 1);
};
const arr1 = arr.map(bumpOctave);
const arr2 = arr1.map(bumpOctave);
const finalArr = [...arr, ...arr1, ...arr2];
return finalArr.slice(0, len);
};
var arp = (chordsOrParams) => {
let finalArr = [];
const params = {
count: 4,
order: "0123",
chords: ""
};
if (typeof chordsOrParams === "string") {
params.chords = chordsOrParams;
} else {
if (chordsOrParams.order?.match(/\D/g)) {
throw new TypeError("Invalid value for order");
}
if (chordsOrParams.count > 8 || chordsOrParams.count < 2) {
throw new TypeError("Invalid value for count");
}
if (chordsOrParams.count && !chordsOrParams.order) {
params.order = Array.from(Array(chordsOrParams.count).keys()).join("");
}
Object.assign(params, chordsOrParams);
}
if (typeof params.chords === "string") {
const chordsArr = params.chords.split(" ");
chordsArr.forEach((c, i) => {
try {
const filledArr = fillArr(inlineChord(c), params.count);
const reorderedArr = params.order.split("").map((idx) => filledArr[Number(idx)]);
finalArr = [...finalArr, ...reorderedArr];
} catch (_e) {
throw new Error(
`Cannot decode chord ${i + 1} "${c}" in given "${params.chords}"`
);
}
});
} else if (Array.isArray(params.chords)) {
params.chords.forEach((c, i) => {
try {
const filledArr = fillArr(c, params.count);
const reorderedArr = params.order.split("").map((idx) => filledArr[Number(idx)]);
finalArr = [...finalArr, ...reorderedArr];
} catch (e) {
throw new Error(
`${errorHasMessage(e) ? e.message : e} in chord ${i + 1} "${c}"`
);
}
});
} else {
throw new TypeError("Invalid value for chords");
}
return finalArr;
};
// src/channel/instrument-factory.ts
function checkToneObjLoaded(toneObject, resolve) {
const skipRecursion = toneObject instanceof Tone.Sampler;
if ("loaded" in toneObject) {
if (toneObject.loaded) {
resolve();
return;
}
if (skipRecursion) {
return;
}
let handled = false;
["buffer", "_buffer", "_buffers"].forEach((key) => {
if (key in toneObject) {
checkToneObjLoaded(toneObject[key], resolve);
handled = true;
}
});
if (handled) {
return;
}
}
const hasOnload = toneObject instanceof Tone.ToneAudioBuffer || toneObject instanceof Tone.ToneBufferSource || // Falback for "future" objects
"loaded" in toneObject && "onload" in toneObject;
if (!hasOnload) {
resolve();
} else {
const oldOnLoad = toneObject.onload;
toneObject.onload = () => {
if (oldOnLoad && typeof oldOnLoad === "function") {
toneObject.onload = oldOnLoad;
oldOnLoad();
}
resolve();
};
}
}
function recreateToneObjectInContext(toneObject, context) {
context = context || Tone.getContext();
return new Promise((resolve, _reject) => {
if (toneObject instanceof Tone.PolySynth) {
const newObj = new Tone[toneObject._dummyVoice.name]({
...toneObject.get(),
context
});
checkToneObjLoaded(newObj, () => resolve(newObj));
} else if (toneObject instanceof Tone.Player) {
const newObj = new Tone.Player({
url: toneObject._buffer,
context,
onload: () => checkToneObjLoaded(newObj, () => resolve(newObj))
});
} else if (toneObject instanceof Tone.Sampler) {
const { attack, curve, release, volume } = toneObject.get();
const paramsFromSampler = {
attack,
curve,
release,
volume
};
const paramsFromBuffers = {
baseUrl: toneObject._buffers.baseUrl,
urls: Object.fromEntries(toneObject._buffers._buffers.entries())
};
const newObj = new Tone.Sampler({
...paramsFromSampler,
...paramsFromBuffers,
context,
onload: () => checkToneObjLoaded(
newObj,
() => resolve(newObj)
)
});
} else {
const newObj = new Tone[toneObject.name]({
...toneObject.get(),
context,
onload: () => checkToneObjLoaded(newObj, () => resolve(newObj))
});
checkToneObjLoaded(newObj, () => resolve(newObj));
}
});
}
function createInstrument(context, params, channelMeta) {
let instrument;
let external;
context = context || Tone.getContext();
const loadPromise = new Promise((resolve, reject) => {
if (params.synth) {
if (params.instrument) {
throw new Error(
"Either synth or instrument can be provided, but not both."
);
}
if (params.synth.synth) {
const synthName = params.synth.synth;
const preset = params.synth.preset || {};
instrument = new Tone[synthName]({
...preset,
context,
// Use onload for cases when synthName calls out Tone.Sample/Player/Sampler.
// It could be a universal way to load Tone.js instruments.
onload: () => checkToneObjLoaded(instrument, resolve)
// This onload is ignored in all synths. Therefore we call checkToneObjLoaded() again below.
// It is safe to call resolve() multiple times for Promise<void>
});
checkToneObjLoaded(instrument, resolve);
} else {
instrument = params.synth;
console.warn(
'The "synth" parameter with instrument will be deprecated in the future. Please use the "instrument" parameter instead.'
);
checkToneObjLoaded(instrument, resolve);
}
} else if (typeof params.instrument === "string") {
instrument = new Tone[params.instrument]({ context });
checkToneObjLoaded(instrument, resolve);
} else if (params.instrument) {
instrument = params.instrument;
checkToneObjLoaded(instrument, resolve);
} else if (params.sample || params.buffer) {
instrument = new Tone.Player({
url: params.sample || params.buffer,
context,
onload: () => checkToneObjLoaded(instrument, resolve)
});
} else if (params.samples) {
instrument = new Tone.Sampler({
urls: params.samples,
context,
onload: () => checkToneObjLoaded(instrument, resolve)
});
} else if (params.sampler) {
instrument = params.sampler;
checkToneObjLoaded(instrument, resolve);
} else if (params.player) {
instrument = params.player;
checkToneObjLoaded(instrument, resolve);
} else if (params.external) {
external = { ...params.external };
instrument = {
context,
volume: { value: 0 }
};
if (params.external.init) {
return params.external.init(context.rawContext).then(() => {
resolve();
}).catch((e) => {
reject(
new Error(
`${errorHasMessage(e) ? e.message : e} loading external output module of channel idx ${channelMeta.idx}, ${channelMeta.name ?? "(no name)"}`
)
);
});
} else {
resolve();
}
} else {
throw new Error(
"One of required synth|instrument|sample|sampler|samples|buffer|player|external is not provided!"
);
}
if (!instrument) {
throw new Error("Failed instantiating instrument from given params.");
}
});
const initPromise = loadPromise.then(() => {
if (!external && instrument?.context !== context) {
return recreateToneObjectInContext(instrument, context).then((newObj) => {
instrument = newObj;
});
}
}).then(() => {
if (params.volume) {
instrument.volume.value = params.volume;
external?.setVolume?.(params.volume);
}
return instrument;
});
return { instrument, external, initPromise };
}
// src/channel/sequence-builder.ts
function buildSequenceCallback(params, host, playerCb, eventCb) {
if (host.external) {
const ext = host.external;
return (time, el) => {
if (el === "x" || el === "R") {
const counter = host.clipNoteCount;
if (host.hasLoaded) {
const note = getNote(el, params, counter)[0];
const duration = getDuration(params, counter);
const durSeconds = Tone.Time(duration).toSeconds();
playerCb({ note, duration, time, counter });
try {
ext.triggerAttackRelease?.(note, durSeconds, time);
} catch (e) {
eventCb("error", { e });
}
}
host.clipNoteCount++;
}
};
}
if (host.instrument instanceof Tone.Player) {
return (time, el) => {
if (el === "x" || el === "R") {
const counter = host.clipNoteCount;
if (host.hasLoaded) {
playerCb({ note: "", duration: "", time, counter });
try {
host.instrument.start(time);
} catch (e) {
eventCb("error", { e });
}
}
host.clipNoteCount++;
}
};
}
if (host.instrument instanceof Tone.PolySynth || host.instrument instanceof Tone.Sampler) {
return (time, el) => {
if (el === "x" || el === "R") {
const counter = host.clipNoteCount;
if (host.hasLoaded) {
const note = getNote(el, params, counter);
const duration = getDuration(params, counter);
playerCb({ note, duration, time, counter });
try {
host.instrument.triggerAttackRelease(note, duration, time);
} catch (e) {
eventCb("error", { e });
}
}
host.clipNoteCount++;
}
};
}
if (host.instrument instanceof Tone.NoiseSynth) {
return (time, el) => {
if (el === "x" || el === "R") {
const counter = host.clipNoteCount;
if (host.hasLoaded) {
const duration = getDuration(params, counter);
playerCb({ note: "", duration, time, counter });
try {
host.instrument.triggerAttackRelease(
duration,
time
);
} catch (e) {
eventCb("error", { e });
}
}
host.clipNoteCount++;
}
};
}
return (time, el) => {
if (el === "x" || el === "R") {
const counter = host.clipNoteCount;
if (host.hasLoaded) {
const note = getNote(el, params, counter)[0];
const duration = getDuration(params, counter);
playerCb({ note, duration, time, counter });
try {
host.instrument.triggerAttackRelease(note, duration, time);
} catch (e) {
eventCb("error", { e });
}
}
host.clipNoteCount++;
}
};
}
// src/clip-utils.ts
var defaultParams = {
notes: ["C4"],
pattern: "x",
shuffle: false,
sizzle: false,
sizzleReps: 1,
arpegiate: false,
subdiv: "4n",
amp: 100,
accentLow: 70,
randomNotes: null,
offlineRendering: false
};
var validatePattern = (pattern) => {
if (/[^x\-_[\]R]/.test(pattern)) {
throw new TypeError(
`pattern can only comprise x - _ [ ] R, found ${pattern}`
);
}
};
var preprocessClipParams = (params, extraDefaults) => {
params = { ...defaultParams, ...extraDefaults, ...params || {} };
if (typeof params.notes === "string") {
params.notes = params.notes.replace(/\s{2,}/g, " ").split(" ");
}
params.notes = params.notes ? params.notes.map(convertChordsToNotes) : [];
validatePattern(params.pattern);
if (params.shuffle) {
params.notes = shuffle(params.notes);
}
if (params.randomNotes && typeof params.randomNotes === "string") {
params.randomNotes = params.randomNotes.replace(/\s{2,}/g, " ").split(/\s/);
}
if (params.randomNotes) {
params.randomNotes = params.randomNotes.map(
convertChordsToNotes
);
}
return params;
};
// src/browser-clip.ts
var defaultSubdiv = "4n";
var defaultDur = "8n";
var getNote = (el, params, counter) => {
if (el === "R" && params.randomNotes && params.randomNotes.length > 0) {
return params.randomNotes[randomInt(params.randomNotes.length - 1)];
}
if (params.notes) {
return params.notes[counter % (params.notes.length || 1)];
}
return "";
};
var getDuration = (params, counter) => {
return params.durations ? params.durations[counter % params.durations.length] : params.dur || params.subdiv || defaultDur;
};
var recursivelyApplyPatternToDurations = (patternArr, length, durations = []) => {
patternArr.forEach((char) => {
if (typeof char === "string") {
if (char === "x" || char === "R") {
durations.push(length);
}
if (char === "_" && durations.length) {
durations[durations.length - 1] += length;
}
}
if (Array.isArray(char)) {
recursivelyApplyPatternToDurations(char, length / char.length, durations);
}
});
return durations;
};
var generateSequence = (params, channel, context) => {
context = context || Tone.getContext();
if (!params.pattern) {
throw new Error("No pattern provided!");
}
if (!params.durations && !params.dur) {
params.durations = recursivelyApplyPatternToDurations(
expandStr(params.pattern),
Tone.Ticks(params.subdiv || defaultSubdiv).toSeconds()
);
}
let callback;
if (channel) {
callback = channel.getSeqFn(params);
} else if (params.sample) {
const player = new Tone.Player({ url: params.sample, context });
player.toDestination();
player.sync();
const host = {
instrument: player,
hasLoaded: false,
clipNoteCount: 0
};
checkToneObjLoaded(player, () => {
host.hasLoaded = true;
});
const noop = () => {
};
callback = buildSequenceCallback(params, host, noop, noop);
} else {
throw new Error(
"Either a Channel or a sample URL must be provided to create a clip."
);
}
return new Tone.Sequence({
callback,
events: expandStr(params.pattern),
subdivision: params.subdiv || defaultSubdiv,
context
});
};
var totalPatternDuration = (pattern, subdivOrLength) => {
return typeof subdivOrLength === "number" ? subdivOrLength * expandStr(pattern).length : Tone.Ticks(subdivOrLength).toSeconds() * expandStr(pattern).length;
};
var leastCommonMultiple = (n1, n2) => {
const [smallest, largest] = n1 < n2 ? [n1, n2] : [n2, n1];
let i = largest;
while (i % smallest !== 0) {
i += largest;
}
return i;
};
var renderingDuration = (pattern, subdivOrLength, notes, randomNotes) => {
const patternRegularNotesCount = pattern.split("").filter((c) => {
return c === "x";
}).length;
const patternRandomNotesCount = pattern.split("").filter((c) => {
return c === "R";
}).length;
const patternNotesCount = randomNotes?.length ? patternRegularNotesCount : patternRegularNotesCount + patternRandomNotesCount;
const notesCount = notes.length || 1;
return totalPatternDuration(pattern, subdivOrLength) / patternNotesCount * leastCommonMultiple(notesCount, patternNotesCount);
};
var ongoingRenderingCounter = 0;
var originalContext;
var offlineRenderClip = (params, duration) => {
if (!originalContext) {
originalContext = Tone.getContext();
}
ongoingRenderingCounter++;
const player = new Tone.Player({ context: originalContext, loop: true });
Tone.Offline(async (context) => {
const sequence = generateSequence(params, context);
await Tone.loaded();
sequence.start();
context.transport.start();
}, duration).then((buffer) => {
player.buffer = buffer;
ongoingRenderingCounter--;
if (ongoingRenderingCounter === 0) {
if (originalContext) {
Tone.setContext(originalContext);
}
params.offlineRenderingCallback?.();
}
});
player.toDestination();
player.sync();
return player;
};
var clip = (params, channel) => {
params = preprocessClipParams(params, { align: "1m", alignOffset: "0" });
if (params.offlineRendering) {
return offlineRenderClip(
params,
renderingDuration(
params.pattern,
params.subdiv || defaultSubdiv,
params.notes || [],
params.randomNotes
)
);
}
return generateSequence(params, channel, originalContext);
};
// src/midi.ts
var import_node_fs = __toESM(__require("fs"), 1);
// node_modules/@scribbletune/midi/dist/index.js
var midi_letter_pitches = {
a: 21,
b: 23,
c: 12,
d: 14,
e: 16,
f: 17,
g: 19
};
function midiPitchFromNote(n) {
const matches = /([a-g])(#+|b+)?([0-9]+)$/i.exec(n);
if (!matches) {
throw new Error(`Invalid note name: ${n}`);
}
const note = matches[1].toLowerCase();
const accidental = matches[2] || "";
const octave = parseInt(matches[3], 10);
return 12 * octave + midi_letter_pitches[note] + (accidental.substring(0, 1) === "#" ? 1 : -1) * accidental.length;
}
function ensureMidiPitch(p) {
if (typeof p === "number" || !/[^0-9]/.test(p)) {
return parseInt(String(p), 10);
}
return midiPitchFromNote(p);
}
function mpqnFromBpm(bpm) {
let mpqn = Math.floor(6e7 / bpm);
const ret = [];
do {
ret.unshift(mpqn & 255);
mpqn >>= 8;
} while (mpqn);
while (ret.length < 3) {
ret.push(0);
}
return ret;
}
function codes2Str(byteArray) {
return String.fromCharCode(...byteArray);
}
function str2Bytes(str, finalBytes) {
let s = str;
if (finalBytes) {
while (s.length / 2 < finalBytes) {
s = "0" + s;
}
}
const bytes = [];
for (let i = s.length - 1; i >= 0; i = i - 2) {
const chars = i === 0 ? s[i] : s[i - 1] + s[i];
bytes.unshift(parseInt(chars, 16));
}
return bytes;
}
function translateTickTime(ticks) {
let buffer = ticks & 127;
let t = ticks >> 7;
while (t) {
buffer <<= 8;
buffer |= t & 127 | 128;
t = t >> 7;
}
const bList = [];
while (true) {
bList.push(buffer & 255);
if (buffer & 128) {
buffer >>= 8;
} else {
break;
}
}
return bList;
}
var MidiEventType = {
NOTE_OFF: 128,
NOTE_ON: 144,
AFTER_TOUCH: 160,
CONTROLLER: 176,
PROGRAM_CHANGE: 192,
CHANNEL_AFTERTOUCH: 208,
PITCH_BEND: 224
};
var MetaEventType = {
SEQUENCE: 0,
TEXT: 1,
COPYRIGHT: 2,
TRACK_NAME: 3,
INSTRUMENT: 4,
LYRIC: 5,
MARKER: 6,
CUE_POINT: 7,
CHANNEL_PREFIX: 32,
END_OF_TRACK: 47,
TEMPO: 81,
SMPTE: 84,
TIME_SIG: 88,
KEY_SIG: 89,
SEQ_EVENT: 127
};
var _a;
var MidiEvent = (_a = class {
constructor(params) {
__publicField(this, "time");
__publicField(this, "type");
__publicField(this, "channel");
__publicField(this, "param1");
__publicField(this, "param2");
this.setTime(params.time);
this.setType(params.type);
this.setChannel(params.channel);
this.setParam1(params.param1);
if (params.param2 !== void 0) {
this.setParam2(params.param2);
}
}
setTime(ticks) {
this.time = translateTickTime(ticks || 0);
}
setType(type) {
if (type < _a.NOTE_OFF || type > _a.PITCH_BEND) {
throw new Error("Trying to set an unknown event: " + type);
}
this.type = type;
}
setChannel(channel) {
if (channel < 0 || channel > 15) {
throw new Error("Channel is out of bounds.");
}
this.channel = channel;
}
setParam1(p) {
this.param1 = p;
}
setParam2(p) {
this.param2 = p;
}
toBytes() {
const byteArray = [];
const typeChannelByte = this.type | this.channel & 15;
byteArray.push(...this.time);
byteArray.push(typeChannelByte);
byteArray.push(this.param1);
if (this.param2 !== void 0 && this.param2 !== null) {
byteArray.push(this.param2);
}
return byteArray;
}
}, __publicField(_a, "NOTE_OFF", MidiEventType.NOTE_OFF), __publicField(_a, "NOTE_ON", MidiEventType.NOTE_ON), __publicField(_a, "AFTER_TOUCH", MidiEventType.AFTER_TOUCH), __publicField(_a, "CONTROLLER", MidiEventType.CONTROLLER), __publicField(_a, "PROGRAM_CHANGE", MidiEventType.PROGRAM_CHANGE), __publicField(_a, "CHANNEL_AFTERTOUCH", MidiEventType.CHANNEL_AFTERTOUCH), __publicField(_a, "PITCH_BEND", MidiEventType.PITCH_BEND), _a);
var _a2;
var MetaEvent = (_a2 = class {
constructor(params) {
__publicField(this, "time");
__publicField(this, "type");
__publicField(this, "data");
this.time = translateTickTime(0);
this.type = 0;
this.setTime(params.time);
this.setType(params.type);
this.setData(params.data);
}
setTime(ticks) {
this.time = translateTickTime(ticks || 0);
}
setType(t) {
this.type = t;
}
setData(d) {
this.data = d;
}
toBytes() {
if (!this.type) {
throw new Error("Type for meta-event not specified.");
}
const byteArray = [];
byteArray.push(...this.time);
byteArray.push(255, this.type);
if (Array.isArray(this.data)) {
byteArray.push(this.data.length);
byteArray.push(...this.data);
} else if (typeof this.data === "number") {
byteArray.push(1, this.data);
} else if (this.data !== null && this.data !== void 0) {
byteArray.push(this.data.length);
const dataBytes = this.data.split("").map((x) => x.charCodeAt(0));
byteArray.push(...dataBytes);
} else {
byteArray.push(0);
}
return byteArray;
}
}, __publicField(_a2, "SEQUENCE", MetaEventType.SEQUENCE), __publicField(_a2, "TEXT", MetaEventType.TEXT), __publicField(_a2, "COPYRIGHT", MetaEventType.COPYRIGHT), __publicField(_a2, "TRACK_NAME", MetaEventType.TRACK_NAME), __publicField(_a2, "INSTRUMENT", MetaEventType.INSTRUMENT), __publicField(_a2, "LYRIC", MetaEventType.LYRIC), __publicField(_a2, "MARKER", MetaEventType.MARKER), __publicField(_a2, "CUE_POINT", MetaEventType.CUE_POINT), __publicField(_a2, "CHANNEL_PREFIX", MetaEventType.CHANNEL_PREFIX), __publicField(_a2, "END_OF_TRACK", MetaEventType.END_OF_TRACK), __publicField(_a2, "TEMPO", MetaEventType.TEMPO), __publicField(_a2, "SMPTE", MetaEventType.SMPTE), __publicField(_a2, "TIME_SIG", MetaEventType.TIME_SIG), __publicField(_a2, "KEY_SIG", MetaEventType.KEY_SIG), __publicField(_a2, "SEQ_EVENT", MetaEventType.SEQ_EVENT), _a2);
var DEFAULT_VOLUME = 90;
var _a3;
var Track = (_a3 = class {
constructor(config) {
__publicField(this, "events");
this.events = config?.events ?? [];
}
addEvent(event) {
this.events.push(event);
return this;
}
addNoteOn(channel, pitch, time, velocity) {
this.events.push(
new MidiEvent({
type: MidiEvent.NOTE_ON,
channel,
param1: ensureMidiPitch(pitch),
param2: velocity || DEFAULT_VOLUME,
time: time || 0
})
);
return this;
}
addNoteOff(channel, pitch, time, velocity) {
this.events.push(
new MidiEvent({
type: MidiEvent.NOTE_OFF,
channel,
param1: ensureMidiPitch(pitch),
param2: velocity || DEFAULT_VOLUME,
time: time || 0
})
);
return this;
}
addNote(channel, pitch, dur, time, velocity) {
this.addNoteOn(channel, pitch, time, velocity);
if (dur) {
this.addNoteOff(channel, pitch, dur, velocity);
}
return this;
}
addChord(channel, chord2, dur, velocity) {
if (!Array.isArray(chord2) || chord2.length === 0) {
throw new Error("Chord must be a non-empty array of pitches");
}
chord2.forEach((note) => {
this.addNoteOn(channel, note, 0, velocity);
});
chord2.forEach((note, index) => {
if (index === 0) {
this.addNoteOff(channel, note, dur);
} else {
this.addNoteOff(channel, note);
}
});
return this;
}
setInstrument(channel, instrument, time) {
this.events.push(
new MidiEvent({
type: MidiEvent.PROGRAM_CHANGE,
channel,
param1: instrument,
time: time || 0
})
);
return this;
}
setTempo(bpm, time) {
this.events.push(
new MetaEvent({
type: MetaEvent.TEMPO,
data: mpqnFromBpm(bpm),
time: time || 0
})
);
return this;
}
setTimeSignature(numerator, denominator, time) {
const ddlog2 = Math.log2(denominator);
if (ddlog2 !== Math.floor(ddlog2)) {
throw new Error(
"Time signature denominator must be an exact power of 2!"
);
}
this.events.push(
new MetaEvent({
type: MetaEvent.TIME_SIG,
data: [numerator & 255, Math.floor(ddlog2) & 255, 24, 8],
time: time || 0
})
);
return this;
}
setKeySignature(accidentals, minor, time) {
this.events.push(
new MetaEvent({
type: MetaEvent.KEY_SIG,
data: [accidentals & 255, minor ? 1 : 0],
time: time || 0
})
);
return this;
}
toBytes() {
let trackLength = 0;
const eventBytes = [];
const startBytes = _a3.START_BYTES;
const endBytes = _a3.END_BYTES;
this.events.forEach((event) => {
const bytes = event.toBytes();
trackLength += bytes.length;
eventBytes.push(...bytes);
});
trackLength += endBytes.length;
const lengthBytes = str2Bytes(trackLength.toString(16), 4);
return startBytes.concat(lengthBytes, eventBytes, endBytes);
}
}, __publicField(_a3, "START_BYTES", [77, 84, 114, 107]), __publicField(_a3, "END_BYTES", [0, 255, 47, 0]), _a3);
Track.prototype.noteOn = Track.prototype.addNoteOn;
Track.prototype.noteOff = Track.prototype.addNoteOff;
Track.prototype.note = Track.prototype.addNote;
Track.prototype.chord = Track.prototype.addChord;
Track.prototype.instrument = Track.prototype.setInstrument;
Track.prototype.tempo = Track.prototype.setTempo;
Track.prototype.timeSignature = Track.prototype.setTimeSignature;
Track.prototype.keySignature = Track.prototype.setKeySignature;
var _a4;
var File = (_a4 = class {
constructor(config) {
__publicField(this, "ticks");
__publicField(this, "tracks");
const c = config || {};
if (c.ticks !== void 0) {
if (typeof c.ticks !== "number") {
throw new Error("Ticks per beat must be a number!");
}
if (c.ticks <= 0 || c.ticks >= 1 << 15 || c.ticks % 1 !== 0) {
throw new Error("Ticks per beat must be an integer between 1 and 32767!");
}
}
this.ticks = c.ticks || 128;
this.tracks = [];
}
addTrack(track) {
if (track) {
this.tracks.push(track);
return this;
} else {
const newTrack = new Track();
this.tracks.push(newTrack);
return newTrack;
}
}
toBytes() {
const trackCount = this.tracks.length.toString(16);
let bytes = _a4.HDR_CHUNKID + _a4.HDR_CHUNK_SIZE;
if (parseInt(trackCount, 16) > 1) {
bytes += _a4.HDR_TYPE1;
} else {
bytes += _a4.HDR_TYPE0;
}
bytes += codes2Str(str2Bytes(trackCount, 2));
bytes += String.fromCharCode(this.ticks / 256, this.ticks % 256);
this.tracks.forEach((track) => {
bytes += codes2Str(track.toBytes());
});
return bytes;
}
toUint8Array() {
const str = this.toBytes();
const arr = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) {
arr[i] = str.charCodeAt(i);
}
return arr;
}
toBlob(genericType) {
return new Blob([this.toUint8Array()], {
type: genericType ? "application/octet-stream" : "audio/x-midi"
});
}
}, __publicField(_a4, "HDR_CHUNKID", "MThd"), __publicField(_a4, "HDR_CHUNK_SIZE", "\0\0\0"), __publicField(_a4, "HDR_TYPE0", "\0\0"), __publicField(_a4, "HDR_TYPE1", "\0"), _a4);
// src/midi.ts
var midi = (notes, fileName = "music.mid", bpm) => {
const file = createFileFromNotes(notes, bpm);
const bytes = file.toBytes();
if (fileName === null) {
return bytes;
}
if (!fileName.endsWith(".mid")) {
fileName = `${fileName}.mid`;
}
if (typeof window !== "undefined" && window.URL && typeof window.URL.createObjectURL === "function") {
return createDownloadLink(bytes, fileName);
}
import_node_fs.default.writeFileSync(fileName, bytes, "binary");
console.log(`MIDI file generated: ${fileName}.`);
};
var createDownloadLink = (b, fileName) => {
const bytes = new Uint8Array(b.length);
for (let i = 0; i < b.length; i++) {
const ascii = b.charCodeAt(i);
bytes[i] = ascii;
}
const blob = new Blob([bytes], { type: "audio/midi" });
const link = document.createElement("a");
link.href = typeof window !== "undefined" && typeof window.URL !== "undefined" && typeof window.URL.createObjectURL !== "undefined" && window.URL.createObjectURL(blob) || "";
link.download = fileName;
link.innerText = "Download MIDI file";
return link;
};
var createFileFromNotes = (notes, bpm) => {
const file = new File();
const track = new Track();
if (typeof bpm === "number") {
track.setTempo(bpm);
}
file.addTrack(track);
for (const noteObj of notes) {
const level = noteObj.level || 127;
if (noteObj.note) {
if (typeof noteObj.note === "string") {
track.noteOn(0, noteObj.note, noteObj.length, level);
track.noteOff(0, noteObj.note, noteObj.length, level);
} else {
track.addChord(0, noteObj.note, noteObj.length, level);
}
} else {
track.noteOff(0, "", noteObj.length);
}
}
return file;
};
// src/progression.ts
var getChordDegrees = (mode) => {
const theRomans = {
ionian: ["I", "ii", "iii", "IV", "V", "vi", "vii\xB0"],
dorian: ["i", "ii", "III", "IV", "v", "vi\xB0", "VII"],
phrygian: ["i", "II", "III", "iv", "v\xB0", "VI", "vii"],
lydian: ["I", "II", "iii", "iv\xB0", "V", "vi", "vii"],
mixolydian: ["I", "ii", "iii\xB0", "IV", "v", "vi", "VII"],
aeolian: ["i", "ii\xB0", "III", "iv", "v", "VI", "VII"],
locrian: ["i\xB0", "II", "iii", "iv", "V", "VI", "vii"],
"melodic minor": ["i", "ii", "III+", "IV", "V", "vi\xB0", "vii\xB0"],
"harmonic minor": ["i", "ii\xB0", "III+", "iv", "V", "VI", "vii\xB0"]
};
theRomans.major = theRomans.ionian;
theRomans.minor = theRomans.aeolian;
return theRomans[mode] || [];
};
var idxByDegree = {
i: 0,
ii: 1,
iii: 2,