Gebruikersavatar
Flisk
Artikelen: 0
Berichten: 1.264
Lid geworden op: vr 02 mar 2012, 14:21

WAV bestanden Java

Ik ben bezig met het maken van geluid in Java. Het WAV formaat werkt het makkelijkste en met 8bit mono (44.1kHz) geluid gaat alles goed en krijg ik heel zuiver klinkende golven. Nu probeer ik mijn code om te schrijven naar 16bit stereo(44.1kHz) maar ik krijg geen zuiver geluid. Bij een square golf krijg ik gewoon een stil bestand en bij een sinus golf klinkt het heel irritant (de frequentie klopt wel ongeveer). Hier een stukje van mijn code waarin het probleem waarschijnlijk zit:

Code: Selecteer alles

public static void main(String[] args) {

        WaveForm.loadAllWaveforms();
        String name="sine";
        WAV.createFile(sineWave(100), "C:\\wave\\"+name+".wav");

    }

    public static byte[] sineWave(int f){

        byte [] data=new byte[(int)(176400)];
        double pos=0;
        double freq=f;

        //left channel
        for(int i=0;i<data.length;i+=4){
            byte[] ba=intToByteArray(WaveForm.sine[(int)pos%131072]);
            data[i]=ba[0];
            data[i+1]=ba[1];
            pos+=freq*(double)131072/(double)44100;
        }

        //right channel
        for(int i=2;i<data.length;i+=4){
            byte[] ba=intToByteArray(WaveForm.sine[(int)pos%131072]);
            data[i]=ba[0];
            data[i+1]=ba[1];
            pos+=freq*(double)131072/(double)44100;
        }
        return data;

    }

Uitleg bij de code:

In de WaveForm klasse zitten tabellen van vaak gebruikte golven, in elke tabel zitten er 131072 gehele getallen tussen 0 en 2^16-1. Deze heb ik allemaal eens geprint en dat zag er goed uit (vergeleken met de 8bit tabellen). De methode createFile van de WAV klasse neemt een stuk data, plakt de juiste header ervoor en slaat het bestand daarna op. De methode intToByteArray returned de binaire voorstelling van de input in een koppel bytes, little-endian gerangschikt. Die methode wordt ook gebruikt tijdens het maken van de header en klopt dus sowieso.

Het probleem moet dus in de twee for loops zitten denk ik. Iemand een idee waar het foutloopt?
Je leest maar niet verder want je, je voelt het begin van wanhoop.
Gebruikersavatar
Xenion
Artikelen: 0
Berichten: 2.609
Lid geworden op: za 21 jun 2008, 10:41

Re: WAV bestanden Java

Kijk eens met een extern programma (MATLAB, Audacity, ...) naar de gegenereerde wav file. Bekijk de twee kanalen individueel en kijk of die eruit zien zoals jij bedoeld had.
 
Verder hebben we niet echt genoeg informatie om hier het specifieke probleem te vinden.

Code: Selecteer alles

WaveForm.loadAllWaveforms();
String name="sine";
WAV.createFile(sineWave(100), "C:\\wave\\"+name+".wav");
Hier vraag je om een byte-array naar een wav-file te schrijven, maar ik zie nergens de specificatie voor stereo 16bit. Het zou goed kunnen, dat je library die byte-array heeft geïnterpreteerd als een mono 8bit en dat zou wel eens funky kunnen klinken.
 
Verder zou ik ook het interleaven van de twee kanalen iets properder aanpakken. Zorg dat je de twee kanalen als aparte lijsten hebt en schrijf dan een functie om ze te interleaven en de-interleaven.
Gebruikersavatar
Flisk
Artikelen: 0
Berichten: 1.264
Lid geworden op: vr 02 mar 2012, 14:21

Re: WAV bestanden Java

Xenion schreef:Hier vraag je om een byte-array naar een wav-file te schrijven, maar ik zie nergens de specificatie voor stereo 16bit. Het zou goed kunnen, dat je library die byte-array heeft geïnterpreteerd als een mono 8bit en dat zou wel eens funky kunnen klinken.
Hieronder de code waarmee ik het WAV bestand maak (verborgen inhoud). Ik heb de header aangepast naar 16bit dus daar lijkt het probleem niet te zitten
Spoiler: [+]

Code: Selecteer alles

public static void createFile(byte[] data, String filepath){

        byte[] total = new byte[data.length+44];
        byte[] header =wavHeader(data.length);

        for(int i=0;i<44;i++){
            total[i]=header[i];
        }
        for(int i=0;i<data.length;i++){

            total[i+44]=data[i];
        }

        try{
            FileOutputStream file = new FileOutputStream(filepath);
            file.write(total);
            file.close();
        }catch(IOException e){
            System.out.println("Failed to create audio file");
            System.out.println("Check if the fail isn't opened elsewhere.");
        }
    }

    public static byte[] wavHeader(long datasize){          //Alle getallen zijn little-endian, woorden big-endian.
        byte[] chunkSize=longToByteArray(datasize+36);      //De methodes long-int ToByteArray returnen dus little-endian arrays.
        byte[] subchunk1Size=longToByteArray(16);           //De bits in de bytes zelf zijn big-endian.
        byte[] sampleRate=longToByteArray(44100);           //
        byte[] byteRate=longToByteArray(176400);            //
        byte[] subchunk2Size=longToByteArray(datasize);     //
        byte[] formatType=intToByteArray(1);                //1=pcm,6=mulaw,7=alaw
        byte[] numChannels=intToByteArray(2);               //
        byte[] blockAlign=intToByteArray(4);                //(bitsPerSample*numChannels)/8
        byte[] bitsPerSample=intToByteArray(16);
        byte[] header= new byte[44];

        header[0]='R';
        header[1]='I';
        header[2]='F';
        header[3]='F';
        header[4]=chunkSize[0];
        header[5]=chunkSize[1];
        header[6]=chunkSize[2];
        header[7]=chunkSize[3];
        header[8]='W';
        header[9]='A';
        header[10]='V';
        header[11]='E';
        header[12]='f';
        header[13]='m';
        header[14]='t';
        header[15]=' ';
        header[16]=subchunk1Size[0];
        header[17]=subchunk1Size[1];
        header[18]=subchunk1Size[2];
        header[19]=subchunk1Size[3];
        header[20]=formatType[0];
        header[21]=formatType[1];
        header[22]=numChannels[0];
        header[23]=numChannels[1];
        header[24]=sampleRate[0];
        header[25]=sampleRate[1];
        header[26]=sampleRate[2];
        header[27]=sampleRate[3];
        header[28]=byteRate[0];
        header[29]=byteRate[1];
        header[30]=byteRate[2];
        header[31]=byteRate[3];
        header[32]=blockAlign[0];
        header[33]=blockAlign[1];
        header[34]=bitsPerSample[0];
        header[35]=bitsPerSample[1];
        header[36]='d';
        header[37]='a';
        header[38]='t';
        header[39]='a';
        header[40]=subchunk2Size[0];
        header[41]=subchunk2Size[1];
        header[42]=subchunk2Size[2];
        header[43]=subchunk2Size[3];
        return header;
    }
 
Xenion schreef:Kijk eens met een extern programma (MATLAB, Audacity, ...) naar de gegenereerde wav file.
Goeie tip! Met behulp van Audacity krijg ik volgende golf:
sinus16bit
sinus16bit 874 keer bekeken
Twee kanalen, frequentie is goed maar de golf duidelijk verkeerd, is de data van 16 bit wav signed? Want nu heb ik het unsigned geprogrammeerd, daar zit de fout misschien.

Wel raar dat de 8bit golf dan, op dezelfde wijze gemaakt, wél mooi klopt:
sine8bit
sine8bit 874 keer bekeken
Of ligt het ergens anders aan?

EDIT:

Verder zou ik ook het interleaven van de twee kanalen iets properder aanpakken. Zorg dat je de twee kanalen als aparte lijsten hebt en schrijf dan een functie om ze te interleaven en de-interleaven.
Zou er inderdaad beter uitzien maar gaat de code dan niet twee keer zo traag gaan (twee keer zoveel assignaties)? Ik wil het ook relatief snel laten lopen op mijn kleine laptop en die is wat traag.
Je leest maar niet verder want je, je voelt het begin van wanhoop.
Gebruikersavatar
Flisk
Artikelen: 0
Berichten: 1.264
Lid geworden op: vr 02 mar 2012, 14:21

Re: WAV bestanden Java

Kleine update:
wikipedia schreef:...There are some inconsistencies in the WAV format: for example, 8-bit data is unsigned while 16-bit data is signed,...
Het lag dus daaraan, wavetabels even omgeschreven naar signed en nu krijg ik weer zuiver geluid.
Je leest maar niet verder want je, je voelt het begin van wanhoop.
Gebruikersavatar
Xenion
Artikelen: 0
Berichten: 2.609
Lid geworden op: za 21 jun 2008, 10:41

Re: WAV bestanden Java

Haha dat had ik ook over het hoofd gezien. In het algemeen is dit de beste werkwijze wanneer je een gestandaardiseerde bitstream genereert: kijk of een bekend programma waarvan je weet dat het werkt het kan openen en check hoe de content erin weergegeven wordt.

Betreft dat interleaven hangt het ervan af wat je precies wil bereiken. Op dit moment is je code ook niet geweldig efficiënt omdat je twee for loops hebt terwijl het evengoed met eentje zou kunnen. Je kan in 1 loop beide kanalen genereren en ineens wegschrijven naar de juiste plaats in de array. Ik lijkt me dan iets cleaner om het maken en interleaven in aparte functies te zetten omdat je die dan makkelijker kan hergebruiken.
willyb
Artikelen: 0
Berichten: 30
Lid geworden op: vr 12 aug 2005, 00:02

Re: WAV bestanden Java

Een klein beetje offtopic, maar wist je dat je hier een behoorlijk beveiligingslek creëert in je applicatie?
Zoek maar eens op Path Traversal bij OWASP.

Code: Selecteer alles



        WAV.createFile(sineWave(100), "C:\\wave\\"+name+".wav");

Gebruikersavatar
Flisk
Artikelen: 0
Berichten: 1.264
Lid geworden op: vr 02 mar 2012, 14:21

Re: WAV bestanden Java

Kan geen kwaad denk ik, ik heb het geschreven voor persoonlijk gebruik, het is niet voor een web applicatie oid.

De voorbije maanden is er heel wat code bijgekomen en momenteel heb ik het geheel in een GUI gegoten (runnable jar), de gebruiker kan trouwens zelf nergens die name variabele kiezen, die wordt in de source code bepaald. Of zie ik iets over het hoofd?
Naamloos
Naamloos 874 keer bekeken
Je leest maar niet verder want je, je voelt het begin van wanhoop.

Terug naar “Informatica en programmeren”