create, write Wave file?

canalrun

Well-Known Member
Licensed User
Longtime User
Hello,
I need to create a Wave and then write it to a file to be read back and played with the media player.

A Wave has a very specific format consisting of a Header with certain data sizes and positions followed by an array of data which represent samples.

In C I would do this using a struct. This guarantees the data size and relative position of each field.

Can I do the same thing in B4A? Below I show my attempt by using a type, then initializing the fields, and finally writing it to a file using streams.

Is this the correct way to do this? I get a compile error because my stream WriteBytes first parameter is not an array of bytes.

Does using a "Type" work like a struct? That is, are the data sizes what you would expect and is the data positioned contiguously - like it would be in a C struct?

How do I cast or reference WaveData in the stream.WriteBytes line of code so that it thinks my WaveData is an array of bytes?

Can this be done in Basic? Do I need to take a different approach?

Thanks,
Barry.

B4X:
Sub Globals
...  
  Type TWaveHeader _
        (ChunkID(4) As Char, _
        ChunkSize As Int, _
        Format(4) As Char, _
        Subchunk1ID(4) As Char, _
        Subchunk1Size As Int, _
        AudioFormat As Short, _
        NumChannels As Short, _
        SampleRate As Int, _
        ByteRate As Int, _
        BlockAlign As Short, _
        BitsPerSample As Short, _
        Subchunk2ID(4) As Char, _
        Subchunk2Size As Int, _
   Data(75000) As Short)
  
  Dim WaveData As TWaveHeader 
End Sub


B4X:
Sub Activity_Create(FirstTime As Boolean)
  Dim OutS As OutputStream
  
  WaveData.Initialize
  
  WaveData.ChunkID(0) = "R"
  WaveData.ChunkID(1) = "I"
  WaveData.ChunkID(2) = "F"
  WaveData.ChunkID(3) = "F"

  WaveData.Format(0) = "W"
  WaveData.Format(1) = "A"
  WaveData.Format(2) = "V"
  WaveData.Format(3) = "E"
  
  WaveData.Subchunk1ID(0) = "f"
  WaveData.Subchunk1ID(1) = "m"
  WaveData.Subchunk1ID(2) = "t"
  WaveData.Subchunk1ID(3) = " "
  
  WaveData.Subchunk1Size = 16
  WaveData.AudioFormat = 1
  WaveData.NumChannels = 1
  WaveData.SampleRate = 44100
  WaveData.BitsPerSample = 16
  WaveData.ByteRate = WaveData.SampleRate * WaveData.NumChannels * WaveData.BitsPerSample / 2
  WaveData.BlockAlign = WaveData.NumChannels * WaveData.BitsPerSample / 2
  
  WaveData.Subchunk2ID(0) = "d"
  WaveData.Subchunk2ID(1) = "a"
  WaveData.Subchunk2ID(2) = "t"
  WaveData.Subchunk2ID(3) = "a"
  
  WaveData.Subchunk2Size = WaveData.Data.Length * 2
  
  WaveData.ChunkSize = 36 + WaveData.Subchunk2Size
  
  OutS = File.OpenOutput(File.DirDefaultExternal, "data.wav", False)
  OutS.WriteBytes(Array As Byte(WaveData), 0, WaveData.ChunkSize + 8)
 

canalrun

Well-Known Member
Licensed User
Longtime User
You should use RandomAccessFile library. It will give you more control.

You cannot convert a Type to a bytes array this way. You will need to write the fields with RandomAccessFile methods.

Thanks,
RandomAccessFile looks good - WriteShort, WriteInt ...

WriteObject looks intriguing. Although, I am guessing that probably writes all the object fields in addition to any data.

Barry.
 
Upvote 0

canalrun

Well-Known Member
Licensed User
Longtime User
WriteObject will add additional metadata. It is useful for persisting data, not to implement a specific protocol.

Yes, thanks. I have been playing around and saw that.

RandomAccessFile is very useful. I like the initialize2 and initialize3 methods.

I am using the ByteConverter also to convert my data Array of Shorts to bytes, then adding it to the file.

I have written a test file, transferred it to a PC, then looked at the file with a hex editor, and it looks good.

I can post the code I came up with, if you want to see it. Otherwise, again, thanks much for your help.

Barry.
 
Upvote 0

canalrun

Well-Known Member
Licensed User
Longtime User
Thanks,
Jerry

Here is what I came up with. I have removed code that does not pertain to this task and replaced it with "...".

I checked that this code produces a valid wave by copying the file produced from Android to my PC then plotting the wave file data and spectrum in AudACity.

B4X:
Sub Globals

...

  Type TWaveHeader _
        (ChunkID(4) As Byte, _
        ChunkSize As Int, _
        Format(4) As Byte, _
        Subchunk1ID(4) As Byte, _
        Subchunk1Size As Int, _
        AudioFormat As Short, _
        NumChannels As Short, _
        SampleRate As Int, _
        ByteRate As Int, _
        BlockAlign As Short, _
        BitsPerSample As Short, _
        Subchunk2ID(4) As Byte, _
        Subchunk2Size As Int)
  
  Dim WavHead As TWaveHeader 
  Dim NumSamples As Int: NumSamples = 1000
  Dim WavData(2*NumSamples) As Short
End Sub

B4X:
Sub Activity_Create(FirstTime As Boolean)
  Dim l1 As Long
  Dim Buf(44+4*NumSamples), Buf2(4*NumSamples) As Byte
  Dim bc As ByteConverter
  Dim RAB, RAF As RandomAccessFile
  
...

  WavHead.Initialize
  bc.LittleEndian = True
  RAB.Initialize3(Buf, True)
  
  WavHead.ChunkID = "RIFF".GetBytes("UTF8")
  WavHead.Format = "WAVE".GetBytes("UTF8")
  WavHead.Subchunk1ID = "fmt ".GetBytes("UTF8")
  WavHead.Subchunk2ID = "data".GetBytes("UTF8")
  
  WavHead.Subchunk1Size = 16
  WavHead.AudioFormat = 1
  WavHead.NumChannels = 2
  WavHead.SampleRate = 44100
  WavHead.BitsPerSample = 16
  WavHead.ByteRate = WavHead.SampleRate * WavHead.NumChannels * WavHead.BitsPerSample / 8
  WavHead.BlockAlign = WavHead.NumChannels * WavHead.BitsPerSample / 8
  WavHead.Subchunk2Size = NumSamples * WavHead.NumChannels * WavHead.BitsPerSample / 8
  WavHead.ChunkSize = 36 + WavHead.Subchunk2Size
  
  l1 = GenSin(WavData, 0, 32000, 5000, NumSamples, 44100)
  Buf2 = bc.ShortsToBytes(WavData)
  
  RAB.WriteBytes(WavHead.ChunkID, 0, 4, 0)
  RAB.WriteInt(WavHead.Subchunk1Size, 4)
  RAB.WriteBytes(WavHead.Format, 0, 4, 8)
  RAB.WriteBytes(WavHead.Subchunk1ID, 0, 4, 12)
  RAB.WriteInt(WavHead.Subchunk1Size, 16)
  RAB.WriteShort(WavHead.AudioFormat, 20)
  RAB.WriteShort(WavHead.NumChannels, 22)
  RAB.WriteInt(WavHead.SampleRate, 24)
  RAB.WriteInt(WavHead.ByteRate, 28)
  RAB.WriteShort(WavHead.BlockAlign, 32)
  RAB.WriteShort(WavHead.BitsPerSample, 34)
  RAB.WriteBytes(WavHead.Subchunk2ID, 0, 4, 36)
  RAB.WriteInt(WavHead.Subchunk2Size, 40)
  
  RAB.WriteBytes(Buf2, 0, 2*l1, 44)  
  
  RAF.Initialize2(File.DirDefaultExternal, "data.wav", False, True)
  RAF.WriteBytes(buf, 0, 44+2*l1, 0)
 
...

End Sub

B4X:
' GenSin = (2 * pi * t * freq) / samplerate [return sample count]
' buf - sample buffer
' ch -  channel 0-left, 1-right
' v -   volume (magnitude multiplier)
' fr -  frequency
' ns -  number of samples max
' sr -  sample rate [clock time = ( 1.0 / (ns/ sr) )]

Sub GenSin(buf() As Short, ch As Int, v As Float, fr As Float, ns As Int, sr As Int) As Int
  Dim i, j As Int
  Dim g, h, tp, fsr As Float
  
  tp = 2 * cPI
  fsr = sr
  
  For i=0 To ns-1
    j = 2 * i + ch
   g = (i * tp * fr) / fsr
   h = v * Sin(g)
   buf(j) = h
  Next
  
  Return(i)
End Sub
 
Upvote 0

LA3QMA

Member
Licensed User
Longtime User
nice

is it possible to make this as a library?

Or is it just as easy to include it as a code module?

Is it also possible to modify this code so that you can create two frequencies on top of each other? as in DTMF etc ?
 
Upvote 0

canalrun

Well-Known Member
Licensed User
Longtime User
is it possible to make this as a library?

Or is it just as easy to include it as a code module?

Is it also possible to modify this code so that you can create two frequencies on top of each other? as in DTMF etc ?

I think this code is probably better to use as an example, rather than being generic enough to make into a library.

To do multiple frequencies, you can just add two waveforms together (add the samples together at each index position). The thing you have to be careful about is exceeding the range of the Short when adding together both waveforms. In that case, it would probably be better to generate and add the waveforms using Doubles, then normalize and convert to Shorts in the final step.

After further testing, I find that Windows likes my wav file, but Android doesn't like it. I get a "prepare exception 0x01" when I try to play the wav on Android using the media player.

There is no further information about what is causing the error. But, Googling this error, I see it is fairly common. If anyone can shed some light on this error, I would appreciate it very much.

Thanks,
Barry.
 
Upvote 0
Cookies are required to use this site. You must accept them to continue using the site. Learn more…