Ruis een synthesizer maken voor retro geluidseffecten - audioprocessors

Dit is het laatste deel in onze reeks zelfstudies over het maken van een op synthesizer gebaseerde audiomachine die kan worden gebruikt om geluiden te genereren voor retro-gestileerde games. De audiomachine kan alle runtime-geluiden genereren zonder dat er externe afhankelijkheden nodig zijn, zoals MP3-bestanden of WAV-bestanden. In deze zelfstudie voegen we ondersteuning toe voor audioprocessors en code a vertragingsprocessor wat een rottend echo-effect kan toevoegen aan onze geluiden.

Als u de eerste zelfstudie of de tweede zelfstudie in deze serie nog niet hebt gelezen, moet u dat doen voordat u doorgaat.

De programmeertaal die in deze zelfstudie wordt gebruikt, is ActionScript 3.0, maar de gebruikte technieken en concepten kunnen eenvoudig worden vertaald in elke andere programmeertaal die een low-levelgeluid-API biedt.

U moet ervoor zorgen dat Flash Player 11.4 of hoger is geïnstalleerd voor uw browser als u de interactieve voorbeelden in deze zelfstudie wilt gebruiken.


Demo van de geluidsprocessor

In deze laatste tutorial voegen we toe audio-processors tot de kernmotor en het creëren van een eenvoudige vertragingsprocessor. De volgende demonstratie toont de vertragingsprocessor in actie:

Er wordt slechts één geluid in die demonstratie gespeeld, maar de frequentie van het geluid wordt willekeurig verdeeld en de audiosamples die door de motor worden gegenereerd, worden door een vertragingsprocessor geduwd, waardoor het het vergane echo-effect krijgt.


audioprocessor Klasse

Het eerste dat we moeten doen is een basisklasse maken voor de audioprocessors:

pakketruis public class AudioProcessor // public var enabled: Boolean = true; // openbare functie AudioProcessor () if (Object (this) .constructor == AudioProcessor) gooi nieuwe fout ("AudioProcessor-klasse moet worden uitgebreid");  // interne functieproces (voorbeelden: Vector. ): void 

Zoals je kunt zien, is de klas erg eenvoudig; het bevat een intern werkwijze() methode die wordt aangeroepen door de AudioEngine klasse wanneer een monster moet worden verwerkt, en een publiek ingeschakeld eigenschap die kan worden gebruikt om de processor in en uit te schakelen.


audiodelay Klasse

De audiodelay class is de klasse die feitelijk de audiovertraging creëert en verlengt de audioprocessor klasse. Hier is de basis lege klasse waarmee we zullen werken:

pakketruis public class AudioDelay breidt AudioProcessor uit // openbare functie AudioDelay (tijd: Getal = 0.5) this.time = time; 

De tijd argument dat aan de klasse-constructor is doorgegeven, is de tijd (in seconden) van de vertragingstap - dat wil zeggen, de hoeveelheid tijd tussen elke audiovertraging.

Laten we nu de privé-eigenschappen toevoegen:

private var m_buffer: Vector. = nieuwe Vector.(); private var m_bufferSize: int = 0; private var m_bufferIndex: int = 0; private var m_time: Number = 0.0; private var m_gain: Number = 0.8;

De m_buffer vector is in feite een feedbacklus: het bevat alle audiomonsters die aan de. worden doorgegeven werkwijze methode, en die monsters worden voortdurend gewijzigd (in dit geval verminderd in amplitude) als de m_bufferIndex passeert de buffer. Dit is logisch als we bij de werkwijze() methode.

De m_bufferSize en m_bufferIndex eigenschappen worden gebruikt om de status van de buffer bij te houden. De m_time eigenschap is de tijd van de vertragingstap, in seconden. De m_gain eigenschap is een vermenigvuldigingsfactor die wordt gebruikt om de amplitude van de gebufferde audiomonsters in de loop van de tijd te verminderen.

Deze klasse heeft maar één methode en dat is de interne methode werkwijze() methode die de werkwijze() methode in de audioprocessor klasse:

intern overschrijffunctieproces (voorbeelden: Vector. ): void var i: int = 0; var n: int = samples.length; var v: Number = 0.0; // terwijl ik < n )  v = m_buffer[m_bufferIndex]; // grab a buffered sample v *= m_gain; // reduce the amplitude v += samples[i]; // add the fresh sample // m_buffer[m_bufferIndex] = v; m_bufferIndex++; // if( m_bufferIndex == m_bufferSize )  m_bufferIndex = 0;  // samples[i] = v; i++;  

Ten slotte moeten we de getters / setters voor privé toevoegen m_time en m_gain eigenschappen:

public function get time (): Number return m_time;  public function set time (value: Number): void // clamp the time to the range 0.0001 - 8.0 value = value < 0.0001 ? 0.0001 : value > 8.0? 8.0: waarde; // niet nodig om de buffergrootte aan te passen als de tijd niet is veranderd als (m_time == value) return;  // stel de tijd in m_time = waarde; // update de buffergrootte m_bufferSize = Math.floor (44100 * m_time); m_buffer.length = m_bufferSize; 
public function get gain (): Number return m_gain;  publieke functie set gain (waarde: Number): void // clamp de gain naar het bereik 0,0 - 1,0 m_gain = waarde < 0.0 ? 0.0 : value > 1.0? 1.0: waarde; 

Geloof of niet, dat is het audiodelay klas voltooid. Audiovertragingen zijn eigenlijk heel gemakkelijk als je eenmaal begrijpt hoe de feedbacklus (de m_buffer eigendom) werkt.


Updaten van de AudioEngine Klasse

Het laatste wat we moeten doen is het updaten AudioEngine klasse, zodat er audioprocessors aan kunnen worden toegevoegd. Laten we eerst een vector toevoegen om de instanties van de audioprocessor op te slaan:

static private var m_processorList: Vector. = nieuwe Vector.();

Om processors daadwerkelijk toe te voegen en te verwijderen van en naar de AudioEngine klasse gebruiken we twee openbare methoden:

AudioEngine.addProcessor ()

statische openbare functie addProcessor (processor: AudioProcessor): void if (m_processorList.indexOf (processor) == -1) m_processorList.push (processor); 

AudioEngine.removeProcessor ()

statische openbare functie removeProcessor (processor: AudioProcessor): void var i: int = m_processorList.indexOf (processor); if (i! = -1) m_processorList.splice (i, 1); 

Makkelijk genoeg - al die methoden doen is toevoegen en verwijderen audioprocessor exemplaren van of naar de m_processorList vector.

De laatste methode die we zullen toevoegen, rolt door de lijst van audioprocessors en geeft, als de processor is ingeschakeld, audiosamples door aan de processor. werkwijze() methode:

static private function processSamples (): void var i: int = 0; var n: int = m_processorList.length; // terwijl ik < n )  if( m_processorList[i].enabled )  m_processorList[i].process( m_sampleList );  i++;  

Nu is het tijd om het laatste stukje code toe te voegen, en dit is een enkele regel code die moet worden toegevoegd aan de privécode onSampleData () methode in de AudioEngine klasse:

if (m_soundChannel == null) while (i < n )  b.writeFloat( 0.0 ); b.writeFloat( 0.0 ); i++;  return;  // generateSamples(); processSamples(); // while( i < n )  s = m_sampleList[i] * m_amplitude; b.writeFloat( s ); b.writeFloat( s ); m_sampleList[i] = 0.0; i++; 

De gemarkeerde regel code moet worden toegevoegd aan de klasse; het roept gewoon de processSamples () methode die we eerder hebben toegevoegd.


Conclusie

Dat is, zoals ze zeggen, dat. In de eerste zelfstudie hebben we verschillende golfvormen bekeken en hoe geluidsgolven digitaal zijn opgeslagen. Vervolgens hebben we in de tweede zelfstudie de kerncode van de audiomachine geconstrueerd en nu hebben we dingen ingepakt met de toevoeging van audioprocessors.

Er is veel meer mogelijk met deze code of met een variant van deze code, maar het belangrijkste om in gedachten te houden is de hoeveelheid werk die een audiomachine tijdens runtime moet doen. Als je een audiomachine te ver duwt (en dat is gemakkelijk te doen), dan kan dit de algehele prestatie van je game negatief beïnvloeden - zelfs als je een audiomachine naar zijn eigen thread verplaatst (of ActionScript 3.0-werknemer), zal hij nog steeds graag bijten chunks uit de CPU als je niet voorzichtig bent.

Veel professionele en niet-zo-professionele spellen doen echter veel röntgenverwerking tijdens het verwerken van audio omdat het hebben van dynamische geluidseffecten en muziek in een game veel kan toevoegen aan de algehele ervaring en de speler dieper in het spel kan trekken wereld. De audiomachine die we in deze serie tutorials hebben samengevoegd, kan net zo goed werken met normale (niet-gegenereerde) geluidseffecten die uit bestanden worden geladen: in wezen is alle digitale audio een reeks voorbeelden in de meest eenvoudige vorm.

Nog een laatste ding om over na te denken: audio is een zeer belangrijk aspect van gameontwerp, het is net zo belangrijk en krachtig als de visuele kant van de dingen, en het is niet iets dat moet worden samengegooid of op een wedstrijd wordt geschroefd op het laatste moment ontwikkeling als je echt om de productiekwaliteit van je games geeft. Neem de tijd met het audio-ontwerp voor je games en je zult de vruchten plukken.

Ik hoop dat je deze reeks tutorials leuk vond en er iets positiefs uit kunt halen: zelfs als je vanaf nu iets meer over de audio in je games nadenkt, heb ik mijn werk gedaan.

Alle broncode van de audiomachine is beschikbaar in de brondownload.

Veel plezier!