Lesson 11. Wave Audio


Lesson 10 : Advance Audio Buffer | Lesson 12 : Audio Map | Contents


To work with Wave Files, we need the DvmWave as well as the DvmBasic package. If you are using Tcl shell instead of Dvm Shell, you need to load these packages by :

cd C:/Dali/doc/tutorial/
package require DvmBasic
package require DvmWave

Then we're going to do the following things:

  1. Read the audio data stored in a 16-bit stereo Wave file into audio buffer.
  2. Play various parts of the wave audio.
  3. Write the left channel of the original audio to a Wave file.

We will work with the file testsnd.wav. It is a 16-bit stereo Wave file in PCM format. You could substitute it with any other 16-bit stereo Wave file in PCM format (although it is likely to work for most other formats too). Just change the filename in red below to the one that you are using. But make sure the file is situated in the current directory of Tcl shell or Dvm Shell.

First we open the Wave file and read the whole file into a bitstream bs.

set size [file size testsnd.wav]
set bs [bitstream_new $size]
set bp [bitparser_new]  
bitparser_wrap $bp $bs
set infile [open testsnd.wav r]
fconfigure $infile -translation binary -buffersize 65536
bitstream_channel_read $bs $infile 0
close $infile

Now the bitstream bs contains the whole Wave file and is referenced by bitparser bp. Every Windows Wave file comprises header information before the actual audio data. These information are abstracted as Wave Header in Dali. So before reading the actual audio data, we need to parse the header information into the Wave Header first.

set hdr [wave_hdr_new]
set len [wave_hdr_parse $bp $hdr]

Note that we pass in the BitParser to the command wave_hdr_parse instead of the BitStream. wave_hdr_parse return the number of bytes read from the BitParser. (This is the case for many primitives that uses the BitStream.)

Now we can retrive the information stored in the Wave Header. The following information are in the Wave Header. For the commands to inquire these fields, see the WAV package specification:

The wave file we are working with is in raw PCM format. We would like to check if it is the case. We can do this by getting the format field from the Wave Header. The format code for raw PCM is 1.

if {[wave_hdr_get_format $hdr] != 1} {
    puts "input file is not in pcm format."
}

We can compute the total number of samples of both channels in the wave file by the formula (Note: if there are 10 samples in the left channel, 10 samples in the right channel, total number of samples of both channels is 20) :

numOfSamples = numOfBytes / (bitsPerSample / 8)

set bytes_per_sample [expr [wave_hdr_get_bits_per_sample $hdr] / 8]
set nbytes [wave_hdr_get_data_len $hdr]
set nsamples [expr $nbytes / $bytes_per_sample]

Now we can cast the read bitstream into 16-bit audio buffer. Generally we need to check the bits_per_sample field to find out whether to use bitstream_cast_to_audio_8 or bs_cast_to_audio_16. Here we assume it's 16-bit audio.

set audio [bitstream_cast_to_audio_16 $bs [expr $len/$bytes_per_sample] $nsamples]

We have casted the audio data portion of the bitstream into an audio buffer. It is particularly important to note that audio is just a virtual buffer sharing the same location as bitstream bs. That means you should not free bs when you still need to use audio.

We are done with the reading of Wave file. What we have now is a Wave Header containing all the neccessary information of the audio data, and a 16-bit audio buffer containing the audio data stored in the Wave file.

We are now going to play the audio data to the speaker. First we open the wave output device. The Wave Header is needed as an argument because the wave output device need to know the type of the audio data.

wave_out_open $hdr

Before we can actually play the audio buffer in audio, we need to prepare it.

wave_audio_prep_play $audio
wave_audio_play $nbytes

Hear it? That's what you need to do to play a wave file in Dali. Note that the concept of wave_audio commands is different from that of the audio buffers. It takes argument in number of bytes, but not number of samples.

The command wave_out_done tells us whether the wave output device finished playing the audio data already. wave_out_done returns 0 if and only if it is playing some audio data. The following procedure uses this command to loop until the currently playing audio is finished.

proc wait {} {
    while {![wave_out_done]} {}
}

Since the audio buffer is prepared, we could play the first half of it pretty easily. We just tell wave_audio_play that we only want to play half the number of bytes in the prepared audio.

wait
wave_audio_play [expr $nbytes/2]

Playing the second half is a little more work. we need to clip out the second half of the audio and prepare it again.

set secondhalf [audio_16_clip $audio [expr $nsamples/2] [expr $nsamples/2]]
wait
wave_audio_prep_play $secondhalf
wave_audio_play [expr $nbytes/2]

Now let's play the left and right channel individually. Note that we need to allocate memory for both channels before we could put the splited audio into them by audio_16_split

set left [audio_16_new [expr $nsamples/2]]
set right [audio_16_new [expr $nsamples/2]]
audio_16_split $audio $left $right

We have the left channel in left and right channel in right here. It's always safe to call wait before playing an audio

wait
wave_audio_prep_play $left
wave_audio_play [expr $nbytes/2]
wait
wave_audio_prep_play $right
wave_audio_play [expr $nbytes/2]

Doesn't sound right? Yes, it's because we opened the device for playing 16-bit stereo audio. To play channels individually, we have to reopen the device for 16-bit mono audio.

wave_hdr_set_num_of_chan $hdr 1
wave_hdr_set_bytes_per_sec $hdr [expr [wave_hdr_get_bytes_per_sec $hdr] / 2]
wave_hdr_set_block_align $hdr [expr [wave_hdr_get_block_align $hdr] / 2]
wave_hdr_set_data_len $hdr [expr [wave_hdr_get_data_len $hdr] / 2]
wait
wave_out_close
wave_out_open $hdr
wave_audio_prep_play $left
wave_audio_play [expr $nbytes/2]
wait
wave_audio_prep_play $right
wave_audio_play [expr $nbytes/2]
wait

Okay, you might have been sick of this wave audio clip already. Sorry about that. We're not going to play it any more. We'll now write its right channel to a file. But to do it correctly, we have to get a correct Wave Header. Fortunately, we have set the Wave Header to appropriate values already

Let's open the target output file.

set outfile [open testsndr.wav w]
fconfigure $outfile -translation binary -buffersize 65536

Then we make a bitstream and wrap a bitparser to it. After that we can encode the header to the bitstream with the bitparser and write the bitstream to the target output file.

set hdrbs [bitstream_new 100]
set hdrbp [bitparser_new]
bitparser_wrap $hdrbp $hdrbs
set hdrlen [wave_hdr_encode $hdr $hdrbp]
bitstream_channel_write_segment $hdrbs $outfile 0 $hdrlen

Finally we cast the audio buffer containing the right channel audio data into bitstream and write the bitstream to file.

set rightbs [audio_16_cast_to_bs $right]
bitstream_channel_write $rightbs $outfile 0

Remember the wave out device we opened is among one of the resources that we need to free.

wave_out_close
wave_hdr_free $hdr
audio_free $audio
audio_free $left
audio_free $right
audio_free $secondhalf
bitstream_free $bs
bitstream_free $hdrbs
bitstream_free $rightbs
bitparser_free $bp
bitparser_free $hdrbp
close $outfile

This is the result you should get : testsndr.wav


Lesson 10 : Advance Audio Buffer | Lesson 12 : Audio Map | Contents


Last Updated : 06/09/2025 00:34:01