/** * Create music from text. * examples/wave.html * * Music Makers is an application using the same libraries as Wave: * https://github.com/jussiry/musicmakers * http://edide.xyz/musicmakers.app * https://edide.io/musicmakers.app */ "use strict"; (function(){ var MODSELF, edide = {}; edide.global = (function (edide, mod_global) { return typeof global !== 'undefined' ? global : window; }).call(MODSELF={}, edide, MODSELF); edide.set = (function (edide, set) {Object.defineProperty(this, 'module_name', {value:'set'}); set = (...args) => new Set(args) set.addArr = (s, arr) => { var i, len for (i = 0, len = arr.length; i < len; i++) { s.add(arr[i]) } } set.map = (s, func) => Array.from(s).map(func) return set; }).call(MODSELF={}, edide, MODSELF); edide.membrameSynth = (function (edide, membrameSynth) {Object.defineProperty(this, 'module_name', {value:'membrameSynth'}); this.startNote = 'A0'; this.init = () => { var bd, compressor, distortion, gain, reverb; distortion = new Tone.Distortion({ distortion: 0.1, oversample: "4x" // none, 2x, 4x }); reverb = new Tone.Freeverb(0.75, 1000); gain = new Tone.Gain(0.5); compressor = new Tone.Compressor({ ratio: 12, threshold: -24, release: 0.05, attack: 0.003, knee: 1 }); bd = new Tone.MembraneSynth({ pitchDecay: 0.05, octaves: 4, envelope: { attack: 0.01, decay: 0.74, sustain: 0.71, release: 0.05, attackCurve: "exponential" } }); bd.chain(gain, distortion, reverb, compressor); return [bd, compressor]; }; return membrameSynth; }).call(MODSELF={}, edide, MODSELF); edide.toneSynth = (function (edide, toneSynth) {Object.defineProperty(this, 'module_name', {value:'toneSynth'}); this.startNote = 'C3'; this.init = () => { var ss; return ss = new Tone.PolySynth(12, Tone.Synth, { oscillator: { type: 'sine' }, envelope: { attack: 0.005, decay: 0.1, sustain: 0.3, release: 1 } }); }; return toneSynth; }).call(MODSELF={}, edide, MODSELF); edide.instrumentConfigs = (function (edide, instrumentConfigs) { var configs = { 'bass-electric': { startNote: 'C#2', notes: 8, // 16 step: 3 }, 'cello': { startNote: 'C2', notes: 11, step: 2, skipNotes: edide.set('F#2', 'C4') }, 'drum-electric': edide.membrameSynth, 'guitar-acoustic': { startNote: 'D1', notes: 26, // 36 step: 2, skipNotes: edide.set('E4', 'F#4', 'G#4', 'A#4', 'C5', 'D5', 'E5') }, 'guitar-electric': { startNote: 'F#2', notes: 15, // 15 step: 3, }, 'piano': { startNote: 'A1', notes: 30, // 29 step: 2, baseUrl: "https://tonejs.github.io/examples/audio/salamander/" }, 'synth-simple': edide.toneSynth, 'xylophone': { startNote: 'G3', notes: ['G3', 'C4', 'G4', 'C5', 'G5', 'C6', 'G6', 'C7'] } } for (let inst in configs) { configs[inst].name = inst } return configs }).call(MODSELF={}, edide, MODSELF); edide.objectFromArray = (function (edide, objectFromArray) { var identity; identity = function(el) { return el; }; return (array, valFromEl, keyFromEl) => { var el, i, ind, len, obj; if (valFromEl == null) { valFromEl = identity; } if (keyFromEl == null) { keyFromEl = identity; } obj = {}; for (ind = i = 0, len = array.length; i < len; ind = ++i) { el = array[ind]; obj[keyFromEl(el, ind)] = valFromEl(el, ind); } return obj; }; }).call(MODSELF={}, edide, MODSELF); edide.noteFreq = (function (edide, noteFreq) {Object.defineProperty(this, 'module_name', {value:'noteFreq'}); var a4, noteMap, notesObj; this.notes = [['C0', 16.35, 2109.89], ['C#0', 17.32, 1991.47], ['D0', 18.35, 1879.69], ['D#0', 19.45, 1774.20], ['E0', 20.60, 1674.62], ['F0', 21.83, 1580.63], ['F#0', 23.12, 1491.91], ['G0', 24.50, 1408.18], ['G#0', 25.96, 1329.14], ['A0', 27.50, 1254.55], ['A#0', 29.14, 1184.13], ['B0', 30.87, 1117.67], ['C1', 32.70, 1054.94], ['C#1', 34.65, 995.73], ['D1', 36.71, 939.85], ['D#1', 38.89, 887.10], ['E1', 41.20, 837.31], ['F1', 43.65, 790.31], ['F#1', 46.25, 745.96], ['G1', 49.00, 704.09], ['G#1', 51.91, 664.57], ['A1', 55.00, 627.27], ['A#1', 58.27, 592.07], ['B1', 61.74, 558.84], ['C2', 65.41, 527.47], ['C#2', 69.30, 497.87], ['D2', 73.42, 469.92], ['D#2', 77.78, 443.55], ['E2', 82.41, 418.65], ['F2', 87.31, 395.16], ['F#2', 92.50, 372.98], ['G2', 98.00, 352.04], ['G#2', 103.83, 332.29], ['A2', 110.00, 313.64], ['A#2', 116.54, 296.03], ['B2', 123.47, 279.42], ['C3', 130.81, 263.74], ['C#3', 138.59, 248.93], ['D3', 146.83, 234.96], ['D#3', 155.56, 221.77], ['E3', 164.81, 209.33], ['F3', 174.61, 197.58], ['F#3', 185.00, 186.49], ['G3', 196.00, 176.02], ['G#3', 207.65, 166.14], ['A3', 220.00, 156.82], ['A#3', 233.08, 148.02], ['B3', 246.94, 139.71], ['C4', 261.63, 131.87], ['C#4', 277.18, 124.47], ['D4', 293.66, 117.48], ['D#4', 311.13, 110.89], ['E4', 329.63, 104.66], ['F4', 349.23, 98.79], ['F#4', 369.99, 93.24], ['G4', 392.00, 88.01], ['G#4', 415.30, 83.07], ['A4', 440.00, 78.41], ['A#4', 466.16, 74.01], ['B4', 493.88, 69.85], ['C5', 523.25, 65.93], ['C#5', 554.37, 62.23], ['D5', 587.33, 58.74], ['D#5', 622.25, 55.44], ['E5', 659.25, 52.33], ['F5', 698.46, 49.39], ['F#5', 739.99, 46.62], ['G5', 783.99, 44.01], ['G#5', 830.61, 41.54], ['A5', 880.00, 39.20], ['A#5', 932.33, 37.00], ['B5', 987.77, 34.93], ['C6', 1046.50, 32.97], ['C#6', 1108.73, 31.12], ['D6', 1174.66, 29.37], ['D#6', 1244.51, 27.72], ['E6', 1318.51, 26.17], ['F6', 1396.91, 24.70], ['F#6', 1479.98, 23.31], ['G6', 1567.98, 22.00], ['G#6', 1661.22, 20.77], ['A6', 1760.00, 19.60], ['A#6', 1864.66, 18.50], ['B6', 1975.53, 17.46], ['C7', 2093.00, 16.48], ['C#7', 2217.46, 15.56], ['D7', 2349.32, 14.69], ['D#7', 2489.02, 13.86], ['E7', 2637.02, 13.08], ['F7', 2793.83, 12.35], ['F#7', 2959.96, 11.66], ['G7', 3135.96, 11.00], ['G#7', 3322.44, 10.38], ['A7', 3520.00, 9.80], ['A#7', 3729.31, 9.25], ['B7', 3951.07, 8.73], ['C8', 4186.01, 8.24], ['C#8', 4434.92, 7.78], ['D8', 4698.63, 7.34], ['D#8', 4978.03, 6.93], ['E8', 5274.04, 6.54], ['F8', 5587.65, 6.17], ['F#8', 5919.91, 5.83], ['G8', 6271.93, 5.50], ['G#8', 6644.88, 5.19], ['A8', 7040.00, 4.90], ['A#8', 7458.62, 4.63], ['B8', 7902.13, 4.37]]; notesObj = null; noteMap = () => { if (notesObj) { return notesObj; } return notesObj = edide.objectFromArray(this.notes, (val, ind) => { return [ind, ...val]; }, (key) => { return key[0]; }); }; this.findNote = (name) => { return noteMap()[name]; }; a4 = this.findNote('A4'); this.diffToA4 = (name) => { var note; note = this.findNote(name); return note[0] - a4[0]; }; this.diff = (n1, n2) => { return this.findNote(n2)[0] - this.findNote(n1)[0]; }; return noteFreq; }).call(MODSELF={}, edide, MODSELF); edide.strRandom = (function (edide, strRandom) { return (limit = 20) => { return (Math.random() + '').slice(2, +(limit + 1) + 1 || 9e9); }; }).call(MODSELF={}, edide, MODSELF); edide.inEditor = (function (edide, inEditor) {Object.defineProperty(this, 'module_name', {value:'inEditor'}); inEditor = (inEditor === true) || false return inEditor; }).call(MODSELF={}, edide, MODSELF); edide.edideNamespace = (function (edide, edideNamespace) { return 'edide' }).call(MODSELF={}, edide, MODSELF); edide.edideNs = (function (edide, edideNs) { var base, name; return (base = edide.global)[name = edide.edideNamespace] != null ? base[name] : base[name] = {}; }).call(MODSELF={}, edide, MODSELF); edide.editorModule = (function (edide, editorModule) { var editorModule; editorModule = edide.inEditor ? edide.edideNs : null; return editorModule; }).call(MODSELF={}, edide, MODSELF); edide.keep = (function (edide, keep) {Object.defineProperty(this, 'module_name', {value:'keep'}); var keep; keep = (prop) => { return prop; }; return keep; }).call(MODSELF={}, edide, MODSELF); edide.logProd = (function (edide, logProd) { return (...args) => { var console, ref, ref1; console = (ref = (ref1 = edide.editorModule) != null ? ref1.console : void 0) != null ? ref : edide.global.console; return console.log(...args); }; }).call(MODSELF={}, edide, MODSELF); edide.onUnload = (function (edide, onUnload) { var ref, ref1; return (ref = (ref1 = edide.editorModule) != null ? ref1.unload.add : void 0) != null ? ref : () => {}; }).call(MODSELF={}, edide, MODSELF); edide.var = (function (edide, mod_var) { var clearVar, currentReact, debugging, dependees, dependsOn, depsRequired, inInitCall, infinityCheck, initSetter, loopLimit, newVar, parent, setLinks, setters, updateVar, values; values = edide.keep(new Map); // #edide.keep if clearVar added again.. setters = edide.keep(new Map); // varName => setter:func dependees = edide.keep(new Map); // varName => deps: setLinks = edide.keep(new Map); // for reactiveGraph, show setters inside reactive/setter:s debugging = false; //edide.editorModule? depsRequired = new Map; // varName : dependsOn inInitCall = false; // TODO use dependsOn? instead dependsOn = new Set(); // remove initSetter = (name, setter) => { var debugName, err, parent, ref, val; debugName = (ref = setter.type) != null ? ref : name; if ((setters.get(name)) != null) { throw Error(`Reactive for '${debugName}' already exists`); } setters.set(name, setter); if (inInitCall) { throw Error(`can't create reactive setter (for '${debugName}') inside reactive context`); } inInitCall = name; dependsOn.clear(); // TODO clear => new Set try { val = setter(); // TODO: some day add revar and unvar as params; helps with multiple reactives to keep the separated } catch (error) { err = error; inInitCall = false; err.message = `Reactive initialization of '${debugName}' failed: ${err.message}`; throw err; } parent = null; dependsOn.forEach((depName) => { var deps; if ((deps = dependees.get(depName)) == null) { dependees.set(depName, deps = new Set); } return deps.add(name); }); inInitCall = false; return val; }; loopLimit = 0; infinityCheck = new Map; //edide.keep parent = null; updateVar = function(name, val) { var ref, ref1, type; if (arguments.length === 1) { //unless val? val = setters.get(name)(); if (debugging && (type = setters.get(name).type)) { edide.logProd(`running ${(setters.get(name).type)}`); } } if (typeof name !== 'string') { // symbol ~ react function return; } if (values.get(name) === val && typeof val !== 'object') { return; } if (infinityCheck.get(name) > loopLimit) { infinityCheck.forEach((k) => { return edide.logProd(k); }); edide.logProd(name); if ((ref = edide.editorModule) != null) { if (typeof ref.reactiveGraph === "function") { ref.reactiveGraph(); } } throw Error("Inifinite loop in \:var dependencies"); } if (debugging) { edide.logProd(`updating ${name}`); } values.set(name, val); if (!inInitCall) { infinityCheck.set(name, (infinityCheck.get(name) || 0) + 1); if ((ref1 = dependees.get(name)) != null) { ref1.forEach((depName) => { return updateVar(depName); }); } infinityCheck.delete(name); } return val; }; currentReact = []; newVar = function(name, setter) { var context, contextSet, err; if (arguments.length === 1) { if (typeof name === 'string') { if (inInitCall) { dependsOn.add(name); edide.onUnload(() => { return clearVar(name); // TODO make specific clear? }); } return values.get(name); } else { setter = name; name = Symbol(); values.set(name, name); // for debugging (showing react/dom funcs in graph) edide.onUnload(() => { return clearVar(name); // TODO make specific clear? }); } } if (currentReact.length) { // and debugging context = currentReact[currentReact.length - 1]; if (!(contextSet = setLinks.get(context))) { setLinks.set(context, contextSet = new Set); } contextSet.add(name); } currentReact.push(name); if (values.get(name) == null) { edide.onUnload(() => { return clearVar(name); }); } if (typeof setter === 'function') { setter = initSetter(name, setter); // setter becomes value } if (typeof name === 'string') { try { updateVar(name, setter); } catch (error) { err = error; infinityCheck.clear(); throw err; } } currentReact.pop(); return setter; }; clearVar = (name) => { var i, len, map, ref; ref = [values, setters, dependees, setLinks]; for (i = 0, len = ref.length; i < len; i++) { map = ref[i]; map.delete(name); } }; Object.assign(newVar, {dependees, values, setters, setLinks}); return newVar; }).call(MODSELF={}, edide, MODSELF); edide.reactiveWithFilters = (function (edide, reactiveWithFilters) { return (initialVars = {}, filters = {}) => { var handler, id, key, react, revar, todoMap, unvar, val; id = edide.strRandom(); handler = { get: (map, prop) => { var ref; if ((ref = edide.editorModule) != null ? ref.editor_inspector.inspectingNow : void 0) { console.log('IN inspector? Find out is it possible to end up here form inside setter'); return edide.var.values.get(`${id}.${prop}`); } return edide.var(`${id}.${prop}`); }, set: (map, prop, value) => { if ((filters[prop] != null) && !filters[prop](value)) { throw Error(`Illegal reactive (${prop}: ${value})`); } edide.var(`${id}.${prop}`, value); return true; // Proxy set must return true if set is successfull; In the future use Reflect.set, which returns true automatically? } }; revar = new Proxy((todoMap = new Map), handler); // NOTE: map is not used yet unvar = new Proxy(todoMap, { get: (map, prop) => { return edide.var.values.get(`${id}.${prop}`); }, set: (map, prop, value) => { return edide.var.values.set(`${id}.${prop}`, value); } }); for (key in initialVars) { val = initialVars[key]; revar[key] = val; } react = (nameOrFunc, func) => { if (func != null) { func.type = nameOrFunc; } else { func = nameOrFunc; func.type = 'react'; // for debugging } return edide.var(func); }; return { react, revar, unvar, un: unvar, re: revar // , dom }; }; }).call(MODSELF={}, edide, MODSELF); edide.mmState = (function (edide, mmState) {Object.defineProperty(this, 'module_name', {value:'mmState'}); var filters, instruments; this.defaults = { playing: false, recorderOn: false, fullSheet: '', sheet: '', // selected/play part of fullSheet diffText: '', note: null, // currently playing note // state properties pace: 400, // -> bpm -> beatDelay bpm: 400, // deprecate beatDelay: (1 / 400) * 60 * 1000, // silly! unify with pace as one statestate variable balance: 0, volume: 0, detune: 0, blur: 0, itch: 0, instrumentsLoading: 0, // dropdowns instrument: 'guitar-electric', scale: 'pentatonic', root: 'C3', highlight: null, keyboardInd: 0 // TODO --> keyboard }; instruments = Object.keys(edide.instrumentConfigs); filters = { scale: (val) => { return ['pentatonic', 'minor', 'major'].includes(val); }, instrument: (val) => { return instruments.includes(val); }, root: (val) => { return edide.noteFreq.findNote(val); } }; this.react = null; this.init = (startingProps = {}) => { var props; if (this.react != null) { return this; } props = Object.assign({}, this.defaults, startingProps); ({react: this.react, revar: this.revar, unvar: this.unvar} = edide.reactiveWithFilters(props, filters)); return this; }; return mmState; }).call(MODSELF={}, edide, MODSELF); edide.mmEffects = (function (edide, mmEffects) {Object.defineProperty(this, 'module_name', {value:'mmEffects'}); ({revar: this.revar} = edide.mmState.init()); this.maxLowpass = 10000; this.maxDistortion = 3; this.revar.lowpass = () => { var blur; ({blur} = this.revar); return this.maxLowpass - blur * (this.maxLowpass - 200); }; this.revar.distortion = () => { var itch; ({itch} = this.revar); return this.maxDistortion * itch; }; return mmEffects; }).call(MODSELF={}, edide, MODSELF); edide.clone = (function (edide, clone) { return (...objects) => { if (Array.isArray(objects[0])) { return Object.assign([], ...objects); } else { return Object.assign({}, ...objects); } }; }).call(MODSELF={}, edide, MODSELF); edide.strParsesToNumber = (function (edide, strParsesToNumber) {Object.defineProperty(this, 'module_name', {value:'strParsesToNumber'}); var strParsesToNumber; strParsesToNumber = (str) => { return !Number.isNaN(parseInt(str)); }; return strParsesToNumber; }).call(MODSELF={}, edide, MODSELF); edide.mmConfigs = (function (edide, mmConfigs) {Object.defineProperty(this, 'module_name', {value:'mmConfigs'}); var defaultVars, mutatingProp, mutatingVars, stateVars, vars; vars = {}; // varName: config object mutatingVars = {}; stateVars = edide.set('itch', 'blur', 'instrument', 'bpm', 'pace', 'beatDelay', 'scale', 'root', 'balance', 'detune', 'volume'); this.hasVar = (varName) => { var ref, ref1; return !!((ref = (ref1 = vars[varName]) != null ? ref1 : mutatingVars[varName]) != null ? ref : stateVars.has(varName)); }; this.activateVar = (name, value) => { var configs; if (value != null) { configs = stateVars.has(name) ? { [`${name}`]: value } : mutatingVars[name] != null ? edide.clone(mutatingVars[name]) : void 0; if (edide.strParsesToNumber(value)) { value = parseFloat(value); } configs[mutatingProp(configs)] = value; return this.activate(configs); } else { return this.activate(vars[name]); } }; mutatingProp = (configs) => { var name, val; for (name in configs) { val = configs[name]; if (val === '*') { return name; } } return false; }; this.activate = (conf) => { if (conf.name != null) { return; // throw warning? } return Object.assign(edide.mmState.revar, conf); }; this.reset = () => { stateVars.forEach((name) => { return edide.mmState.revar[name] = edide.mmState.defaults[name]; }); }; this.addVar = (varName, config) => { // , activate=true if (mutatingProp(config)) { return mutatingVars[varName] = config; } else { return vars[varName] = config; } }; defaultVars = { bass: { "instrument": "bass-electric" }, cello: { "instrument": "cello" }, guitar: { "instrument": "guitar-acoustic" }, eguitar: { "instrument": "guitar-electric" }, piano: { "instrument": "piano" }, synth: { "instrument": "synth-simple" }, xylophone: { "instrument": "xylophone" }, pentatonic: { "scale": "pentatonic" }, major: { "scale": "major" }, minor: { "scale": "minor" } }; this.init = () => { var conf, name; vars = {}; for (name in defaultVars) { conf = defaultVars[name]; this.addVar(name, conf, false); } }; this.init(); return mmConfigs; }).call(MODSELF={}, edide, MODSELF); edide.setTimeout = (function (edide, mod_setTimeout) { var nativeSetTimeout, ref; nativeSetTimeout = edide.global.nativeSetTimeout = (ref = edide.global.nativeSetTimeout) != null ? ref : edide.global.setTimeout; return function(arg1, arg2) { var fun, id, num; [fun, num] = typeof arg2 === 'function' ? [arg2, arg1] : [arg1, arg2]; id = nativeSetTimeout(fun, num); edide.onUnload(() => { return clearTimeout(id); }); return id; }; }).call(MODSELF={}, edide, MODSELF); edide.musicScales = (function (edide, musicScales) {Object.defineProperty(this, 'module_name', {value:'musicScales'}); this.full = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; this.scaleSteps = { // rock // 0, 3, 2, 2, 3, (2) pentatonic: [ 0, 3, 2, 2, 3 // hidden 2 # "pentatonicMinor" ], //pentatonicMajor: ['C, D, E, G, A'] // blues: minor penta + // whole – whole – half – whole – whole – whole – half major: [ 0, 2, 2, 1, 2, 2, 2 // hidden to beginning: 1 # "naturalMajor" ], // whole – half – whole – whole – half – whole – whole minor: [ 0, 2, 1, 2, 2, 1, 2 // hidden to beginning: 2 # "naturalMinor" ] }; // http://whatmusicreallyis.com/papers/sacred_sounds_scale.html this.triadSteps = { major: [ 0, 4, 3 // positive valence ], minor: [0, 3, 4] }; // M = major triad // m = minor triar // i = number of (half) steps from first triad to second // (source: https://www.youtube.com/watch?v=YSKAt3pmYBs) this.triadCombinations = { protagonism: 'M2M', outerSpace: 'M6M', fantastical: 'M8M', sadness: 'M4m', romantic: 'M5m', // and middle eastern wonder: 'm5M', // and trancendence mystery: 'm2M', // and dark comedy dramatic: 'm11M', antagonismDanger: 'm6m', // less character based antagonismEvil: 'm8m' // cahracter based }; return musicScales; }).call(MODSELF={}, edide, MODSELF); edide.mathOp = (function (edide, mathOp) {Object.defineProperty(this, 'module_name', {value:'mathOp'}); this.sum = function(a, b) { return a + b; }; this.multiply = function(a, b) { return a * b; }; return mathOp; }).call(MODSELF={}, edide, MODSELF); edide.mmKeyboard = (function (edide, mmKeyboard) {Object.defineProperty(this, 'module_name', {value:'mmKeyboard'}); var A, a, char, chari, i, j, len, len1, noncaps, qwertyChar, qwertyRows, revar, row, rowi, space, unvar; ({revar, unvar} = edide.mmState.init()); revar.scaleSteps = () => { return edide.musicScales.scaleSteps[revar.scale]; }; this.special = { rest: '.', long: '=', comment: '#', var: ':', confStart: '{', confEnd: '}' }; this.specialKeyCodes = new Set(Object.values(this.special).map((s) => { return s.charCodeAt(0); })); this.specialChars = new Set(Object.values(this.special)); this.isPauseKey = (keyCode) => { return this.specialKeyCodes.has(keyCode); }; this.isSpecialChar = (char) => { return this.specialChars.has(char); }; this.isPauseChar = this.isSpecialChar; // not really... this.keyboards = ['qwerty', 'abc']; a = 'a'.charCodeAt(0); A = 'A'.charCodeAt(0); space = ' '.charCodeAt(0); this.isCaps = (key) => { return A <= key && key < a; }; this.capsDiff = a - A; noncaps = (key) => { if (this.isCaps(key)) { return key + this.capsDiff; } else { return key; } }; this.getNoteInd = (key) => { var maxInstrumentNoteInd, noteBaseInd, noteInd, notes, startNote, step; ({startNote, notes, step} = unvar.instrumentConf); startNote = unvar.root; noteBaseInd = edide.noteFreq.findNote(startNote)[0]; noteInd = this[this.keyboards[unvar.keyboardInd]](noncaps(key), noteBaseInd); maxInstrumentNoteInd = noteBaseInd + notes * step; while (noteInd > maxInstrumentNoteInd) { noteInd -= 2 * 12; } return noteInd; }; this.abc = (key, baseInd) => { var fromLowest, noteInd, stepsFromClosestOctave; fromLowest = key - a; stepsFromClosestOctave = fromLowest % revar.scaleSteps.length; noteInd = 0; noteInd += revar.scaleSteps.slice(0, +stepsFromClosestOctave + 1 || 9e9).reduce(edide.mathOp.sum); noteInd += 12 * Math.floor(fromLowest / revar.scaleSteps.length); return baseInd + noteInd; }; qwertyRows = ['qwertyuiop', 'asdfghjkl', 'zxcvbnm']; qwertyChar = {}; for (rowi = i = 0, len = qwertyRows.length; i < len; rowi = ++i) { row = qwertyRows[rowi]; for (chari = j = 0, len1 = row.length; j < len1; chari = ++j) { char = row[chari]; qwertyChar[char.charCodeAt(0)] = [rowi, chari]; } } this.qwerty = (key, baseInd) => { var charSteps, halfSteps, noteInd, octave, rowchar; if (!(rowchar = qwertyChar[key])) { return; } [row, char] = rowchar; octave = qwertyRows.length - 1 - row + Math.floor(char / unvar.scaleSteps.length); char = char % revar.scaleSteps.length; charSteps = revar.scaleSteps.slice(0, +char + 1 || 9e9).reduce((a, b) => { return a + b; }); halfSteps = 12 * octave + charSteps; return noteInd = baseInd + halfSteps; }; return mmKeyboard; }).call(MODSELF={}, edide, MODSELF); edide.chars = (function (edide, chars) {Object.defineProperty(this, 'module_name', {value:'chars'}); this.space = ' '; this.nonBreakingSpace = ' '; this.enter = "\n"; return chars; }).call(MODSELF={}, edide, MODSELF); edide.showError = (function (edide, showError) { var error; error = (err) => { if (edide.inEditor) { return edide.editorModule.editor_error.show(err); } else if (typeof edide.global.require === 'object' && (edide.global[edide.edideNamespace].prodErrorPrinter != null)) { return edide.global[edide.edideNamespace].prodErrorPrinter.showError(err); } else { return console.error(err); } }; return (err) => { var err2; if (err != null ? err.stack : void 0) { return error(err); // create error to capture stack trace } else { try { throw Error(err); } catch (error1) { err2 = error1; return error(err2); } } }; }).call(MODSELF={}, edide, MODSELF); edide.mmParserSpecial = (function (edide, mmParserSpecial) {Object.defineProperty(this, 'module_name', {value:'mmParserSpecial'}); // parse config { .. } and variable : .. : syntaxes this.config = (trackStr, ts = {}) => { var endInd, err, name; endInd = trackStr.search('}') || trackStr.length; try { ts.conf = JSON.parse(trackStr.slice(0, +endInd + 1 || 9e9)); if (ts.conf.name != null) { ({name} = ts.conf); delete ts.conf.name; edide.mmConfigs.addVar(name, ts.conf); } else { edide.mmConfigs.activate(ts.conf); // add variable if exists } } catch (error) { err = error; ts.skip = true; // could be used to show error highlighting in editor edide.showError(err); // remove this if error highlighting implemented } return endInd; }; // parse variable from curren track row // returns false if parsing fails this.var = (track, trackState) => { var err, i, value, varLength, varName, varStr; ({i} = trackState); varLength = track.slice(i + 1).indexOf(edide.mmKeyboard.special.var); if (varLength === -1) { return false; } varStr = track.slice(i + 1, +(i + varLength) + 1 || 9e9); [varName, value] = varStr.split(' '); if (!edide.mmConfigs.hasVar(varName)) { // TODO: with errors shown in UI the following check could be removed: return false; } try { edide.mmConfigs.activateVar(varName, value); trackState.i += varLength + 2; return true; } catch (error) { err = error; //throw err if edide.inEditor return false; } }; return mmParserSpecial; }).call(MODSELF={}, edide, MODSELF); edide.mmNote = (function (edide, mmNote) {Object.defineProperty(this, 'module_name', {value:'mmNote'}); this.fromChar = (char) => { return this.fromKeyCode(char.charCodeAt(0)); }; this.fromKeyCode = (key) => { var noteInd, ref; if (typeof (noteInd = edide.mmKeyboard.getNoteInd(key)) === 'number') { return (ref = edide.noteFreq.notes[noteInd]) != null ? ref[0] : void 0; } }; return mmNote; }).call(MODSELF={}, edide, MODSELF); edide.mmParser = (function (edide, mmParser) {Object.defineProperty(this, 'module_name', {value:'mmParser'}); var getNoteLength, nonBreakingSpace, processTrack, revar, space, unvar; ({revar, unvar} = edide.mmState); getNoteLength = (row, noteInd) => { var char, length; length = 1; while (char = row[++noteInd]) { if (char === edide.mmKeyboard.special.long) { length++; } else { break; } } return length; }; ({space, nonBreakingSpace} = edide.chars); // Processes one char in a track, and iteratively calls itself if needed. // ccs - "current column state" - inline chord + played? // ts - track state: char index, configs processTrack = (track, ccs, ts) => { var bef, chord, groupingChars, keyCode, lastSpace, note, repeat, repeatStart; if (!track[ts.i]) { return; } if (ts.skip) { // comment or erroneous chars return; } // bracket chords switch (track[ts.i]) { case '{': return ts.i += edide.mmParserSpecial.config(track.slice(ts.i), ts); // parseConfigs case '*': if (ts.repeat != null) { // inside a repeating pattern ts.repeat--; if (ts.repeat > 0) { // still repeats left to do, start from the beginning ts.i = ts.iStart = ts.repeatStart; // repeats done } else { ts.repeat = ts.repeatStart = null; ts.i++; // step over '*' so won't end up starting repeats again } } else { if (isNaN(repeat = parseInt(track.slice(ts.i + 1)))) { ts.i++; // unable to parse number of repeats, skip them } else { // initialize new repeating patter (-1 because repeat pattern already played once) ts.repeat = repeat - 1; // search for beginning of repeat group bef = track.slice(0, ts.i); groupingChars = ['(', space, nonBreakingSpace, '*']; lastSpace = Math.max(...groupingChars.map((c) => { return bef.lastIndexOf(c); })); repeatStart = lastSpace !== -1 ? lastSpace + 1 : 0; // beginning of row if no grouping char found // set track state at the beginning of repeats ts.i = ts.iStart = ts.repeatStart = repeatStart; } } return processTrack(track, ccs, ts); case '[': ccs.chord = []; ts.i++; return processTrack(track, ccs, ts); case ']': ts.i++; ({chord} = ccs); if (!chord) { // illegal - missing beginning [ return processTrack(track, ccs, ts); } ccs.chord = null; if (chord.length) { if (!unvar.preprocessing) { revar.playNote = [chord, getNoteLength(track, ts.i)]; } //edide.mmInstrument.playNote chord, getNoteLength track, ts.i ccs.played = true; } else { processTrack(track, ccs, ts); } return; case edide.mmKeyboard.special.var: if (edide.mmParserSpecial.var(track, ts)) { processTrack(track, ccs, ts); // recursive, since next char could be another var, e.g. \:bass::minor: } else { ts.skip = true; // parse error } return; case edide.mmKeyboard.special.comment: return ts.skip = true; } // TODO: merge switch and bottom code OR put them in a different functions if (!(keyCode = track.charCodeAt(ts.i))) { return; } ts.i++; if (note = edide.mmNote.fromKeyCode(keyCode)) { if (ccs.chord) { ccs.chord.push(note); processTrack(track, ccs, ts); } else { if (!unvar.preprocessing) { revar.playNote = [note, getNoteLength(track, ts.i)]; } //edide.mmInstrument.playNote note, getNoteLength track, ts.i ccs.played = true; } } else if (edide.mmKeyboard.isPauseKey(keyCode)) { ccs.played = true; // "play silence" // meaningless char } else { processTrack(track, ccs, ts); } }; // TODO figure sheetStart != 0 -- maybe makes more sense to remove textarea first this.splitSections = (str) => { var i, j, prevInd, ref, row, section, sections; str = str.replace('&\n', ''); // TODO fix row tracking with &\n sections = []; section = null; row = 0; prevInd = 0; for (i = j = 0, ref = str.length; (0 <= ref ? j <= ref : j >= ref); i = 0 <= ref ? ++j : --j) { if (!(str[i] === '\n' || i === str.length)) { continue; } if (str[i - 1] === '\n') { if (section != null) { sections.push(section); } section = null; } if (i > prevInd) { if (section == null) { section = { row: row, tracks: [] }; } section.tracks.push(str.slice(prevInd, i)); } row++; prevInd = i + 1; } if (section != null) { sections.push(section); } sections.row = 0; return sections; }; this.play = (song, sectionInd, trackStates) => { var ccs, section; if (!unvar.playing) { return; } revar.highlight = null; sectionInd = sectionInd || 0; if (typeof song === 'string') { song = this.splitSections(song); } section = song[sectionInd]; if (!section) { // song finished if (!unvar.preprocessing) { revar.playing = false; } return; } if (!Array.isArray(trackStates)) { trackStates = section.tracks.map(() => { return { i: 0 // initi track indices on first call to section }; }); } // init @play ccs ccs = {}; // chord, played # TODO change back to chord: [C3, D3] and [].played = true // OR: why not just ts.chord? section.tracks.forEach((track, tInd) => { var conf, i, iStart; if (conf = trackStates[tInd].conf) { // config row and already parsed (NOTE: config row can only have one config object and nothing else) return edide.mmConfigs.activate(conf); } else { trackStates[tInd].iStart = trackStates[tInd].i; processTrack(track, ccs, trackStates[tInd]); ({iStart, i} = trackStates[tInd]); if (i > iStart) { return revar.highlight = [section.row + tInd, iStart, i - 1]; } } }); if (ccs.played) { if (unvar.preprocessing) { this.play(song, sectionInd, trackStates); } else { edide.setTimeout(() => { return this.play(song, sectionInd, trackStates); }, unvar.beatDelay); } } else { // using timeout fixes bug where revar.playing is set false in same exec where it's set true (infinite loop) this.play(song, sectionInd + 1); } }; return mmParser; }).call(MODSELF={}, edide, MODSELF); edide.moduleGate = (function (edide, moduleGate) {Object.defineProperty(this, 'module_name', {value:'moduleGate'}); if (!edide.inEditor) { return this; } this.root; // module that is edited in root this.rootName; this.active; // module that is currently being edited (can be root or in window) this.activeName; this.executing; this.executingName; return moduleGate; }).call(MODSELF={}, edide, MODSELF); edide.rejectIfRecompiled = (function (edide, rejectIfRecompiled) { if (!edide.inEditor) { return () => {}; } return (promise) => { var recompiled, rootName; ({rootName} = edide.moduleGate); recompiled = false; edide.editorModule.editor_events.on('before_recompile', () => { return recompiled = true; }); return promise.then(function(arg) { if (recompiled || rootName !== edide.moduleGate.rootName) { // root module changed return Promise.reject(); // quiet rejection; no need to show error } else { return arg; // arg get wrapped in promise } }); }; }).call(MODSELF={}, edide, MODSELF); edide.promise = (function (edide, promise) {Object.defineProperty(this, 'module_name', {value:'promise'}); var localPromise, ref; localPromise = edide.global.origPromise = (ref = edide.global.origPromise) != null ? ref : edide.global.Promise; this.new = function(cb) { if (edide.inEditor) { return edide.rejectIfRecompiled(new Promise(cb)); // don't fire cb if code has been re-executed in the meantime } else { return new Promise(cb); } }; this.all = function(cbArray) { if (edide.inEditor) { // check that after resolved, still editing same module return edide.rejectIfRecompiled(Promise.all(cbArray)); } else { return Promise.all(cbArray); } }; this.resolve = Promise.resolve.bind(Promise); this.reject = Promise.reject.bind(Promise); return promise; }).call(MODSELF={}, edide, MODSELF); edide.qs = (function (edide, qs) { return (selector, el = document) => { return el.querySelector(selector); }; }).call(MODSELF={}, edide, MODSELF); edide.scriptContainer = (function (edide, scriptContainer) { var createContainer, ref; createContainer = () => { var s; s = document.createElement('div'); s.id = 'scripts'; return s; }; return (ref = edide.qs('#scripts')) != null ? ref : document.body.appendChild(createContainer()); }).call(MODSELF={}, edide, MODSELF); edide.scriptAdd = (function (edide, scriptAdd) { return (src, cb) => { var el; el = document.createElement('script'); edide.scriptContainer.appendChild(el); if (cb) { el.onload = cb; } el.type = 'application/javascript'; el.src = src; }; }).call(MODSELF={}, edide, MODSELF); edide.requireScript = (function (edide, requireScript) { var base; if ((base = edide.global).requireScriptPromises == null) { base.requireScriptPromises = new Map; } return (scriptSrc) => { var promise; if (promise = requireScriptPromises.get(scriptSrc)) { return promise; } else { requireScriptPromises.set(scriptSrc, promise = edide.promise.new((resolve) => { return edide.scriptAdd(scriptSrc, resolve); })); return promise.catch((err) => { edide.showError(err); return requireScriptPromises.delete(scriptSrc); }); } }; }).call(MODSELF={}, edide, MODSELF); edide.mmPipe = (function (edide, mmPipe) {Object.defineProperty(this, 'module_name', {value:'mmPipe'}); var dist, lowpass, panner, react, revar, reverber, unvar; ({revar, unvar, react} = edide.mmState.init()); dist = lowpass = reverber = panner = null; this.initPipe = () => { lowpass = new Tone.Filter(unvar.lowpass, 'lowpass', -12); dist = new Tone.Distortion(unvar.distortion); panner = new Tone.Panner(); panner.connect(dist); dist.connect(lowpass); lowpass.toMaster(); this.output = lowpass; revar.pipeReady = true; return edide.onUnload(() => { if (dist != null) { if (typeof dist.dipose === "function") { dist.dipose(); } } if (lowpass != null) { if (typeof lowpass.dispose === "function") { lowpass.dispose(); } } return panner != null ? typeof panner.dispose === "function" ? panner.dispose() : void 0 : void 0; }); }; this.initInstrument = (instrument) => { instrument.connect(panner); edide.onUnload(() => { return instrument != null ? instrument.disconnect() : void 0; }); return instrument; }; revar.panner = () => { return revar.balance; }; react('panner', () => { revar.panner; return panner != null ? panner.pan.value = revar.panner : void 0; }); react('pipe distortion', () => { var ref; revar.distortion; revar.lowpass; if (dist != null) { dist.distortion = revar.distortion; } return lowpass != null ? (ref = lowpass.frequency) != null ? ref.linearRampTo(revar.lowpass, 0) : void 0 : void 0; }); return mmPipe; }).call(MODSELF={}, edide, MODSELF); edide.mmInstruments = (function (edide, mmInstruments) {Object.defineProperty(this, 'module_name', {value:'mmInstruments'}); var defaultUrl = 'https://nbrosowsky.github.io/tonejs-instruments/samples/' var { revar, unvar, react } = edide.mmState var defaultInstrument = 'electric-guitar' this.initInstrument = async (resolve) => { await edide.requireScript('https://cdnjs.cloudflare.com/ajax/libs/tone/13.8.9/Tone.js') edide.mmPipe.initPipe() } var instrumentCache = edide.keep({}) this.createNew = () => { var {name} = unvar.instrumentConf var instrument, endOfPipe var instrumentConf = this.instruments[name] if (instrumentConf.module_name) { var res = instrumentConf.init() if (Array.isArray(res)) { instrument = res[0] endOfPipe = res[1] } else { instrument = endOfPipe = res } } else if (instrumentCache[name]) { instrument = endOfPipe = instrumentCache[name] } else { var noteFiles = buildNotes(instrumentConf) revar.instrumentsLoading++ var inst = new Tone.Sampler(noteFiles, { //edide.mmInstrumentssAll[name] "release" : 1, "baseUrl" : instrumentConf.url || defaultUrl + name + '/', "onload" : () => revar.instrumentsLoading-- }) inst.soundFontInstrument = true instrument = endOfPipe = instrumentCache[name] = inst //[inst, inst] } edide.mmPipe.initInstrument(endOfPipe) return instrument } this.isReady = () => { var { current } = this return current && (typeof current.loaded == 'undefined' || current.loaded === true) } function buildNotes({startNote, notes, step, skipNotes}) { var [startInd] = edide.noteFreq.findNote(startNote) var noteFiles = {} if (Array.isArray(notes)) { notes.forEach(note => { noteFiles[note] = note.replace('#','s') + '.[mp3|ogg]' }) } else { for (let i=0; i < notes*step; i+=step) { let note = edide.noteFreq.notes[startInd + i][0]; if (skipNotes && skipNotes.has(note)) continue noteFiles[note] = note.replace('#','s') + '.[mp3|ogg]' } } return noteFiles } this.instruments = edide.instrumentConfigs this.instrumentList = Object.values(this.instruments) react('active instrument config', () => { revar.instrumentConf = this.instruments[revar.instrument] // conf }) return mmInstruments; }).call(MODSELF={}, edide, MODSELF); edide.cloneSibling = (function (edide, cloneSibling) { return (srcObj) => { var o; o = Object.create(Object.getPrototypeOf(srcObj)); Object.assign(o, srcObj); return o; }; }).call(MODSELF={}, edide, MODSELF); edide.times = (function (edide, times) { return (timesNum, action) => { while (timesNum-- > 0) { action(timesNum + 1); } }; }).call(MODSELF={}, edide, MODSELF); edide.sleep = (function (edide, sleep) { var sleep; sleep = (ms) => { var resolve, timeout; timeout = resolve = null; return edide.promise.new((res) => { resolve = res; return timeout = edide.setTimeout(ms, res); }); }; return sleep; }).call(MODSELF={}, edide, MODSELF); edide.mmInstrument = (function (edide, mmInstrument) {Object.defineProperty(this, 'module_name', {value:'mmInstrument'}); var cloneOrCreateInstrument, getInstruments, instruments, play, react, revar, unvar, updateVolAndTune; instruments = {}; // name: [instrument instances...] ({unvar, revar, react} = edide.mmState); revar.bpm = () => { return revar.pace; }; revar.beatDelay = () => { return (1 / revar.bpm) * 60 * 1000; }; react(() => {}); updateVolAndTune = (inst) => { var detune, volume; ({volume, detune} = unvar); if ((detune != null) && (inst.detune != null)) { inst.set("detune", detune); } if ((volume != null) && (inst.volume != null)) { return inst.set("volume", volume); } }; react(() => { var inst, insts, j, len, name; revar.volume; revar.detune; for (name in instruments) { insts = instruments[name]; for (j = 0, len = insts.length; j < len; j++) { inst = insts[j]; updateVolAndTune(inst); } } }); react('create first instrument', () => { var name, ref; if (!(name = (ref = revar.instrumentConf) != null ? ref.name : void 0)) { // e.g. illegal instrument name return edide.showError(`Unknown instrument: ${unvar.instrument}`); } if (revar.pipeReady && !instruments[name]) { return instruments[name] = [edide.mmInstruments.createNew()]; } }); cloneOrCreateInstrument = (name) => { var inst; inst = instruments[name][0].soundFontInstrument ? (inst = edide.cloneSibling(instruments[name][0]), inst.isPlaying = false, inst) : edide.mmInstruments.createNew(); updateVolAndTune(inst); return inst; }; getInstruments = (n) => { var all, free, name; ({name} = unvar.instrumentConf); all = instruments[name]; free = all.filter((i) => { return !i.isPlaying; }); edide.times(n - free.length, () => { var inst; free.push(inst = cloneOrCreateInstrument(name)); return all.push(inst); }); return free.slice(0, n); }; this.playChord = (chord, noteLength = 1) => {}; this.playNote = (chord, noteLength = 1) => { var ind, inst, insts, j, len, ref; if (!Array.isArray(chord)) { chord = [chord]; } ref = insts = getInstruments(chord.length); for (ind = j = 0, len = ref.length; j < len; ind = ++j) { inst = ref[ind]; play(inst, chord[ind], noteLength); } }; play = async(instrument, note, length) => { var err; instrument.isPlaying = note; // true try { instrument.triggerAttackRelease(note, (length * unvar.beatDelay) / 1000); } catch (error) { err = error; err.message = `Error in playing note ${note}, '${unvar.instrumentConf.name}' probably not loaded yet`; edide.showError(err); } revar.note = note; await edide.sleep(unvar.nextDelay); return instrument.isPlaying = false; }; return mmInstrument; }).call(MODSELF={}, edide, MODSELF); edide.mmPlayer = (function (edide, mmPlayer) {Object.defineProperty(this, 'module_name', {value:'mmPlayer'}); var react, revar, unvar; ({revar, unvar, react} = edide.mmState.init()); this.play = (str) => { revar.sheet = str; return revar.playing = true; }; react('play from sheet', () => { if (!revar.playing) { return; } edide.mmConfigs.init(); revar.preprocessing = true; return edide.setTimeout(() => { // infinite revar.playing loop edide.mmParser.play(unvar.sheet); return revar.preprocessing = 'done'; }); }); react('play notes/chords', () => { var chord, playNote, playing, preprocessing, time; ({playNote, playing, preprocessing} = revar); if (!(playNote && playing && preprocessing === false)) { return; } [chord, time] = revar.playNote; return edide.mmInstrument.playNote(chord, time); }); this.toggle = () => { return revar.playing = !unvar.playing; }; revar.instrumentsLoading; react('play after preprocess', () => { var instrumentsLoading, preprocessing; ({instrumentsLoading, preprocessing} = revar); if (!(preprocessing === 'done' && instrumentsLoading === 0)) { return; } unvar.preprocessing = false; edide.mmConfigs.reset(); return edide.mmParser.play(unvar.sheet); }); return mmPlayer; }).call(MODSELF={}, edide, MODSELF); edide.waves = (function (edide, waves) {Object.defineProperty(this, 'module_name', {value:'waves'}); edide.global.music = this edide.mmEffects var { revar, unvar, react } = edide.mmState var playQueue = [] this.play = (str) => { if (!unvar.pipeReady) return playQueue.push(str) revar.playing = true edide.mmPlayer.play(str) } this.stop = () => revar.playing = false edide.mmInstruments.initInstrument() react(() => { if(!revar.pipeReady) return edide.mmConfigs.reset() this.play(playQueue.join("\n\n")) // JSON.stringify(initialSettings) }) return waves; }).call(MODSELF={}, edide, MODSELF); edide.wave = (function (edide, wave) { var parent = { play: function(){ edide.waves.play(this.sheet) return this }, stop: function(){ edide.waves.stop(this.sheet) return this }, blur: function(val){ this.sheet = `:blur ${val}:` + this.sheet return this }, itch: function(val){ this.sheet = `:itch ${val}:` + this.sheet return this }, long: function(val){ // note delay in seconds this.sheet = `:beatDelay ${val*1000}:` + this.sheet return this }, pace: function(val) { // note delay in beats per minute this.sheet = `:pace ${val}:` + this.sheet return this }, vary: function(val){ this.sheet = `:detune ${val}:` + this.sheet return this }, loud: function(val){ // increase (>0) or decrease (<0) amplitude in decibels this.sheet = `:volume ${val}:` + this.sheet return this }, balance: function(val){ this.sheet = `:balance ${val}:` + this.sheet return this } } return edide.global.wave = (str) => { edide.mmConfigs.reset() var obj = Object.create(parent) obj.sheet = str return obj } }).call(MODSELF={}, edide, MODSELF); edide.global.wave = edide.wave; })()