Game Audio vereenvoudigd

De Web Audio API is een krachtige bondgenoot voor iedereen die JavaScript-spellen maakt, maar met die kracht komt complexiteit. Web Audio is een modulair systeem; audioknooppunten kunnen met elkaar worden verbonden om complexe grafieken te vormen voor alles, van het afspelen van een enkel geluid tot een volledig uitgeruste muzieksequencing-applicatie. Dit is indrukwekkend, om het zachtjes uit te drukken.

Als het aankomt op het programmeren van games, willen de meeste ontwikkelaars een eenvoudige API die geluiden laadt en speelt, en opties biedt voor het wijzigen van het volume, de toonhoogte en de pan (stereopositie) van die geluiden. Deze tutorial biedt een elegante oplossing door de Web Audio API in een snel, lichtgewicht formaat te verpakken Geluid klas die alles voor je afhandelt.

Notitie: Deze tutorial is in eerste instantie bedoeld voor JavaScript-programmeurs, maar de technieken die worden gebruikt om audio in de code te mixen en te manipuleren, kunnen worden toegepast op vrijwel elke programmeeromgeving die toegang biedt tot een low-levelgeluid-API.

Live demonstratie

Voordat we aan de slag gaan, bekijk de live demo van de Geluid klasse in actie. U kunt op de knoppen in de demo klikken om geluiden af ​​te spelen:

  • SFX 01 is een single-shotgeluid met standaardinstellingen. 
  • SFX 02 is een single-shot-geluid waarbij de pan (stereopositie) en het volume worden gerandomiseerd telkens wanneer deze wordt afgespeeld. 
  • SFX 03 is een looping-geluid; Als u op de knop klikt, wordt het geluid in- en uitgeschakeld en de positie van de muisaanwijzer binnen de knop past de toonhoogte van het geluid aan.

Notitie: Als u geen geluiden hoort die worden afgespeeld, ondersteunt de webbrowser die u gebruikt de Web Audio API of OGG Vorbis-audiostreams niet. Als u Chrome of Firefox gebruikt, moet dit probleem worden opgelost.

Het leven kan eenvoudiger zijn

De volgende afbeelding visualiseert een standaard Web Audio knooppuntgrafiek:

Visueel voorbeeld van een knooppuntgrafiek voor webcommunicatie.

Zoals je ziet zijn er nogal wat audioknooppunten in de grafiek om het afspelen van vier geluiden op een voor spel geschikte manier aan te kunnen. De panner- en gainknooppunten hebben betrekking op panning en volume en er zijn een aantal dynamiekcompressorknooppunten om te voorkomen dat er hoorbare artefacten (clips, ploffen, enzovoort) optreden als de grafiek wordt overladen door luide audio.

Het is geweldig om in staat te zijn om audioknoopgrafieken zo te maken in JavaScript, maar het constant creëren, verbinden en loskoppelen van die knooppunten kan een echte last worden. We gaan dingen vereenvoudigen door de audiomixing en -manipulatie programmatisch af te handelen met behulp van een enkele-scriptprocessorknooppunt.

Visueel voorbeeld van een vereenvoudigde knooppuntengrafiek voor webaudio.

Ja, dat is absoluut veel eenvoudiger - en het vermijdt ook de verwerkingsoverhead bij het maken, verbinden en loskoppelen van een lading audioknooppunten telkens wanneer er een geluid moet worden afgespeeld. 

Er zijn andere eigenaardigheden in de Web Audio API die dingen moeilijk kunnen maken. Het pansknooppunt, bijvoorbeeld, is specifiek ontworpen voor geluiden die zijn gepositioneerd in 3D-ruimte, geen 2D-ruimte, en audiobufferbronknooppunten (gelabeld "geluid" in de vorige afbeelding) kunnen slechts één keer worden afgespeeld, vandaar de noodzaak om constant te creëren en verbind die knooppunttypes.

Het processorknooppunt met één script dat wordt gebruikt door de Geluid class vraagt ​​periodiek om geluidsmonsters die aan JavaScript moeten worden doorgegeven, en dat maakt het voor ons een stuk eenvoudiger. We kunnen zeer snel en eenvoudig geluidsmonsters mixen en manipuleren in JavaScript om de volume-, pitch- en panning-functionaliteit te produceren die we nodig hebben voor 2D-games.

De geluidsklasse

In plaats van baby-stappen door de creatie van de Geluid klasse, zullen we de kerngedeelten van de code bekijken die direct gerelateerd zijn aan de Web Audio API en de manipulatie van geluidssamples. De demo-bronbestanden bevatten de volledig functionele Geluid klas, die je vrij kunt bestuderen en gebruiken in je eigen projecten.

Geluidsbestanden laden

De Geluid klasse laadt geluidsbestanden over een netwerk als matrixbuffers gebruiken XMLHttpRequest voorwerpen. De arraybuffers worden vervolgens gedecodeerd in onbewerkte geluidssamples door een audio-contextobject.

request.open ("GET", "sound.ogg"); request.onload = decoderen; request.responseType = "arraybuffer"; request.open (); function decode () if (request.response! == null) audioContext.decodeAudioData (request.response, done);  functie voltooid (audioBuffer) ...

Uiteraard is er geen foutafhandeling in die code, maar het laat wel zien hoe de geluidsbestanden worden geladen en gedecodeerd. De audioBuffer doorgegeven aan de gedaan() functie bevat de onbewerkte geluidssamples van het geladen geluidsbestand.

Geluidsamples mixen en manipuleren

Voor het mixen en manipuleren van de geladen geluidssamples, de Geluid klasse koppelt een luisteraar aan een scriptprocessorknooppunt. Deze luisteraar wordt periodiek gebeld om meer geluidsfragmenten aan te vragen.

// Bereken een buffergrootte. // Dit levert een redelijke waarde op die audio / wachttijd en CPU-gebruik in evenwicht brengt voor games die met 60 Hz werken. var v = audioContext.sampleRate / 60; var n = 0; while (v> 0) v >> = 1; n ++;  v = Math.pow (2, n); // buffergrootte // Maak de scriptprocessor. processor = audioContext.createScriptProcessor (v); // Sluit de luisteraar aan. processor.onaudioprocess = processamples; functie procesSamples (event) ...

De frequentie waarmee de processSamples () functie wordt genoemd, varieert op verschillende hardware-instellingen, maar is meestal ongeveer 45 keer per seconde. Dat klinkt misschien als veel, maar het is vereist om de audio-latentie laag genoeg te houden om bruikbaar te zijn in moderne games die typisch draaien op 60 frames per seconde. Als de audio-latentie te hoog is, zullen de geluiden te laat worden gehoord om te synchroniseren met wat er op het scherm gebeurt, en dat zou een irritante ervaring zijn voor iedereen die een game speelt.

Ondanks de frequentie waarmee de processSamples () functie wordt genoemd, blijft het CPU-gebruik laag, dus maak je geen zorgen over te veel tijd weggenomen te worden van de spellogica en rendering. Op mijn hardware (Intel Core i3, 3 GHz) overschrijdt het CPU-gebruik zelden 2%, zelfs wanneer er veel geluiden gelijktijdig worden afgespeeld.

De processSamples () functie bevat eigenlijk het vlees van de Geluid klasse; het is waar de geluidsstalen worden gemixt en gemanipuleerd voordat ze via de webaudio naar de hardware worden geduwd. De volgende code laat zien wat er gebeurt binnen de functie:

// Pak het geluidsvoorbeeld. sampleL = samplesL [soundPosition >> 0]; sampleR = samplesR [soundPosition >> 0]; // Verhoog de positie van de afspeelkop van het geluid. soundPosition + = soundScale; // Pas het globale volume toe (beïnvloedt alle geluiden). sampleL * = globalVolume; sampleR * = globalVolume; // Pas het volume van het geluid toe. sampleL * = soundVolume; sampleR * = soundVolume; // Pas de pan van het geluid toe (stereopositie). sampleL * = 1.0 - soundPan; sampleR * = 1.0 + soundPan;

Dat is min of meer alles wat er is. Dat is de magie: een handvol eenvoudige bewerkingen veranderen het volume, de toonhoogte en de stereopositie van een geluid.

Als je een programmeur bent en bekend bent met dit type geluidsverwerking, denk je misschien: "dat kan niet alles zijn" en zou je gelijk hebben: de klasse Sound moet de geluidsinstanties, voorbeeldbuffers en doe nog een paar andere dingen, maar dat is alles standaard!

De geluidsklasse gebruiken

De volgende code demonstreert hoe de klasse Sound te gebruiken. Je kunt ook de bronbestanden downloaden voor de live demo bij deze tutorial.

// Maak een paar geluidsobjecten. var boom = nieuw Geluid ("boom.ogg"); var tick = new Sound ("tick.ogg"); // Optioneer een luisteraar naar de klasse Sound. Sound.setListener (luisteraar); // Hiermee worden alle nieuw gemaakte Sound-objecten geladen. Sound.load (); // De luisteraar. functie luisteraar (geluid, status) if (state === Sound.State.LOADED) if (geluid === tick) setInterval (playTick, 1000);  else if (geluid === boom) setInterval (playBoom, 4000);  else if (state === Sound.State.ERROR) console.warn ("Sound error:% s", sound.getPath ());  // Speelt het tick-geluid. function playTick () tick.play ();  // Speelt het giekgeluid af. function playBoom ​​() boom.play (); // Willekeurig de toonhoogte en het volume van het geluid. boom.setScale (0.8 + 0.4 * Math.random ()); boom.setVolume (0.2 + 0.8 * Math.random ()); 

Leuk en gemakkelijk.

Eén ding om op te merken: het maakt niet uit of de Web Audio API niet beschikbaar is in een browser, en het maakt niet uit of de browser een specifieke geluidsindeling niet kan afspelen. Je kunt nog steeds het spelen() en hou op() functies op een Geluid object zonder dat er fouten worden gesmeten. Dat is opzettelijk; het stelt je in staat om je spelcode zoals gewoonlijk uit te voeren zonder je zorgen te maken over problemen met browsercompatibiliteit of vertakking van je code om die problemen op te lossen. Het ergste dat kan gebeuren is stilte.

De Sound Class-API

  • spelen()
  • hou op()
  • getPath (): Haalt het bestandspad van het geluid op.
  • getState ()
  • GetPan ()
  • setPan (waarde): Stelt de pan van het geluid in (stereopositie).
  • getScale ()
  • setScale (waarde): Stelt de schaal van het geluid in (toonhoogte).
  • GetVolume ()
  • setVolume (waarde): Stelt het volume van het geluid in.
  • is in afwachting van()
  • Laadt()
  • is geladen()
  • isLooped ()

De klasse Sound bevat ook de volgende statische functies.

  • laden(): Laad nieuw gecreëerde geluiden.
  • hou op(): Stopt alle geluiden.
  • GetVolume ()
  • setVolume (waarde): Stelt het algemene (hoofd) volume in.
  • getListener ()
  • setListener (waarde): Houdt de voortgang van het laden van geluid bij, enz.
  • canPlay (formaat): Controleert of verschillende geluidsindelingen kunnen worden afgespeeld.

Documentatie is te vinden in de broncode van de demo.

Conclusie

Het spelen van geluidseffecten in een JavaScript-spel moet eenvoudig zijn, en deze tutorial maakt het zo door de krachtige Web Audio API te verpakken in een snelle, lichtgewicht Sound-klasse die alles voor je afhandelt.

Verwante bronnen

Als je meer wilt weten over geluidsfragmenten en hoe je ze kunt manipuleren, heb ik een serie voor Tuts + geschreven die je een tijdje bezig moet houden ...

  1. Een synthesizer maken - Inleiding
  2. Een synthesizer maken - kernmotor
  3. Een synthesizer maken - Audio-processors

De volgende koppelingen zijn voor de W3C en Khronos gestandaardiseerde specificaties die rechtstreeks verband houden met de Web Audio API:

  • Web Audio API
  • Getypeerde reeksen