Search code examples
javascriptreactjshtml5-canvasaudiocontext

Drawing sound bars around a canvas circle


I have the following which draws the frequency of an audio clip as soundbars:

const drawSinewave = function() {
    requestAnimationFrame(drawSinewave);

    analyser.getByteFrequencyData(sinewaveDataArray);

    canvasCtx.fillStyle = 'white';
    canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
    canvasCtx.lineWidth = 2;
    canvasCtx.strokeStyle = "#40a9ff";
    canvasCtx.beginPath();

    const sliceWidth = canvas.width * 1.0 / analyser.fftSize;
    let x = 0;

    var barWidth = (canvas.width  / analyser.fftSize) * 2.5;
    var barHeight;

    for(let i = 0; i < analyser.fftSize; i++) {
        barHeight = sinewaveDataArray[i];

        canvasCtx.fillStyle = 'rgb(' + (barHeight+100) + ',50,50)';
        canvasCtx.fillRect(x,canvas.height-barHeight/2,barWidth,barHeight);

        x += barWidth + 1;
    }

    canvasCtx.lineTo(canvas.width, canvas.height / 2);
    canvasCtx.stroke();
};

It looks like this: soundbars line

However I would like to draw that around a circle, so like the bars come out like rays from a circle border. I have failed to figure this out. Could someone assist me?


Solution

  • A simple method is to draw one bar on its side, offset from the center of the circle. before you draw the bar rotate the current transform to the correct position for the bar.

    The example uses some random test data as I could not be bothered setting up an analyser

    const steps = 100;
    const testData = [];
    
    const ctx = canvas.getContext("2d");
    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;
    const innerRadius = Math.min(canvas.width, canvas.height) / 2 * (3/4);
    const outerRadius = Math.min(canvas.width, canvas.height) / 2 - 20;
    const barHeight = outerRadius - innerRadius;
    const angStep = (Math.PI * 2) / steps;
    const barWidth = (innerRadius * Math.PI * 2 / steps) * 0.9;
    const barWidthHalf= barWidth * 0.5;
    const startAngle = -Math.PI / 2; // 12 oclock
    requestAnimationFrame(drawBars)
    
    function drawBars() {
        const color = h => 'rgb(' + (100+h*150) + ',' + (50+h*100) + ',' + (100+h*40) + ')';
    
        //analyser.getByteFrequencyData(sinewaveDataArray);
        //const steps = analyser.fftSize;
        animateTestData();
        
        ctx.fillStyle = 'white';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
    
        ctx.lineWidth = 2;
        ctx.strokeStyle = "#40a9ff";
        ctx.beginPath();
        ctx.arc(centerX, centerY, innerRadius - 4, 0, Math.PI * 2);
        ctx.stroke();
    
        for(let i = 0; i < steps; i++) {
            const h = testData[i];
            const ang = i * angStep + startAngle;
            const xAx = Math.cos(ang);  // direction of x axis
            const xAy = Math.sin(ang);
            ctx.setTransform(xAx, xAy, -xAy, xAx, centerX, centerY);
    
            ctx.fillStyle = color(h);
            ctx.fillRect(innerRadius, -barWidthHalf, h * barHeight, barWidth);
        }
    
        ctx.setTransform(1,0,0,1,0,0);  // reset the transform;
        requestAnimationFrame(drawBars);
    };
    
    
    for(let i = 0; i < steps; i ++) { testData.push(Math.random()) }
    var sOffset = 0;
    function animateTestData() {
        var i = 0, t, phase = Math.sin(sOffset/ 10) * 100;
        while(i < steps) {
            var t = testData[i];
            t += (Math.random() - 0.5) * 0.01;
            t += Math.sin(i * 0.6 + phase + sOffset) * 0.03
            t += Math.sin(i * 0.1 + sOffset * 2) * 0.07
            testData[i++] = t <= 0 ? 0 : t >= 1 ? 1 : t;
        }
        sOffset += 0.1;
    }
    <canvas id = "canvas" width = "600" height = "600"></canvas>