Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save basicfeatures/1ce8b7f143a23b221aab407841bfc244 to your computer and use it in GitHub Desktop.
Save basicfeatures/1ce8b7f143a23b221aab407841bfc244 to your computer and use it in GitHub Desktop.

Revisions

  1. basicfeatures created this gist Mar 26, 2024.
    7 changes: 7 additions & 0 deletions distorted-warptunnel-audio-visualizer.markdown
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    Distorted Warptunnel Audio Visualizer
    -------------------------------------
    A mouse sensitive canvas/audio-visualizer experiment in pure JS

    A [Pen](https://codepen.io/favoriteusername/pen/KKREBjP) by [BergenBergen BergenBergen](https://codepen.io/favoriteusername) on [CodePen](https://codepen.io).

    [License](https://codepen.io/license/pen/KKREBjP).
    2 changes: 2 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2 @@
    <a id="btStartAudioVisualization" class="bt">Start Audio Visualization</a>
    <p id="txtStatus"></p>
    711 changes: 711 additions & 0 deletions script.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,711 @@
    /*
    Song: LAKEY INSPIRED - Chill Day (Vlog No Copyright Music) Music provided by Vlog No Copyright Music. Video Link: https://youtu.be/vtHGESuQ22s
    */
    //---

    var audio, audioContext, audioSrc;
    var analyser, analyserBufferLength;

    //---

    var w;
    var h;

    var btStart;
    var txtStatus;
    var canvas;
    var context;

    var imageData;
    var data;

    var mouseActive = false;
    var mouseDown = false;
    var mousePos = { x:0, y:0 };
    var mouseFollowSpeed = 0.015;

    var fov = 250;

    var speed = 0.75;

    var particles = [];
    var particlesCenter = [];

    var time = 0;

    var colorInvertValue = 0;

    //---

    function init() {

    canvas = document.createElement( 'canvas' );
    canvas.addEventListener( 'mousedown', mouseDownHandler, false );
    canvas.addEventListener( 'mouseup', mouseUpHandler, false );
    canvas.addEventListener( 'mousemove', mouseMoveHandler, false );
    canvas.addEventListener( 'mouseenter', mouseEnterHandler, false );
    canvas.addEventListener( 'mouseleave', mouseLeaveHandler, false );

    document.body.appendChild( canvas );

    context = canvas.getContext( '2d' );

    window.addEventListener( 'resize', onResize, false );

    onResize();

    addParticles();

    render();

    clearImageData();

    render();

    context.putImageData( imageData, 0, 0 );

    btStart = document.getElementById( 'btStartAudioVisualization' );
    btStart.addEventListener( 'mousedown', userStart, false );

    txtStatus = document.getElementById( 'txtStatus' );
    txtStatus.innerHTML = 'Waiting Patiently For You... Please Click the Start Button.';

    };

    //---

    function userStart() {

    btStart.removeEventListener( 'mousedown', userStart );

    btStart.addEventListener( 'mousedown', audioBtHandler, false );
    btStart.innerHTML = 'Pause Audio';

    txtStatus.innerHTML = 'Loading Audio...';

    audioSetup();
    animate();

    };

    //---

    function audioSetup() {

    audio = new Audio();
    audio.src = 'http://nkunited.de/ExternalImages/jsfiddle/audio/ChillDay_comp.mp3';
    audio.controls = false;
    audio.loop = true;
    audio.autoplay = true;
    audio.crossOrigin = 'anonymous';
    audio.addEventListener( 'canplaythrough', audioLoaded, false );

    audioContext = new ( window.AudioContext || window.webkitAudioContext )();

    analyser = audioContext.createAnalyser();
    analyser.connect( audioContext.destination );
    analyser.smoothingTimeConstant = 0.65;
    analyser.fftSize = 512 * 32;//circleSegments * 32;
    analyserBufferLength = analyser.frequencyBinCount;

    audioSrc = audioContext.createMediaElementSource( audio );
    audioSrc.connect( analyser );

    };

    function audioLoaded( event ) {

    txtStatus.innerHTML = 'Song: LAKEY INSPIRED - Chill Day';
    //txtStatus.style.display = 'none';

    };

    //---

    function clearImageData() {

    for ( var i = 0, l = data.length; i < l; i += 4 ) {

    data[ i ] = 0;
    data[ i + 1 ] = 0;
    data[ i + 2 ] = 0;
    data[ i + 3 ] = 255;

    }

    };

    function setPixel( x, y, r, g, b, a ) {

    var i = ( x + y * imageData.width ) * 4;

    data[ i ] = r;
    data[ i + 1 ] = g;
    data[ i + 2 ] = b;
    data[ i + 3 ] = a;

    };

    //---

    function drawLine( x1, y1, x2, y2, r, g, b, a ) {

    var dx = Math.abs( x2 - x1 );
    var dy = Math.abs( y2 - y1 );

    var sx = ( x1 < x2 ) ? 1 : -1;
    var sy = ( y1 < y2 ) ? 1 : -1;

    var err = dx - dy;

    var lx = x1;
    var ly = y1;

    while ( true ) {

    if ( lx > 0 && lx < w && ly > 0 && ly < h ) {

    setPixel( lx, ly, r, g, b, a );

    }

    if ( ( lx === x2 ) && ( ly === y2 ) )
    break;

    var e2 = 2 * err;

    if ( e2 > -dx ) {

    err -= dy;
    lx += sx;

    }

    if ( e2 < dy ) {

    err += dx;
    ly += sy;

    }

    }

    };

    //---

    function getCirclePosition( centerX, centerY, radius, index, segments ) {

    var angle = index * ( ( Math.PI * 2 ) / segments ) + time;

    var x = centerX + Math.cos( angle ) * radius;
    var y = centerY + Math.sin( angle ) * radius;

    return { x:x, y:y };

    };

    function drawCircle( centerPosition, radius, segments ) {

    var coordinates = [];

    var radiusSave;

    var diff = 0;//Math.floor( Math.random() * segments );

    for ( var i = 0; i <= segments; i++ ) {

    //var radiusRandom = radius + Math.random() * ( radius / 8 );
    //var radiusRandom = radius + Math.random() * ( radius / 32 );
    var radiusRandom = radius;// + ( radius / 8 );

    if ( i === 0 ) {

    radiusSave = radiusRandom;

    }

    if ( i === segments ) {

    radiusRandom = radiusSave;

    }

    var centerX = centerPosition.x;
    var centerY = centerPosition.y;

    var position = getCirclePosition( centerX, centerY, radiusRandom, i, segments );

    coordinates.push( { x:position.x, y:position.y, index:i + diff, radius:radiusRandom, segments:segments, centerX:centerX, centerY:centerY } );

    }

    return coordinates;

    };

    //---

    function addParticle( x, y, z, audioBufferIndex ) {

    var particle = {};
    particle.x = x;
    particle.y = y;
    particle.z = z;
    particle.x2d = 0;
    particle.y2d = 0;
    particle.audioBufferIndex = audioBufferIndex;

    return particle;

    };

    function addParticles() {

    var audioBufferIndexMin = 8;
    var audioBufferIndexMax = 1024;
    var audioBufferIndex = audioBufferIndexMin;

    var centerPosition = { x:0, y:0 };
    var center = { x:0, y:0 };
    var c = 0;

    var w1 = Math.random() * ( w / 1 );
    var h1 = Math.random() * ( h / 1 );

    for ( var z = -fov; z < fov; z += 4 ) {

    var coordinates = drawCircle( centerPosition, 75, 64 );
    var particlesRow = [];

    center.x = ( ( w / 2 ) - w1 ) * ( c / 15 ) + w / 2;
    center.y = ( ( h / 2 ) - h1 ) * ( c / 15 ) + w / 2;

    c++;

    //var center = { x:w / 2, y:h / 2 };

    particlesCenter.push( center );

    audioBufferIndex = Math.floor( Math.random() * audioBufferIndexMax ) + audioBufferIndexMin;

    for ( var i = 0, l = coordinates.length; i < l; i++ ) {

    var coordinate = coordinates[ i ];

    var particle = addParticle( coordinate.x, coordinate.y, z, audioBufferIndex );
    particle.index = coordinate.index;
    particle.radius = coordinate.radius;
    particle.radiusAudio = particle.radius;
    particle.segments = coordinate.segments;
    particle.centerX = coordinate.centerX;
    particle.centerY = coordinate.centerY;

    particlesRow.push( particle );

    if ( i < coordinates.length / 2 ) {

    audioBufferIndex++;

    } else {

    audioBufferIndex--;

    }

    if ( audioBufferIndex > audioBufferIndexMax ) {

    audioBufferIndex = audioBufferIndexMin;

    }

    if ( audioBufferIndex < audioBufferIndexMin ) {

    audioBufferIndex = audioBufferIndexMax;

    }

    /*
    if ( i < audioBufferIndexMax / 2 ) {
    //if ( i < Math.random() * audioBufferIndexMax ) {
    audioBufferIndex++;
    } else {
    audioBufferIndex--;
    }
    */

    /*
    audioBufferIndex++;
    //if ( audioBufferIndex > Math.random() * audioBufferIndexMax ) {
    if ( audioBufferIndex > audioBufferIndexMax ) {
    audioBufferIndex = audioBufferIndexMin;
    }
    */
    //audioBufferIndex = Math.floor( Math.random() * audioBufferIndexMax ) + audioBufferIndexMin;

    }

    particles.push( particlesRow );

    }

    };

    //---

    function onResize(){

    w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
    h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

    canvas.width = w;
    canvas.height = h;

    context.fillStyle = '#000000';
    context.fillRect( 0, 0, w, h );

    imageData = context.getImageData( 0, 0, w, h );
    data = imageData.data;

    };

    //---

    function audioBtHandler( event ) {

    if ( audio.paused ) {

    audio.play();

    btStart.innerHTML = 'Pause Audio';

    } else {

    audio.pause();

    btStart.innerHTML = 'Play Audio';

    }

    };

    //---

    function mouseDownHandler( event ) {

    mouseDown = true;

    };

    function mouseUpHandler( event ) {

    mouseDown = false;

    };

    function mouseEnterHandler( event ) {

    mouseActive = true;

    };

    function mouseLeaveHandler( event ) {

    mouseActive = false;

    mousePos.x = w / 2;
    mousePos.y = h / 2;

    mouseDown = false;

    };

    function mouseMoveHandler( event ) {

    mousePos = getMousePos( canvas, event );

    };

    function getMousePos( canvas, event ) {

    var rect = canvas.getBoundingClientRect();

    return { x:event.clientX - rect.left, y:event.clientY - rect.top };

    };

    //---

    function render() {

    var frequencySource;

    if ( analyser ) {

    frequencySource = new Uint8Array( analyser.frequencyBinCount );

    analyser.getByteFrequencyData( frequencySource );

    }

    //---

    var sortArray = false;

    //---

    for ( var i = 0, l = particles.length; i < l; i++ ) {

    var particlesRow = particles[ i ];
    var particlesRowBack;

    if ( i > 0 ) {

    particlesRowBack = particles[ i - 1 ];

    }

    //---

    var center = particlesCenter[ i ];

    if ( mouseActive ) {

    //center.x = ( ( w / 2 ) - mousePos.x ) * ( -i / 150 ) + w / 2;
    //center.y = ( ( h / 2 ) - mousePos.y ) * ( -i / 150 ) + h / 2;
    center.x = ( ( w / 2 ) - mousePos.x ) * ( ( particlesRow[ 0 ].z - fov ) / 500 ) + w / 2;
    center.y = ( ( h / 2 ) - mousePos.y ) * ( ( particlesRow[ 0 ].z - fov ) / 500 ) + h / 2;

    } else {

    center.x += ( ( w / 2 ) - center.x ) * mouseFollowSpeed;
    center.y += ( ( h / 2 ) - center.y ) * mouseFollowSpeed;

    }

    //---

    for ( var j = 0, k = particlesRow.length; j < k; j++ ) {

    var particle = particlesRow[ j ];

    var scale = fov / ( fov + particle.z );

    particle.x2d = ( particle.x * scale ) + center.x;
    particle.y2d = ( particle.y * scale ) + center.y;

    //---

    if ( analyser ) {

    var frequency = frequencySource[ particle.audioBufferIndex ];
    var frequencyAdd = frequency / 8;

    particle.radiusAudio = particle.radius + frequencyAdd;

    } else {

    particle.radiusAudio = particle.radius;// + Math.random() * 4;

    }

    //---

    if ( mouseDown ) {

    particle.z += speed;

    if ( particle.z > fov ) {

    particle.z -= ( fov * 2 );

    sortArray = true;

    }

    } else {

    particle.z -= speed;

    if ( particle.z < -fov ) {

    particle.z += ( fov * 2 );

    sortArray = true;

    }

    }

    //---

    var lineColorValue = 0;

    if ( j > 0 ) {

    var p = particlesRow[ j - 1 ];

    //var lineColorValue = Math.round( ( ( i - ( fov / 5 ) ) / l ) * 255 );
    lineColorValue = Math.round( i / l * 200 );//255

    /*
    if ( analyser ) {
    lineColorValue = Math.round( i / l * ( 200 + frequency ));//255
    if ( lineColorValue > 255 ) {
    lineColorValue = 255;
    }
    }
    */

    drawLine( particle.x2d | 0, particle.y2d | 0, p.x2d | 0, p.y2d | 0, 0, Math.round( lineColorValue / 2 ), lineColorValue, 255 );


    }

    var position;

    if ( j < k - 1 ) {

    position = getCirclePosition( particle.centerX, particle.centerY, particle.radiusAudio, particle.index, particle.segments );

    } else {

    var p1 = particlesRow[ 0 ];

    position = getCirclePosition( p1.centerX, p1.centerY, p1.radiusAudio, p1.index, p1.segments );

    }

    particle.x = position.x;
    particle.y = position.y;

    //---

    if ( i > 0 && i < l - 1 ) {

    var pB;// = particlesRowBack[ j ];

    if ( j === 0 ) {

    pB = particlesRowBack[ particlesRowBack.length - 1 ];

    } else {

    pB = particlesRowBack[ j - 1 ];

    }

    drawLine( particle.x2d | 0, particle.y2d | 0, pB.x2d | 0, pB.y2d | 0, 0, Math.round( lineColorValue / 2 ), lineColorValue, 255 );

    }


    }

    }

    //---

    if ( sortArray ) {

    particles = particles.sort( function( a, b ) {

    return ( b[ 0 ].z - a[ 0 ].z );

    } );

    }

    //---

    if ( mouseDown ) {

    time -= 0.005;

    } else {

    time += 0.005;

    }

    //---

    //soft invert colors
    if ( mouseDown ) {

    if ( colorInvertValue < 255 )
    colorInvertValue += 5;
    else
    colorInvertValue = 255;

    softInvert( colorInvertValue );

    } else {

    if ( colorInvertValue > 0 )
    colorInvertValue -= 5;
    else
    colorInvertValue = 0;

    if ( colorInvertValue > 0 )
    softInvert( colorInvertValue );

    }

    };

    //---

    function softInvert( value ) {

    for ( var j = 0, n = data.length; j < n; j += 4 ) {

    data[ j ] = Math.abs( value - data[ j ] ); // red
    data[ j + 1 ] = Math.abs( value - data[ j + 1 ] ); // green
    data[ j + 2 ] = Math.abs( value - data[ j + 2 ] ); // blue
    data[ j + 3 ] = 255;// - data[ j + 3 ]; // alpha

    }

    };

    //---

    function animate() {

    clearImageData();

    render();

    context.putImageData( imageData, 0, 0 );

    requestAnimationFrame( animate );

    };

    window.requestAnimFrame = ( function() {

    return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    function( callback ) {
    window.setTimeout( callback, 1000 / 60 );
    };

    } )();

    //---

    init();
    61 changes: 61 additions & 0 deletions style.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,61 @@
    html, body, div {
    margin: 0;
    padding: 0;
    border: 0;
    }
    body {
    overflow: hidden;
    background-color:#000;
    }

    .bt {
    -moz-box-shadow:inset 0px 1px 0px 0px #54a3f7;
    -webkit-box-shadow:inset 0px 1px 0px 0px #54a3f7;
    box-shadow:inset 0px 1px 0px 0px #54a3f7;
    background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #007dc1), color-stop(1, #0061a7));
    background:-moz-linear-gradient(top, #007dc1 5%, #0061a7 100%);
    background:-webkit-linear-gradient(top, #007dc1 5%, #0061a7 100%);
    background:-o-linear-gradient(top, #007dc1 5%, #0061a7 100%);
    background:-ms-linear-gradient(top, #007dc1 5%, #0061a7 100%);
    background:linear-gradient(to bottom, #007dc1 5%, #0061a7 100%);
    background-color:#007dc1;
    -moz-border-radius:3px;
    -webkit-border-radius:3px;
    border-radius:3px;
    border:1px solid #124d77;
    display:inline-block;
    cursor:pointer;
    color:#ffffff;
    font-family:Arial;
    font-size:13px;
    padding:6px 24px;
    text-decoration:none;
    text-shadow:0px 1px 0px #154682;
    }
    .bt:hover {
    background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #0061a7), color-stop(1, #007dc1));
    background:-moz-linear-gradient(top, #0061a7 5%, #007dc1 100%);
    background:-webkit-linear-gradient(top, #0061a7 5%, #007dc1 100%);
    background:-o-linear-gradient(top, #0061a7 5%, #007dc1 100%);
    background:-ms-linear-gradient(top, #0061a7 5%, #007dc1 100%);
    background:linear-gradient(to bottom, #0061a7 5%, #007dc1 100%);
    background-color:#0061a7;
    }
    .bt:active {
    position:relative;
    top:1px;
    }
    #btStartAudioVisualization {
    position: absolute;
    top: 10px;
    left: 10px;
    }

    #txtStatus {
    position: absolute;
    bottom: 0px;
    left: 10px;
    font-family: "Arial", serif;
    font-size: 12px;
    color: rgb(255,255,255);
    }