Maak een the­re­min met de Web Audio API

Leer hoe je de kracht van de Web Audio API ge­bruikt om een the­re­min voor in je brow­ser te ma­ken.

Web Designer Magazine - - Inhoud -

Leer hoe je de Web Audio API ge­bruikt om een mu­ziek­in­stru­ment voor de brow­ser te ma­ken.

Audio heeft het moei­lijk ge­had op het web. Voor­heen wa­ren er ex­ter­ne plug-ins zo­als Flash no­dig om audio con­sis­tent te la­den en af te spe­len. Daar­door was het moei­lijk om audio en Ja­vascript te com­bi­ne­ren. Zelfs na het toe­voe­gen van de <audio>-tag blijft het ma­ni­pu­le­ren be­perkt tot het af­spe­len en pau­ze­ren zon­der eni­ge aan­pas­sin­gen aan de output.

De Web Audio API is zo ont­wik­keld dat hij ge­bruikt kan wor­den in veel ver­schil­len­de si­tu­a­ties. De API ac­cep­teert ie­de­re vorm van in­put, ver­werkt die en le­vert dan een output die weer over­al ge­bruikt kan wor­den. De in­put kan uit een be­stand ko­men, rechtstreeks van een mi­cro­foon of door een brow­ser ge­ge­ne­reerd wor­den.

De API biedt di­ver­se me­tho­des om audio te ma­ni­pu­le­ren, bij­voor­beeld met een echo-ef­fect of door het vo­lu­me aan te pas­sen. De­ze effecten zijn bij­zon­der flexi­bel in te voe­gen, zo­dat je ze kunt ge­brui­ken waar en wan­neer je dat pas­send vindt. Je kunt ook ei­gen script-pro­ces­sors cre­ë­ren om de audio te be­wer­ken af­han­ke­lijk van de ei­sen van je ap­pli­ca­tie, zo lang als er maar een in­put en een output is.

In de­ze stap-voor-stap tutorial ma­ken we een ei­gen ver­sie van een the­re­min. Ce­lia Sheen mu­si­ceer­de hier­mee het ty­pi­sche ge­luid dat veel men­sen ken­nen van de in­tro­tu­ne van Midsomer Mur­ders. Een the­re­min is een elek­tro­nisch in­stru­ment waar­bij de fre­quen­tie en het vo­lu­me van een syn­the­tisch ge­maak­te output va­ri­ë­ren op ba­sis van de po­si­ties van je han­den in de ruim­te. In on­ze ap­pli­ca­tie wor­den die po­si­ties ge­con­tro­leerd door een klik of een touch. Door ver­schil­len­de in­ge­bouw­de no­des te ge­brui­ken, kun je de ge­lui­den dan ana­ly­se­ren en een vi­su­a­li­sa­tie op het scherm la­ten zien met een fulls­creen can­vas.

1. Veran­der de can­vas­groot­te

Het <can­vas>-ele­ment heeft om te be­gin­nen nog zijn stan­daard groot­te links­bo­ven, maar het moet het he­le ven­ster be­strij­ken. Als de brow­ser­groot­te aan­ge­past wordt, moet het <can­vas> mee ver­an­de­ren.

Bo­ven­aan script.js staat een re­fe­ren­tie naar het can­vas en dat pas je aan als de ven­ster­groot­te ver­an­dert. var can­vas = do­cu­ment. ge­tele­ment­by­id(“the­re­min”); var can­vas­con­text = can­vas.get­con­text(“2d”); win­dow.ad­de­vent­lis­te­ner( “re­si­ze”, re­si­ze­can­vas); func­ti­on re­si­ze­can­vas() { can­vas.width = win­dow.in­ner­width; can­vas.height = win­dow.in­ner­height; } re­si­ze­can­vas();

2. Click-event-lis­te­ners toe­voe­gen

Om het sim­pel te hou­den, kij­ken we eerst naar de muis­klik­ken. Om­dat het om een cur­sor gaat, is er al­tijd maar één Point-ob­ject te­ge­lij­ker­tijd. Je moet de­tec­te­ren wan­neer een klik be­gint om het ge­luid te star­ten, wan­neer er be­wo­gen wordt om het vo­lu­me aan te pas­sen en wan­neer er los­ge­la­ten wordt om het ge­luid te stop­pen.

On­der­aan het be­stand voeg je de vol­gen­de event­lis­te­ners toe om die ge­beur­te­nis­sen op te pik­ken: can­vas.ad­de­vent­lis­te­ner(‘mou­se­down’, mou­sestart); can­vas.ad­de­vent­lis­te­ner(‘mou­semo­ve’, mou­semo­ve); can­vas.ad­de­vent­lis­te­ner(‘mou­seup’, mouseend); can­vas.ad­de­vent­lis­te­ner(‘mou­se­out’, mouseend);

3. De audio-context op­zet­ten

Al­le com­mu­ni­ca­tie met de Web Audio API ge­beurt door de audio-context-in­ter­fa­ce. Die is ver­ant­woor­de­lijk voor veel din­gen, maar zijn pri­mai­re rol is alles te be­vat­ten wat ge­luid maakt. Bo­ven­aan de be­lang­rijk­ste script­func­tie maak je een nieu­we context aan. Voor Sa­fa­ri moet je in plaats daar­van web­ki­tau­dio­con­text ge­brui­ken. var au­dio­con­text = new ( win­dow.au­dio­con­text || win­dow. web­ki­tau­dio­con­text)();

4. Maak een Point bij een klik

Als een ge­brui­ker bin­nen het ven­ster klikt, moet je af­van­gen waar hij ge­klikt heeft om la­ter een ge­luid te ma­ken. Bin­nen mou­sestart maak je een nieuw Poin­t­ob­ject met een re­fe­ren­tie naar het mou­se-event zelf, de audio-context die je net aan­ge­maakt hebt en de re­fe­ren­tie naar het can­vas voor la­ter ge­bruik. Hou de re­fe­ren­tie in de points-ar­ray voor la­ter. func­ti­on mou­sestart(e) { e.pre­vent­de­fault(); var point = new Point(e, au­dio­con­text, can­vas);

points.push(point); }

5. Ver­wij­der Point bij mou­se-up

Als de muis­knop wordt los­ge­la­ten, moet je ook het Point-ob­ject uit de point-ar­ray ver­wij­de­ren. Als je ge­luid be­gint te ma­ken, moet je daar ook mee stop­pen.

Je moet de po­si­tie van het click-event in de ar­ray ach­ter­ha­len, dat doen we bij de vol­gen­de stap. Er kan één klik zijn, dus daar is hier geen spe­ci­fie­ke iden­ti­fier voor no­dig. func­ti­on mouseend(e) { e.pre­vent­de­fault(); var pos = get­pos(un­de­fi­ned);

if(pos !== null) { points[pos].stop(); points.splice(pos, 1); } }

6. Po­si­tie in de points-ar­ray

Het is mak­ke­lijk als je maar één Point-ob­ject te­ge­lij­ker­tijd aan het af­spe­len bent. Maar als je meer­de­re tou­ches gaat in­bou­wen, moet je een ma­nier heb­ben om uit te vis­sen op wel­ke er ge­drukt is. Ie­der Point-ob­ject heeft een iden­ti­fier. Die komt van het Touch-ob­ject voor tou­ches. Voor klik­ken blijft het on­ge­de­fi­ni­eerd. Voeg het vol­gen­de toe om de in­dex van een ge­ge­ven iden­ti­fier te vin­den: func­ti­on get­pos(id) { for (var i=0; i<points.length; i++) { if (points[i].ge­t­iden­ti­fier()==id) { re­turn i;

}

} re­turn null; }

7. Click-po­si­ties re­gi­stre­ren

Ie­der Point-ob­ject haalt de x- en y-po­si­ties met­een uit ie­de­re mou­se-event en slaat die op in va­ri­a­be­len om la­ter te ge­brui­ken.

Som­mi­ge brow­sers kun­nen een po­si­tie bui­ten de di­men­sies van het can­vas rap­por­te­ren, je moet de waar­den dus li­mi­te­ren om verkeerde be­re­ke­nin­gen te voor­ko­men. Aan het be­gin van Point.js zet je va­ri­a­be­len om de x- en y-po­si­tie op te slaan. var x = Math.min(math.max(

point.clientx, 0), can­vas.width); var y = Math.min(math.max(

point.clien­ty, 0), can­vas.height);

8. Ver­bind de no­des

De audio-context is ver­ant­woor­de­lijk voor het ma­ken van no­des die de ge­lui­den van de the­re­min ma­ken en aan­pas­sen. Een os­cil­la­tor-no­de pro­du­ceert een si­nus­golf, wat de bron is van het ge­luid. Een gain-no­de past de am­pli­tu­de van die golf aan, dus die moet er la­ter ook mee ver­bon­den wor­den. Ver­bind de os­cil­la­tor met de gain-no­de en stuur dat naar het doel dat door de context ge­ge­ven wordt. Stan­daard zijn dat de luid­spre­kers van het ap­pa­raat. var os­cil­la­tor = au­dio­con­text. cre­a­teos­cil­la­tor(); var ga­in­no­de = au­dio­con­text.cre­a­te­gain(); os­cil­la­tor.con­nect(ga­in­no­de); ga­in­no­de.con­nect(

au­dio­con­text.des­ti­na­ti­on);

9. Start de os­cil­la­tor

De os­cil­la­tor is de bron van het ge­luid, dus die moet als eer­ste ge­start wor­den om ge­luid te kun­nen pro­du­ce­ren. Dat kun je doen zo gauw je alles met el­kaar ver­bon­den hebt.

De audio-context werkt met een tijd­sys­teem, zo­dat no­des hun func­ties goed kun­nen ti­men. Om­dat het ge­luid met­een moet gaan be­gin­nen, geef je de hui­di­ge tijd door. os­cil­la­tor.start(

au­dio­con­text.cur­rent­ti­me);

10. Stop het ge­luid

Als je dan op de pa­gi­na klikt, hoor je de si­nus­golf. Er is ech­ter nog geen ma­nier om hem te stop­pen, dus leidt een vol­gen­de klik al­leen tot een ex­tra ge­luid daar bo­ven­op, ook al heb je het Point-ob­ject uit de ar­ray ver­wij­derd.

Je roept de func­tie stop() al aan in script.js als je het ge­luid ver­wij­dert. Voeg het vol­gen­de aan die func­tie in Point.js toe om de os­cil­la­tor met­een te stop­pen en ont­kop­pel hem van de an­de­re no­des zo­dat die uit het ge­heu­gen ge­haald kun­nen wor­den. func­ti­on stop() { os­cil­la­tor.stop(

au­dio­con­text.cur­rent­ti­me); os­cil­la­tor.dis­con­nect(); }

11. Fre­quen­tie en vo­lu­me in­stel­len

Het vo­lu­me en de fre­quen­tie moe­ten aan­ge­past wor­den af­han­ke­lijk van waar er ge­klikt was. De func­ties _cal­cu­la­te­gain() en _cal­cu­la­te­fre­quen­cy() le­ve­ren de bij­be­ho­ren­de waar­den ge­ba­seerd op de x- en y-war­den bin­nen het point-ob­ject. Die moe­ten bij de no­des dan in­ge­steld wor­den.

Voeg het vol­gen­de toe tus­sen het aan­ma­ken en het ver­bin­den van de no­des: os­cil­la­tor.fre­quen­cy.set­tar­ge­tat­ti­me (_cal­cu­la­te­fre­quen­cy(), au­dio­con­text. cur­rent­ti­me, 0.01); ga­in­no­de.gain.set­tar­ge­tat­ti­me(_ cal­cu­la­te­gain(), au­dio­con­text.cur­rent­ti­me, 0.01);

12. Up­da­te het Point-ob­ject

Zo­als je eer­der zag, blij­ven de ge­lui­den door­gaan tot je ze zegt te stop­pen. Dat be­te­kent dat je de waar­den van de os­cil­la­tor en de gain-no­des tij­dens het af­spe­len kunt up­da­ten.

Je kunt de­zelf­de code ge­brui­ken als eer­der om ze up-to-da­te te hou­den. Voeg het vol­gen­de toe aan de up­da­te()-func­tie:

func­ti­on up­da­te(point) {

x = Math.min(math.max(point.clientx, 0), can­vas.width);

y = Math.min(math.max(point.clien­ty, 0), can­vas.height);

os­cil­la­tor.fre­quen­cy.set­tar­ge­tat­ti­me (_cal­cu­la­te­fre­quen­cy(), au­dio­con­text. cur­rent­ti­me, 0.01);

ga­in­no­de.gain.set­tar­ge­tat­ti­me (_cal­cu­la­te­gain(), au­dio­con­text.cur­rent­ti­me, 0.01); }

13. Up­da­te bij be­we­ging

Het ge­luid ver­an­dert niet uit zich­zelf. Je moet up­da­te() aan­roe­pen als de muis be­weegt en de event­da­ta mee­ge­ven. Dat werkt ver­ge­lijk­baar met mouseend(), maar in plaats van het Point-ob­ject te ver­wij­de­ren, ga je het nu up­da­ten. Voeg het vol­gen­de toe aan mou­semo­ve() in script.js en geef weer ‘un­de­fi­ned’ mee aan get­pos() om de muis­klik er­uit te pik­ken: func­ti­on mou­semo­ve(e) { e.pre­vent­de­fault(); var pos = get­pos(un­de­fi­ned); if(pos !== null) {

points[pos].up­da­te(e);

} }

14. Voeg touch-lis­te­ners toe

Als je dat op een touch-de­vi­ce pro­beert, werkt dat niet zo­als ver­wacht. Som­mi­ge ap­pa­ra­ten in­ter­pre­te­ren tou­ches wel als klik­ken, maar de mees­te ne­ge­ren ze. Je moet multi-touch on­der­steu­nen, dus moet je spe­ci­fiek naar touch-events luis­te­ren. Voeg het vol­gen­de toe on­der­aan script.js bij de click-events: can­vas.ad­de­vent­lis­te­ner(‘tou­ch­start’, tou­ch­start); can­vas.ad­de­vent­lis­te­ner(‘tou­chmo­ve’, tou­chmo­ve); can­vas.ad­de­vent­lis­te­ner(‘tou­chend’, tou­chend); can­vas.ad­de­vent­lis­te­ner(‘touch­can­cel’, tou­chend);

15. Star­ten via touch

Door de ma­nier waar­op het Point-ob­ject ge­bruikt wordt om ge­luid te re­pre­sen­te­ren, kun je die voor tou­ches op de­zelf­de ma­nier cre­ë­ren als voor muis­klik­ken. Als er een touch is, loop je door chan­ge­dtou­ches in plaats van door tou­ches, om te voor­ko­men dat je meer ob­jec­ten aan­maakt dan no­dig. Voeg het vol­gen­de toe aan tou­ch­start en zorg er­voor dat je het stan­daard ge­drag van tou­ches stopt om zoo­men te voor­ko­men. func­ti­on tou­ch­start(e) { e.pre­vent­de­fault(); for (var i = 0; i < e.chan­ge­dtou­ches. length; i++) {

var point = new Point(e. chan­ge­dtou­ches[i], au­dio­con­text, can­vas);

points.push(point);

} }

16. Ver­wij­de­ren bij touch-ein­de

Net als bij de muis­klik wil je dat ver­wij­de­ren uit de points-ar­ray als de touch los­ge­la­ten wordt. In te­gen­stel­ling tot bij een muis­klik moet je de iden­ti­fier van het Point-ob­ject door­ge­ven aan get­pos(). Dat is een unie­ke iden­ti­fier die de brow­ser aan die touch heeft toe­ge­kend, waar­door je hem over de tijd kunt vol­gen. func­ti­on tou­chend(e) { e.pre­vent­de­fault(); for (var i = 0; i < e.chan­ge­dtou­ches. length; i++) {

var pos = get­pos(e.chan­ge­dtou­ches[i]. iden­ti­fier); if(pos !== null) { points[pos].stop(); points.splice(pos, 1);

}

} }

17. Up­da­te de touch-be­we­ging

Als laat­ste moet je net als bij de click-hand­ler het Point-ob­ject up­da­ten als de touch ver­an­dert. In dit ge­val zal chan­ge­dtouch al­leen de tou­ches be­vat­ten die heb­ben be­wo­gen sinds de laat­ste event. An­ders kun je een min­der krach­tig ap­pa­raat ver­tra­gen door waar­den te up­da­ten die niet ver­an­derd zijn. func­ti­on tou­chmo­ve(e) { e.pre­vent­de­fault(); for (var i = 0; i < e.chan­ge­dtou­ches. length; i++) {

var pos = get­pos(e.chan­ge­dtou­ches[i]. iden­ti­fier); if(pos !== null) {

points[pos].up­da­te(e. chan­ge­dtou­ches[i]);

}

} }

18. Ana­ly­ser­no­de toe­voe­gen

Om de ge­luids­golf­s­i­mu­la­ties te ma­ken, moet je de sta­tus van de ge­luids­golf op­ha­len van ie­der Poin­t­ob­ject in de audio-con­tent. Je kunt dat doen door een Ana­ly­ser­no­de te ge­brui­ken, die ver­bon­den wordt aan het laat­ste deel van de audio-graaf. In dit ge­val is dat de gain-no­de bin­nen ie­der Poin­t­ob­ject.

Na het aan­ma­ken van de gain-no­de in Point.js maak je een Ana­ly­ser­no­de en ver­bind je die met de gain-no­de. var au­dio­ana­ly­ser = au­dio­con­text. cre­a­te­ana­ly­ser(); var buf­fer­length = au­dio­ana­ly­ser.fft­si­ze; […] ga­in­no­de.con­nect(au­dio­ana­ly­ser);

19. Te­ken op het can­vas

Om de lij­nen zo vaak mo­ge­lijk te (her)te­ke­nen, moet je re­quest­a­ni­ma­ti­on­fra­me ge­brui­ken. Die zal de draw()-func­tie ie­de­re keer aan­roe­pen als de brow­ser iets op het scherm te­kent. In scripts,js leeg je het can­vas aan het be­gin van ie­de­re aan­roep en dan loop je door ie­der Point-ob­ject en roep je draw() aan. De rest ge­beurt dan van­zelf. Ver­geet niet om de func­tie aan te roe­pen om de cy­clus te be­gin­nen. func­ti­on draw() { re­quest­a­ni­ma­ti­on­fra­me(draw); can­vas­con­text.clear­rect(0, 0, can­vas. width, can­vas.height); for(var i = 0; i < points.length; i++) { points[i].draw();

20. Be­reid de wa­ve-da­ta voor

ie­de­re keer als je draw() in het Point-ob­ject aan­roept, moet je da­ta ha­len van de Ana­ly­ser­no­de. Die kan in ver­schil­len­de for­ma­ten ge­le­verd wor­den. Hier moe­ten we ze heb­ben als un­sig­ned by­te­ar­ray.

Ver­vol­gens de­fi­ni­eer je hoe ie­de­re golf er­uit komt te zien. Ge­bruik een wil­le­keu­ri­ge kleur voor ie­de­re lijn om ze uit el­kaar te kun­nen hou­den. Als laat­ste be­re­ken je hoe ver de ver­schil­len­de golf­de­len van el­kaar ver­wij­derd moe­ten zijn om ze op het can­vas te la­ten pas­sen. var da­taar­ray = new Uint8ar­ray(buf­fer­length); au­dio­ana­ly­ser.get­by­te­ti­me­do­main­da­ta (da­taar­ray); can­vas­con­text.li­ne­width = 3; can­vas­con­text.stro­ke­sty­le = co­lor; can­vas­con­text.be­gin­path(); var sli­ce­width = can­vas.width / buf­fer­length;

21. Te­ken de ge­luids­golf

Ten slot­te loop je door de ge­ge­ven da­ta heen om een ver­za­me­ling co­ör­di­na­ten aan te ma­ken waar het can­vas lij­nen tus­sen kan te­ke­nen. Na ie­de­re loop ver­schuif je de x-po­si­tie van ie­de­re co­ör­di­naat om op de he­le pa­gi­na te te­ke­nen.

Dan res­teert al­leen nog het daad­wer­ke­lij­ke te­ke­nen van de lijn op het can­vas. De gol­ven ko­men bo­ven el­kaar te staan op ba­sis van de po­si­tie van hun ob­jec­ten in de points-ar­ray. var x = 0; for (var i = 0; i < buf­fer­length; i++){ var am­pli­tu­de = da­taar­ray[i] / 128; var y = am­pli­tu­de * can­vas.height / 2; if (i === 0) {

can­vas­con­text.mo­ve­to(x, y); } el­se {

can­vas­con­text.li­ne­to(x, y);

} x += sli­ce­width; } can­vas­con­text.stro­ke();

Newspapers in Dutch

Newspapers from Netherlands

© PressReader. All rights reserved.