Dette er det andre i en serie av opplæringsprogrammer der vi skal lage en syntetiseringsbasert lydmotor som kan generere lyder for retro-stilte spill. Lydmotoren vil generere alle lydene ved kjøretid uten behov for eksterne avhengigheter som MP3-filer eller WAV-filer. Sluttresultatet vil være et fungerende bibliotek som kan løses enkelt i spillene dine.
Hvis du ikke allerede har lest den første opplæringen i denne serien, bør du gjøre det før du fortsetter.
Programmeringsspråket som brukes i denne opplæringen, er ActionScript 3.0, men de teknikker og begreper som brukes kan enkelt oversettes til et hvilket som helst annet programmeringsspråk som gir en lavnivå lyd API.
Du bør sørge for at du har Flash Player 11.4 eller høyere installert for nettleseren din hvis du vil bruke de interaktive eksemplene i denne opplæringen.
Ved slutten av denne opplæringen vil all kjernekode som kreves for lydmotoren, være fullført. Følgende er en enkel demonstrasjon av lydmotor i aksjon.
Bare én lyd blir spilt i den demonstrasjonen, men frekvensen av lyden blir randomisert sammen med utgivelsestiden. Lyden har også en modulator festet til den for å produsere vibrato-effekten (modulere lydens amplitude) og frekvensen av modulatoren blir også randomisert.
Den første klassen som vi vil opprette vil bare holde konstante verdier for bølgeformene som lydmotoren vil bruke til å generere lydlydene.
Start med å opprette en ny klassepakke som heter bråk
, og legg deretter til følgende klasse i den pakken:
pakkestøy offentlig endelig klasse AudioWaveform statisk offentlig const PULSE: int = 0; statisk offentlig const SAWTOOTH: int = 1; statisk offentlig konst SINE: int = 2; statisk offentlig const TRIANGLE: int = 3;
Vi vil også legge til en statisk offentlig metode for klassen som kan brukes til å validere en bølgeform verdi, metoden kommer tilbake ekte
eller falsk
for å indikere om bølgeformverdien er gyldig eller ikke.
statisk offentlig funksjon validere (bølgeform: int): Boolsk hvis (bølgeform == PULSE) returnere sann; hvis (bølgeform == SAWTOOTH) returnere sann; hvis (bølgeform == SINE) returnere sann; hvis (bølgeform == TRIANGLE) returnere sann; returner falsk;
Til slutt bør vi forhindre at klassen blir instantiated fordi det ikke er noen grunn til at noen kan lage forekomster av denne klassen. Vi kan gjøre dette innen klassekonstruktøren:
offentlig funksjon AudioWaveform () kaste ny feil ("AudioWaveform-klassen kan ikke bli instantiated");
Denne klassen er nå fullført.
Å forhindre enum-stil klasser, all-statiske klasser og singleton klasser fra å være direkte instantiated er en god ting å gjøre fordi disse typer klasser bør ikke bli instantiated; Det er ingen grunn til å instantiere dem. Programmeringsspråk som Java gjør dette automatisk for de fleste av disse klassetyper, men for øyeblikket i ActionScript 3.0 må vi håndheve denne oppførselen manuelt i klassekonstruktøren.
Neste på listen er Audio
klasse. Denne klassen ligner i naturen til den native ActionScript 3.0 Lyd
klasse: hver lydmotor lyd vil bli representert av en Audio
klasse eksempel.
Legg til følgende barebones klasse til bråk
pakke:
pakke støy offentlig klasse lyd offentlig funksjon lyd ()
De første tingene som må legges til i klassen, er egenskaper som forteller lydmotorens hvordan man genererer lydbølgen når lyden spilles. Disse egenskapene inkluderer typen av bølgeform som brukes av lyden, frekvensen og amplituden til bølgeformen, lydens varighet og dens frigivelsestid (hvor raskt det fades ut). Alle disse egenskapene vil være private og nås via getters / setters:
privat var m_waveform: int = AudioWaveform.PULSE; privat var m_frequency: Number = 100.0; privat var m_amplitude: tall = 0,5; privat var m_duration: tall = 0,2; privat var m_release: tall = 0,2;
Som du kan se, har vi satt en fornuftig standardverdi for hver eiendom. De amplitude
er en verdi i området 0.0
til 1.0
, de Frekvens
er i Hertz, og varighet
og utgivelse
Tider er i sekunder.
Vi må også legge til to flere private eiendommer for modulatorer som kan festes til lyden; igjen disse egenskapene vil bli åpnet via getters / setters:
private var m_frequencyModulator: AudioModulator = null; privat var m_amplitudeModulator: AudioModulator = null;
Endelig, den Audio
klassen vil inneholde noen interne egenskaper som kun vil bli åpnet av Audioengine
klasse (vi vil lage den klassen kort tid). Disse egenskapene trenger ikke å være skjult bak getters / setters:
intern varposisjon: tall = 0,0; intern var å spille: boolsk = false; intern var release: Boolsk = false; interne varprøver: Vector.= null;
De stilling
er i sekunder og det tillater det Audioengine
klassen for å holde oversikt over lydens posisjon mens lyden spiller, er dette nødvendig for å beregne bølgeform lydprøver for lyden. De spiller
og frigjøre
egenskaper forteller Audioengine
hva staten lyden er i, og prøver
Egenskapen er en referanse til de hurtigbufrede kurvformene som lyden bruker. Bruken av disse egenskapene blir tydelig når vi lager Audioengine
klasse.
For å fullføre Audio
klasse vi må legge til getters / setters:
Audio.bølgeform
offentlig sluttfunksjon få bølgeform (): int return m_waveform; Offentlig sluttfunksjonssett bølgeform (verdi: int): void if (AudioWaveform.isValid (verdi) == false) return; bytte (verdi) tilfelle AudioWaveform.PULSE: samples = AudioEngine.PULSE; gå i stykker; tilfelle AudioWaveform.SAWTOOTH: samples = AudioEngine.SAWTOOTH; gå i stykker; tilfelle AudioWaveform.SINE: samples = AudioEngine.SINE; gå i stykker; tilfelle AudioWaveform.TRIANGLE: samples = AudioEngine.TRIANGLE; gå i stykker; m_waveform = verdi;
Audio.Frekvens
[Inline] offentlig sluttfunksjon få frekvens (): Nummer return m_frequency; Offentlig sluttfunksjonssettfrekvens (verdi: Nummer): void // klemme frekvensen til intervallet 1,0 - 14080,0 m_frequency = verdi < 1.0 ? 1.0 : value > 14080.0? 14080.0: verdi;
Audio.amplitude
[Inline] offentlig sluttfunksjon få amplitude (): Nummer return m_amplitude; Offentlig sluttfunksjonssett amplitude (verdi: Nummer): void // klem amplituden til intervallet 0.0 - 1.0 m_amplitude = verdi < 0.0 ? 0.0 : value > 1,0? 1,0: verdi;
Audio.varighet
[Inline] Offentlig sluttfunksjon få varighet (): Nummer return m_duration; Offentlig sluttfunksjon satt varighet (verdi: Nummer): void // klemme varigheten til intervallet 0.0 - 60.0 m_duration = verdi < 0.0 ? 0.0 : value > 60,0? 60,0: verdi;
Audio.utgivelse
[Inline] offentlig sluttfunksjon få utgivelse (): Nummer return m_release; Offentlig funksjon satt utgivelse (verdi: Nummer): void // klemme utgivelsestiden til området 0,0 - 10,0 m_release = verdi < 0.0 ? 0.0 : value > 10,0? 10.0: verdi;
Audio.frequencyModulator
[Inline] offentlig sluttfunksjon få frequencyModulator (): AudioModulator return m_frequencyModulator; Offentlig sluttfunksjonssett frekvensModulator (verdi: AudioModulator): void m_frequencyModulator = value;
Audio.amplitudeModulator
[Inline] offentlig sluttfunksjon få amplitudeModulator (): AudioModulator return m_amplitudeModulator; Offentlig sluttfunksjonssett amplitudeModulator (verdi: AudioModulator): void m_amplitudeModulator = value;
Du har uten tvil lagt merke til [På linje]
metadata tag bundet til noen av getter-funksjonene. Den metadata-taggen er en skinnende ny funksjon av Adobes nyeste ActionScript 3.0-kompiler, og det gjør det som står på tinnet: det innbringer (utvider) innholdet i en funksjon. Dette er ekstremt nyttig for optimalisering når det brukes fornuftig, og generering av dynamisk lyd ved kjøring er absolutt noe som krever optimalisering.
Formålet med AudioModulator
er å tillate amplitude og frekvens av Audio
eksempler på å bli modulert for å skape nyttige og galne lydeffekter. Modulatorer er faktisk lik Audio
forekomster, de har en bølgeform, en amplitude og frekvens, men de produserer faktisk ingen lydlyd som de bare modiferer hørbare lyder.
Første ting først, opprett følgende barebones klasse i bråk
pakke:
pakkestøy offentlig klasse AudioModulator offentlig funksjon AudioModulator ()
La oss nå legge til private private eiendommer:
privat var m_waveform: int = AudioWaveform.SINE; privat var m_frequency: tall = 4,0; privat var m_amplitude: tall = 1,0; privat var m_shift: tall = 0,0; private var m_samples: Vector.= null;
Hvis du tenker dette ser veldig ut som Audio
så er du riktig: alt bortsett fra skifte
Eiendommen er den samme.
For å forstå hva skifte
Egenskapen, tenk på en av de grunnleggende bølgeformene som lydmotoren bruker (puls, savetann, sinus eller trekant) og så forestill deg en vertikal linje som går rett gjennom bølgeformen i en hvilken som helst posisjon du liker. Den horisontale posisjonen til den vertikale linjen vil være skifte
verdi; det er en verdi i serien 0.0
til 1.0
som forteller modulatoren hvor du skal begynne å lese den er bølgeform fra og i sin tur kan ha en dyp innvirkning på modifikasjonene som modulatoren gjør til lydens amplitude eller frekvens.
Som et eksempel, hvis modulatoren brukte en sinusbølgeform for å modulere frekvensen av en lyd, og skifte
ble satt til 0.0
, lydens frekvens ville først stige og deretter falle på grunn av sinusbukkens krumning. Men hvis skifte
ble satt til 0.5
lydens frekvens vil først falle og deretter stige.
Uansett, tilbake til koden. De AudioModulator
inneholder en intern metode som bare brukes av Audioengine
; metoden er som følger:
[Inline] intern sluttfunksjon prosess (tid: Nummer): Nummer var p: int = 0; var s: tall = 0,0; hvis (m_shift! = 0.0) time + = (1.0 / m_frequency) * m_shift; p = (44100 * m_frequency * tid)% 44100; s = m_samples [p]; returner s * m_amplitude;
Den funksjonen er innstilt fordi den brukes mye, og når jeg sier "mye", mener jeg 44100 ganger i sekundet for hver lyd som spiller, som har en modulator festet til den (dette er hvor inlining blir utrolig verdifullt). Funksjonen tar bare en lydprøve fra bølgeformen som modulatoren bruker, justerer prøvenes amplitude og returnerer deretter resultatet.
For å fullføre AudioModulator
klasse vi må legge til getters / setters:
AudioModulator.bølgeform
offentlig funksjon få bølgeform (): int return m_waveform; Offentlig funksjon satt bølgeform (verdi: int): void if (AudioWaveform.isValid (verdi) == false) return; bytte (verdi) tilfelle AudioWaveform.PULSE: m_samples = AudioEngine.PULSE; gå i stykker; tilfelle AudioWaveform.SAWTOOTH: m_samples = AudioEngine.SAWTOOTH; gå i stykker; tilfelle AudioWaveform.SINE: m_samples = AudioEngine.SINE; gå i stykker; tilfelle AudioWaveform.TRIANGLE: m_samples = AudioEngine.TRIANGLE; gå i stykker; m_waveform = verdi;
AudioModulator.Frekvens
offentlig funksjon få frekvens (): tall return m_frequency; offentlig funksjonsinnstilt frekvens (verdi: tall): void // klemme frekvensen til området 0,01 - 100,0 m_frequency = verdi < 0.01 ? 0.01 : value > 100,0? 100,0: verdi;
AudioModulator.amplitude
offentlig funksjon få amplitude (): tall return m_amplitude; offentlige funksjonsinnstilt amplitude (verdi: tall): void // klem amplituden til intervallet 0.0 - 8000.0 m_amplitude = verdi < 0.0 ? 0.0 : value > 8000.0? 8000.0: verdi;
AudioModulator.skifte
offentlig funksjon få skift (): tall retur m_shift; Offentlig funksjon sett skift (verdi: Nummer): void // klemme skiftet til intervallet 0.0 - 1.0 m_shift = verdi < 0.0 ? 0.0 : value > 1,0? 1,0: verdi;
Og det bryter opp AudioModulator
klasse.
Nå for den store: den Audioengine
klasse. Dette er en allstatisk klasse og administrerer stort sett alt relatert til Audio
forekomster og lydgenerering.
La oss starte med en barebones klasse i bråk
pakke som vanlig:
pakkestøy import flash.events.SampleDataEvent; importere flash.media.Sound; importere flash.media.SoundChannel; importere flash.utils.ByteArray; // offentlig sluttklasse AudioEngine offentlig funksjon AudioEngine () kaste ny feil ("AudioEngine-klassen kan ikke bli instantiated");
Som nevnt før, bør ikke alle statiske klasser bli instantiated, derfor unntaket som kastes i klassekonstruktøren hvis noen prøver å ordne klassen. Klassen er også endelig
fordi det ikke er grunn til å utvide en allstatisk klasse.
De første tingene som blir lagt til i denne klassen er interne konstanter. Disse konstantene vil bli brukt til å cache prøvene for hver av de fire bølgeformene som lydmotoren bruker. Hver cache inneholder 44,100 prøver som tilsvarer en hertz bølgeform. Dette gjør det mulig for lydmotor å produsere veldig rent lavfrekvent lydbølger.
Konstantene er som følger:
statisk intern const PULSE: Vector.= Ny Vector. (44100); statisk indre const SAWTOOTH: Vector. = Ny Vector. (44100); statisk indre const SINE: Vector. = Ny Vector. (44100); statisk intern const TRIANGLE: Vector. = Ny Vector. (44100);
Det er også to private konstanter som brukes av klassen:
statisk privat const BUFFER_SIZE: int = 2048; statisk privat const SAMPLE_TIME: Number = 1.0 / 44100.0;
De BUFFER STØRRELSE
er antall lydprøver som skal sendes til ActionScript 3.0-lyd-API når en forespørsel om lydprøver blir gjort. Dette er det minste antall prøver tillatt og det resulterer i lavest mulig lydforsinkelse. Antallet prøver kunne økes for å redusere CPU-bruk, men det ville øke lydlatensen. De SAMPLE_TIME
er varigheten av en enkelt lydprøve, i sekunder.
Og nå for de private variablene:
statisk privat varemisjon: tall = 0,0; statisk privat var m_amplitude: tall = 0,5; statisk privat var m_soundStream: Lyd = null; statisk privat var m_soundChannel: SoundChannel = null; statisk privat var m_audioList: Vector.
m_position
brukes til å holde oversikt over lydstrømtiden, i sekunder.m_amplitude
er en global sekundær amplitude for alle Audio
forekomster som spiller.m_soundStream
og m_soundChannel
bør ikke ha noen forklaring.m_audioList
inneholder referanser til noen Audio
forekomster som spiller.m_sampleList
er en midlertidig buffer som brukes til å lagre lydprøver når de blir bedt om av ActionScript 3.0 lyd-API.Nå må vi initialisere klassen. Det er mange måter å gjøre dette på, men jeg foretrekker noe fint og enkelt, en statisk klassekonstruktør:
statisk privat funksjon $ AudioEngine (): void var i: int = 0; var n: int = 44100; var p: tall = 0,0; // mens jeg < n ) p = i / n; SINE[i] = Math.sin( Math.PI * 2.0 * p ); PULSE[i] = p < 0.5 ? 1.0 : -1.0; SAWTOOTH[i] = p < 0.5 ? p * 2.0 : p * 2.0 - 2.0; TRIANGLE[i] = p < 0.25 ? p * 4.0 : p < 0.75 ? 2.0 - p * 4.0 : p * 4.0 - 4.0; i++; // m_soundStream = new Sound(); m_soundStream.addEventListener( SampleDataEvent.SAMPLE_DATA, onSampleData ); m_soundChannel = m_soundStream.play(); $AudioEngine();
Hvis du har lest den forrige veiledningen i denne serien, vil du sannsynligvis se hva som skjer i den koden: prøvene for hver av de fire bølgeformene blir generert og bufret, og dette skjer bare en gang. Lydstrømmen blir også instantiated og startet og vil løpe kontinuerlig til appen er avsluttet.
De Audioengine
klassen har tre offentlige metoder som brukes til å spille og stoppe Audio
instanser:
Audioengine.spille()
statisk funksjon for offentlig funksjon (lyd: lyd): void if (audio.playing == false) m_audioList.push (lyd); // dette lar oss vite nøyaktig når lyden ble startet audio.position = m_position - (m_soundChannel.position * 0.001); audio.playing = true; audio.releasing = false;
Audioengine.Stoppe()
statisk offentlig funksjonstopp (lyd: Lyd, allowRelease: Boolean = true): void if (audio.playing == false) // lyden spilles ikke tilbake; hvis (allowRelease) // hopp over til slutten av lyden og merk det som å frigjøre audio.position = audio.duration; audio.releasing = true; komme tilbake; audio.playing = false; audio.releasing = false;
Audioengine.STOPALL ()
statisk offentlig funksjon stopAll (allowRelease: Boolean = true): void var i: int = 0; var n: int = m_audioList.length; var o: Lyd = null; // hvis (allowRelease) mens (i < n ) o = m_audioList[i]; o.position = o.duration; o.releasing = true; i++; return; while( i < n ) o = m_audioList[i]; o.playing = false; o.releasing = false; i++;
Og her kommer de viktigste lydbehandlingsmetodene, som begge er private:
Audioengine.onSampleData ()
statisk privat funksjon onSampleData (hendelse: SampleDataEvent): void var i: int = 0; var n: int = BUFFER_SIZE; var s: tall = 0,0; var b: ByteArray = event.data; // hvis (m_soundChannel == null) mens (i < n ) b.writeFloat( 0.0 ); b.writeFloat( 0.0 ); i++; return; // generateSamples(); // while( i < n ) s = m_sampleList[i] * m_amplitude; b.writeFloat( s ); b.writeFloat( s ); m_sampleList[i] = 0.0; i++; // m_position = m_soundChannel.position * 0.001;
Så i den første hvis
erklæring vi sjekker om m_soundChannel
er fortsatt null, og vi må gjøre det fordi EKSEMPELDATA
Hendelsen sendes så snart som m_soundStream.play ()
Metoden er påkalt, og før metoden får en sjanse til å returnere a SoundChannel
forekomst.
De samtidig som
loop ruller gjennom lydprøver som er forespurt av m_soundStream
og skriver dem til den oppgitte ByteArray
forekomst. Lydprøver genereres ved hjelp av følgende metode:
Audioengine.generateSamples ()
statisk privat funksjon generereSamples (): void var i: int = 0; var n: int = m_audioList.length; var j: int = 0; var k: int = BUFFER_SIZE; var p: int = 0; var f: tall = 0,0; var a: tall = 0,0; var s: tall = 0,0; var o: Lyd = null; // rulle gjennom lydinstansene mens (i < n ) o = m_audioList[i]; // if( o.playing == false ) // the audio instance has stopped completely m_audioList.splice( i, 1 ); n--; continue; // j = 0; // generate and buffer the sound samples while( j < k ) if( o.position < 0.0 ) // the audio instance hasn't started playing yet o.position += SAMPLE_TIME; j++; continue; if( o.position >= o.duration) hvis (o.position> = o.duration + o.release) // lyd forekomsten har stoppet o.playing = false; j ++; Fortsette; // lyden forekommer slipper o.releasing = true; // Ta tak i lydinstansens frekvens og amplitude f = o.frequency; a = o.amplitude; // hvis (o.frequencyModulator! = null) // moduler frekvensen f + = o.frequencyModulator.process (o.position); // hvis (o.amplitudeModulator! = null) // modulere amplituden a + = o.amplitudeModulator.process (o.position); // beregne posisjonen i bølgeformbufferen p = (44100 * f * o.position)% 44100; // ta tak i bølgeformprøven s = o.samples [p]; // hvis (o.releasing) // beregne fade-out amplitude for prøven s * = 1.0 - ((o.position - o.duration) / o.release); // legge prøven til bufferen m_sampleList [j] + = s * a; // Oppdater lydinstansens posisjon o.position + = SAMPLE_TIME; j ++; i ++;
Til slutt, for å fullføre ting, må vi legge til getteren / setteren for den private m_amplitude
variabel:
statisk offentlig funksjon få amplitude (): tall return m_amplitude; statisk offentlig funksjonssett amplitude (verdi: tall): void // klem amplituden til intervallet 0.0 - 1.0 m_amplitude = verdi < 0.0 ? 0.0 : value > 1,0? 1,0: verdi;
Og nå trenger jeg en pause!
I den tredje og siste opplæringen i serien vil vi legge til lydprosessorer til lydmotor. Disse vil tillate oss å skyve alle de genererte lydprøverne selv om prosesseringsenheter som harde begrensere og forsinkelser. Vi vil også ta en titt på hele koden for å se om noe kan optimaliseres.
All kildekoden for denne opplæringsserien vil bli gjort tilgjengelig med neste opplæring.
Følg oss på Twitter, Facebook eller Google+ for å holde deg oppdatert med de siste innleggene.