Прочитать аудиофайл для Web-Audio API Analyzer (сервер Node.Js, интерфейс JS) - PullRequest
2 голосов
/ 25 июня 2019

Я настроил простой сервер Node.js для предоставления файла .wav моему локальному веб-интерфейсу.

require('dotenv').config();
const debugBoot = require('debug')('boot');
const cors = require('cors')
const express = require('express');
const app = express();
app.set('port', process.env.PORT || 3000);

app.use(cors());
app.use(express.static('public'));


const server = app.listen(app.get('port'), () => {
    const port = server.address().port;
    debugBoot('Server running at http://localhost:' + port);
});

На моем локальном интерфейсе я получаю файл:

fetch('http://localhost:3000/audio/8bars60bpmOnlyKick.wav').then(response => process(response.body))

function process(stream) {
    console.log(stream);
    const context = new AudioContext();
    const analyser = context.createAnalyser();
    const source = context.createMediaStreamSource(stream);
    source.connect(analyser);
    const data = new Uint8Array(analyser.frequencyBinCount);

Я хочу передать поток в AudioContext().createMediaStreamSource. Я мог бы сделать это с помощью Media Stream , например, с микрофона.

Но с ReadableStream я получаю ошибку Failed to execute 'createMediaStreamSource' on 'AudioContext': parameter 1 is not of type 'MediaStream'.

Я хочу передать / получить аудио таким образом, чтобы я мог подключить его к веб-аудио API и использовать анализатор. Это не должно быть потоком, если есть другое решение.

1 Ответ

0 голосов
/ 26 июня 2019

Я объединил в основном эти оба примера вместе:

https://www.youtube.com/watch?v=hYNJGPnmwls (https://codepen.io/jakealbaugh/pen/jvQweW/)

и пример из веб-аудио API:

https://github.com/mdn/webaudio-examples/blob/master/audio-analyser/index.html

let audioBuffer;
let sourceNode;
let analyserNode;
let javascriptNode;
let audioData = null;
let audioPlaying = false;
let sampleSize = 1024;  // number of samples to collect before analyzing data
let frequencyDataArray;     // array to hold time domain data
// Global Variables for the Graphics
let canvasWidth = 512;
let canvasHeight = 256;
let ctx;

document.addEventListener("DOMContentLoaded", function () {
    ctx = document.body.querySelector('canvas').getContext("2d");
    // the AudioContext is the primary 'container' for all your audio node objects
    try {
        audioContext = new AudioContext();
    } catch (e) {
        alert('Web Audio API is not supported in this browser');
    }
    // When the Start button is clicked, finish setting up the audio nodes, play the sound,
    // gather samples for the analysis, update the canvas
    document.body.querySelector('#start_button').addEventListener('click', function (e) {
        e.preventDefault();
        // Set up the audio Analyser, the Source Buffer and javascriptNode
        initCanvas();
        setupAudioNodes();
        javascriptNode.onaudioprocess = function () {
            // get the Time Domain data for this sample
            analyserNode.getByteFrequencyData(frequencyDataArray);
            // draw the display if the audio is playing
            console.log(frequencyDataArray)
            draw();
        };
        loadSound();
    });

    document.body.querySelector("#stop_button").addEventListener('click', function(e) {
        e.preventDefault();
        sourceNode.stop(0);
        audioPlaying = false;
    });

    function loadSound() {
        fetch('http://localhost:3000/audio/8bars60bpmOnlyKick.wav').then(response => {
            response.arrayBuffer().then(function (buffer) {
                audioContext.decodeAudioData(buffer).then((audioBuffer) => {
                    console.log('audioBuffer', audioBuffer);
                    // {length: 1536000, duration: 32, sampleRate: 48000, numberOfChannels: 2}
                    audioData = audioBuffer;
                    playSound(audioBuffer);
                });
            });
        })
    }

    function setupAudioNodes() {
        sourceNode = audioContext.createBufferSource();
        analyserNode = audioContext.createAnalyser();
        analyserNode.fftSize = 4096;
        javascriptNode = audioContext.createScriptProcessor(sampleSize, 1, 1);
        // Create the array for the data values
        frequencyDataArray = new Uint8Array(analyserNode.frequencyBinCount);
        // Now connect the nodes together
        sourceNode.connect(audioContext.destination);
        sourceNode.connect(analyserNode);
        analyserNode.connect(javascriptNode);
        javascriptNode.connect(audioContext.destination);
    }

    function initCanvas() {
        ctx.fillStyle = 'hsl(280, 100%, 10%)';
        ctx.fillRect(0, 0, canvasWidth, canvasHeight);
    };

    // Play the audio once
    function playSound(buffer) {
        sourceNode.buffer = buffer;
        sourceNode.start(0);    // Play the sound now
        sourceNode.loop = false;
        audioPlaying = true;
    }

    function draw() {
        const data = frequencyDataArray;
        const dataLength = frequencyDataArray.length;
        console.log("data", data);

        const h = canvasHeight / dataLength;
        // draw on the right edge
        const x = canvasWidth - 1;

        // copy the old image and move one left
        let imgData = ctx.getImageData(1, 0, canvasWidth - 1, canvasHeight);
        ctx.fillRect(0, 0, canvasWidth, canvasHeight);
        ctx.putImageData(imgData, 0, 0);

        for (let i = 0; i < dataLength; i++) {
            // console.log(data)
            let rat = data[i] / 255;
            let hue = Math.round((rat * 120) + 280 % 360);
            let sat = '100%';
            let lit = 10 + (70 * rat) + '%';
            // console.log("rat %s, hue %s, lit %s", rat, hue, lit);
            ctx.beginPath();
            ctx.strokeStyle = `hsl(${hue}, ${sat}, ${lit})`;
            ctx.moveTo(x, canvasHeight - (i * h));
            ctx.lineTo(x, canvasHeight - (i * h + h));
            ctx.stroke();
        }
    }
});

Я кратко объясню, что делает каждая часть:

создание аудио контекста

Когда DOM загружается, создается AudioContext.

загрузка аудиофайла и преобразование его в AudioBuffer

Затем я загружаю звук с бэкэнд-сервера (код такой, как показано выше). Ответ затем преобразуется в буфер, который затем декодируется в AudioBuffer. Это в основном основное решение вопроса выше.

Process AudioBuffer

Чтобы показать немного больше контекста, как использовать загруженный аудиофайл, я включил остальную часть файла.

Для дальнейшей обработки AudioBuffer создается источник, и буферу назначается источник: sourceNode.buffer = buffer.

javascriptNode действует IMHO как поток, в котором вы можете получить доступ к выходу анализатора.

...