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.