<header>
  <h1>Random Music</h1>
  <p>Using the characteristics of <em>Auld Lang Syne</em></p>
</header>

<section id="staff">
</section>

<div class="triggers">
  <a id="play">
    <span class="play"></span>
    <span class="stop"></span>
  </a>
  <a id="new">New Song</a>
</div>

<article>
  
  <h1>Why this exists</h1>
  
  <p>After trying my hand at <a href="//codepen.io/jakealbaugh/pen/EaypRL" target="blank">random beats</a>, I finally summoned the courage to attempt random music. This is a program through which you can pass fairly arbitrary statistical data about a piece of music and then get a response with a random composition based on the provided data. The result can often seem patternless, but occasionally it creates peieces with a clear theme. The dots above are in fact two different clef's, with three octaves worth of intervals distributed vertically inside.</p>

  <h1>How it works</h1>
  
  <h2>Interpretting a written piece of music</h2>  
  <p>This program relies on source data. I decided that the source material for this demo would be <a href="http://www.8notes.com/scores/1476.asp" target="blank">Auld Lang Syne</a> for the sake of the new year. I decided the best way to interpret the source material would be to do it as abstractly as possible in an effort to achieve a truly unique result. It is for that reason, I chose to measure it in the following ways.</p>
  
  <h3>Piece Structure</h3>
  <p>The global values captured for the piece of music are fed in to the program in the following format:</p>
  <pre>piece = {
  measures:    16 <em># measures in analyzed piece</em>
  beats:       4  <em># beats per measure in analyzed piece</em>
  treble_clef: {} <em># all the data for the treble clef</em>
  bass_clef:   {} <em># all the data for the bass clef</em>
  composition: {} <em># all the data for our output</em>
}</pre>
  
  <h3>Clef-specific</h3>
  <p>To maintain abstract specificity, I measure the following data per clef and store it in <code>piece.[clefname]_clef</code>.</p>
  
  <h3>Clef Values (note length)</h3>
  <p>Values are the number of occurances of each <code>value</code> (note length) in the piece. Note: I chose to disregard triplets for now.</p>
  <pre>values: {
  1:   0  <em># whole (occurs 0 times)</em>
  1.5: 4  <em># dotted half (occurs 4 times)</em>
  2:   5  <em># half</em>
  3:   12 <em># dotted quarter</em>
  4:   28 <em># quarter</em>
  6:   0  <em># dotted</em>
  8:   12 <em># eighth</em>
  12:  0  <em># dotted sixteenth</em>
  16:  0  <em># sixteenth</em>
  32:  0  <em># thirty-second</em>
}</pre>
  
  <h3>Clef Intervals</h3>
  <p><code>Intervals</code> are the number of occurances of each <code>interval</code> relative to the key. For example, if the key was F, the first interval <code>0</code> would be F, the second interval <code>1</code> would be F#, and so on. In an effort to stay abstract, this measurement does not care about octaves. A low and a high F would both count towards <code>0</code>. In the same way, a <code>chord</code> (multiple notes) would count for each note in the chord's interval.</p>
  <pre>intervals: {
  0:  32 <em># root / perfect unison (F)</em>
  1:  0  <em># minor second (F#)</em>
  2:  9  <em># major second (G)</em>
  3:  0  <em># minor third (G#)</em>
  4:  13 <em># major third (A)</em>
  5:  0  <em># perfect fourth (A#)</em>
  6:  6  <em># tritone (B)</em>
  7:  13 <em># perfect fifth (C)</em>
  8:  0  <em># minor sixth (C#)</em>
  9:  15 <em># major sixth (D)</em>
  10: 0  <em># minor seventh (D#)</em>
  11: 12 <em># major seventh (E)</em>
}</pre>
  
  <h3>Clef Octaves</h3>
  <p><code>Octaves</code> are the number of occurances of each <code>octave</code> relative to the key. For example, in the key of F the F on the clef is the start of the first octave (filed below as <code>2</code>), anything over twelve intervals away would be classified as an octave up (<code>3</code>) and anything below it would be an octave down (<code>1</code>).</p>
  <pre>octaves: {
  1: 37 <em># clef - 1</em>
  2: 67 <em># clef</em>
  3: 1  <em># clef + 1</em>
}</pre>
  
  <h3>Clef Chords</h3>
  <p>In this program, any note(s) occuring in the clef are classified as a <code>chord</code> (even if a single note). <code>Chords</code> are the number of occurances of each <code>chord</code> size.</p>
  <pre>chords: {
  1: 9  <em># 1 note</em>
  2: 47 <em># 2 notes</em>
  3: 0  <em># 3 notes</em>
  4: 0  <em># 4 notes</em>
  5: 0  <em># 5 notes</em>
}</pre>
  
  
  <h2>Asking for a unique composition</h2>
  <p>Once we have all the data ready for processing, we must tell the program what we want in return. We can specify <code>measures</code>, <code>beats</code> per measure, <code>tempo</code>, <code>resolution</code>, and the <code>root</code> of the key (relative to C).</p>
  
  <pre>composition: {
  measures:   32  <em># bars to generate</em>
  beats:      4   <em># beats per measure</em>
  tempo:      120 <em># tempo</em>
  resolution: 16  <em># resolution scale of piece  </em>
  root:       5   <em># root of key (0-11), 0 is 'C'</em>
}</pre>
  
  <h2>Silence / Noise</h2>
  <p>The first thing that must be done when creating a <code>sequence</code> is to calculate a <code>sound_ratio</code>, which I am defining as the <code>amount of sound</code> <code>/</code> <code>(amount of sound + amount of silence)</code>. This allows us to randomly choose to play a note at each step of the grid before we can select <code>chords</code>, <code>values</code>, <code>intervals</code>, and <code>octaves</code>.</p>

  <h2>Selecting Anything Randomly</h2>
  <p>The program takes our count data and creates <code>chance_packages</code> which are used to select each variable. An example of a <code>chance_package</code> output looks something like this:</p>
  <pre>{
  chances: [16000, 100000] 
  vals: [1, 2]
}</pre>  
  <p>A random number between 0 and 100000 is generated. Then using that number, the program finds the first <code>chance</code> in the package's <code>chances</code> that is greater than our number, grabs its index in the array, and then selects the <code>val</code> in the <code>vals</code> array with that index. In the above case, a random number <code>17000</code> would return <code>2</code> from the <code>vals</code> array and <code>15000</code> would return with a <code>1</code>.</p>
  
  <h2>Building a chord</h2>
  <p>First, we randomly calculate the size of <code>chord</code> to use. Say that we got a <code>2</code>. We would then randomly select a <code>value</code> (note length) and two <code>intervals</code>. When each <code>interval</code> is generated, we randomly select an <code>octave</code> for that <code>interval</code>, relative to the key. From there, we need a <code>frequency</code> value (measured in Hz) for the <code>Oscillator</code> to play.</p>
  <p>Frequency values can be calculated using the speed of sound and a constant, but I elected to just find the values online and turn the data into an array of arrays, one for each <code>octave</code> (10). Then, using the clef's <code>base_octave</code> value, we can select the right <code>octave</code> array in the <code>frequency</code> array and then select the right <code>interval</code> from that <code>octave</code> array.</p>
  
  <h2>The Performance</h2>
  <p>I already went into detail on the same general concept for how the step sequencer works in my <a href="//codepen.io/jakealbaugh/pen/EaypRL" target="blank">random beats</a> pen, and I feel like it would not make sense to copy and paste most of it here.</p>
    
  <h1>Improvements</h1>
  <p>Right now this thing requires a lot of input if you want to have it interpret another song. It would be awesome if it could read MIDI data and perform its randomization. I'm also planning on building another version that takes influence inputs via the DOM instead of a hard-coded analysis of an existing piece of music.</p>
  <p>I'm looking into how to optimize these web Oscillators more effectively, specifically with layering, as well as tone parameters like attack, decay, etc. Any hints there would be fantastic.</p>
  <p>The graphic representation isn't fully accurate yet. There are a few cases in which the drawn notation is improperly placed.</p>
  
</article>
@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,300italic|Inconsolata);

$c-primary: #545454;
$c-dark: darken($c-primary,10%);
$c-black: darken($c-dark,20%);
$c-med: lighten($c-primary,10%);
$c-gray: #c0c0c0;
$c-light: #f0f0f0;
$c-white: #ffffff;
$c-off-white: transparentize($c-white, 0.8);
$c-barely-white: transparentize($c-white, 0.9);

$clef_h: 100px;
#staff {
  height: $clef_h * 2;
  width: 95%;
  margin: 4em auto;
  position: relative;
  .clef {
    height: $clef_h;
    width: 100%;
    position: relative;
    margin-bottom: 24px;
    .beat {
      display: block;
      position: absolute;
      top: 0; bottom: 0; 
      transition: background 200ms;
      &.sus {
        z-index: 1;
      }
      &.chord {
        z-index: 2;
        font-size: 10px;
        $note-di: 4px;
        .note {
          transition: transform 500ms, background 100ms;
          position: absolute;
          left: 50%;
          transform: translate3d(-50%, -50%, 0) scale(1);
          background-color: $c-white;
          width: $note-di;
          height: $note-di;
          border-radius: 50%;
          @for $i from 0 through 36 {
            &.note-#{$i} {
              $ratio: $i / 36;
              top: $ratio * 100%;
              background-color: hsl($ratio * 360, 50%, 40%);  
            }
          }
        }
      }
      &.active {
        transition-duration: 50ms;
/*         background: $c-barely-white; */
        z-index: 9;
        .note {
          transform: translate3d(-50%, -50%, 0) scale(2);
          @for $i from 0 through 36 {
            &.note-#{$i} {
              $ratio: $i / 36;
              background-color: hsl($ratio * 360, 100%, 60%);  
            }
          }          
        }
      }
    }
  }
}

.triggers {
  width: 100%;
  text-align: center;
  $button-di: 40px;
  a {
    color: $c-white;
    font-weight: 100;
    cursor: pointer;
    display: block;
    margin: 0 auto;
    text-transform: uppercase;
    opacity: 0.8;
    width: 100px;
    transition: opacity 200ms;
    font-size: 2em;
    &.inactive {
      opacity: 0.3; 
      pointer-events: none;      
    }
    &:hover, &:active {
      opacity: 1;
    }
    &#play { 
      width: $button-di;
      height: $button-di;
      line-height: $button-di;
      border-radius: 50%;
      background-color: $c-off-white;
      margin-bottom: 12px;
      position: relative;
      .play, .stop {
        display: block;
        position: absolute;
        top: 50%;
        left: 50%;
      }
      .play {         
        border-left: 8px solid $c-white;
        border-top: 8px solid transparent;
        border-bottom: 8px solid transparent;
        transform: translate3d(-40%, -50%, 0);
      }      
      .stop {
        background-color: $c-white;
        height: 10px;
        width: 10px;
        transform: translate3d(-50%, -50%, 0);
        display: none;
      }      
      &.playing {
        .play { display: none;}      
        .stop { display: block;}
      }
    }
    &#new {
      font-size: 0.875em;
    }
  }
}


body {
  background-color: $c-black;
  font-family: 'Source Sans Pro';
  font-weight: 300;
  color: $c-white;
  header, article, #staff {
    transition: opacity 300ms;
  }
  #staff {
    opacity: 0.5;
  }
  &.playing {
    header, article {
      opacity: 0.4;
    }
    #staff {
      opacity: 1;
    }
  }
}
pre {
  font-family: 'Inconsolata';
  font-size: 0.9em;
  background-color: $c-barely-white;
  box-sizing: border-box;
  padding: 12px;
  em {
    font-style: normal;
    color: darken($c-white,70%);
  }
}
code {
  font-family: 'Inconsolata';
  font-size: 0.9em;
  padding: 6px;
  white-space: nowrap;
  background-color: $c-off-white;
}
h1, h2, h3 {
  font-weight: 300;
  text-transform: uppercase;
  letter-spacing: 0.125em;
}
h1 {
  margin-bottom: 0.25em;
}
h1 { font-size: 1.4em; }
h2 { font-size: 1.1em; }
h3 { font-size: 0.9em; }
h1, h2 {
  text-align: center;
}

header {
  text-align: center;    
  margin-top: 4em;
  h1, p {
    margin: 0;
  }
  p {
    margin-top: 0.5em;
    color: $c-light;
  }
}

article {
  width: 90%;
  max-width: 600px;
  margin: 8em auto;
  line-height: 1.8;
  text-align: justify;
  text-justify: inter-word;
  h1,h2 {
    margin-top: 1em;
  }
  h1, h2, h3 {
    margin-bottom: 0;
    & + p {
      margin-top: 0;
    }
  }
  a {
    color: $c-white;
  }
}
View Compiled
###
///
random music generator
jake albaugh
///
###



###
# globals
###

# generate a package of chances and values based on an object containing multiple possible zero values
ChancePkg = (vals) ->
  # generating vals and their chances of occuring
  new_vals = []
  # get total number of vals    
  vals_count = 0
  for value of vals
    if vals.hasOwnProperty(value) && vals[value] > 0
      vals_count += vals[value]
      new_vals.push [value, vals[value]]
  # go through new vals and generate chance for each value
  new_vals_chances = []  
  total = 0  
  for val, i in new_vals
    # relative amount for random selection    
    amount = total + ( (val[1] / vals_count ) * 100000)
    # push amount into chances array    
    new_vals_chances.push amount    
    # set value to be the value, no longer an array of data    
    new_vals[i] = parseInt(val[0])
    # increase base total    
    total = amount  
  # return package which has array for both non-zero vals and their corrosponding chance
  return {chances: new_vals_chances, vals: new_vals}



# get the sound to silence ratio at the smallest scale
getSoundRatio = (measures, beats, values) ->
  # generating vals and their chances of occuring
  new_values = []
  # get total number of vals    
  values_count = 0
  for value of values
    if values.hasOwnProperty(value) && values[value] > 0
      values_count += values[value]
      new_values.push [value, values[value]]  
  # resolution is the smallest sized note  
  resolution = (beats * (new_values[new_values.length - 1][1] / 4))
  # possible is how many resolution sized notes are in the piece  
  possible = measures * (beats * (resolution / 4))
  # usage to be used in the loop  
  usage = 0  
  for value in new_values
    # how many resolution values are in a single value? (ie. how many sixteenth notes are in a quarter note)    
    res_value = (1 / value[0]) * resolution
    # use resolution value * occurances    
    usage += res_value * value[1]    
  return usage / possible



# generate clef values based on data
Piece = (piece) ->
  {measures, beats, treble_clef, bass_clef, composition} = piece
  this.treble_clef = {
    # sound ratio is the presence of sound divided by the presenece of silence in the analyzed piece  
    sound_ratio: getSoundRatio(measures, beats, treble_clef.values)
    # value package has array for both non-zero values and their corrosponding chance
    values_pkg: new ChancePkg(treble_clef.values)
    # interval package has array for both non-zero intervals and their corrosponding chance
    intervals_pkg: new ChancePkg(treble_clef.intervals)
    # octaves package has array for both non-zero octaves and their corrosponding chance
    octaves_pkg: new ChancePkg(treble_clef.octaves)
    # chords package has array for both non-zero chords and their corrosponding chance
    chords_pkg: new ChancePkg(treble_clef.chords)
    # setting the base octave
    base_octave: 5
    # tone
    wave: 'sine'
    # gain
    gain: 0.3
  }
  this.bass_clef = {
    # sound ratio is the presence of sound divided by the presenece of silence in the analyzed piece  
    sound_ratio: getSoundRatio(measures, beats, bass_clef.values)
    # value package has array for both non-zero values and their corrosponding chance
    values_pkg: new ChancePkg(bass_clef.values)
    # interval package has array for both non-zero intervals and their corrosponding chance
    intervals_pkg: new ChancePkg(bass_clef.intervals)
    # octaves package has array for both non-zero octaves and their corrosponding chance
    octaves_pkg: new ChancePkg(bass_clef.octaves)
    # chords package has array for both non-zero chords and their corrosponding chance
    chords_pkg: new ChancePkg(bass_clef.chords)
    # setting the base octave
    base_octave: 3
    # tone
    wave: 'sine'
    # gain
    gain: 0.3
  }
  this.composition = composition
  return
  

  
# initiate audio context
audio_context = undefined
(init = (g) ->
  try
    # "crossbrowser" audio context.
    audio_context = new (g.AudioContext or g.webkitAudioContext)
  catch e
    console.log "No web audio oscillator support in this browser"
  return
) window



# oscillator prototype
Oscillator = (tone) ->
  max_gain = tone.gain
  this.tone = tone
  this.play = () ->
    # capturing current time for play start and stop
    current_time = audio_context.currentTime
    # create oscillator    
    o = audio_context.createOscillator()
    # create gain    
    gn = audio_context.createGain()    
    # set waveform    
    o.type = this.tone.wave
    # set frequency    
    if this.tone.frequency
      o.frequency.value = this.tone.frequency
    # connect oscillator to gain    
    o.connect gn
    # connect gain to output
    gn.connect audio_context.destination
    # set gain amount
    gn.gain.value = (max_gain / this.tone.vol) / 1
    # play it    
    o.start(current_time)
    # stop after sustain    
    o.stop(current_time + this.tone.sustain)
  return this



# note frequencies array of octave arrays that start on c (our root note)
freqs = [
  [16.351, 17.324, 18.354, 19.445, 20.601, 21.827, 23.124, 24.499, 25.956, 27.5, 29.135, 30.868]
  [32.703, 34.648, 36.708, 38.891, 41.203, 43.654, 46.249, 48.999, 51.913, 55, 58.27, 61.735]
  [65.406, 69.296, 73.416, 77.782, 82.407, 87.307, 92.499, 97.999, 103.826, 110, 116.541, 123.471]
  [130.813, 138.591, 146.832, 155.563, 164.814, 174.614, 184.997, 195.998, 207.652, 220, 233.082, 246.942]
  [261.626, 277.183, 293.665, 311.127, 329.628, 349.228, 369.994, 391.995, 415.305, 440, 466.164, 493.883]
  [523.251, 554.365, 587.33, 622.254, 659.255, 698.456, 739.989, 783.991, 830.609, 880, 932.328, 987.767]
  [1046.502, 1108.731, 1174.659, 1244.508, 1318.51, 1396.913, 1479.978, 1567.982, 1661.219, 1760, 1864.655, 1975.533]
  [2093.005, 2217.461, 2349.318, 2489.016, 2637.021, 2793.826, 2959.955, 3135.964, 3322.438, 3520, 3729.31, 3951.066]
  [4186.009, 4434.922, 4698.636, 4978.032, 5274.042, 5587.652, 5919.91, 6271.928, 6644.876, 7040, 7458.62, 7902.132]
  [8372.018, 8869.844, 9397.272, 9956.064, 10548.084, 11175.304, 11839.82, 12543.856, 13289.752, 14080, 14917.24, 15804.264]
]



# get random val from chance package
randomVal = (chance_pkg) ->
  random = Math.random() * 100000
  for chance, i in chance_pkg.chances
    return chance_pkg.vals[i] if random < chance
  return

# get sequence function
getSequence = (clef, composition) ->
  # get the duration in smallest resolution amount
  duration = composition.measures * (composition.beats * (composition.resolution / 4))  
  # note sequence
  sequence = []
  # while there is still duration
  while duration > 0
    # random hit
    random = Math.random()
    # if a hit    
    if random < clef.sound_ratio 
      # random chord note count
      chord = randomVal(clef.chords_pkg)
      # random length for the chord      
      value = randomVal(clef.values_pkg)
      # if there isnt enough space
      if ((1 / value) / (1 / composition.resolution)) >= duration
        # make it the length of remaining   
        value = ((1 / duration) / (1 / composition.resolution))
      # the new chord
      new_chord = {length: (1 / value), notes: []}
      # for each note in the chord      
      for note in [1..chord]
        # get a random interval
        interval = randomVal(clef.intervals_pkg)
        # get the random octave        
        octave = randomVal(clef.octaves_pkg)
        # make intervale relative to key
        interval += composition.root
        # if key pushes interval into new octave
        if interval > 12
          interval -= 12
        # make the octave relative to the clef's octave
        new_octave = clef.base_octave + ((2 - octave) * -1)
        # the frequency of the note
        note = freqs[new_octave - 1][interval]
        # if note doesnt already exist in chord
        if new_chord.notes.indexOf(note) == -1
          # push the frequency into the chord's notes        
          new_chord.notes.push {freq: note, int: interval, octave: octave}
        else
          # duplicate note in chord, ignoring it for now.
          console.log 'duplicate note in chord, ignoring it for now.'
      # push the chord into the sequence        
      sequence.push new_chord
      # get values resolution-relative value        
      res_value = Math.floor((1 / value) / (1 / composition.resolution))
      # if we need to add sustain notes                   
      if res_value > 1
        # add blank values      
        for blank in [1..res_value - 1]      
          sequence.push 'sus'
      # subtract from the duration      
      duration -= res_value

    else
      # it was a miss, add a zero to the sequence
      sequence.push 0
      # subtract tick from duration      
      duration--
  return sequence



Note = (tone) ->
  this.osc = () ->
    return new Oscillator(tone)
  return


###
# data (to be derived from an analyzed piece of music)
###

piece = {
  # piece data (auld lang syne)
  measures: 16 # measures in analyzed piece
  beats:    4  # beats per measure in analyzed piece
  # all our analysis will be relative to each clef
  treble_clef: {  
    # note values are the duration of the note / chord. 
    # this is a map of how many times each value appears in the analyzed data
    values: {
      1:   0  # whole
      1.5: 4  # dotted half
      2:   5  # half
      3:   12 # dotted quarter
      4:   28 # quarter
      6:   0  # dotted eighth
      8:   12 # eighth
      12:  0  # dotted sixteenth
      16:  0  # sixteenth
      32:  0  # thirty-second
    }
    # relative to the key, the intervals are steps between notes and the root (no octaves)
    # this includes all single notes and instances of a note in a chord
    # this is a map of how many times each interval appears in the analyzed data
    intervals: {
      0:  32 # root / perfect unison (F)
      1:  0  # minor second (F#)
      2:  9  # major second (G)
      3:  0  # minor third (G#)
      4:  13 # major third (A)
      5:  0  # perfect fourth (A#)
      6:  6  # tritone (B)
      7:  13 # perfect fifth (C)
      8:  0  # minor sixth (C#)
      9:  15 # major sixth (D)
      10: 0  # minor seventh (D#)
      11: 12 # major seventh (E)
    }
    # octaves are how many times a note appears in each octave (relative to the key)
    octaves: {
      1: 37 # clef - 1
      2: 67 # clef
      3: 1  # clef + 1
    }
    # instead of using proper chords (dyad, triad, 7th, 9th, and 11th), we only analyze how many notes are in the chord
    # this is a map of how many times each chord size appears in the analyzed data
    chords: {
      1: 9  # 1 note
      2: 47 # 2 notes
      3: 0  # 3 notes
      4: 0  # 4 notes
      5: 0  # 5 notes
    }
  }
  bass_clef: {  
    # note values are the duration of the note / chord. 
    # this is a map of how many times each value appears in the analyzed data
    values: {
      1:   0  # whole
      1.5: 4  # dotted half
      2:   5  # half
      3:   12 # dotted quarter
      4:   28 # quarter
      6:   0  # dotted eighth
      8:   12 # eighth
      12:  0  # dotted sixteenth
      16:  0  # sixteenth
      32:  0  # thirty-second
    }
    # relative to the key, the intervals are steps between notes and the root (no octaves)
    # this includes all single notes and instances of a note in a chord
    # this is a map of how many times each interval appears in the analyzed data
    intervals: {
      0:  25 # root / perfect unison (F)
      1:  0  # minor second (F#)
      2:  1  # major second (G)
      3:  0  # minor third (G#)
      4:  17 # major third (A)
      5:  2  # perfect fourth (A#)
      6:  0  # tritone (B)
      7:  38 # perfect fifth (C)
      8:  1  # minor sixth (C#)
      9:  4  # major sixth (D)
      10: 0  # minor seventh (D#)
      11: 2  # major seventh (E)
    }
    # octaves are how many times a note appears in each octave (relative to the key)
    octaves: {
      1: 2  # clef - 1
      2: 53 # clef
      3: 50 # clef + 1
    }
    # instead of using proper chords (dyad, triad, 7th, 9th, and 11th), we only analyze how many notes are in the chord
    # this is a map of how many times each chord size appears in the analyzed data
    chords: {
      1: 9  # 1 note
      2: 46 # 2 notes
      3: 0  # 3 notes
      4: 0  # 4 notes
      5: 0  # 5 notes
    }

  }
  # defining the desired output composition data
  composition: {
    measures:   32  # bars to generate
    beats:      4   # beats per measure
    tempo:      120 # tempo
    resolution: 16  # resolution scale of piece  
    root:       5   # root of key (0-11), 0 is 'C'
  }
}


# lets analyze our piece data!
p = new Piece(piece)

# sequence stores
trebleSequence = undefined
bassSequence = undefined
# get sequences
getSequences = () ->
  # creating our treble clef
  trebleSequence = getSequence(p.treble_clef, p.composition)
  bassSequence = getSequence(p.bass_clef, p.composition)
  draw_sequences()

  
  
getSequenceHtml = (name, composition, sequence) ->
  $seq_html = $('<div class="' + name + ' clef"></div>')
  beats = composition.measures * (composition.beats * (composition.resolution / 4))  
  width_increment = 1 / beats * 100
  left = 0  
  for chord in sequence
    if typeof chord == "object" 
      width = width_increment * (chord.length / (1 / composition.resolution))      
      width = width_increment
      classname = 'chord value-' + Math.round( (1 / chord.length) * 100) / 100
      classname = classname.replace('.', '-')
      notes = ''  
      for note in chord.notes
        out_of_36 = 36 - (note.int + ((note.octave - 1) * 12))
        notes += '<span class="note note-' + out_of_36 + '"></span>'
    else if chord == 'sus'  
      classname = 'sus'
      width = width_increment
      notes = ''  
    else
      width = width_increment      
      classname = 'blank'
      notes = ''
    $seq_html.append '<span class="beat ' + classname + '" style="width: ' + width + '%; left: ' + left + '%">' + notes + '</span>'
    left += width
  return $seq_html



draw_sequences = () ->
  composition = p.composition

  $staffs = $('<div class="staffs-wrapper"></div>')
  $staffs.append getSequenceHtml('treble', composition, trebleSequence)
  $staffs.append getSequenceHtml('bass', composition, bassSequence)
  $('#staff').html $staffs
  return
    


performance_interval = undefined

# play sequences
playSequences = () ->
  composition = p.composition
  sequences = [trebleSequence, bassSequence]
  waves = [p.treble_clef.wave, p.bass_clef.wave]
  gains = [p.treble_clef.gain, p.bass_clef.gain]
  # total beat count
  beats = composition.measures * (composition.beats * (composition.resolution / 4))
  # css width of beat  
  beat_width = 100 / beats
  # relative index  
  index = 0
  # tempo to ms  
  tempo_time = 60000 / composition.tempo        
  # single beat instance
  next_beat = () ->
    for sequence, i in sequences
      chord = sequence[index]
      # if beat in any rhythm array has value   
      if typeof chord == "object"
        # beats per second
        bps = composition.tempo / 60
        # how much of a beat is the length        
        beat_count = chord.length / 0.25
        # sustain of the note in seconds
        chord_length_secs = beat_count * bps / 2
        sustain = (chord_length_secs / bps) - 0.1
        for note in chord.notes
          # new note
          n = new Note({frequency: note.freq, sustain: sustain, wave: waves[i], gain: gains[i], vol: chord.notes.length})
          # new oscillator
          o = n.osc()
          # play oscillator        
          o.play()
        
        if i == 0 then clef = 'treble' else clef = 'bass'
          
      if typeof chord == "object"         
        $('.' + clef + ' .beat.active').removeClass 'active'
        $('.' + clef + ' .beat.go').removeClass clef + '-go'
        $('.' + clef + ' .beat:nth-child(' + (index + 1) + ')').addClass clef + '-go'
      else if chord != 'sus'     
        $('.' + clef + ' .beat.active').removeClass 'active'
        $('.' + clef + ' .beat.go').removeClass clef + '-go'
        
      $('.beat:nth-child(' + (index + 1) + ')').addClass 'active'
        
    # update index    
    index = (index + 1) % beats
  # first call of next beat
  next_beat()
  # ms to relative speed (based on resolution)  
  time = tempo_time / (composition.resolution / 4)
  # set interval for next beat to occur at approriate time
  performance_interval = window.setInterval(next_beat, time)

# stop button
stopSequences = () ->
  window.clearInterval(performance_interval)

# get sequences
getSequences()

playing = false
play_handler = (p) ->
  if p == true  
    playSequences()
  else    
    stopSequences()
  playing = p   

$('#play').click () ->
  $(this).toggleClass 'playing'
  $('body').toggleClass 'playing'
  $('#new').toggleClass 'inactive'
  play_handler(!playing)
  
$('#new').click () ->
  getSequences()  
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js