Saving WAV files

Blitz3D Forums/Blitz3D Programming/Saving WAV files

Axel Wheeler(Posted 2015) [#1]
Hi.

This site:
http://www.topherlee.com/software/pcm-tut-wavformat.html

shows the format for a WAV file, which I'm trying to create.

However, I'm not sure how to save certain aspects of the header. Am I right that text portions like "RIFF" can be saved as:

WriteString(myFile, "RIFF")


Also, how would a number like 44100 be saved in four bytes, and how would numbers like 2 be save in 2 bytes? Should each byte be saved separately with WriteByte()? Would I then need to convert to hex first?

Thanks!


Zethrax(Posted 2015) [#2]
WriteString would add a 4 byte length header to the value being written, which would likely bork the file format.

For this sort of thing you need to use banks ( http://www.blitzbasic.com/b3ddocs/command_list_2d_cat.php?show=Bank ) so that you have precise control of the data format. You can save a bank to file using WriteBytes ( http://www.blitzbasic.com/b3ddocs/command.php?name=WriteBytes&ref=2d_cat ) and read it in using ReadBytes ( http://www.blitzbasic.com/b3ddocs/command.php?name=ReadBytes&ref=2d_cat ).


Matty(Posted 2015) [#3]
Something to consider is that even if you get the format right you may need to also know whether the file's bytes are stored in little or big endian format. Most of the time it will be fine....but every now and then....


Axel Wheeler(Posted 2015) [#4]
I'm examining the tada.wav file in Windows\Media to see how it works as well. I've used banks before so I'll try them again as well. Thanks.


Axel Wheeler(Posted 2015) [#5]
Here's the first portion of tada.wav (interestingly it is identical to the one in the link in the OP):

5249 4646 245a 0400 5741 5645 666d 7420
1000 0000 0100 0200 44ac 0000 10b1 0200
0400 1000 6461 7461 005a 0400

Here it is broken down:
5249 4646 - "RIFF"
245a 0400 - Size of file
5741 5645 - "WAVE"
666d 7420 - "fmt "
1000 0000 - 16
0100 - 1
0200 - 2
44ac 0000 - 44100
10b1 0200 - 176400
0400 - 4
1000 - 16
6461 7461 - "data"
005a 0400 - Size of data portion of file


So I would guess that PokeInt would work for the four byte ints, and PokeShort would work for the two byte ints. The strings would require, perhaps, four PokeBytes with the ascii values for "R", "I", "F", "F". Does that make sense?


Matty(Posted 2015) [#6]
Yep.


Axel Wheeler(Posted 2015) [#7]
FYI it works. The code is below, if anyone wants to save sound files, this will give you some idea of the process. You need a sound type with an array of frequency values, of course.

I'm working on a random sound generator. There are lots of methods to explore in terms of coming up with frequencies to include, for example creating frequency patterns that are variations of musical instruments or other sounds. Then the spectrum has to be transformed to a waveform and output to disk. So far I've just created simple notes and chords, but now I can extend it.

This function gives me the ability to save the file.

Note that the sound data doesn't go from 0 to 255 as I thought, but instead wraps around from 0, so negative numbers appear below 256, so to speak.

Anyway, thanks all for the help!

Function SaveWav(name$,sound.Sound)
	Local i,j,index,totalAmps
	Local result
	
	Local fileName$="C:\"+name$
	
	Local riff4$="RIFF"
	Local fileSize4=44+SAMPLERATE*4;sound\length*4
	Local wave4$="WAVE"
	Local fmt4$="fmt "
	Local formatData4=16
	Local formatType2=1
	Local numChannels2=2
	Local sampleRate4=44100
	Local sbcd4=176400
	Local bitsPerSampleTimesChannels2=4
	Local bitsPerSample2=16
	Local data4$="data"
	Local soundSize4=sound\length*4
	
	Local b=CreateBank(fileSize4)
	PokeByte(b,0,Asc("R"))
	PokeByte(b,1,Asc("I"))
	PokeByte(b,2,Asc("F"))
	PokeByte(b,3,Asc("F"))
	PokeInt(b,4,fileSize4)
	PokeByte(b,8,Asc("W"))
	PokeByte(b,9,Asc("A"))
	PokeByte(b,10,Asc("V"))
	PokeByte(b,11,Asc("E"))
	PokeByte(b,12,Asc("f"))
	PokeByte(b,13,Asc("m"))
	PokeByte(b,14,Asc("t"))
	PokeByte(b,15,Asc(" "))
	PokeInt(b,16,formatData4) ; 16
	PokeShort(b,20,formatType2) ; 1
	PokeShort(b,22,numChannels2) ; 2
	PokeInt(b,24,sampleRate4) ; 44100
	PokeInt(b,28,sbcd4) ; 176400
	PokeShort(b,32,bitsPerSampleTimesChannels2) ; 4
	PokeShort(b,34,bitsPerSample2) ; 16
	PokeByte(b,36,Asc("d"))
	PokeByte(b,37,Asc("a"))
	PokeByte(b,38,Asc("t"))
	PokeByte(b,39,Asc("a"))
	PokeInt(b,40,soundSize4)
	
	; Sound Data
	Local amp ; We have to convert from 128 at the center of the curve to 0 (with negative values marked as less then 256 instead of 0)
	For i=0 To sound\sampleRate-1
		amp=sound\waveformFreq[i]-128
		If amp<0 Then amp=amp+256
		PokeShort(b,43+i*4,amp);sound\waveformFreq[i])
		PokeShort(b,43+i*4+2,amp);sound\waveformFreq[i])
	Next
	
	; Save the file
	Local myFile$=WriteFile(fileName$)
	result=WriteBytes(b,myFile,0,fileSize4)
	CloseFile(myFile)
	Print fileName+" saved."
	Return result
End Function



jfk EO-11110(Posted 2015) [#8]
I could be wrong, but didn't you set it to 16 bit in the header? Values -128 to 127 are 8 bit. So maybe you should instead use values of -($ffff/2) to ($ffff/2). There may also be one parameter in the header that defines whether the data is signed or unsigned integer.


Axel Wheeler(Posted 2015) [#9]
jfk & Matty: (Regarding 16 bit vs 8 bit and big vs little endianness)

I got the sound working fine at 8 bit, but then I read jfk's comment and tried putting it into 16 bit format and found it was hard to get it to work. I was using PokeByte instead of PokeShort so I could have control over which byte went where, but the results were random or just weird. I finally realized that my offset is incorrect in the original program above. Note the offset 43; it should be 44 if the previous PokeInt was 4 bytes starting at at byte 40. So the 8 bit value poked as a PokeShort, but offset by one byte, seemed to work fine! Anyway I found it amusing.

Now I'm using the code above, but corrected to 44 offset, and using 16 bit sound and it works. Thanks so much guys. Hopefully I'll have a random sound effect generator to post soon.