Ajattelin tässä tehdä jonkinlaista ääntä c++:lla. Ohjelman pitäisi tehdä ääni itse (siis EI valmiilla äänitiedostoilla). Miten sen voi tehdä? Onko se mahdollista SDL:llä?
Eli koodin pitäisi mennä jotenkin näin:
y=sin(x);
MakeSound(y);
x++;
Ja kun tota toistettaisiin, tulisi sinaaltoon perustuva ääni.
Olisi myös kiva tietää, missä rajoissa luvun arvo voi olla (esim. 0-127) ja saako siinä olla desimaaleja. Ja jos sitä ei ole SDL:llä, ohjeet linkkerin säätöön SDL-linkkerin kanssa ym. olisivat tarpeellisia...
Paljon kysymyksiä...
Kyllä onnistuu. Tarvitset funktion, joka kirjoittaa ääniaallon annettuun puskuriin. Arvojen rajat riippuvat käytettävästä äänenlaadusta
#define TAAJUUS 440 // soitettavan äänen taajuus typedef signed short LAATU; // 16-bittiset kokonaisluvut #define MINIMI -32768 // -"- #define MAKSIMI 32767 // -"- #define RESOLUUTIO 22050 // digitaalisen äänen resoluutio void siniaalto(void *userdata, Uint8 *buf, int len) { static float vaihe=0.0; LAATU *arvot = (LAATU*)buf; for(int i=0; i<len/sizeof(LAATU); i++) arvot[i] = (LAATU)(std::sin(2.0*PI*frequency(vaihe+TAAJUUS*i/ (float)RESOLUUTIO) * (MAKSIMI-MINIMI) + MINIMI); vaihe = std::fmod(vaihe+TAAJUUS*len/sizeof(LAATU)/(float)resoluutio,2*PI); }
SDL_OpenAudio
-funktiolle annettaan parametrinä SDL_AudioSpec
-rakenne, johon syötetään ko. funktion osoite.
SDL_AudioSpec a_spec; a_spec.freq = RESOLUUTIO; a_spec.format = AUDIO_S16SYS; // 16-bittiset kokonaisluvut a_spec.channels = 1; // mono a_spec.samples = 1024; // äänipuskurin koko a_spec.callback = &siniaalto; a_spec.userdata = NULL; if (SDL_OpenAudio(&a_spec, NULL)<0) { /*...*/ } // virhe SDL_PauseAudio(0); // aloitetaan soitto // pääsilmukka tähän, ääni soi jatkuvasti... SDL_PauseAudio(1); // lopetetaan soitto
Ääniaalto kannattaa ehkä laskea etukäteen siten, ettei sinifunktiota tarvitse laskea soitettaessa.
Kääntäjä valittaa, ettei ole olemassa frequency-funktiota. Näytti yksi sulku jääneen auki, joten laitoin sen frequencyn perään. Ei toimi. Kääntäjä sanoo:
'sin((#'float_expr' not supported by dump_expr#<expression error>*6.29518...
Laitoinko sulun väärään kohtaan?
Ei niitä sulkuja kannata sinne arvailla... Tällainen muoto varmaankin toimii:
arvot[i] = (LAATU)(std::sin(2.0*PI*frequency(vaihe+TAAJUUS*i/ (float)RESOLUUTIO) * (MAKSIMI-MINIMI) + MINIMI));
Aiemmassa siniaaltofunktiossa on ainakin unohdettu, että sinin arvoalue on [-1, 1] eikä [0, 1], ja Heikkikään ei osannut kristallipallollaan kertoa, mikä tuo os:n frequency-funktio olisi ollut.
Tällainen olisi hieman yleiskäyttöisempi systeemi (vaikka optimointi sitten onkin tarpeen, jos noilla oikeasti meinaa musiikkia saada aikaan).
typedef double (*funktio_t)(double kohta); struct omainfo_t { funktio_t f; double vaihe; }; double siniaaltofunktio(double kohta) { return 0.5 * (1.0 + std::sin(2 * PI * kohta)); // Jotakin väliltä [0, 1]. } void tee_aalto(void *userdata, Uint8 *ibuf, int ilen) { omainfo_t * const info = (omainfo_t*) userdata; LAATU * const buf = (LAATU*) ibuf; const int len = ilen / sizeof(LAATU); int i; for (i = 0; i < len; ++i) { info->vaihe = info->vaihe + (TAAJUUS / (double)RESOLUUTIO); // - (int)info->vaihe; // jos tarvitsisi pitää se aina välillä [0, 1[ buf[i] = MINIMI + (LAATU)((MAKSIMI - (double)MINIMI) * info->f(info->vaihe)); } // Pidetään se välillä [0, 1[ (kun ei jo aiemmin pidetty) info->vaihe = info->vaihe - (int)info->vaihe; } // Muuten samat, mutta... a_spec.callback = tee_aalto; a_spec.userdata = new omainfo_t; ((omainfo_t *)a_spec.userdata)->vaihe = 0; ((omainfo_t *)a_spec.userdata)->f = siniaaltofunktio; // Tätä vaihtamalla saisi sitten erilaisia ääniä // Ja kun soitto on loppunut (joo, siis ihan lopuksi vasta) delete a_spec.userdata;
Toimii :D
Siellä on jotain outoa naksahtelua taustalla...Huomasin että se poistuu kun vähän laittaa ääntä pienemmälle. Eli ei johdu koodista.
Tuo naksahtelu saattaa johtua siitä että äänen alku ja loppupää eivät sovi yhteen, joka aiheuttaa suuren eron ääniaallossa joka sitten kuuluu naksahdellen...
En tiedä, mistä naksuminen voisi johtua, mutta yksi virhelähde voi olla sinifunktio itsessään. Vaikka y=sin(x) on määritelty kaikilla x:n arvoilla, numeerisessa toteutuksessa häviää y:n tarkkuudesta pari bittiä joka kerta kun x loittonee nollasta 2*PI verran. Parinkymmenen kierroksen jälkeen funktion paluuarvo alkaa jo olla vähän mitä sattuu. Ja ei, jakojäännöksen käyttö ei auta, vaan se on osa tätä ongelmaa.
Kannattaa laskea vain yksi tai muutama sykli ääniaaltoa ja kopioida sitä tarvittavan monta kertaa puskuriin, niin virhe voi pysyä paremmin aisoissa. Mutta kuten sanottu, en minä oikeasti tiedä, mikä siellä naksuu.
Eipä tuo tässä kuulunut minnekään naksuvan. Tuo, mitä koo selitteli syyksi, on minusta aivan perätöntä, tässä kuitenkin pelataan niin epämääräisillä tarkkuuksilla. Jakojäännöskin on välttämätön, koska muuten luvun kasvaessa suureksi pienet lisäykset eivät enää muuttaisi sitä ja aalto pysähtyisi.
Äänen loppuessa kyllä kuuluu toisinaan naksahdus, joka johtuu juuri tuosta aallon katkeamisesta, kuten T.M. totesi.
...
Kas, enpä tullut ajatelleeksi, että SDL osaa soittaa vain yhtä ääntä kerrallaan. Tuo periaatteessa laajennettavissa oleva systeemini on siis melko turha, kun vain yhtä voi käyttää. :)
No ei se mikään kovin paha juttu ole kun en varmaan siniaaltoa paljoa käytä... Esim. sawtooth-aalto muodostuu pelkistä naksahduksista ja käytän sitä enemmän kuin siniä.
Mutta se,että on vain yksi ääni...huhhuh. Saako kaksi eritaajuista aaltoa soimaan samaan aikaan lisäämällä ne toisiinsa vai tarvitsenko logaritmeja yms.?
No jonkinlaisilla laskelmilla se pitää tehdä (enpä ole perehtynyt, kokeile nyt alkuun sitä lisäystä vaikka), ellei ala käyttää kehittyneempiä kirjastoja (vaikkapa SDL_mixer, josta kertovat ainakin jotkin koodivinkit).
KoodiNoppa kirjoitti:
No ei se mikään kovin paha juttu ole kun en varmaan siniaaltoa paljoa käytä... Esim. sawtooth-aalto muodostuu pelkistä naksahduksista ja käytän sitä enemmän kuin siniä.
Mutta se,että on vain yksi ääni...huhhuh. Saako kaksi eritaajuista aaltoa soimaan samaan aikaan lisäämällä ne toisiinsa vai tarvitsenko logaritmeja yms.?
Laske ne vaan yhteen. Toki voit kokeilla myös kertoa aaltoja toisillaan, tulee hassuja efektejä. Kokeile myös FM-synteesiä ja kaikkea muuta kivaa. Tälleen yksinkertaistettuna FM-synteesi ois sin(taajuus1 * aika + modulaationmäärä * sin(taajuus2 * aika)) ja voi tehdä jotain monimutkaisempaakin, kuten sin(taajuus1 * aika + modulaationmäärä1 * sin(taajuus2 * aika + modulaationmäärä2 * sin(taajuus3 * aika))) jne. Eli heilutellaan sinin vaihetta (eli muuttuu myös taajuus) toisella siniaallolla ja saadaan kivoja ääniä.
Aiempien esimerkkien tyyliin se olisi tietenkin sin(info1->vaihe + modulaatio1*sin(info2->vaihe + modulaatio2*sin(info3->vaihe))). Kannattaa varmaankin mieluummin jo valmiiksi laskea tuo 2*PI vaiheeseen lisättävään lukuun, ettei joka samplelle tarvitse turhaan laskea sitä. Nopeuttaa ehkä vähän.
Itselläni muuten tulee joskus varsinkin korkeammilla äänillä pientä naksumista, jos samplerate on jotain muuta kuin 48000Hz. Luultavasti äänikorttini toimii aina 48kHz:lla, ja muut sampleratet sitten resamplaa vähän huonosti (kaikkea hassua ylimääräistä aiheuttaen) tuohon. Ei välttämättä nyt tässä, mutta kokeile vaihtaa RESOLUUTIOksi 48000.
Saha-aallot saattaa muuten helposti aliasoida korkeilla taajuuksilla tuoden mukaan ylimääräisiä taajuuksia, kun sampleraten puolikkaan yli menevistä tuleekin jotain muuta outoa... Mutta joo, yks tapa (jonka opin kun satuin vilkasemaan sopivalle irc-kanavalle sopivaan aikaan) on kertoa saha itsellään ja laittaa ääneksi erotus sen ja edellisen...ÄÄH EMMÄ OSAA SELITTÄÄ eikä sua varmaan ees kiinnosta, oonpas hiljaa, kirjotin taas vahingossa ihan liikaa turhaa asiaa yhteen viestiin, miks mä onnistun aina tekemään näin?
Pitkiä tekstejä on kiva lukea, varsinkin kun aihe kiinnostaa ;)
Toi FM-synteesi kuullostaa hyvältä. Täytyykin tässä joku päivä kokeilla... Nyt ei jaksa. Tulen luultavasti leikkimään sillä kauan ja hartaasti :D
Pakko oli topic avata uudelleen, koska uusi kysymys liittyy aiheeseen aika paljon...
Eli miten saa luettua ääniaaltoja? Esim. wav-tiedostosta. Eli se ääni pitäisi saada jonkinlaiseksi taulukoksi tms. ja sitten vaikkapa korottaa jokainen luku toiseen potenssiin ja soittaa järjestyksessä. Tai lisätä joku muu efekti.
Lukea voi ihan normaaleilla tiedostonlukufunktiolla fread. Formaatista on lisätietoa Wikipedian WAV-artikkelin lopussa mainituilla ulkopuolisilla sivustoilla, joillakin tarkemmin ja joillakin vähemmän. Jos jokin käyttämäsi ohjelma osaa tallentaa raakaa aaltodataa määrätyssä muodossa, se voi olla vielä helpompi vaihtoehto.
Tässä on vielä esimerkki itse funktioista:
TYYPPI muuttuja, taulu[LKM]; FILE *tiedosto = fopen("tiedosto", "rb"); fread(&muuttuja, sizeof(TYYPPI), 1, tiedosto); fread(taulu, sizeof(TYYPPI), LKM, tiedosto); fclose(tiedosto);
SDL:llä soittaminen onnistuu sitten tuolla samanlaisella callback-systeemillä, mutta nyt vain pitää pitää muistissa, missä kohti ääntä ollaan menossa, ja aina funktiossa kopioida siitä eteenpäin pyydetyn verran omasta taulusta SDL:n puskuriin.
Aihe on jo aika vanha, joten et voi enää vastata siihen.