Using the characteristics of Auld Lang Syne for the New Year.
A Pen by Jake Albaugh on CodePen.
<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> |
Using the characteristics of Auld Lang Syne for the New Year.
A Pen by Jake Albaugh on CodePen.
### | |
/// | |
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 | |
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() |
@import url(http://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; | |
} | |
} |