Song courtesy of MRSJXN: https://soundcloud.com/mrsjxn
Forked from Tyler Benziger's Pen Visualizr.
A Pen by Herbert Joseph on CodePen.
| <div class="visualizr"> | |
| <div class="controls"> | |
| <div class="field"> | |
| <label for="width">Width</label> | |
| <input type="range" name="width" value="20"> | |
| </div> | |
| <div class="field"> | |
| <label for="height">Height</label> | |
| <input type="range" name="height" value="50"> | |
| </div> | |
| <div class="field"> | |
| <label for="gap">Gap</label> | |
| <input type="range" name="gap" value="25"> | |
| </div> | |
| <div class="field"> | |
| <label for="delay">Delay</label> | |
| <input type="range" name="delay" value="8"> | |
| </div> | |
| <div class="field"> | |
| <label for="hue">Hue Offset</label> | |
| <input type="range" name="hue" value="0"> | |
| </div> | |
| <div class="field"> | |
| <label style="margin-right:1em">Animate</label> | |
| <input type="radio" name="animate" value="out" checked> <span>Out</span> | |
| <input type="radio" name="animate" value="in"> <span>In</span> | |
| <input type="radio" name="animate" value="auto"> <span>Auto</span> | |
| </div> | |
| <div class="field"> | |
| <label>Delay</label> | |
| <input type="range" name="auto-delay" value="50"> | |
| </div> | |
| </div> | |
| <button class="playpause"></button> | |
| <canvas></canvas> | |
| </div> |
| window.addEventListener( 'load', init, false ); | |
| var context; | |
| var $body = $( 'body' ); | |
| var playing = false; | |
| var songSource = null; | |
| var songBuffer = null; | |
| var startOffset = 0; | |
| var startTime = 0; | |
| var analyser; | |
| var canvas = $( 'canvas' )[ 0 ]; | |
| var ctx = canvas.getContext( '2d' ); | |
| var bars = Array( 300 ); | |
| var forward = true; | |
| // Settings | |
| var globalHash; | |
| var hash = globalHash = getHash(); | |
| if ( hash.hide_controls ) { | |
| $( '.controls' ).addClass( 'hide' ); | |
| } | |
| if ( hash.small ) { | |
| $body.addClass( 'small' ); | |
| } | |
| console.log( hash ); | |
| var barCount = 60; | |
| var lineWidth = hash.width || 10; | |
| var lineGap = hash.gap || 10; | |
| var heightFactor = hash.height || 5; | |
| var delay = hash.delay || 10; | |
| var animate = hash.animate || 'out'; | |
| var animateSwitch = hash.auto_delay || 5 * 1000; | |
| var hue = hash.hue || 0; | |
| var songUrl = hash.song ? | |
| 'https://s3.amazonaws.com/tybenz.assets/visualizr/' + hash.song + '.mp3' : | |
| "https://dl.dropboxusercontent.com/u/15727879/T4IgFQfHfPFO.128.mp3"; | |
| var songName = hash.song || 'do_it_like'; | |
| var $out = $( '[name=animate][value=out]' ); | |
| var $in = $( '[name=animate][value=in]' ); | |
| var $auto = $( '[name=animate][value=auto]' ); | |
| var $hue = $( '[name=hue]' ); | |
| var $delay = $( '[name=delay]' ); | |
| var $width = $( '[name=width]' ); | |
| var $height = $( '[name=height]' ); | |
| var $gap = $( '[name=gap]' ); | |
| var $autoDelay = $( '[name=auto-delay]' ); | |
| function init() { | |
| try { | |
| window.AudioContext = window.AudioContext || window.webkitAudioContext; | |
| context = new AudioContext(); | |
| resize(); | |
| $( window ).on( 'resize', resize ); | |
| flip(); | |
| loadSong( songUrl ); | |
| } catch ( err ) { | |
| console.error( 'Web Audio API is not supported in this browser' ); | |
| } | |
| } | |
| function flip() { | |
| if ( animate == 'auto' ) { | |
| if ( forward ) { | |
| forward = false; | |
| } else { | |
| forward = true; | |
| } | |
| } | |
| setTimeout( flip, animateSwitch ); | |
| } | |
| function loadSong( url ) { | |
| var request = new XMLHttpRequest(); | |
| request.open( 'GET', url, true ); | |
| request.responseType = 'arraybuffer'; | |
| request.onload = function() { | |
| context.decodeAudioData( request.response, function( buffer ) { | |
| songBuffer = buffer; | |
| analyser = context.createAnalyser(); | |
| analyser.smoothingTimeConstant = 0.3; | |
| analyser.fftSize = 1024; | |
| $body.addClass( 'loaded' ); | |
| update(); | |
| play(); | |
| }, onError ); | |
| } | |
| request.send(); | |
| } | |
| function resize( evt ) { | |
| var $win = $( window ); | |
| var winWidth = $win.width(); | |
| var winHeight = $win.height(); | |
| barCount = ( winWidth / ( lineWidth * 2 ) ) / 2; | |
| canvas.width = winWidth; | |
| canvas.height = winHeight; | |
| } | |
| function update() { | |
| // get the average, bincount is fftsize / 2 | |
| var array = new Uint8Array( analyser.frequencyBinCount ); | |
| analyser.getByteFrequencyData( array ); | |
| var average = getAverageVolume( array ); | |
| var average = average * heightFactor; | |
| bars[ 0 ] = average; | |
| average *= 0.8; | |
| if ( playing ) { | |
| var reduce = 0; | |
| for ( var i = 1; i < barCount; i++ ) { | |
| average = average - Math.sqrt( average ) + 1; | |
| if ( average < 0 ) { | |
| average = 0; | |
| } | |
| (function( i, average ) { | |
| setTimeout( function() { | |
| bars[ i ] = average; | |
| }, delay * ( forward ? i : 60 - i ) ); | |
| })( i, average ); | |
| } | |
| } | |
| draw(); | |
| updateHash(); | |
| requestAnimationFrame( update ); | |
| } | |
| function draw() { | |
| var canvasWidth = canvas.width; | |
| var canvasHeight = canvas.height; | |
| // clear the current state | |
| ctx.clearRect( 0, 0, canvasWidth, canvasHeight ); | |
| // set the fill style | |
| var average = bars[ 0 ]; | |
| var color = getColor( average ); | |
| rect( ( canvasWidth / 2 ) - ( lineWidth / 2 ), ( canvasHeight / 2 ) - ( average / 2 ), lineWidth, average, color ); | |
| for ( var i = 1; i < barCount; i++ ) { | |
| var average = bars[ i ]; | |
| color = getColor( average ); | |
| if ( average === undefined || average <= 0 ) { | |
| average = 0; | |
| } else { | |
| rect( ( canvasWidth / 2 ) - ( lineWidth / 2 ) + ( ( lineWidth + lineGap ) * i ), ( canvasHeight / 2 ) - ( average / 2 ), lineWidth, average, color ); | |
| rect( ( canvasWidth / 2 ) - ( lineWidth / 2 ) - ( ( lineWidth + lineGap ) * i ), ( canvasHeight / 2 ) - ( average / 2 ), lineWidth, average, color ); | |
| } | |
| } | |
| } | |
| var originalColors = [ | |
| '#333', | |
| 'purple', | |
| 'magenta', | |
| 'pink', | |
| 'red', | |
| 'orange', | |
| 'yellow', | |
| 'green', | |
| 'cyan', | |
| 'blue' | |
| ]; | |
| var colors = _.extend( [], originalColors ); | |
| function getColor( val ) { | |
| // account for hue index | |
| if ( hue == 0 ) { | |
| colors = _.extend( [], originalColors ); | |
| for ( var i = 0; i < hue; i++ ) { | |
| colors.unshift( colors.pop() ); | |
| } | |
| var whiteIndex = colors.indexOf( '#333' ); | |
| colors.splice( whiteIndex, 1 ); | |
| colors.unshift( '#333' ); | |
| } else { | |
| colors = Array( 10 ); | |
| colors[ 0 ] = '#333'; | |
| var lightness = 49; | |
| for ( var i = 9; i >= 1; i-- ) { | |
| colors[ i ] = 'hsl(' + hue + ', 100%, ' + lightness + '%)' | |
| lightness -= 5; | |
| } | |
| } | |
| var colorIndex = Math.floor( val / ( 10 * heightFactor ) ); | |
| if ( colorIndex > 9 ) { | |
| colorIndex = 9; | |
| } else if ( colorIndex < 0 ) { | |
| colorIndex = 0; | |
| } | |
| return colors[ colorIndex ]; | |
| } | |
| function rect( x, y, width, height, color ) { | |
| ctx.save(); | |
| ctx.beginPath(); | |
| ctx.rect( x, y, width, height ); | |
| ctx.stroke(); | |
| ctx.clip(); | |
| ctx.fillStyle = color; | |
| ctx.fillRect( 0,0,canvas.width,canvas.height ); | |
| ctx.restore(); | |
| } | |
| function getAverageVolume( array ) { | |
| var values = 0; | |
| var average; | |
| var length = array.length; | |
| // get all the frequency amplitudes | |
| for ( var i = 0; i < length; i++ ) { | |
| values += array[ i ]; | |
| } | |
| average = values / length; | |
| return average; | |
| } | |
| function play() { | |
| startTime = context.currentTime; | |
| songSource = context.createBufferSource(); | |
| songSource.connect( analyser ); | |
| songSource.buffer = songBuffer; | |
| songSource.connect( context.destination ); | |
| songSource.loop = true; | |
| songSource.start( 0, startOffset % songBuffer.duration ); | |
| togglePlaying(); | |
| } | |
| function stop() { | |
| songSource.stop( 0 ); | |
| startOffset += context.currentTime - startTime; | |
| togglePlaying(); | |
| } | |
| function togglePlaying() { | |
| if ( playing ) { | |
| $body.removeClass( 'playing' ); | |
| playing = false; | |
| } else { | |
| $body.addClass( 'playing' ); | |
| playing = true; | |
| } | |
| } | |
| function updateHash() { | |
| var props = []; | |
| var hash = ''; | |
| hash = 'width=' + lineWidth + '&' + | |
| 'height=' + heightFactor + '&' + | |
| 'gap=' + lineGap + '&' + | |
| 'delay=' + delay + '&' + | |
| 'hue=' + hue + '&' + | |
| 'animate=' + animate + '&' + | |
| 'auto_delay=' + animateSwitch + '&' + | |
| 'song=' + songName + '&' + | |
| 'hide_controls=' + ( globalHash.hide_controls || 0 ) + '&' + | |
| 'small=' + ( globalHash.small || 0 ); | |
| if ( window.location.hash != hash ) { | |
| window.location.hash = hash; | |
| } | |
| } | |
| function getHash() { | |
| return window.location.hash | |
| .replace( /^\#/, '' ) | |
| .split( '&' ) | |
| .reduce( function( memo, keyVal ) { | |
| var key = keyVal.split( '=' )[ 0 ]; | |
| var val = keyVal.split( '=' )[ 1 ]; | |
| if ( key != 'animate' && key != 'song' ) { | |
| val = parseInt( val ); | |
| } | |
| memo[ key ] = val; | |
| return memo; | |
| }, {} ); | |
| } | |
| function onError( err ) { | |
| console.error( err ); | |
| } | |
| $( '.playpause' ).on( 'click', function() { | |
| if ( playing ) { | |
| stop(); | |
| } else { | |
| play(); | |
| } | |
| }); | |
| $out.on( 'click', function( evt ) { | |
| if ( evt.currentTarget.checked ) { | |
| forward = true; | |
| animate = 'out'; | |
| } | |
| }); | |
| $in.on( 'click', function( evt ) { | |
| if ( evt.currentTarget.checked ) { | |
| forward = false; | |
| animate = 'in'; | |
| } | |
| }); | |
| $auto.on( 'click', function( evt ) { | |
| if ( evt.currentTarget.checked ) { | |
| animate = 'auto'; | |
| } | |
| }) | |
| $delay.on( 'input', function() { | |
| var val = $delay.val(); | |
| // console.log( val * 1.2 ); | |
| delay = Math.floor( val * 1.2 ); | |
| }); | |
| $width.on( 'input', function() { | |
| var winWidth = $( window ).width(); | |
| barCount = ( winWidth / ( lineWidth + lineGap ) ) / 2; | |
| lineWidth = 1 + Math.floor( ( $width.val() / 2 ) ); | |
| }); | |
| $gap.on( 'input', function() { | |
| lineGap = Math.floor( ( $gap.val() / 2.5 ) ); | |
| }); | |
| $height.on( 'input', function() { | |
| heightFactor = 1 + ( $height.val() / 10 ); | |
| }); | |
| $autoDelay.on( 'input', function() { | |
| animateSwitch = Math.floor( $autoDelay.val() / 10 ) * 1000; | |
| }) | |
| $hue.on( 'input', function() { | |
| // hue = Math.floor( $hue.val() / 10 ); | |
| hue = Math.floor( ( 361 * ( $hue.val() / 100 ) ) ); | |
| }); |
| <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> | |
| <script src="https://use.edgefonts.net/source-sans-pro:n2,i2,n3,i3,n4,i4,n6,i6,n7,i7,n9,i9.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.js"></script> |
| html { | |
| box-sizing: border-box; | |
| color: white; | |
| font-family: 'source-sans-pro', sans-serif; | |
| } | |
| .controls { | |
| z-index: 1; | |
| position: absolute; | |
| top: 50px; | |
| right: 50px; | |
| text-align: right; | |
| } | |
| .controls.hide { | |
| display: none; | |
| } | |
| * { | |
| box-sizing: inherit; | |
| } | |
| html, body { | |
| height: 100%; | |
| } | |
| body { | |
| margin: 0; | |
| background: #111; | |
| } | |
| canvas { | |
| display: block; | |
| background: #111; | |
| position: absolute; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| -webkit-transform: translateY(-50%); | |
| } | |
| .visualizr { | |
| position: relative; | |
| height: 100%; | |
| } | |
| .playpause { | |
| z-index: 1; | |
| position: absolute; | |
| top: 50px; | |
| left: 35px; | |
| /* width: 35px; */ | |
| width: 48px; | |
| height: 48px; | |
| background-size: 100% 100%; | |
| background-repeat: no-repeat; | |
| background-position: center center; | |
| background-image: url(https://s3.amazonaws.com/tybenz.assets/spinner.png); | |
| -webkit-appearance: none; | |
| background-color: transparent; | |
| border: none; | |
| outline: none; | |
| cursor: pointer; | |
| -webkit-animation: rotate .75s infinite linear; | |
| -moz-animation: rotate .75s infinite linear; | |
| animation: rotate .75s infinite linear; | |
| } | |
| .small .playpause { | |
| width: 24px; | |
| height: 24px; | |
| } | |
| @-webkit-keyframes rotate { | |
| from {-webkit-transform: rotate(0deg);} | |
| to {-webkit-transform: rotate(-360deg);} | |
| } | |
| @-moz-keyframes rotate { | |
| from {-moz-transform: rotate(0deg);} | |
| to {-moz-transform: rotate(-360deg);} | |
| } | |
| @keyframes rotate { | |
| from {transform: rotate(0deg);} | |
| to {transform: rotate(-360deg);} | |
| } | |
| .playing .playpause { | |
| background-image: url(https://s3.amazonaws.com/tybenz.assets/pause.png) !important; | |
| } | |
| .loaded .playpause { | |
| width: 35px; | |
| height: 48px; | |
| background-image: url(https://s3.amazonaws.com/tybenz.assets/play.png); | |
| background-position: center center; | |
| color: transparent; | |
| -webkit-animation: none; | |
| -moz-animation: none; | |
| animation: none; | |
| top: 50px; | |
| left: 50px; | |
| } | |
| .small.loaded .playpause { | |
| width: 17px; | |
| height: 24px; | |
| } | |
| .controls { | |
| font-size: 0.8em; | |
| font-weight: 100; | |
| } | |
| input[type=range] { | |
| position: relative; | |
| top: 4px; | |
| } | |
| .field { | |
| margin-bottom: 0.4em; | |
| } |
Song courtesy of MRSJXN: https://soundcloud.com/mrsjxn
Forked from Tyler Benziger's Pen Visualizr.
A Pen by Herbert Joseph on CodePen.