|
### |
|
|
|
This is best viewed in Chrome since there is a bug in Safari |
|
when using getByteFrequencyData with MediaElementAudioSource |
|
|
|
@see https://goo.gl/6WLx1 |
|
|
|
### |
|
|
|
# Config |
|
|
|
NUM_PARTICLES = 150 |
|
NUM_BANDS = 128 |
|
SMOOTHING = 0.5 |
|
MP3_PATH = 'https://s3.us-east-2.amazonaws.com/leetcodes/assets/My_House_-_Flo_Rida_Jack_Dyer_Bootleg.mp3' |
|
|
|
SCALE = MIN: 5.0, MAX: 80.0 |
|
SPEED = MIN: 0.2, MAX: 1.0 |
|
ALPHA = MIN: 0.8, MAX: 0.9 |
|
SPIN = MIN: 0.001, MAX: 0.005 |
|
SIZE = MIN: 0.5, MAX: 1.25 |
|
|
|
COLORS = [ |
|
'#060719' |
|
'#4C1B2F' |
|
'#9E332E' |
|
'#EBE54D' |
|
'#EB6528' |
|
'#FC9D1C' |
|
'#B59D1C' |
|
'#FF4E50' |
|
'#FC9D5D' |
|
'#1f6a83' |
|
'#FF006F' |
|
] |
|
|
|
# Audio Analyser |
|
|
|
class AudioAnalyser |
|
|
|
@AudioContext: self.AudioContext or self.webkitAudioContext |
|
@enabled: @AudioContext? |
|
|
|
constructor: ( @audio = new Audio(), @numBands = 256, @smoothing = 0.3 ) -> |
|
|
|
# construct audio object |
|
if typeof @audio is 'string' |
|
|
|
src = @audio |
|
@audio = new Audio() |
|
@audio.controls = yes |
|
@audio.src = src |
|
@audio.crossOrigin = "anonymous" |
|
|
|
# setup audio context and nodes |
|
@context = new AudioAnalyser.AudioContext() |
|
|
|
# createScriptProcessor so we can hook onto updates |
|
@jsNode = @context.createScriptProcessor 2048, 1, 1 |
|
|
|
# smoothed analyser with n bins for frequency-domain analysis |
|
@analyser = @context.createAnalyser() |
|
@analyser.smoothingTimeConstant = @smoothing |
|
@analyser.fftSize = @numBands * 2 |
|
|
|
# persistant bands array |
|
@bands = new Uint8Array @analyser.frequencyBinCount |
|
|
|
# circumvent http://crbug.com/112368 |
|
@audio.addEventListener 'canplay', => |
|
|
|
# media source |
|
@source = @context.createMediaElementSource @audio |
|
|
|
# wire up nodes |
|
|
|
@source.connect @analyser |
|
@analyser.connect @jsNode |
|
|
|
@jsNode.connect @context.destination |
|
@source.connect @context.destination |
|
|
|
# update each time the JavaScriptNode is called |
|
@jsNode.onaudioprocess = => |
|
|
|
# retreive the data from the first channel |
|
@analyser.getByteFrequencyData @bands |
|
|
|
# fire callback |
|
@onUpdate? @bands if not @audio.paused |
|
|
|
start: -> |
|
|
|
@audio.play() |
|
|
|
stop: -> |
|
|
|
@audio.pause() |
|
|
|
# Particle |
|
|
|
class Particle |
|
|
|
constructor: ( @x = 0, @y = 0 ) -> |
|
|
|
@reset() |
|
|
|
reset: -> |
|
|
|
@level = 1 + floor random 4 |
|
@scale = random SCALE.MIN, SCALE.MAX |
|
@alpha = random ALPHA.MIN, ALPHA.MAX |
|
@speed = random SPEED.MIN, SPEED.MAX |
|
@color = random COLORS |
|
@size = random SIZE.MIN, SIZE.MAX |
|
@spin = random SPIN.MAX, SPIN.MAX |
|
@band = floor random NUM_BANDS |
|
|
|
if random() < 0.5 then @spin = -@spin |
|
|
|
@smoothedScale = 0.0 |
|
@smoothedAlpha = 0.0 |
|
@decayScale = 0.0 |
|
@decayAlpha = 0.0 |
|
@rotation = random TWO_PI |
|
@energy = 0.0 |
|
|
|
move: -> |
|
|
|
@rotation += @spin |
|
@y -= @speed * @level |
|
|
|
draw: ( ctx ) -> |
|
|
|
power = exp @energy |
|
scale = @scale * power |
|
alpha = @alpha * @energy * 1.5 |
|
|
|
@decayScale = max @decayScale, scale |
|
@decayAlpha = max @decayAlpha, alpha |
|
|
|
@smoothedScale += ( @decayScale - @smoothedScale ) * 1 |
|
@smoothedAlpha += ( @decayAlpha - @smoothedAlpha ) * 1 |
|
|
|
@decayScale *= 7 |
|
@decayAlpha *= 0.975 |
|
|
|
ctx.save() |
|
ctx.beginPath() |
|
ctx.translate @x + cos( @rotation * @speed ) * 250, @y |
|
ctx.rotate @rotation |
|
ctx.scale @smoothedScale * @level, @smoothedScale * @level |
|
##start to draw |
|
|
|
## layer1/Path |
|
|
|
ctx.stroke(new Path2D("M330.3,102.61L234,198.91l70.71,70.71,89.42-89.42,119,119-22.63,22.63-97.4-97.4-66.79,66.79,91.48,91.48L298.3,502.22l-21-21L375,383.45,304.56,313l-91.22,91.22L94,284.82l22.24-22.24L214,360.35l69-69-92-92L309.08,81.3l21.26,21.26Z")); |
|
|
|
### |
|
ctx.beginPath() |
|
ctx.moveTo(102.0, 9.2) |
|
ctx.lineTo(97.5, 12.7) |
|
ctx.lineTo(92.2, 10.6) |
|
ctx.lineTo(91.4, 4.9) |
|
ctx.lineTo(95.9, 1.3) |
|
ctx.lineTo(101.3, 3.5) |
|
ctx.lineTo(102.0, 9.2) |
|
ctx.closePath() |
|
ctx.stroke() |
|
### |
|
|
|
### |
|
ctx.beginPath() |
|
ctx.moveTo(300, 0) |
|
ctx.lineTo(250, 100) |
|
ctx.lineTo(350, 100) |
|
ctx.closePath() |
|
ctx.stroke() |
|
### |
|
|
|
ctx.restore() |
|
## end drawing |
|
ctx.globalAlpha = @smoothedAlpha / @level |
|
ctx.strokeStyle = @color |
|
ctx.stroke() |
|
ctx.restore() |
|
|
|
# Sketch |
|
|
|
Sketch.create |
|
|
|
particles: [] |
|
|
|
setup: -> |
|
|
|
# generate some particles |
|
for i in [0..NUM_PARTICLES-1] by 1 |
|
|
|
x = random @width |
|
y = random @height * 2 |
|
|
|
particle = new Particle x, y |
|
particle.energy = random particle.band / 500 |
|
|
|
@particles.push particle |
|
|
|
if AudioAnalyser.enabled |
|
|
|
try |
|
|
|
# setup the audio analyser |
|
analyser = new AudioAnalyser MP3_PATH, NUM_BANDS, SMOOTHING |
|
|
|
# update particles based on fft transformed audio frequencies |
|
analyser.onUpdate = ( bands ) => particle.energy = bands[ particle.band ] / 256 for particle in @particles |
|
|
|
# start as soon as the audio is buffered |
|
analyser.start(); |
|
|
|
# show audio controls |
|
document.body.appendChild analyser.audio |
|
|
|
intro = document.getElementById 'intro' |
|
intro.style.display = 'none' |
|
|
|
# bug in Safari 6 when using getByteFrequencyData with MediaElementAudioSource |
|
# @see https://goo.gl/6WLx1 |
|
if /Safari/.test( navigator.userAgent ) and not /Chrome/.test( navigator.userAgent ) |
|
|
|
warning = document.getElementById 'warning2' |
|
warning.style.display = 'block' |
|
|
|
catch error |
|
|
|
else |
|
|
|
# Web Audio API not detected |
|
warning = document.getElementById 'warning1' |
|
warning.style.display = 'block' |
|
|
|
draw: -> |
|
|
|
@globalCompositeOperation = 'lighter' |
|
|
|
for particle in @particles |
|
|
|
# recycle particles |
|
if particle.y < -particle.size * particle.level * particle.scale * 2 |
|
|
|
particle.reset(); |
|
particle.x = random @width |
|
particle.y = @height + particle.size * particle.scale * particle.level |
|
|
|
particle.move() |
|
particle.draw @ |