Een audioscillator maken met de Web Audio API

Wat je gaat creëren

De Web Audio API is een volledig losstaand model

Wat we bouwen

Zie de Pen WebAudio API w / Oscilloscope van Dennis Gaebel (@dennisgaebel) op CodePen.

Onze demo hierboven bevat drie radio-ingangen die, wanneer ze worden geselecteerd, de correlaterende audio zullen afspelen waarnaar ze verwijzen. Wanneer een kanaal is geselecteerd, wordt onze audio afgespeeld en wordt de frequentiegrafiek weergegeven. 

Ik zal niet elke regel van de demo's code uitleggen; ik zal echter de primaire bits uitleggen die helpen bij het weergeven van de audiobron en de frequentiegrafiek. Om te beginnen hebben we een klein beetje markup nodig.

De markup

Het belangrijkste onderdeel van de opmaak is de canvas, welke het element zal zijn dat onze oscilloscoop weergeeft. Als u niet bekend bent met canvas, Ik stel voor dit artikel te lezen met de titel 'Een inleiding tot het werken met canvas'.

Met de fase die is ingesteld voor het weergeven van de grafiek, moeten we de audio maken.

De audio maken

We zullen beginnen met het definiëren van een aantal belangrijke variabelen voor de audio-context en winst. Deze variabelen worden gebruikt om op een later tijdstip in de code te verwijzen.

laat audioContext, masterGain;

De audioContext staat voor een audio-verwerkingsgrafiek (een volledige beschrijving van een audiosignaalverwerkingsnetwerk) die is opgebouwd uit audiomodules die aan elkaar zijn gekoppeld. Elke wordt vertegenwoordigd door een AudioNode, en wanneer ze met elkaar zijn verbonden, maken ze een audiogeleidingsgrafiek. Deze audiocontext bestuurt zowel het maken van de knooppunten die het bevat en het uitvoeren van de audioverwerking en decodering. 

De AudioContext moet vóór alles worden gemaakt, omdat alles in een context gebeurt.

Onze masterGain accepteert een invoer van een of meer audiobronnen en voert het audio-volume uit, dat in gain is aangepast tot een niveau dat is opgegeven door de node GainNode.gain a-rate parameter. Je kunt de master gain als het volume zien. Nu maken we een functie om het afspelen door de browser toe te staan.

function audioSetup () let source = 'http://ice1.somafm.com/seventies-128-aac'; audioContext = new (window.AudioContext || window.webkitAudioContext) (); 

Ik begin met het definiëren van een bron variabele die zal worden gebruikt om naar het audiobestand te verwijzen. In dit geval gebruik ik een URL voor een streamingdienst, maar het kan ook een audiobestand zijn. De audioContext regel definieert een audio-object en is de context die we eerder hebben besproken. Ik controleer ook op compatibiliteit met de WebKit voorvoegsel, maar op dit moment wordt ondersteuning op grote schaal gebruikt, met uitzondering van IE11 en Opera Mini.

function audioSetup () masterGain = audioContext.createGain (); masterGain.connect (audioContext.destination); 

Nadat onze eerste configuratie is voltooid, moeten we het maken en verbinden masterGain naar de audiobestemming. Voor deze taak gebruiken we de aansluiten() methode, waarmee u een van de uitgangen van het knooppunt op een doel kunt aansluiten.

function audioSetup () let song = new Audio (source), songSource = audioContext.createMediaElementSource (song); songSource.connect (masterGain); song.play (); 

De lied variabele maakt een nieuw audio-object met behulp van de Audio () constructeur. U hebt een audio-object nodig zodat de context een bron heeft voor weergave voor luisteraars.

De songSource variabele is de magische saus die de audio afspeelt en is waar we in onze audiobron zullen passeren. Door het gebruiken van createMediaElementSource (), de audio kan naar wens worden gespeeld en gemanipuleerd. De laatste variabele verbindt onze audiobron met de masterversterking (volume). De laatste regel song.play () is de oproep om daadwerkelijk toestemming te geven om de audio af te spelen.

laat audioContext, masterGain; function audioSetup () let source = 'http://ice1.somafm.com/seventies-128-aac'; audioContext = new (window.AudioContext || window.webkitAudioContext) (); masterGain = audioContext.createGain (); masterGain.connect (audioContext.destination); let song = new Audio (source), songSource = audioContext.createMediaElementSource (song); songSource.connect (masterGain); song.play ();  audioSetup ();

Dit is ons eindresultaat met alle coderegels die we tot nu toe hebben besproken. Ik zorg er ook voor dat de aanroep van deze functie op de laatste regel wordt geschreven. Vervolgens maken we het audiogolfformulier.

De audiogolf maken

Om de frequentiegolf voor onze gekozen audiobron weer te geven, moeten we de golfvorm maken.

const analyzer = audioContext.createAnalyser (); masterGain.connect (analysator);

De eerste verwijzing naar createAnalyser () stelt de audiotijd en frequentiegegevens bloot om gegevensvisualisaties te genereren. Deze methode produceert een AnalyserNode die de audiostream doorgeeft van de invoer naar de uitvoer, maar biedt u de mogelijkheid om de gegenereerde gegevens te verwerven, te verwerken en audiovisualisaties te construeren die exact één invoer en één uitvoer hebben. Het analyseknooppunt wordt verbonden met masterversterking die de uitvoer is van ons signaalpad en geeft de mogelijkheid om een ​​bron te analyseren.

const-golfvorm = nieuwe Float32Array (analyser.frequencyBinCount); analyser.getFloatTimeDomainData (golfvorm);

Deze Float32Array () constructor vertegenwoordigt een array van een 32-bits drijvende komma-nummer. De frequencyBinCount eigendom van de AnalyserNode interface is een niet-ondertekende lange waarde de helft van die van de FFT (Fast Fourier Transform) -grootte. Dit komt in het algemeen overeen met het aantal gegevenswaarden dat u voor de visualisatie hebt. We gebruiken deze aanpak om onze frequentiegegevens herhaaldelijk te verzamelen.

De laatste methode getFloatTimeDomainData kopieert de huidige golfvorm, of tijddomeingegevens, naar a Float32Array array is erin overgegaan.

functie updateWaveform () requestAnimationFrame (updateWaveform); analyser.getFloatTimeDomainData (golfvorm); 

Deze volledige hoeveelheid gegevens en verwerkingsgebruik requestAnimationFrame () om tijddomeingegevens te verzamelen herhaaldelijk en teken een "oscilloscoopstijl" -uitgang van de huidige audio-ingang. Ik bel ook nog een keer getFloatTimeDomainData () omdat dit continu moet worden bijgewerkt omdat de audiobron dynamisch is.

const analyzer = audioContext.createAnalyser (); masterGain.connect (analysator); const-golfvorm = nieuwe Float32Array (analyser.frequencyBinCount); analyser.getFloatTimeDomainData (golfvorm); functie updateWaveform () requestAnimationFrame (updateWaveform); analyser.getFloatTimeDomainData (golfvorm); 

Het combineren van alle tot nu toe besproken code resulteert in de volledige bovenstaande functie. De oproep naar deze functie wordt in onze audioSetup functie net onder song.play (). Met de golfvorm op zijn plaats, moeten we deze informatie nog steeds naar het scherm tekenen met behulp van onze canvas element, en dit is het volgende deel van onze discussie.

De audiogolf tekenen

Nu we onze golfvorm hebben gemaakt en over de gegevens beschikken die we nodig hebben, moeten we deze naar het scherm tekenen; dit is waar de canvas element wordt geïntroduceerd.

functie drawOscilloscope () requestAnimationFrame (drawOscilloscope); const scopeCanvas = document.getElementById ('oscilloscope'); const scopeContext = scopeCanvas.getContext ('2d'); 

De bovenstaande code pakt gewoon de canvas element, zodat we ernaar kunnen verwijzen in onze functie. De oproep aan requestAnimationFrame aan de bovenkant van deze functie wordt het volgende animatiekader gepland. Dit wordt eerst geplaatst, zodat we zo dicht mogelijk bij 60FPS kunnen komen.

function drawOscilloscope () scopeCanvas.width = waveform.length; scopeCanvas.height = 200; 

Ik heb een basisstijl geïmplementeerd die de breedte en hoogte van de tekening zal bepalen canvas. De hoogte wordt ingesteld op een absolute waarde, terwijl de breedte de lengte is van de golfvorm die wordt geproduceerd door de audiobron.

functie drawOscilloscope () scopeContext.clearRect (0, 0, scopeCanvas.width, scopeCanvas.height); scopeContext.beginPath (); 

De clearRect (x, y, width, height) methode verwijdert alle eerder getekende inhoud, zodat we voortdurend de frequentiegrafiek kunnen tekenen. Je moet ook zorgen dat je belt beginPath () voordat je begint met het tekenen van het nieuwe frame clearRect (). Deze methode start een nieuw pad door de lijst met alle subpaden leeg te maken. Het laatste stuk in deze puzzel is een lus om de door ons verkregen gegevens te doorlopen, zodat we deze frequentiegrafiek voortdurend naar het scherm kunnen trekken.

functie drawOscilloscope () for (let i = 0; i < waveform.length; i++)  const x = i; const y = ( 0.5 + (waveform[i] / 2) ) * scopeCanvas.height; if(i == 0)  scopeContext.moveTo(x, y);  else  scopeContext.lineTo(x, y);   scopeContext.stroke(); 

Deze lus hierboven trekt onze golfvorm naar de canvas element. Als we de lengte van de golfvorm op de console registreren (tijdens het afspelen van de audio), wordt er 1024 herhaaldelijk gerapporteerd. Dit komt in het algemeen overeen met het aantal gegevenswaarden waarmee u moet spelen voor de visualisatie. Als u zich herinnert uit het vorige gedeelte voor het maken van het golfformulier, krijgen we deze waarde van Float32Array (analyser.frequencyBinCount). Dit is hoe we kunnen verwijzen naar de 1024-waarde die we doorlopen.

De moveTo () methode zal het startpunt van een nieuw subpad letterlijk naar het bijgewerkte verplaatsen (x, y) coördineert. De lineTo () methode verbindt het laatste punt in het subpad met het x, y coördineert met een rechte lijn (maar tekent deze niet echt). Het laatste stuk is aan het roepen beroerte() geleverd door canvas dus we kunnen de frequentielijn daadwerkelijk tekenen. Ik zal het gedeelte met de wiskunde als een uitdaging voor de lezer laten, dus zorg ervoor dat je je antwoord plaatst in de reacties hieronder.

functie drawOscilloscope () requestAnimationFrame (drawOscilloscope); const scopeCanvas = document.getElementById ('oscilloscope'); const scopeContext = scopeCanvas.getContext ('2d'); scopeCanvas.width = waveform.length; scopeCanvas.height = 200; scopeContext.clearRect (0, 0, scopeCanvas.width, scopeCanvas.height); scopeContext.beginPath (); for (let i = 0; i < waveform.length; i++)  const x = i; const y = ( 0.5 + (waveform[i] / 2) ) * scopeCanvas.height; if(i == 0)  scopeContext.moveTo(x, y);  else  scopeContext.lineTo(x, y);   scopeContext.stroke(); 

Dit is de volledige functie die we hebben gemaakt om de golfvorm te tekenen die we zullen noemen song.play () geplaatst binnen onze audioSetup functie, die ook onze updateWaveForm functieaanroep ook.

Afscheid nemen van gedachten

Ik heb alleen de belangrijke stukjes voor de demo uitgelegd, maar lees de andere delen van mijn demo door om een ​​beter begrip te krijgen van hoe de keuzerondjes en de startknop werken in relatie tot de bovenstaande code, inclusief de CSS-styling.

De Web Audio API is echt leuk voor iedereen die geïnteresseerd is in audio van welke aard dan ook, en ik moedig je aan om dieper te gaan. Ik heb ook een aantal echt leuke voorbeelden uit CodePen verzameld die de Web Audio API gebruiken om enkele echt interessante voorbeelden te maken. Genieten!

  • https://codepen.io/collection/XLYyWN
  • https://codepen.io/collection/nNqdoR
  • https://codepen.io/collection/XkNgkE
  • https://codepen.io/collection/ArxwaW

Referenties

  • http://webaudioapi.com
  • https://webaudio.github.io/web-audio-api
  • http://chimera.labs.oreilly.com/books/1234000001552/ch01.html