Dette er det første i en serie av opplæringsprogrammer hvor vi skal lage en synthesizerbasert 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.
Før vi kan begynne å lage lydmotor er det noen ting vi må forstå; disse inkluderer bølgeformene som lydmotorene skal bruke til å generere lydlydene og hvordan lydbølger lagres og representert i digital form.
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.
Lydmotor som vi skal skape, vil bruke fire grunnleggende bølgeformer (også kjent som periodisk bølgeformer, fordi deres grunnleggende former gjentas periodisk), som alle er svært vanlige i både analoge og digitale synthesizer. Hver bølgeform har sin egen unike hørbar karakteristikk.
De følgende avsnittene gir en visuell gjengivelse av hver bølgeform, et hørbart eksempel på hver bølgeform, og koden som kreves for å generere hver bølgeform som en rekke prøvedata.
Pulsbølgen gir en skarp og harmonisk lyd.
For å generere en rekke verdier (i området -1,0 til 1,0) som representerer en pulsbølge, kan vi bruke følgende kode, hvor n
er antall verdier som kreves for å fylle opp arrayet, en
er matrisen, og p
er normalisert posisjon innenfor bølgeformen:
var jeg: int = 0; var n: int = 100; var p: tall; mens jeg < n ) p = i / n; a[i] = p < 0.5 ? 1.0 : -1.0; i ++;
Sågtandbølgen gir en skarp og sterk lyd.
For å generere en rekke verdier (i området -1,0 til 1,0) som representerer en sawtooth-bølge, kan vi bruke følgende kode, hvor n
er antall verdier som kreves for å fylle opp arrayet, en
er matrisen, og p
er normalisert posisjon innenfor bølgeformen:
var jeg: int = 0; var n: int = 100; var p: tall; mens jeg < n ) p = i / n; a[i] = p < 0.5 ? p * 2.0 : p * 2.0 - 2.0; i ++;
Sinebølgen gir en jevn og ren lyd.
For å generere en rekke verdier (i området -1,0 til 1,0) som representerer en sinusbølge, kan vi bruke følgende kode, hvor n
er antall verdier som kreves for å fylle opp arrayet, en
er matrisen, og p
er normalisert posisjon innenfor bølgeformen:
var jeg: int = 0; var n: int = 100; var p: tall; mens jeg < n ) p = i / n; a[i] = Math.sin( p * 2.0 * Math.PI ); i ++;
Triangelbølgen gir en jevn og harmonisk lyd.
For å generere en rekke verdier (i området -1,0 til 1,0) som representerer en trekantbølge, kan vi bruke følgende kode, hvor n
er antall verdier som kreves for å fylle opp arrayet, en
er matrisen, og p
er normalisert posisjon innenfor bølgeformen:
var jeg: int = 0; var n: int = 100; var p: tall; mens jeg < n ) p = i / n; a[i] = p < 0.25 ? p * 4.0 : p < 0.75 ? 2.0 - p * 4.0 : p * 4.0 - 4.0; i ++;
Her er en utvidet versjon av linje 6:
hvis (s < 0.25) a[i] = p * 4.0; else if (p < 0.75) a[i] = 2.0 - (p * 4.0); else a[i] = (p * 4.0) - 4.0;
To viktige egenskaper av en lydbølge er amplitude og Frekvens av bølgeformen: disse dikterer volum og tonehøyde av lyden, henholdsvis. Amplituden er ganske enkelt den absolutte toppverdien av bølgeformen, og frekvensen er antall ganger bølgeformen gjentas per sekund som normalt måles i hertz (Hz).
Følgende bilde er et 200 millisekund snapshot av en sawtooth bølgeform med en amplitude på 0,5 og en frekvens på 20 hertz:
For å gi deg en ide om hvordan frekvensen av en bølgeform direkte relaterer til hørens tonehøyde, ville en bølgeform med en frekvens på 440 hertz produsere samme tonehøyde som standard A4-notatet (midt A) på et moderne konsertpiano. Med den frekvensen i tankene, er vi i stand til å beregne hyppigheten til noen notater ved å bruke følgende kode:
f = Math.pow (2, n / 12) * 440,0;
De n
variabel i koden er antall notater fra A4 (midten A) til notatet vi er interessert i. For eksempel, for å finne frekvensen til A5, en oktav over A4, ville vi sette verdien av n
til 12
fordi A5 er 12 notater over A4. For å finne frekvensen til E2 ville vi sette verdien av n
til -5
fordi E2 er 5 notater under A4. Vi kan også gjøre omvendt og finne et notat (i forhold til A4) for en gitt frekvens:
n = Math.round (12.0 * Math.log (f / 440,0) * Math.LOG2E);
Årsaken til at disse beregningene fungerer, er at notatfrekvenser er logaritmiske - multiplisere en frekvens med to beveger et notat opp en enkelt oktav, mens deling av en frekvens med to beveger et notat ned en enkelt oktav.
I den digitale verden må lydbølger lagres som binære data, og den vanlige måten å gjøre det på er å ta periodiske snapshots (eller prøver) av en lydbølge. Antall bølgeprøver som tas for hvert sekund av lydens varighet er kjent som prøvefrekvens, så en lyd med en prøvefrekvens på 44100 vil inneholde 44100 bølgeprøver (per kanal) for hvert sekund av lydens varighet.
Følgende bilde viser hvordan en lydbølge kan samples:
De hvite blokkene i bildet representerer amplitudepoengene til bølgen som samples og lagres i et digitalt format. Du kan tenke på dette som en oppløsning av et bitmap-bilde: Jo flere piksler et punktmapbilde inneholder den mer visuelle informasjonen den kan holde, og mer informasjon resulterer i større filer (ignorer filkomprimering for nå). Det samme gjelder for digitale lyder: jo mer bølgeprøver en lydfil inneholder jo mer nøyaktig den rekonstruerte lydbølgen vil være.
I tillegg til å ha en samplingsfrekvens har digitale lyder også a bithastighet som måles i biter per sekund. Bithastigheten dikterer hvor mange binære biter som brukes til å lagre hver bølgeprøve. Dette ligner antall biter som brukes til å lagre ARGB-informasjon for hver piksel i et bitmap-bilde. For eksempel vil en lyd med en prøvefrekvens på 44100 og en bithastighet på 705600 lagre hver av sine bølgeprøver som en 16-biters verdi, og vi kan beregne det enkelt nok ved å bruke følgende kode:
bitsPerSample = bitRate / sampleRate;
Her er et fungerende eksempel ved å bruke verdiene nevnt ovenfor:
spor (705600/44100); // "16"
Å forstå hva lydprøver er, er det viktigste her; lydmotor som vi skal skape må generere og manipulere rå lydprøver.
En ting vi bør være oppmerksom på før vi begynner å programmere lydmotor er modulatorer, som er svært vanlige i både analoge og digitale synthesizers. En modulator er i hovedsak bare en standard bølgeform, men i stedet for å bli brukt til å produsere en lyd, blir de vanligvis brukt til å modulere en eller flere egenskaper av en hørbar bølgeform (for eksempel dens amplitude eller frekvens).
Ta vibrato, for eksempel. Vibrato er en vanlig pulserende endring av tonehøyde. For å produsere den effekten ved hjelp av en modulator kan du sette modulatorens bølgeform til en sinusbølge, og sett modulatorens frekvens til et sted rundt 8 hertz. Hvis du så hekta den modulatoren opp til frekvensen av en hørbar bølgeform, vil resultatet være en vibrato effekt - modulatoren vil jevnt øke og senke frekvensen (tonehøyde) av den hørbare bølgeformen åtte ganger per sekund.
Lydmotorene som vi skal lage vil tillate deg å legge modulatorer til lydene dine slik at du kan produsere et stort antall forskjellige effekter.
I neste veiledning vil vi opprette kjernekoden for lydmotoren og få alt i gang. Følg oss på Twitter, Facebook eller Google+ for å holde deg oppdatert med de siste innleggene.