JS Tutorial – Math.random() Computer-Generated Music


Rengga Dev Math.random() is an API in JavaScript. It is a function that gives you a random number. The number returned will be between 0 (inclusive, as in, it’s possible for an actual 0 to be returned) and 1 (exclusive, as in, it’s not possible for an actual 1 to be returned).

# 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()

 


This program takes the traditional melody of “Auld Lang Syne” and plays random notes from it in piano. A change package is created from the count data and a random number is generated to select a value. The octave is also randomly selected.

Nandemo Webtools

Leave a Reply