A basic example of using the Web Audio API AudioContext with an AnalyserNode and frequency-domain analysis (FFT) to create a simple music visualiser.
A Pen by Justin Windle on CodePen.
| #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… |
| ### | |
| 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 = 150 | |
| NUM_BANDS = 128 | |
| SMOOTHING = 0.5 | |
| MP3_PATH = 'http://s.cdpn.io/1715/the_xx_-_intro.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 = [ | |
| '#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 @ |
A basic example of using the Web Audio API AudioContext with an AnalyserNode and frequency-domain analysis (FFT) to create a simple music visualiser.
A Pen by Justin Windle on CodePen.
| @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 |