Created
April 10, 2013 23:24
-
-
Save paultreny/5359330 to your computer and use it in GitHub Desktop.
A CodePen by Paul Reny. Simple Audio Visualisation - A basic example of using the Web Audio API AudioContext with an AnalyserNode and frequency-domain analysis (FFT) to create a simple music visualiser.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#container | |
#warning1.message | |
%h1 | |
This experiment requires the | |
%a{ href:'https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html', target:'_blank' } Web Audio API | |
%h2 | |
Please try in one of | |
%a{ href:'http://caniuse.com/#feat=audio-api', target:'_blank' } these browsers | |
#warning2.message | |
%h1 | |
Safari users. You may hear audio but see no visuals. This is due to | |
%a{ href:'http://goo.gl/6WLx1', target:'_blank' } this bug | |
in Safari 6 | |
#intro.message | |
%h1 Simple music visualiser | |
%h2 Loading audio… |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### | |
Music is by The XX | |
@see http://thexx.info | |
This is best viewed in Chrome since there is a bug in Safari | |
when using getByteFrequencyData with MediaElementAudioSource | |
@see http://goo.gl/6WLx1 | |
### | |
# Config | |
NUM_PARTICLES = 90 | |
NUM_BANDS = 128 | |
SMOOTHING = 0.2 | |
# MP3_PATH = 'http://s.cdpn.io/1715/the_xx_-_intro.mp3' | |
# MP3_PATH = 'http://cld.prdl.me/2L0Q3y1Z123W/Carried%20Away%20(Dillon%20Francis%20Remix).mp3' | |
MP3_PATH = 'http://cld.prdl.me/300a132R0N1R/Birthday%20Song%20(Parisian%20Vision)%20(Ft.%202%20Chainz).mp3' | |
SCALE = MIN: 10.0, MAX: 60.0 | |
SPEED = MIN: 0.2, MAX: 1.5 | |
ALPHA = MIN: 0.6, MAX: 0.8 | |
SPIN = MIN: 0.005, MAX: 0.01 | |
SIZE = MIN: 0.5, MAX: 1.25 | |
COLORS = [ | |
'#69D2E7' | |
'#1B676B' | |
'#BEF202' | |
'#EBE54D' | |
'#00CDAC' | |
'#1693A5' | |
'#F9D423' | |
'#FF4E50' | |
'#E7204E' | |
'#0CCABA' | |
'#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 | |
# setup audio context and nodes | |
@context = new AudioAnalyser.AudioContext() | |
# JavaScriptNode so we can hook onto updates | |
@jsNode = @context.createJavaScriptNode 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 ) * 0.3 | |
@smoothedAlpha += ( @decayAlpha - @smoothedAlpha ) * 0.3 | |
@decayScale *= 0.985 | |
@decayAlpha *= 0.975 | |
ctx.save() | |
ctx.beginPath() | |
ctx.translate @x + cos( @rotation * @speed ) * 250, @y | |
ctx.rotate @rotation | |
ctx.scale @smoothedScale * @level, @smoothedScale * @level | |
ctx.moveTo @size * 0.5, 0 | |
ctx.lineTo @size * -0.5, 0 | |
ctx.lineWidth = 1 | |
ctx.lineCap = 'round' | |
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 / 256 | |
@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 http://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 @ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import "compass" | |
@import url( http://fonts.googleapis.com/css?family=Lato:400,700 ) | |
html, body | |
font-family: 'Lato', sans-serif | |
background: #13242f | |
overflow: hidden | |
#container | |
&:before | |
@include background-image( radial-gradient( center, ellipse cover, rgba(0,0,0,0) 20%, rgba(0,0,0,1) 95% ) ) | |
position: absolute | |
content: '' | |
z-index: 0 | |
opacity: 0.9 | |
height: 100% | |
width: 100% | |
left: 0 | |
top: 0 | |
&:after | |
background: url( 'http://s.cdpn.io/1715/noise-1.png' ) | |
position: absolute | |
content: '' | |
z-index: 1 | |
opacity: 0.8 | |
height: 100% | |
width: 100% | |
left: 0 | |
top: 0 | |
audio | |
position: absolute | |
z-index: 2 | |
right: 0 | |
top: 0 | |
.message | |
$height: 60px | |
$width: 360px | |
box-shadow: 0 2px 4px rgba(0,0,0,0.2) | |
text-transform: uppercase | |
border-radius: 3px | |
text-align: center | |
line-height: 1.2 | |
background: rgba(0,0,0,0.8) | |
position: absolute | |
margin-left: $width * -0.5 | |
margin-top: $height * -0.5 | |
font-size: 13px | |
padding: 20px | |
display: none | |
z-index: 3 | |
height: $height | |
width: $width | |
color: #fff | |
left: 50% | |
top: 50% | |
h1, h2 | |
font-weight: 300 | |
margin: 10px 0 | |
a | |
text-decoration: none | |
font-weight: 700 | |
color: #1B676B | |
#intro | |
display: block |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment