Lesson 12. AudioMap


Lesson 11 : Wave Audio | Contents


In this last lesson, we will do two things with AudioMap. First, we will convert a mu-law Wave file to an A-law Wave file. Then we'll increase the volume of a single channel in a Wave file and output the result as another Wave file.

As usual, we need to read in the Wave file into audio buffers. The Wave file we'll use is mulaw.wav. You could replace the red file name with any other Wave file in mu-law format. Just put the file to the current directory of Tcl shell or Dvm shell.

We need to be able to read and write Wave files before we can do anything. The following procedures read and write Wave files to/from Dali Audio buffers. Readwave takes in the Wave filename and return a list contains the WavHdr, an AudioBuffer with the audio data in the Wave file, and a bitstream (which should be freed when we are done with the audio buffer). Writewave writes the header and AudioBuffer specified in the arguments to a Wave file with the specified filename. You don't need to worry about the details -- for more information on reading and writing Wave files, see Lesson 11.

cd C:/Dali/doc/tutorial
package require DvmBasic
package require DvmWave
package require DvmAmap
proc readwave {filename} {
    set size [file size $filename]
    set bs [bitstream_new $size]
    set bp [bitparser_new]  
    bitparser_wrap $bp $bs
    set infile [open $filename r]
    fconfigure $infile -translation binary -buffersize 65536
    bitstream_channel_read $bs $infile 0
    close $infile
    set hdr [wave_hdr_new]
    set len [wave_hdr_parse $bp $hdr]
    set bits [wave_hdr_get_bits_per_sample $hdr]
    set nbytes [wave_hdr_get_data_len $hdr]
    set bytes_per_sample [expr $bits / 8]
    set nsamples [expr $nbytes / $bytes_per_sample]
    if {$bits == 8} {
        set audio [bitstream_cast_to_audio_8 $bs [expr $len/$bytes_per_sample] $nsamples]
    } elseif {$bits == 16} {
        set audio [bitstream_cast_to_audio_16 $bs [expr $len/$bytes_per_sample] $nsamples]
    } else {
        error "error in wave file header"
    }
    
    return [list $hdr $audio $bs]
}
proc writewave {hdr audio filename} {
    set outfile [open $filename w]
    fconfigure $outfile -translation binary -buffersize 65536
    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
    set bits [wave_hdr_get_bits_per_sample $hdr]
    if {$bits == 8} {
        set outbs [audio_8_cast_to_bitstream $audio]
    } elseif {$bits == 16} {
        set outbs [audio_16_cast_to_bitstream $audio]
    } else {
        error "error in wave file header"
    }
    bitstream_channel_write $outbs $outfile 0

    close $outfile
    bitstream_free $hdrbs
    bitstream_free $outbs
    bitparser_free $hdrbp
}

I want to stress again it is not necessary to understand the above two procedures to complete this lesson. You just need to know how to use them, though you should have understood them if you have completed Lesson 11.

Now, let's read the Wave Header and the mu-law audio from the file mulaw.wav.

set wave [readwave mulaw.wav]
set hdr [lindex $wave 0]
set mu_audio [lindex $wave 1]
set bs [lindex $wave 2]

We have the audio data in the audio buffer mu_audio. How do we convert it toA-law format efficiently? We could create a custom AudioMap, but fortunately Dali provides several built-in AudioMaps that we can use. Dali provides a mu-law to linear map (8-bit to 16-bit) and a linear to A-law map (16-bit to 8-bit). By composing the two maps together we get a mu-law to A-law map (8-bit to 8-bit) and can use this AudioMap for the conversion we want. So let's get started! First of all, we need to allocate memory for the map.

set utolinmap [audiomap_8to16_new]
set lintoamap [audiomap_16to8_new]
set utoamap [audiomap_8to8_new]

Then we initializes the two Audio Maps' contents with built-in procedures.

audiomap_8to16_init_ulaw_to_linear $utolinmap
audiomap_16to8_init_linear_to_alaw $lintoamap

Now we can get the map we want by composing the above two maps.

audiomap_8to16_16to8_compose $utolinmap $lintoamap $utoamap

We then allocate memory for the resulting audio buffer and map the mu-law buffer to it as linear PCM format by applying our composed Audio Map.

set a_audio [audio_8_new [audio_get_num_of_samples $mu_audio]]
audiomap_8to8_apply $utoamap $mu_audio $a_audio

Let's write the result out to Wave file. Don't forget to reconfigure the Wave Header before calling the writewave procedure. The format code for a-law audio is 7. The other fields of the header are the same as a mu-law audio Wave file. It's because both are 8-bit audio.

wave_hdr_set_format $hdr 7
writewave $hdr $a_audio alaw.wav

Once again, we should free up the resources.

wave_hdr_free $hdr
audio_free $mu_audio
audio_free $a_audio
bitstream_free $bs
audiomap_free $utolinmap
audiomap_free $lintoamap
audiomap_free $utoamap

You should get a such a Wave file : alaw.wav. Hear how the result is distorted by mapping between two compressed format? You never want to do this without interpolation!

What happens if the Audio Map you need is not one of our built-in maps and neither it can be derived by composing them? You could generate your own custom Audio Map from Tcl list. For instance, suppose we want to double the volume of some 8-bit audio. We could do it very fast by using Audio Map. First we allocate memory for a 8-to-8 Audio Map.

set double [audiomap_8to8_new]

But how can we initialize the map so that it maps audio samples to its double? We need to generate a list with index from 0 to 255, where each values map to appropriate values. But what are the appropriate values? To do this, we need to note that 8-bit audio samples range from 0 to 255, but its zero lies at 128. This means the actual values represented by the samples is the value when 128 is subtracted from a sample.

Value represented = Sample data - 128
Double(Sample data) = 2 * (Sample data - 128) + 128 = Sample data + Sample data - 128

Moreover, we need to clip the values out of the range between 0 and 255. The first if condition clip values smaller than 0 to 0 and the second if condition clip values larger than 255 to 255.

set list {}
for {set i 0} {$i < 256} {incr i} {
    set v [expr $i + $i-128]
    if {$v < 0} {
        lappend list 0
    } elseif {$v > 255} {
        lappend list 255
    } else {
        lappend list $v
    }
}

Then we initialize our custom Audio Map from this list.

audiomap_8to8_init_custom $list $double

After that, we have the right Audio Map. We then could read in the Audio data from the Wave file. We'll use stereo8bit.wav

set wave [readwave stereo8bit.wav]
set hdr [lindex $wave 0]
set audio [lindex $wave 1]
set bs [lindex $wave 2]

Can we only double the volume of the left channel? Yes, we can, with the apply_some command. Let's apply the doubling map with offset 0 and stride 2. If you only want to double the right channel, apply the map with offset 1 and stride 2. Note that we map the audio buffer to itself here. You can almost always apply audio buffer to itself without the need to allocate memory for another separate audio buffer, including the mu-law to a-law conversion example above.

audiomap_8to8_apply_some $double $audio 0 2 0 2 $audio

That's it! We finally write the result to double.wav and free up the resources.

writewave $hdr $audio double.wav
wave_hdr_free $hdr
audio_free $audio
audiomap_free $double
bitstream_free $bs

Source code for this lesson : l12.tcl


Lesson 11 : Wave Audio | Contents


Last Updated :