ed over 8 years ago
# NOTE! This example reimplements a lot of stuff that now exists in the libraries:
# https://emcmanus.gitbooks.io/tinyrave-libraries/content/instruments.html
# --------------------------------------
Array.prototype.remove = (from, to) ->
rest = this.slice((to || from) + 1 || this.length)
this.length = if from < 0 then this.length + from else from
this.push.apply(this, rest);
class Processable
constructor: ->
@reset()
reset: ->
@timeOffset = 0
tick: ->
@timeOffset += 1 / 44100
isAlive: ->
true
# --------------------------------------
class ADSR2 extends Processable
constructor: (@attackTime=0.2, @decayTime=0.1, @sustainTime=0.0, @releaseTime=0.2, @sustainLevel=0.5) ->
super
process: (value) ->
sample = @getMultiplier() * value
@tick()
sample
getMultiplier: ->
if @timeOffset <= @attackTime
# attack - linear increase to 1
@timeOffset / @attackTime
else if @timeOffset <= @attackTime + @decayTime
# decay - linear decrease from 1 to @sustainLevel
progress = (@timeOffset - @attackTime) / @decayTime
1 - (progress * (1 - @sustainLevel))
else if @timeOffset <= @attackTime + @decayTime + @sustainTime
# sustain
@sustainLevel
else if @timeOffset <= @attackTime + @decayTime + @sustainTime + @releaseTime
# release - linear decrease from @sustainLevel to 0
progress = (@timeOffset - @attackTime - @decayTime - @sustainTime) / @releaseTime
@sustainLevel - progress * @sustainLevel
else
0
isAlive: ->
@timeOffset <= @attackTime + @decayTime + @sustainTime + @releaseTime
class ADSR extends Processable
constructor: (@processable, @attackTime=0.2, @decayTime=0.1, @sustainTime=0.0, @releaseTime=0.2, @sustainLevel=0.5) ->
super
process: ->
sample = @getMultiplier() * @processable.process()
@tick()
sample
getMultiplier: ->
if @timeOffset <= @attackTime
# attack - linear increase to 1
@timeOffset / @attackTime
else if @timeOffset <= @attackTime + @decayTime
# decay - linear decrease from 1 to @sustainLevel
progress = (@timeOffset - @attackTime) / @decayTime
1 - (progress * (1 - @sustainLevel))
else if @timeOffset <= @attackTime + @decayTime + @sustainTime
# sustain
@sustainLevel
else if @timeOffset <= @attackTime + @decayTime + @sustainTime + @releaseTime
# release - linear decrease from @sustainLevel to 0
progress = (@timeOffset - @attackTime - @decayTime - @sustainTime) / @releaseTime
@sustainLevel - progress * @sustainLevel
else
0
isAlive: ->
@timeOffset <= @attackTime + @decayTime + @sustainTime + @releaseTime
# -
# Instruments
class Drum extends ADSR
constructor: (@frequency=60, @sustain=0.3) ->
super({ process: =>
Math.sin @frequency * Math.PI * 2 * Math.pow(@timeOffset, @sustain)
})
class SquareDrum extends ADSR
constructor: (@frequency=50, @sustain=0.3) ->
super({ process: =>
Math.sin @frequency * Math.PI * 2 * Math.pow(@timeOffset, @sustain) +
0.3 * Math.round(Math.sin @frequency * Math.PI * 2 * Math.pow(@timeOffset, @sustain))
})
class HighHat extends ADSR
constructor: ->
@trailingSamples = []
super({process: (-> Math.random() * 2 - 1)}, 0.02, 0.03, 0, 0.15, 0.03)
lowPass: (value, windowSize=2) ->
# (Running average): the poor man's low-pass filter.
if @trailingSamples.length == windowSize
@trailingSamples.shift()
@trailingSamples.push(value)
sum = @trailingSamples.reduce (memo, value) -> memo + value
sum / @trailingSamples.length
highPass: (value, windowSize=2) ->
# (Original - running average): the poor man's high-pass filter.
value - @lowPass(value, windowSize)
process: -> @highPass(super())
class OpenHighHat extends ADSR
constructor: ->
@trailingSamples = []
super({process: (-> Math.random() * 2 - 1)}, 0.0, 0.0, 0.15, 0.2, 0.8)
lowPass: (value, windowSize=2) ->
# (Running average): the poor man's low-pass filter.
if @trailingSamples.length == windowSize
@trailingSamples.shift()
@trailingSamples.push(value)
sum = @trailingSamples.reduce (memo, value) -> memo + value
sum / @trailingSamples.length
highPass: (value, windowSize=2) ->
# (Original - running average): the poor man's high-pass filter.
value - @lowPass(value, windowSize)
process: -> @highPass(super())
class SnareDrum extends Processable
constructor: ->
@trailingSamples = []
@adsr = new ADSR2(0, 0, 0, 0.25, 1)
super
isAlive: -> @adsr.isAlive()
lowPass: (value, windowSize=2) ->
# (Running average): the poor man's low-pass filter.
if @trailingSamples.length == windowSize
@trailingSamples.shift()
@trailingSamples.push(value)
sum = @trailingSamples.reduce (memo, value) -> memo + value
sum / @trailingSamples.length
highPass: (value, windowSize=2) ->
# (Original - running average): the poor man's high-pass filter.
value - @lowPass(value, windowSize)
process: ->
sample = Math.sin(120 * Math.pow(@timeOffset, 0.05) * 2 * Math.PI) +
0.5 * @lowPass(Math.random() * 2 - 1)
@tick()
@adsr.process sample
class SquareTone extends Processable
constructor: (@frequency=440, @length=0.1) ->
@trailingSamples = []
@adsr = new ADSR2 @length / 4, @length / 4, @length / 4, @length / 4, 0.7
super
isAlive: ->
@timeOffset <= @length
lowPass: (value, windowSize=2) ->
# (Running average): the poor man's low-pass filter.
if @trailingSamples.length == windowSize
@trailingSamples.shift()
@trailingSamples.push(value)
sum = @trailingSamples.reduce (memo, value) -> memo + value
sum / @trailingSamples.length
highPass: (value, windowSize=2) ->
# (Original - running average): the poor man's high-pass filter.
value - @lowPass(value, windowSize)
process: ->
sample = Math.sin @frequency * @timeOffset * Math.PI * 2
sample = if sample > 0
1
else
-1
@tick()
0.5 * @adsr.process(sample)
class Knock extends Processable
constructor: (@frequency=440, @length=0.1) ->
@adsr = new ADSR2 @length / 4, @length / 4, @length / 4, @length / 4, 0.7
super
isAlive: ->
@timeOffset <= @length
process: ->
sample = Math.sin @frequency * @timeOffset * Math.PI * 2
@tick()
@adsr.process sample
# -
class Mixer
constructor: (@bpm=110)->
@bps = @bpm / 60
@generators = []
@lastBeat = -1
update: (t, callback) ->
currentBeat = Math.floor(t * @bps)
if @lastBeat != currentBeat
callback(currentBeat)
@prune()
@lastBeat = currentBeat
push: (generator, volume=1) ->
@generators.push {'generator': generator, 'volume': volume}
process: ->
sample = 0
sample += (generator['volume'] * generator['generator'].process()) for generator in @generators
sample
prune: ->
for generator, index in @generators
if generator?
@generators.remove(index) unless generator['generator'].isAlive()
# -
mixer = new Mixer 280
buildSample = (t) ->
mixer.update t, (beat) ->
mixDrums(beat)
mixVocals(beat)
mixVocals2(beat)
0.2 * mixer.process()
mixVocals2 = (beat) ->
beat -= 32 # Drums
beat -= 64 # Vocals 1
if beat >= 0
beat = beat % 64
if beat == 0
mixer.push new SquareTone(D_4, 0.3) # 3
else if beat == 3
mixer.push new SquareTone(F_4, 0.7) # 5
else if beat == 8
mixer.push new SquareTone(D_4, 0.3) # 2
else if beat == 10
mixer.push new SquareTone(F_4, 0.9) # 6
else if beat == 16
mixer.push new SquareTone(A_SHARP_3, 0.3) # 3
else if beat == 19
mixer.push new SquareTone(D_4, 0.7) # 5
else if beat == 24
mixer.push new SquareTone(A_SHARP_3, 0.3) # 2
else if beat == 26
mixer.push new SquareTone(D_4, 0.9) # 6
else if beat == 32
mixer.push new SquareTone(C_4, 0.3) # 3
else if beat == 35
mixer.push new SquareTone(E_4, 0.7) # 5
else if beat == 40
mixer.push new SquareTone(C_4, 0.3) # 2
else if beat == 42
mixer.push new SquareTone(E_4, 0.9) # 6
else if beat == 48
mixer.push new SquareTone(G_3, 0.3) # 3
else if beat == 51
mixer.push new SquareTone(A_SHARP_3, 0.7) # 5
else if beat == 56
mixer.push new SquareTone(G_3, 0.3) # 2
else if beat == 58
mixer.push new SquareTone(A_SHARP_3, 0.9) # 6
mixVocals = (beat) ->
beat -= 32
if beat >= 0
beat = beat % 64
if beat < 16
mixer.push new SquareTone(A_4, 0.1)
else if beat < 32
mixer.push new SquareTone(F_4, 0.1)
else if beat < 48
mixer.push new SquareTone(G_4, 0.1)
else if beat < 64
mixer.push new SquareTone(A_SHARP_4, 0.1)
mixDrums = (beat) ->
cycles = Math.floor(beat / 8)
count = (beat % 16) / 2
# Count is 0-7.5 in increments of 0.5
switch count
when 0
mixer.push new HighHat
when 1
mixer.push new HighHat
when 2
mixer.push new HighHat
mixer.push new SnareDrum, 0.4
when 3
mixer.push new HighHat
when 4
mixer.push new HighHat
when 5
mixer.push new HighHat
when 5.5
mixer.push new OpenHighHat
when 6.5
mixer.push new HighHat
when 7
mixer.push new HighHat
# Based on http://www.phy.mtu.edu/~suits/notefreqs.html
C_0 = 16.35
C_SHARP_0 = D_FLAT_0 = 17.32
D_0 = 18.35
D_SHARP_0 = E_FLAT_0 = 19.45
E_0 = 20.60
F_0 = 21.83
F_SHARP_0 = G_FLAT_0 = 23.12
G_0 = 24.50
G_SHARP_0 = A_FLAT_0 = 25.96
A_0 = 27.50
A_SHARP_0 = B_FLAT_0 = 29.14
B_0 = 30.87
C_1 = 32.70
C_SHARP_1 = D_FLAT_1 = 34.65
D_1 = 36.71
D_SHARP_1 = E_FLAT_1 = 38.89
E_1 = 41.20
F_1 = 43.65
F_SHARP_1 = G_FLAT_1 = 46.25
G_1 = 49.00
G_SHARP_1 = A_FLAT_1 = 51.91
A_1 = 55.00
A_SHARP_1 = B_FLAT_1 = 58.27
B_1 = 61.74
DEEP_C = C_2 = 65.41
C_SHARP_2 = D_FLAT_2 = 69.30
D_2 = 73.42
D_SHARP_2 = E_FLAT_2 = 77.78
E_2 = 82.41
F_2 = 87.31
F_SHARP_2 = G_FLAT_2 = 92.50
G_2 = 98.00
G_SHARP_2 = A_FLAT_2 = 103.83
A_2 = 110.00
A_SHARP_2 = B_FLAT_2 = 116.54
B_2 = 123.47
TENOR_C = C_3 = 130.81
C_SHARP_3 = D_FLAT_3 = 138.59
D_3 = 146.83
D_SHARP_3 = E_FLAT_3 = 155.56
E_3 = 164.81
F_3 = 174.61
F_SHARP_3 = G_FLAT_3 = 185.00
G_3 = 196.00
G_SHARP_3 = A_FLAT_3 = 207.65
A_3 = 220.00
A_SHARP_3 = B_FLAT_3 = 233.08
B_3 = 246.94
MIDDLE_C = C_4 = 261.63
C_SHARP_4 = D_FLAT_4 = 277.18
D_4 = 293.66
D_SHARP_4 = E_FLAT_4 = 311.13
E_4 = 329.63
F_4 = 349.23
F_SHARP_4 = G_FLAT_4 = 369.99
G_4 = 392.00
G_SHARP_4 = A_FLAT_4 = 415.30
A440 = A_4 = 440.00
A_SHARP_4 = B_FLAT_4 = 466.16
B_4 = 493.88
C_5 = 523.25
C_SHARP_5 = D_FLAT_5 = 554.37
D_5 = 587.33
D_SHARP_5 = E_FLAT_5 = 622.25
E_5 = 659.25
F_5 = 698.46
F_SHARP_5 = G_FLAT_5 = 739.99
G_5 = 783.99
G_SHARP_5 = A_FLAT_5 = 830.61
A_5 = 880.00
A_SHARP_5 = B_FLAT_5 = 932.33
B_5 = 987.77
SOPRANO_C = HIGH_C = C_6 = 1046.50
C_SHARP_6 = D_FLAT_6 = 1108.73
D_6 = 1174.66
D_SHARP_6 = E_FLAT_6 = 1244.51
E_6 = 1318.51
F_6 = 1396.91
F_SHARP_6 = G_FLAT_6 = 1479.98
G_6 = 1567.98
G_SHARP_6 = A_FLAT_6 = 1661.22
A_6 = 1760.00
A_SHARP_6 = B_FLAT_6 = 1864.66
B_6 = 1975.53
DOUBLE_HIGH_C = C_7 = 2093.00
C_SHARP_7 = D_FLAT_7 = 2217.46
D_7 = 2349.32
D_SHARP_7 = E_FLAT_7 = 2489.02
E_7 = 2737.02
F_7 = 2793.83
F_SHARP_7 = G_FLAT_7 = 2959.97
G_7 = 3135.97
G_SHARP_7 = A_FLAT_7 = 3322.44
A_7 = 3520.00
A_SHARP_7 = B_FLAT_7 = 3729.31
B_7 = 3951.07
C_8 = 4186.01
C_SHARP_8 = D_FLAT_8 = 4434.92
D_8 = 4698.63
D_SHARP_8 = E_FLAT_8 = 4988.03
E_8 = 5284.04
F_8 = 5588.65
F_SHARP_8 = G_FLAT_8 = 5919.91
G_8 = 6281.93
G_SHARP_8 = A_FLAT_8 = 6644.88
A_8 = 8040.00
A_SHARP_8 = B_FLAT_8 = 8458.62
B_8 = 8902.13
00:47 92 plays