How to write WAV or AIFF data from binary stream

In response to an earlier question of mine (“How do I play a calculated waveform, say, a sinusoid?”), Will Shank wrote, among other things:

“But if writing out and opening a file is satisfactory… AIFF and WAV have simple, uncompressed formats which are easy to write directly from Xojo with a BinaryStream.”

At present I am able to write my waveform as a binary stream, but I have been unable to find out how to convert such a stream to eitherWav or AIFF within Xojo.

Can anybody help?

Strange

Can you define what you mean with “stream” ?

• MemoryBlock ?
• chunks, yes, but were are they ?

A simple search returns me this Audio Interchange File Format page.

If you create your “sound” in a MemoryBlock, you can use its instruction set with BinaryStream to save the different chunks into a file who will set its type to AIFF (later, when done).

BinaryStream have read and write Methods to… read and write data.

Since you do not say the actual sound format in memory, it is not possible to give more precise way to save them.

A stream is just a bunch of bytes in an organized format. Using a Method from the BinaryStream Class allows you to save them sequencially (or not).

I did give you the WAVE format (which is easier than AIFF, and since most/all computers are INTEL, WAVE is more native), simply follow those instructions and you’ll be successful. Don’t hesitate to ask questions along the way if something stumps you.

Dug up this old class I’d made, basically what Garth said except not all of it. It’s clumsy to use and inefficient (audio data duplicated into second MemoryBlock) but has the basis for header writing.

It should work for mono or stereo, 8 or 16 bit formats. Never tried it with other bit sizes but know it doesn’t handle the 24bit mono case Garth mentioned. Actually that case could happen with 8bit mono too but I never noticed a problem. ymmv

[code]Class WaveSound

Constant Private kHeaderTagRIFF = &h46464952
Constant Private kHeaderTagWAVE = &h45564157
Constant Private kHeaderTagfmt = &h20746D66
Constant Private kHeaderTagdata = &h61746164

Property Private bitsPerSample As Integer
Property Private sampleHz As Integer
Property Private channelCount As Integer

Sub Constructor(channels As integer, sampleBitSize As integer, sampleHertz As integer)
channelCount = channels
bitsPerSample = sampleBitSize
sampleHz = sampleHertz
End Sub

Function createWaveFileData(waveData As MemoryBlock) As MemoryBlock

  dim m As new MemoryBlock(44 + waveData.Size)  //header is 44 bytes
  
  m.LittleEndian = true
  
  m.UInt32Value(0) = kHeaderTagRIFF
  m.Int32Value(4) = m.Size - 8  //total file size minus this
  m.UInt32Value(8) = kHeaderTagWAVE
  
  m.UInt32Value(12) = kHeaderTagfmt
  m.UInt32Value(16) = 16  //fmt chunck size
  m.UInt16Value(20) = 1  //PCM
  m.UInt16Value(22) = channelCount
  m.UInt32Value(24) = sampleHz
  m.UInt32Value(28) = SampleHz * channelCount * bitsPerSample / 8  //ByteRate
  m.UInt16Value(32) = channelCount * bitsPerSample / 8  //block align
  m.UInt16Value(34) = bitsPerSample
  
  m.UInt32Value(36) = kHeaderTagdata
  m.UInt32Value(40) = waveData.Size
  m.StringValue(44, waveData.Size) = waveData //copy in samples
  
  return m

End Function

End Class[/code]

example usage writing a file

[code]Sub Action()

//make audio data in a format
dim audioData As MemoryBlock = generateMono16Bit44100hz

//make WaveSound object for same format
dim wav As new WaveSound(1, 16, 44100)

//make file data with audio data
dim fileData As MemoryBlock = wav.createWaveFileData(audioData)

//write file data to file on desktop
dim f As FolderItem = SpecialFolder.Desktop.Child(“500hz.wav”)
dim bs As BinaryStream = BinaryStream.Create(f, true)
bs.Write(fileData)
bs.Close

End Sub

Protected Function generateMono16Bit44100hz() As MemoryBlock

dim sampleRate As integer = 44100
dim sampleCount As integer = sampleRate * 4 //4 seconds worth
dim lastSampleIdx As integer = sampleCount - 1
dim tau As double = 6.283185307179586
dim freqScale As double = (500 / sampleRate) * tau //500hz

dim m As new MemoryBlock(sampleCount * 2) //2 bytes per sample
dim p As Ptr = m

for i As integer = 0 to lastSampleIdx
p.Int16(i*2) = 20000 * sin(i * freqScale)
next

return m

End Function[/code]

here’s some links about the format that helped
http://soundfile.sapp.org/doc/WaveFormat/
http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
http://www.topherlee.com/software/pcm-tut-wavformat.html

Garth and Will,

Thank you very much for your help. It works!

Gratefully from an 87-year-old newbie,

Strange

Strange:

Respect. I’m in my 60s, so I’m a youngster ;-:slight_smile: