// console.clear(); var APP = {}; document.documentElement.addEventListener('mousedown', () => { if (Tone.context.state !== 'running') Tone.context.resume(); }); // There's a bug that happens occasionally. Intercept it. // a seed that triggers it: d12d21ddd window.ABCJS.parse.each = function(a, d, c) { var e = (a === undefined) ? 0 : a.length; // for (var b = 0, e = a.length; b < e; b++) { // ^^ this is the code that gets around it for (var b = 0; b < e; b++) { d.apply(c, [a[b], b]) } } document.getElementById("load").addEventListener("click", function(e) { var seed = document.getElementById("seed").value; if(seed) { // if(seed.match(/^[A-Za-z0-9]+$/g)) { document.getElementById("intro").style.display = "none"; APP.MusicGenerator.init(seed); document.getElementById("play").focus(); document.getElementById("play").addEventListener("click", function(e) { var $el = e.target; if($el.getAttribute("data-playing") === "true") { APP.MusicGenerator.pause(); $el.setAttribute("data-playing", "false"); $el.innerHTML = "Play"; APP.MusicGenerator.report(); } else { APP.MusicGenerator.play(); $el.innerHTML = "Pause"; $el.setAttribute("data-playing", "true"); } }); // } else { // alert("Letters and numbers only"); // } } else { alert("Please enter a seed into the input"); } }); (function() { APP.MusicGenerator = {}; var _v_key = "WeAddaBabyEetsABoy"; // change this to refresh the seed var _roots = "C Db D Eb E F F# G Ab A Bb".split(" "); var _modes = "maj min".split(" "); var _hold = "HOLD"; var _container = document.getElementById("staff-container"); var _staff = document.getElementById("staff"); var _midi = document.getElementById("midi"); var _min_oct = 2; var _max_oct = 5; var _playing = false; var _res = 8; var _curr_meas = 0; var _curr_note = 0; var _scale = []; var _measures = []; var _abc = ""; var _total_abc = ""; var _treb_abc = ""; var _bass_abc = ""; var _gen_meas = 0; var _meas_gen = 4; var _first_one = true; var _bpm = null; var _generator = null; var _len_seqs = null; var _mode = null; var _output = null; var _root = null; var _seed = null; var _synth = null; APP.MusicGenerator.init = function(seed) { _staff.innerHTML = ""; _seed = seed + _v_key; _setGenerator(_seed); _setKey(); if(!_synth) _setSynth(); _setTransport(); _genStaff(); _renderStaff(); _genLengthSequences(); _genScale(); _addMeasure(_meas_gen); }; APP.MusicGenerator.play = playPlayer; APP.MusicGenerator.pause = pausePlayer; APP.MusicGenerator.report = reportData; ////////////// // PUBLIC METHODS ////////////// // play the player function playPlayer() { _midi.innerHTML = ""; Tone.Transport.start(); } // pause the player function pausePlayer() { Tone.Transport.stop(); } // reporting the data function reportData() { _output = { root: _root, mode: _mode, resolution: _res, scale: _scale, measures: _measures, abc: _total_abc }; ABCJS.renderMidi(_midi, _total_abc , {}, {}, {}); console.log(_total_abc); console.log(_output); } ////////////// // PRIVATE METHODS ////////////// // get a unique random number generator based on the seed function _setGenerator(seed) { _generator = new Math.seedrandom(seed); } // get randomly generated key and mode function _setKey() { _root = _roots[Math.floor(_generator.quick() * _roots.length)]; var mode_index = Math.floor(_generator.quick() * _modes.length); _mode = _modes[mode_index]; } // play the bass and treble notes function _playSong() { var measure = _measures[_curr_meas]; if(measure) { var treb = measure.treb[_curr_note]; var bass = measure.bass[_curr_note]; if(treb.chord) { _synth.triggerAttackRelease(treb.chord, treb.len + "n"); } if(bass.chord) { _synth.triggerAttackRelease(bass.chord, bass.len + "n"); } if(_curr_note + _curr_meas === 0) { var hidden = document.querySelector(".hide"); if(hidden) hidden.className = ""; } _curr_note++; if(_curr_note >= _res) { _curr_note = 0; _curr_meas++; if(_gen_meas % _meas_gen === 0) { _addMeasure(_meas_gen); } else if (_gen_meas % _meas_gen === 3) { // scroll to the bottom of container var hidden = document.querySelector(".hide"); if(hidden) hidden.className = ""; _utilScrollTo(_container, _container.scrollHeight, 500); } _gen_meas++; } } else { pausePlayer(); } } // add a measure to the sequence function _addMeasure(how_many, loaded) { loaded = loaded || 0; var get_measure = _getMeasure(); get_measure.then(function(measure) { loaded++; // console.log("_getMeasure", measure); _measures.push(measure); _staffABC(measure); if(loaded < how_many) { _addMeasure(how_many, loaded); } else { _renderStaff(); } }); } // generate a measure's worth of notes function _getMeasure() { return new Promise(function(resMeasure, rejMeasure) { var data = { scale: _scale.length / 2, bass: { max_notes: 1, offset: 0 }, treb: { max_notes: 3, offset: Math.floor(_scale.length / 2) } }; var measure = { bass: [], treb: [] }; // for each clef var get_treb = _getClef('treb', data); get_treb.then(function(t) { // console.log("_getClef: t", t); measure.treb = measure.treb.concat(t); var get_bass = _getClef('bass', data); get_bass.then(function(b) { // console.log("_getClef: b", b); measure.bass = measure.bass.concat(b); resMeasure(measure); }, function(b) { rejMeasure(b); }); }, function(t) { rejMeasure(t); }); }); } // generate notes for a clef function _getClef(clef_name, data) { return new Promise(function(resClef, rejClef) { var _clef_generator = new Math.seedrandom(_generator.int32() + ""); // find a sequence var seq = _len_seqs[Math.floor(_clef_generator.quick() * _len_seqs.length)]; // shuffle the sequence order _utilShuffleArray(seq); // set the data var max_notes = data[clef_name].max_notes; var note_offset = data[clef_name].offset; var chord_promises = []; // for each length in sequence for(var s = 0; s < seq.length; s++) { // generate x notes for a random bass count. no rests. var note_count = Math.ceil(_clef_generator.quick() * max_notes); // generate random note length var note_length = seq[s]; chord_promises.push(_getChord(_clef_generator, data.scale, note_offset, note_count, note_length)); } Promise.all(chord_promises).then(function(chords) { var clef = []; // console.log("_getChord: promises", chords); for(var i = 0; i < chords.length; i++) { var chord = chords[i]; clef.push(chord); if(chord.len) { var chord_length_i = _res / chord.len; for(var e = 0; e < chord_length_i - 1; e++) { clef.push(_hold); } } } resClef(clef); }, function(chords) { rejClef(chords); }); }); } // generate the chord notes function _getChord(_clef_generator, scale, offset, note_count, note_length) { return new Promise(function(resNotes, rejNotes) { // used note indexes var used_note_indexes = []; var note_promises = []; // generate the notes for(var i = 0; i < note_count; i++) { note_promises.push(_getNote(_clef_generator, scale, offset, used_note_indexes)); } Promise.all(note_promises).then(function(chord) { // console.log("_getNote: promises", chord); var abc = _getABC(chord, note_length); // add notes to measure resNotes({ abc: abc, chord: chord, len: note_length }); }, function(chord) { rejNotes(chord); }); }); } // generate a note function _getNote(_clef_generator, scale, offset, used_note_indexes) { return new Promise(function(resNote, rejNote) { // add the note to the chord resNote(_note()); function _note() { // get a random index var index = Math.floor(_clef_generator.quick() * scale) + offset; // for measuring note is a "good" one. var good_note = true; // ensure we havent selected this index if(used_note_indexes.indexOf(index) !== -1) good_note = false; // or one below it if(index > 0 && used_note_indexes.indexOf(index - 1) !== -1) good_note = false; // or one above it if(used_note_indexes.indexOf(index + 1) !== -1) good_note = false; // if we found a good note if(good_note) { // add the index to the used array used_note_indexes.push(index); // grab the note var note = _scale[index]; // build the tone version of the note var n = note.note + note.octave; return n; } else { return _note(); } } }); } ////////////// // ABC MUSICAL NOTATION ////////////// // get abc notation for notes at a certain length function _getABC(notes, len) { if(!len) return ""; var str = notes.length > 1 ? "[" : ""; for(var n = 0; n < notes.length; n++) { str += _ABCNotation(notes[n], len); } str += notes.length > 1 ? "]" : ""; return str; } // render the current abc to staff function _renderStaff() { var $par = document.createElement("div"); if(_first_one) { _first_one = false; } else { $par.className = "hide"; } var $el = document.createElement("div"); // beats per second times four beats * measures per line * 1 second $par.appendChild($el); _staff.appendChild($par); _abc += _treb_abc; _abc += "

" + _bass_abc; var cleaned = _abc; _total_abc += cleaned; _abc = _genStaffHeader(); _treb_abc = "[V: 1] "; _bass_abc = "[V: 2] "; _renderABC($el, cleaned); } function _renderABC(el, abc) { console.log(abc); ABCJS.renderAbc(el, abc, {}, { staffwidth: 800, paddingright: 0, paddingleft: 0, scale: 1, add_classes: true }, {}); } // take a measure and generate the abc output function _staffABC(measure) { var abc = ""; // TODO: every two measures new line with _gen_meas, _treb_abc, _bass_abc for(var i = 0; i < measure.treb.length; i++) { if(measure.treb[i].abc) { _treb_abc += measure.treb[i].abc + " "; } } for(var i = 0; i < measure.bass.length; i++) { if(measure.bass[i].abc) { _bass_abc += measure.bass[i].abc + " "; } } _treb_abc = _treb_abc.replace(/ $/g, "|"); _bass_abc = _bass_abc.replace(/ $/g, "|"); // abc += [treb, bass].join("

"); // return abc; } // set the initial staff function _genStaff() { _abc = [ "M:/4/4", "O:I", "R:R", "Q:1/4=" + _bpm, "", "X:2", "T:" + _seed.replace(_v_key, ""), "C:" + _root.replace(/b$/g, "♭") + " " + _mode + ", " + _bpm + " bpm", ].join("

"); } // generate the staff header function _genStaffHeader() { return [ "K:" + _root + _mode, "L:1/" + _res, "%%staves {1 2}", "V: 1 clef=treble", "V: 2 clef=bass", "" ].join("

"); } // abc notation function _ABCNotation(note, length) { // note = note.replace(/([A-G])b/,"_$1"); // TODO: know if this is needed based on mode. note = note.replace(/([A-G])b/,"$1"); note = note .replace(/(.)1/, "$1,,,") .replace(/(.)2/, "$1,,") .replace(/(.)3/, "$1,") .replace(/(.)4/, "$1"); if(note.match(/(.)5|6/)) { note = note .replace(/(.)5/, "$1") .replace(/(.)6/, "$1'") .toLowerCase(); } note += _res / length; return note; } ////////////// // DATA ////////////// // generate the scale function _genScale() { // generate the scale var _steps_lib = _genStepsLib(); var _notes_lib = _genNotesLib(); var steps = _steps_lib; var start = _notes_lib.indexOf(_root + _min_oct); var last_note = (_max_oct - _min_oct) * 8; for(var o = 0; o < last_note; o++) { var index = start + steps[o % steps.length] + (Math.floor(o / steps.length) * 12); var note = _notes_lib[index]; if(note) { _scale.push({ note: note.match(/[^\d]+/)[0], octave: note.match(/\d/)[0] }); } } if(!_scale.length) console.error("Too high up. Lower the octave or note count."); } // potential combinations of note lengths // this could be done programmatically. function _genLengthSequences() { _len_seqs = [ [1], [2,2], [2,4,4], [2,4,8,8], [4,4,4,4], [2,8,8,8,8], [4,4,4,8,8], [8,8,8,8,8,8,8,8], // [2,4,8,16,16], // [2,4,16,16,16,16], // [2,8,8,8,16,16], // [2,8,8,16,16,16,16], // [2,8,16,16,16,16,16,16], // [2,16,16,16,16,16,16,16,16], // [4,4,4,8,16,16], // [4,4,4,16,16,16,16], // [4,4,8,8,16,16,16,16], // [4,4,8,16,16,16,16,16,16], // [4,4,16,16,16,16,16,16,16,16], // [4,8,8,16,16,16,16,16,16,16,16], // [4,8,16,16,16,16,16,16,16,16,16,16], // [4,16,16,16,16,16,16,16,16,16,16,16,16], // [8,8,8,8,8,8,8,16,16], // [8,8,8,8,8,8,16,16,16,16], // [8,8,8,8,8,16,16,16,16,16,16], // [8,8,8,8,16,16,16,16,16,16,16,16], // [8,8,8,16,16,16,16,16,16,16,16,16,16], // [8,8,16,16,16,16,16,16,16,16,16,16,16,16], // [8,16,16,16,16,16,16,16,16,16,16,16,16,16,16], // [16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16], ]; } // set the notes for the key function _genStepsLib() { // generate major and minor integer step arrays from whole/half notation // whole/half note steps for minor and major var mode = { // ionian: "W W H W W W H", // dorian: "W H W W W H W", // phrygian: "H W W W H W W", // lydian: "W W W H W W H", // mixolydian: "W W H W W H W", // aeolian: "W H W W H W W", // locrian: "H W W H W W W", maj: "W W H W W W H", min: "W H W W H W W", }[_mode].split(" "); // start each with root step var steps = [0]; // loop through var step = 0; for(var s = 0; s < mode.length; s++) { var key = mode[s]; if(key === "W") { steps.push(steps[s] + 2); } else if(key == "WH") { steps.push(steps[s] + 3); } else { steps.push(steps[s] + 1); } step++; } return steps; } // generate every possible note/octave pairing in order function _genNotesLib() { var notes = []; for(var i = 0; i < 9; i++) { var n = "C Db D Eb E F Gb G Ab A Bb B".split(" "); for(var x = 0; x < n.length; x++) notes.push(n[x] + i); } return notes; } ////////////// // TONE.js ////////////// // set the tone js transport function _setTransport() { _bpm = Math.round(_generator.quick() * 60) + 60; Tone.Transport.bpm.value = _bpm; Tone.Transport.scheduleRepeat(function(time) { _playSong(); }, _res + "n"); } // set the synthesizer function _setSynth() { // master compression var multiband = new Tone.MultibandCompressor({ lowFrequency: 200, highFrequency: 1300, low: { threshold: -12 } }); var reverb = new Tone.Freeverb(); reverb.roomSize.value = 0.6; reverb.wet.value = 0.2; Tone.Master.chain(reverb, multiband); // _synth = new Tone.PolySynth(6, Tone.SimpleAM).toMaster(); _synth = new Tone.PolySynth(6, Tone.SimpleSynth).toMaster(); _synth.set({ oscillator: { type: "triangle" }, envelope: { attack: 0.05, decay: 0.1, sustain: 0.3, release: 1 } }); // _synth.set({ // envelope: { // attack: 0.5, // release: 0.5 // } // }); // _synth.set("carrier", { // volume: -8, // oscillator: { // type: "triangle8", // partials: [1, 0.2, 0.01] // }, // envelope: { // attack: 0.05, // decay: 0.02, // sustain: 0.6, // release: 0.8 // } // }); // _synth.set("modulator", { // volume: -16, // oscillator: { // type: "triangle8", // detune: 0, // phase: 90, // partials: [1, 0.2, 0.01] // }, // envelope: { // attack: 0.05, // decay: 0.01, // sustain: 1, // release: 1 // } // }); } ////////////// // UTILS ////////////// // shuffle an array // http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array function _utilShuffleArray(array) { var currentIndex = array.length, temporaryValue, randomIndex; // While there remain elements to shuffle... while (0 !== currentIndex) { // Pick a remaining element... randomIndex = Math.floor(_generator() * currentIndex); currentIndex -= 1; // And swap it with the current element. temporaryValue = array[currentIndex]; array[currentIndex] = array[randomIndex]; array[randomIndex] = temporaryValue; } return array; } function _utilScrollTo(element, to, duration) { if (duration <= 0) return; var difference = to - element.scrollTop; var perTick = difference / duration * 10; setTimeout(function() { element.scrollTop = element.scrollTop + perTick; if (element.scrollTop === to) return; _utilScrollTo(element, to, duration - 10); }, 10); } }());

!