Cum se creează un oscilator audio cu API Web Audio

Ce veți crea

API-ul Web Audio este un model complet diferit de modelul

Ce construim

A se vedea Pen WebAudio API cu Osciloscop de Dennis Gaebel (@dennisgaebel) pe CodePen.

Demo-ul nostru de mai sus conține trei intrări radio care, atunci când sunt selectate, vor reda sunetul corelat pe care îl referă fiecare. Când este selectat un canal, sunetul nostru va fi redat și graficul de frecvență va fi afișat. 

Nu voi explica fiecare linie a codului demo; cu toate acestea, voi explica biții primari care ajută la afișarea sursei audio și graficul de frecvență al acesteia. Pentru a începe, vom avea nevoie de un pic de marcaj.

Marcajul

Cea mai importantă parte a marcajului este pânză, care va fi elementul care ne afișează osciloscopul. Dacă nu sunteți familiarizați cu pânză, Vă sugerăm să citiți acest articol intitulat "O introducere în lucrul cu panza".

Cu setul de scenă pentru afișarea graficului, trebuie să creați sunetul.

Crearea sunetului

Vom începe prin definirea câtorva variabile importante pentru contextul audio și câștig. Aceste variabile vor fi folosite pentru a face trimitere la un punct ulterior al codului.

lăsați audioContext, masterGain;

audioContext reprezintă un grafic de procesare audio (o descriere completă a unei rețele de procesare a semnalelor audio) construit din module audio conectate împreună. Fiecare este reprezentată de un AudioNode, și când sunt conectate împreună, ele creează un grafic de rutare audio. Acest context audio controlează atât crearea nodului (nodurilor) pe care le conține, cât și executarea prelucrării și decodificării audio. 

AudioContext trebuie să fie creată înainte de orice altceva, deoarece totul se întâmplă într-un context.

Al nostru masterGain acceptă o intrare a uneia sau mai multor surse audio și emite volumul sonor, care a fost ajustat în câștig la un nivel specificat de nodul GainNode.gain o rată parametru. Vă puteți gândi la câștigul de master ca volum. Acum vom crea o funcție care să permită redarea de către browser.

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

Încep să definim a sursă care va fi utilizată pentru a face referire la fișierul audio. În acest caz, folosesc o adresă URL pentru un serviciu de streaming, dar ar putea fi și un fișier audio. audioContext line definește un obiect audio și este contextul pe care l-am discutat anterior. De asemenea, verific pentru compatibilitate folosind WebKit prefix, dar sprijinul este adoptat pe scară largă în acest moment, cu excepția IE11 și Opera Mini.

funcția audioSetup () masterGain = audioContext.createGain (); masterGain.connect (audioContext.destination); 

Odată cu finalizarea instalării inițiale, va trebui să creați și să vă conectați masterGain la destinația audio. Pentru acest post, vom folosi conectați() , care vă permite să conectați unul dintre ieșirile unui nod la o țintă.

funcția audioSetup () lăsați piesa = nou Audio (sursă), songSource = audioContext.createMediaElementSource (melodie); songSource.connect (masterGain); song.play (); 

cântec variabila creează un nou obiect audio folosind Audio() constructor. Veți avea nevoie de un obiect audio, astfel încât contextul să aibă o sursă de redare pentru ascultători.

 songSource variabilă este sosul magic care joacă audio și este locul în care vom trece în sursa noastră audio. Prin utilizarea createMediaElementSource (), audio pot fi redate și manipulate după cum doriți. Variabila finală conectează sursa audio la câștigul master (volum). Linia finală song.play () este chemarea de a da permisiunea de a juca audio.

lăsați audioContext, masterGain; funcția audioSetup () let source = 'http://ice1.somafm.com/seventies-128-aac'; audioContext = nou (window.AudioContext || window.webkitAudioContext) (); masterGain = audioContext.createGain (); masterGain.connect (audioContext.destination); lăsați melodia = audio nou (sursă), songSource = audioContext.createMediaElementSource (melodie); songSource.connect (masterGain); song.play ();  audioSetup ();

Iată rezultatul final care conține toate liniile de cod pe care le-am discutat până acum. De asemenea, asigurați-vă că faceți apel la această funcție scrisă pe ultima linie. Apoi, vom crea forma de undă audio.

Crearea undelor audio

Pentru a afișa undele de frecvență pentru sursa audio aleasă, trebuie să creați forma de undă.

const analizator = audioContext.createAnalyser (); masterGain.connect (analizor);

Prima referire la createAnalyser () expune datele audio și de frecvență pentru a genera vizualizări de date. Această metodă va produce un AnalyserNode care transmite fluxul audio de la intrare la ieșire, dar vă permite să achiziționați datele generate, să le procesați și să construiți vizualizări audio care au exact o intrare și o ieșire. Nodul analizorului va fi conectat la câștigul master, care este ieșirea căii noastre de semnal și oferă posibilitatea de a analiza o sursă.

const waveform = float32Array nou (analyser.frequencyBinCount); analyser.getFloatTimeDomainData (formă de undă);

Acest Float32Array () Constructorul reprezintă o matrice de număr de punct floating pe 32 de biți. frequencyBinCount proprietate a AnalyserNode interfața este o jumătate lungă nesemnată de valoare a dimensiunii FFT (Transformare rapidă Fourier). Acest lucru este, în general, egal cu numărul de valori de date pe care le veți utiliza pentru vizualizare. Utilizăm această abordare pentru a colecta în mod repetat datele noastre de frecvență.

Metoda finală getFloatTimeDomainData copiază datele de undă curente sau date din domeniul temporal într-un a Float32Array a intrat în ea.

funcția updateWaveform () requestAnimationFrame (updateWaveform); analyser.getFloatTimeDomainData (formă de undă); 

Această întreagă cantitate de date și de procesare folosește requestAnimationFrame () pentru a colecta date de domeniu de timp repetat și desenați o ieșire "stil osciloscop" a intrării audio curente. De asemenea, fac un alt apel getFloatTimeDomainData () deoarece aceasta trebuie să fie actualizată în permanență, deoarece sursa audio este dinamică.

const analizator = audioContext.createAnalyser (); masterGain.connect (analizor); const waveform = float32Array nou (analyser.frequencyBinCount); analyser.getFloatTimeDomainData (formă de undă); funcția updateWaveform () requestAnimationFrame (updateWaveform); analyser.getFloatTimeDomainData (formă de undă); 

Combinând tot codul discutat până acum, rezultă întreaga funcție de mai sus. Chemarea la această funcție va fi plasată în interiorul nostru audioSetup funcția de mai jos song.play (). Cu forma de undă în loc, trebuie să extragem această informație pe ecran cu ajutorul nostru pânză element, iar aceasta este următoarea parte a discuției noastre.

Desenarea undelor audio

Acum, că ne-am creat forma de undă și posedăm datele pe care le solicităm, va trebui să o atragem pe ecran; aici este locul unde pânză element este introdus.

funcția drawOscilloscope () requestAnimationFrame (drawOscilloscope); const scopCanvas = document.getElementById ("osciloscop"); const domeniul scopeContext = scopeCanvas.getContext ("2d"); 

Codul de mai sus simplă apucă pânză astfel încât să putem face referire în funcția noastră. Apelul la requestAnimationFrame în partea de sus a acestei funcții va programa următorul cadru de animație. Acest lucru este plasat primul astfel încât să putem obține cât mai aproape de 60FPS posibil.

funcția drawOscilloscope () scopeCanvas.width = waveform.length; scopeCanvas.height = 200; 

Am implementat stilul de bază care va desena lățimea și înălțimea pânză. Înălțimea este setată la o valoare absolută, în timp ce lățimea va fi lungimea formei de undă produsă de sursa audio.

funcția drawOscilloscope () scopeContext.clearRect (0, 0, scopeCanvas.width, scopeCanvas.height); scopeContext.beginPath (); 

clearRect (x, y, lățime, înălțime) metoda va șterge orice conținut desenat anterior, astfel încât să putem trage continuu graficul de frecvență. De asemenea, trebuie să vă asigurați că ați sunat beginPath () înainte de a începe să desenați noul cadru după apelare clearRect (). Această metodă inițiază o nouă cale prin golirea listei oricăror subcale. Piesa finală din acest puzzle este o buclă pentru a rula prin datele pe care le-am obținut, astfel încât să putem trage continuu acest grafic de frecvență pe ecran.

funcția drawOscilloscope () pentru (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(); 

Această bucla de mai sus atrage forma noastră de undă la pânză element. Dacă înregistram lungimea formei de undă la consola (în timp ce redați sunetul), va raporta în mod repetat 1024. În general, acest lucru este egal cu numărul de valori de date pe care va trebui să le jucați pentru vizualizare. Dacă vă reamintiți din secțiunea anterioară pentru crearea formei de undă, obținem această valoare Float32Array (analyser.frequencyBinCount). Acesta este modul în care suntem capabili să menționăm valoarea 1024 pe care o vom străpunge.

MoveTo () metoda va muta literal punctul de plecare al unei noi sub-căi către actualizată (X y) coordonate. lineTo () metoda conectează ultimul punct din sub-cale spre X y coordonează cu o linie dreaptă (dar nu o trasează). Ultima piesă sună accident vascular cerebral() furnizat de pânză astfel încât să putem desena linia de frecvență. Voi lăsa porțiunea care conține matematica ca o provocare pentru cititor, deci asigurați-vă că ați postat răspunsul dvs. în comentariile de mai jos.

funcția drawOscilloscope () requestAnimationFrame (drawOscilloscope); const scopCanvas = document.getElementById ("osciloscop"); const domeniul scopeContext = scopeCanvas.getContext ("2d"); scopeCanvas.width = waveform.length; scopeCanvas.height = 200; scopeContext.clearRect (0, 0, scopeCanvas.width, scopeCanvas.height); scopeContext.beginPath (); pentru (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(); 

Aceasta este întreaga funcție pe care am creat-o pentru a desena forma de undă pe care o vom apela song.play () plasate în interiorul nostru audioSetup care include și noi updateWaveForm funcția de apel, de asemenea.

Gânduri de împărțire

Am explicat doar biții importanți pentru demo, dar asigurați-vă că ați citit celelalte porțiuni ale demo-ului meu pentru a înțelege mai bine cum funcționează butoanele radio și butonul de pornire în legătură cu codul de mai sus, inclusiv stilul CSS.

API-ul Web Audio este foarte distractiv pentru oricine interesat de orice tip de audio și vă încurajez să mergeți mai adânc. De asemenea, am colectat câteva exemple foarte interesante de la CodePen care folosesc API-ul Web Audio pentru a construi câteva exemple interesante. se bucura!

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

Referințe

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