Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <header id="intro">
  <section>
    <h1>Procedurally Generated Music</h1>
    <p>
      This will infinitely generate a random song based on the <em>seed</em> that you provide it with. 
      Procedural generation ensures that the song will always be the exact 
      same song for the seed data you provide, but will be unique to any other seed.
    </p>
    <p>
      Type anything into the input below and hit "Load" to generate and hear your song.
    </p>
    <input id="seed" type="text" placeholder="Seed" />
    <button id="load">Load</button><br>
    <small>
      You can download a MIDI file of whatever you have generated in the browser when you hit "Pause". 
      To import into Ableton, you may need to rename the extension to ".mid". 
      The conversion is handled by ABCjs, and the midi accuracy varies with different software.
    </small>
  </section>    
</header>

<main>
  <button id="play">Play</button><br>
  <p id="midi"></p>
  <section id="staff-container">
    <div id="staff"></div>
  </section>
</main>
              
            
!

CSS

              
                $staff-w: 800px;
$staff-h: 220px;

header {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  z-index: 9;
  background: rgba(0,0,0,0.6);
  
  section {
    position: absolute;
    border-radius: 2px;
    width: 90%;
    max-width: 600px;
    top: 50%; left: 50%;
    background: white;
    color: #222;
    padding: 2rem;
    transform: translate(-50%, -50%);
    text-align: center;
  }
  
  h1 { margin-top: 0; }
  p { text-align: left; }
  small {
    margin-top: 1rem;
    color: #CCC;
    display: inline-block;
  }
}

#staff-container {
  width: $staff-w;
  overflow: auto;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 0 4rem;
  background-color: #FFF;
  border-radius: 4px;
  box-shadow: 0px 8px 16px rgba(0,0,0,0.1);
}

input, button {
  border-radius: 2px;
  line-height: 1.4;
  margin: 0; 
  padding: 0.25em 0.5em;
}

input {
  border: 1px solid darken(#FFF, 10%);
  width: 280px;
}
button {
  background: #444;
  color: white;
  border: 1px solid darken(#444, 10%);
}

#play, #midi {
  position: absolute;
  top: 1rem;
  z-index: 8;
}  
#play {
  left: 50%;
  transform: translateX(-50%);
}
#midi {
  right: 1rem;
  text-align: right;
  margin: 0;
  // ABCJS gives us an embed player that requires quicktime plugin. gross.
  embed { position: absolute; top: -99999px; opacity: 0; pointer-events: none; }
  a {
    color: #444;
    text-transform: uppercase;
    font-size: 0.8em;
    letter-spacing: 0.0125em;
  }
}


#staff {
  height: $staff-h * 2;
  
  > div {
    box-sizing: border-box;
    height: $staff-h;
    display: flex;
    opacity: 1;
    transition: opacity 250ms ease-out;
    &:not(:nth-child(2)) path.symbol.l0 { opacity: 0; }
    &:not(:nth-child(2)) { transition-delay: 250ms; }
    &.hide { opacity: 0; }
    &:first-child { 
      border-bottom: 1px solid #F0F0F0; 
    }
    &:first-child > div { align-self: flex-end; }
  }
}

@for $i from 3 through 6 {
  @media (min-height: #{$i * $staff-h * 1.2}) {
    #staff {
      height: $staff-h * $i;
    }
    
  }
}

html, body {
  height: 100%;
}

body {
  background-color: #F0F0F0;
  color: #222;
  line-height: 1.7;
}
              
            
!

JS

              
                // 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 += "\n" + _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("\n");
    // 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("\n");
  }
  
  // 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("\n");
  }

  // 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);
  }


}());


              
            
!
999px

Console