UNPKG

scribbletune

Version:

Create music with JavaScript and Node.js!

1,523 lines (1,510 loc) 63.5 kB
"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,