web-audioAan de slag met webaudio


Opmerkingen

De Web Audio API is een W3C-standaard voor toegang op lager niveau tot het audiosysteem dan de standaard <audio> -tag, via een API op hoog niveau.

Gebruiksscenario's omvatten games, kunst, audiosynthese, interactieve toepassingen, audioproductie en elke toepassing waarbij nauwkeurige controle van de audiogegevens vereist is.

De API kan invoer van een aantal bronnen accepteren, inclusief het laden van audiobestanden en deze decoderen via de API of <audio> -elementen. Het biedt ook faciliteiten om geluid rechtstreeks via de API te genereren door het gebruik van oscillator-knooppunten.

Er is ook een aantal verwerkingsknooppunten, zoals versterkingen, vertragingen en scriptprocessors (die uiteindelijk worden afgeschaft en vervangen door efficiëntere knooppunten). Deze kunnen op hun beurt worden gebruikt om complexere effecten en audiografieken te bouwen.

Audio afspelen

Om audio af te spelen met behulp van de Web Audio API, hebben we een ArrayBuffer van audiogegevens nodig en deze aan een BufferSource doorgegeven voor weergave.

Om een audiobuffer van het geluid te krijgen om te spelen, moet u de methode AudioContext.decodeAudioData als volgt gebruiken:

const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// Fetch the MP3 file from the server
fetch("sound/track.mp3")
    // Return the data as an ArrayBuffer
    .then(response => response.arrayBuffer())
    // Decode the audio data
    .then(buffer => audioCtx.decodeAudioData(buffer))
    .then(decodedData => {
        // ...
    });
 

Wanneer de laatste belofte is opgelost, krijgt u de audio in de vorm van een AudioBuffer . Dit kan vervolgens worden gekoppeld aan een AudioBufferSourceNode - en zo worden afgespeeld:

const source = context.createBufferSource();
source.buffer = decodedData; // <==
source.start(...);
 

Waar .start() drie parameters heeft die compenseren wanneer de sample moet worden afgespeeld, offset waar vandaan in de sample om af te spelen en vertellen hoe lang de sample respectievelijk moet worden afgespeeld.

Meer informatie over het manipuleren van de bufferbron is te vinden op MDN .

Realtime wijzigen van twee audiobronnen

Dit voorbeeld laat zien hoe u twee audiobronnen kunt gebruiken en de ene kunt wijzigen op basis van de andere. In dit geval maken we een audio-ducker, die het volume van het primaire spoor verlaagt als het secundaire spoor geluid produceert.

De ScriptProcessorNode verzendt regelmatig gebeurtenissen naar zijn audioprocess-handler. In deze handler, die is gekoppeld aan de secundaire audiobron, berekenen we de "luidheid" van de audio en gebruiken deze om een dynamische compressor op de primaire audiobron te wijzigen. Beide worden vervolgens naar de luidsprekers / koptelefoons van de gebruiker gestuurd. Het resultaat is zeer abrupte volumeveranderingen in het primaire audiospoor wanneer geluid wordt gedetecteerd in het secundaire audiospoor. We kunnen dit vloeiender maken door een gemiddelde te gebruiken en een vertragingslijn te gebruiken om het volume te wijzigen voordat de secundaire audio wordt gedetecteerd, maar het proces moet in dit voorbeeld duidelijk zijn.

//The DuckingNode will heavily compress the primary source when the secondary source produces sound
class DuckingNode {
  constructor(context) {
    let blocksize = 2048;
    let normalThreshold = -50;
    let duckThreshold = 0.15;
  
    //Creating nodes
    this.compressor = context.createDynamicsCompressor();
    this.processor = context.createScriptProcessor(blocksize, 2, 2);
    
    //Configuring nodes
    this.compressor.threshold.value = normalThreshold;
    this.compressor.knee.value = 0;
    this.compressor.ratio.value = 12;
    this.compressor.reduction.value = -20;
    this.compressor.attack.value = 0;
    this.compressor.release.value = 1.5;
    
    let self = this;
    this.processor.onaudioprocess = function(audioProcessingEvent) {
      let inputBuffer = audioProcessingEvent.inputBuffer;
      let outputBuffer = audioProcessingEvent.outputBuffer;
      let rms;
      let total = 0.0;
      let len = blocksize * outputBuffer.numberOfChannels;

      for (let channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
        let inputData = inputBuffer.getChannelData(channel);
        let outputData = outputBuffer.getChannelData(channel);

        for (let sample = 0; sample < inputBuffer.length; sample++) {
          // make output equal to the same as the input
          outputData[sample] = inputData[sample];
          total += Math.abs(inputData[sample]);
        }
      }
      
      //Root mean square
      rms = Math.sqrt( total / len );

      //We set the threshold to at least 'normalThreshold'
      self.compressor.threshold.value = normalThreshold + Math.min(rms - duckThreshold, 0) * 5 * normalThreshold;
    }
  }
  
  get primaryInput () {
    return this.compressor;
  }
  
  get secondaryInput () {
    return this.processor;
  }
  
  connectPrimary(node) {
    this.compressor.connect(node);
  }
  
  connectSecondary(node) {
    this.processor.connect(node);
  }
};
 
let audioContext = new (window.AudioContext || window.webkitAudioContext)();

//Select two <audio> elements on the page. Ideally they have the autoplay attribute
let musicElement = document.getElementById("music");
let voiceoverElement = document.getElementById("voiceover");

//We create source nodes from them
let musicSourceNode = audioContext.createMediaElementSource(musicElement);
let voiceoverSourceNode = audioContext.createMediaElementSource(voiceoverElement);

let duckingNode = new DuckingNode(audioContext);

//Connect everything up
musicSourceNode.connect(duckingNode.primaryInput);
voiceoverSourceNode.connect(duckingNode.secondaryInput);
duckingNode.connectPrimary(audioContext.destination);
duckingNode.connectSecondary(audioContext.destination);
 

Een deel van dit voorbeeld komt uit dit antwoord van Kevin Ennis en de code op mdn .

Audio opnemen van een microfoonbron

Om audio van de microfoon van een gebruiker op te nemen, moeten we eerst toestemming van de gebruiker krijgen voor toegang tot het apparaat:

navigator.mediaDevices.getUserMedia({ audio: true })
    .then(successCallback)
    .catch(failureCallback);
 

Bij succes wordt onze successCallback aangeroepen met een MediaStream object dat we kunnen gebruiken om toegang te krijgen tot de microfoon:

var audioContext = new (window.AudioContext || window.webkitAudioContext)();
// Create a source from our MediaStream
var source = audioContext.createMediaStreamSource(mediaStream);
// Now create a Javascript processing node with the following parameters:
// 4096 = bufferSize (See notes below)
// 2 = numberOfInputChannels (i.e. Stereo input)
// 2 = numberOfOutputChannels (i.e. Stereo output)
var node = audioContext.createScriptProcessor(4096, 2, 2);
node.onaudioprocess = function(data) {
    console.log(data);
}
// Connect the microphone to the script processor
source.connect(node);
node.connect(audioContext.destination);
 

De gebeurtenis onaudioprocess geeft ons toegang tot de Raw PCM-gegevensstroom vanaf de microfoon. We kunnen de gebufferde gegevens als volgt openen:

node.onaudioprocess = function(data) {
    var leftChannel = data.inputBuffer.getChannelData(0).buffer;
    var rightChannel = data.inputBuffer.getChannelData(1).buffer;
}
 

Wanneer we klaar zijn met opnemen, moeten we de verbinding met de bron verbreken en de scriptprocessor weggooien:

node.disconnect();
source.disconnect();
 

Belangrijke opmerkingen over bufferSize

De bufferSize bepaalt hoe vaak de onaudioprocess callback wordt aangeroepen. Lagere waarden resulteren in lagere (betere) latentie, hogere waarden zullen het uiteenvallen / glitches van audio verminderen. Met een waarde van nul kan de browserimplementatie een geschikte waarde kiezen. Als het handmatig wordt ingevoerd, moet dit een van de volgende waarden zijn: 256, 512, 1024, 2048, 4096, 8192, 16384.

Opstelling

We beginnen met het maken van een audiocontext en maken vervolgens een oscillator, wat de gemakkelijkste manier is om te controleren of uw installatie werkt. (Voorbeeld viool)

// We can either use the standard AudioContext or the webkitAudioContext (Safari)
var audioContext = (window.AudioContext || window.webkitAudioContext);

// We create a context from which we will create all web audio nodes
var context = new audioContext();

// Create an oscillator and make some noise
var osc = context.createOscillator();

// set a frequecy, in this case 440Hz which is an A note
osc.frequency.value = 440;

// connect the oscillator to the context destination (which routes to your speakers)
osc.connect(context.destination);

// start the sound right away
osc.start(context.currentTime);

// stop the sound in a second
osc.stop(context.currentTime + 1);
 

Synthese van audio

In dit voorbeeld laten we zien hoe u een eenvoudige sinusgolf genereert en deze uitvoert via de luidsprekers / hoofdtelefoon van de gebruiker.

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

let sourceNode = audioContext.createOscillator();
sourceNode.type = 'sine';
sourceNode.frequency.value = 261.6;
sourceNode.detune.value = 0;

//Connect the source to the speakers
sourceNode.connect(audioContext.destination);

//Make the sound audible for 100 ms
sourceNode.start();
window.setTimeout(function() { sourceNode.stop(); }, 100);
 

De start en stop methoden van de sourceNode variabele boven hebben elk een optionele parameter when die aangeeft hoeveel seconden moet wachten voordat het starten of stoppen.

Dus een alternatieve manier om het geluid te stoppen is:

sourceNode.start();
sourceNode.stop(0.1);
 

Het type parameter van een oscillator knooppunt kan worden ingesteld op de volgende waarden:

  • sine (standaard)
  • plein
  • zaagtand
  • traingle
  • een aangepaste golf

Aangepaste golven zijn PeriodicWaves en kunnen worden gemaakt met de methode AudioContext.createPeriodicWave .

Effecten op audio gebruiken

Effecten kunnen op audio worden toegepast door knooppunten tussen de bron en het doelknooppunt te koppelen. In dit voorbeeld gebruiken we een versterkingsknooppunt om de bron te dempen en laten we alleen geluid door op specifieke tijden. Hiermee kunnen we morsecode maken.

function morse(gainNode, pattern) {
  let silenceTimeout = 300;
  let noiseTimeout;
  
  if(pattern === '') {
    //We are done here
    return;
  } else if(pattern.charAt(0) === '.') {
    noiseTimeout = 100;
  } else if(pattern.charAt(0) === '-') {
    noiseTimeout = 400;
  } else {
    console.error(pattern.charAt(0), ': Character not recognized.');
    return;
  }
  
  //Briefly let sound through this gain node
  gainNode.gain.value = 1;
  window.setTimeout(function() {
    gainNode.gain.value = 0;
    window.setTimeout(morse, silenceTimeout, gainNode, pattern.substring(1));
  }, noiseTimeout);
}
 
let audioContext = new (window.AudioContext || window.webkitAudioContext)();

let sourceNode = audioContext.createOscillator();
let gainNode = audioContext.createGain();

sourceNode.type = 'sine';
sourceNode.frequency.value = 261.6;
sourceNode.detune.value = 0;

//Mute sound going through this gain node
gainNode.gain.value = 0;

//SourceNode -> GainNode -> Speakers
sourceNode.connect(gainNode);
gainNode.connect(audioContext.destination);

//The source node starts outputting
sourceNode.start();

//Output SOS
morse(gainNode, '...---...');