Blog der Heimetli Software AG

Töne erzeugen mit der Web Audio API

Haben Sie schon gewusst dass die Browser auch eine Audio-API haben?

Mit dieser API kann man einiges anstellen denn sie stellt einige interessante Objekte zur Verfügung. Unter anderem gibt es Oszillatoren mit vier verschiedenen Kurvenformen, Verstärker/Abschwächer, Filter und Convolver.

Viele Beispiele im Netz funktionieren aber nicht mehr, weil sich die Bedingungen für den Einsatz geändert haben. Der Audio-Kontext darf erst erzeugt werden wenn der Benutzer mit der Seite interagiert hat. Das kann zum Beispiel durch einen Klick auf die Seite oder auf einen Button passieren.

In diesem Beispiel wird das Audiosignal berechnet und in einen Buffer abgefüllt. Dieser Buffer wird dann auf die Lautsprecher oder den Kopfhörer ausgegeben.

Struktur des Codes

Als erstes wird die Konstruktorfunktion für den AudioKontext geholt. Wie üblich gibt es auch hier mindestens zwei Varianten, und deshalb versucht der Code gleich beide:

let AudioContext = window.AudioContext || window.webkitAudioContext ;

Dafür gibt es bisher keine Restriktionen, das kann gleich nach dem Laden des Scripts geschehen.

Der nächste Schritt darf erst ausgeführt werden wenn der User eine Aktion auf der Seite ausgeführt hat:

let context = new AudioContext() ;

Der Kontext wird für praktisch alle Operationen mit der API gebraucht. Ein wichtiges Attribut dieser Klasse ist die sampleRate. Die wird zum Beispiel für die Bestimmung der Buffergrösse benötigt.

Ein Audio-Buffer wird durch den Kontext erzeugt und mit den berechneten Werten gefüllt:

let buffer = context.createBuffer( channels, frames, context.sampleRate ) ;

for( let channel = 0; channel < channels; channel++ )
{
   let buf = buffer.getChannelData( channel ) ;

   for( let i = 0; i < frames; i++ )
   {
      buf[i] = f( i ) ; // f(i) liefert Werte im Bereich -1.0...1.0
   }
}

Der gefüllte Buffer wird als Sound-Source verwendet, mit dem Ausgabegerät verbunden, und die Wiedergabe gestartet:

let source    = context.createBufferSource() ;
source.buffer = buffer ;

source.connect( context.destination ) ;

source.start() ;

Probieren Sie es aus

Die Frequenz ist die Tonhöhe, die Modulation sorgt dafür dass es keinen langweiligen Sinuston gibt. Die Verzögerung bestimmt wie lang die Zeit bis zum Echo ist. Dieser Regler ist nur aktiv wenn Echo angewählt ist.




Der vollständige Code

const channels   = 2 ;

let AudioContext = window.AudioContext || window.webkitAudioContext;
let context;

function play()
{
   if( !context )
      context = new AudioContext() ;
 
   let frequency = +document.querySelector( "#frequency" ).value ;
   let echo      =  document.querySelector( "#echo" ).checked ;
   let delay     =  document.querySelector( "#delay" ).value * context.sampleRate ;
   let fm        = +document.querySelector( "#fm" ).value ;
 
   let frames    = context.sampleRate * 7.0;
   let buffer    = context.createBuffer( channels, frames, context.sampleRate ) ;

   let theta     = Math.PI / (context.sampleRate / (2 * frequency))
 
   for( let channel = 0; channel < channels; channel++ )
   {
      let buf = buffer.getChannelData( channel ) ;
 
      for( let i = 0; i < frames; i++ )
      {
         buf[i] = Math.sin( i * theta + Math.sin(fm*i*theta) + Math.sin(4.5*i*theta)/2 ) * Math.exp( -i / context.sampleRate * 1.5 )  ;

         if( echo )
         {
            if( i > delay )
               buf[i] += buf[i - delay] / 1.25 ;
         }
      }
   }
 
   let source    = context.createBufferSource() ;
   source.buffer = buffer ;
 
   source.connect(context.destination) ;
 
   source.start() ;
}