A tutorial on using Snack with Tcl

Snack can be used for a wide array of tasks relating to sound files and audio data. From simple one line commands, like playing a sound file or finding its extreme values to complex sound analysis applications with graphical user interfaces. This tutorial is intended to get you started using Snack and to familiarize you with some basic concepts. The tutorial describes Snack in combination with Tcl, but many of the examples are applicable to Python as well using simple translation. Only a rudimentary knowledge of Tcl is needed to understand the examples.

Getting started with Snack

Playing a sound file from disk

Real-time sound generation

Streaming audio using pipes

Streaming audio using sockets

Batch processing of sound files

Compiling scripts into stand-alone executables
 

Getting started with Snack

First you need to start the wish console application (the Tcl interpreter), this is done in different ways on different platforms

Unix: type wish8.4 at a terminal window prompt (depending on which version you have installed, or simply wish)

Windows: choose wish from the Start menu (Start | Programs | Tcl | Wish)

Macintosh: double-click the wish icon in the Tcl/Tk folder

Two windows will appear, one console and one initial application window which can be used for graphical user interfaces.
Now load the Snack extension by typing

package require snack

at the console prompt. Wish will return a number which corresponds to the version of Snack that has been installed.
Create an initial sound object by typing

snack::sound s

You have now created a Snack sound object named s. Sound objects are the main building blocks of Snack scripts.
You can read a sound file into a sound object using the read command.
Do this by typing

s read ../snack2.2/ex1.wav

the path name might vary depending on your installation, Unix users can find the file as snack2.2a1/demos/tcl/ex1.wav.
The command will return the string WAV corresponding to the file format of the file read.
As a convenience you can combine the last two commands into one. That is, you can specify a file to be read when the sound object is first created

snack::sound s -load ../snack2.2/ex1.wav

In order to play the sound you simply type

s play

Note that if you want to play the same sound again you simply repeat this command. There is no need to create new objects and read the sound file all over again.

If you want to save this sound in AU file format you can do this by typing

s write test.au

If you want the length of the sound in seconds type

s length -unit seconds

The console is only usable for small tasks because of the need to type everything by hand. The normal use would be to put the commands in a text file and run it as a script. Scripts should have the extension .tcl and have this general form

#!/bin/sh
# the next line restarts using wish \
exec wish8.4 "$0" "$@"

package require snack

snack::sound snd

snd read ex1.wav
snd write ex1.au

exit

The first three lines makes this script executable (with correct permissions) on Unix. It is good practice to start every script like that. Next two statements load snack and creates a sound object named snd.
The read command is used to read the sound file ex1.wav in the current directory and write is used to save it back as an AU file (the file format to use for saving is inferred from the file name extension).
Finally exit quits the script.
 

Playing a sound file from disk

If the sound file you want to play is very large it might not be practical to read it into memory first. Snack sound objects can link to a disk file as an alternative to storing the audio data in memory. To use this feature create the sound object like this

snack::sound s -file ex1.wav

The audio data will now reside on disk and only be accessed as necessary.
If you want to play another file later, you can configure which file the sound object should link to using the configure command

s configure -file ex2.wav
 

Real-time sound generation

Snack can generate sound data on-the-fly during play. The following commands would play a short 440Hz sine beep

set f [snack::filter generator 440.0 30000 0.0 sine 8000]
set s [snack::sound]
$s play -filter $f

The last parameter to the generator filter is the number of samples to generate, in this case 8000. The value -1 would generate samples for ever, or until a "stop" command is issued.

A procedure for generating beeps might be defined like this

proc beep {freq} {
  set len 8000
  set f [snack::filter generator $freq 30000 0.0 sine $len]
  set s [snack::sound -rate 22050]
  $s play -filter $f -command "$s destroy;$f destroy"
}

This would create the necessary filter and sound objects, play the sound, and clean up everything once the sound has been played. The option -command, which is given to the play command, specifies a command to execute once playback has completed. In this case it is the commands needed to deallocate (destroy) the objects. The newly defined procedure is called with one argument giving the freqency of the beep

beep 440

Note that the sound generated is immediately sent to the audio device, none of it is kept in memory. What you actually have done is to play an empty sound with a generator filter applied. This means that you can not save it to disk with a subsequent write command. If you want to generate the beep and keep the result in the sound object the following statements would be used

set f [snack::filter generator 440.0 30000 0.0 sine 8000]
set s [snack::sound]
$s filter $f

At this point the sound can be written to a file using a write command. In this case you can simply play it using

$s play

Try the commands below for a more advanced example. The filters create a simple formant speech synthesizer using a square wave generator pipelined with four formant filters. The resulting combination will generate a neutral vowel.

set g [snack::filter generator 75 2500 0.1 rectangle -1]

set f1 [snack::filter formant 500 50]

set f2 [snack::filter formant 1500 75]

set f3 [snack::filter formant 2500 100]

set f4 [snack::filter formant 3500 150]

set synth [snack::filter compose $g $f1 $f2 $f3 $f4]

snack::sound s

s play -filter $synth

The filters can naturally be reconfigured for other frequencies while the sound is being generated. Try this by adding a slider connected to the first formant filter

pack [scale .s -from 200 -to 1000 -command "$f1 configure"]

See also the formant.tcl demo in the distribution.
 
 

Streaming audio using pipes

Snack sound objects can not only be linked to files but also to channels such as pipes. This allows you to play sound data coming in through a pipe or to record sound data and send it.
Put the following statements in a Snack script called playpipe.tcl package require snack

sound s -channel stdin
s play -command exit -blocking yes

Now try this command line in an ordinary terminal window, e.g. in an xterm or similar

cat ex1.wav | playpipe.tcl

Streaming audio using sockets

Snack sound objects can also be linked to socket channels. This allows you to play sound data coming in from a socket or to record sound data and write it to a socket.
The following code creates a minimal audio server

package require snack

proc ServerCmd {sock args} {
 set snd [snack::sound -channel $sock]
 $snd play -command "[list close $sock];$snd destroy"
}

socket -server ServerCmd 23654

Each time a client makes a connection at port 23654, of the computer that runs this code, the procedure ServerCmd will be called. This will create a new sound object linked to the client channel and start a play operation. Note that this code snippet is capable of playing several simultaneous MP3 streams.

The following commands create matching client connection

package require snack

set sock [socket localhost 23654]
fconfigure $sock -translation binary
fconfigure $sock -encoding binary

sound s -channel $sock
s record -fileformat wav

Change localhost to be the network name of the computer the server code is run on. The socket channel is first configured for binary data, otherwise the stream would be corrupted by end-of-line conversions and similar. There will be a delay in the sound transfer due to buffering.

See also the aserver.tcl, rplay.tcl, and recrplay.tcl demos in the distribution.

Batch processing of sound files

Since Snack is used in conjunction with a scripting language it lends itself quite naturally to batch processing tasks.

Sound file format conversion

The previous script can easily be modified to perform batch processing. The Tcl language contains many commands which are useful for Snack users, such as the glob command, which returns a list with files matching the given patterns. For example, the following script reads all Kay Elemetrics .nsp-files and all Entropic .sd-files in the current directory and saves them as WAV files

#!/bin/sh
# the next line restarts using wish \
exec wish8.4 "$0" "$@"

package require snack

snack::sound s

foreach file [glob *.sd *.nsp] {
  s read $file
  s write [file rootname $file].wav
}

exit

Sound data format normalization

You can also do other kinds of transformation. The following script is similar to the last one but takes all AU files in the current directory and converts them from their original sound format into linear 16 bits, single channel, with sample frequency 16kHz, and saves them as WAV files

#!/bin/sh
# the next line restarts using wish \
exec wish8.4 "$0" "$@"

package require snack

snack::sound s

foreach file [glob *.au] {
  s read $file
  s convert -frequency 16000 -channels mono -format Lin16
  s write [file rootname $file].wav
}

exit

Pitch and formant extraction

It is straight forward to modify the script to read the list of speech files, to be processed, from a text file. The following script reads the text file list.txt, which contains file names, one per line, of sound files which are to be processed. For each sound file, a list of pitch values is computed and saved as a text file with the same root name but with the extension .f0. Similarly, a list of formant values is calculated and saved in a file with the extension .frm.

#!/bin/sh
# the next line restarts using wish \
exec wish8.4 "$0" "$@"

package require snack

snack::sound s

set f [open list.txt]
set list [read $f]
close $f

foreach file $list {
 s read $file

 set fd [open [file rootname $file].f0 w]
 puts $fd [join [s pitch -method esps] \n]
 close $fd

 set fd [open [file rootname $file].frm w]
 puts $fd [join [s formant] \n]
 close $fd
}

exit

Long time average spectra

The above script only needs some minor modifications to generate files containing the long time average spectrum of each sound file instead. These are saved in text files with the extension .ltas

#!/bin/sh
# the next line restarts using wish \
exec wish8.4 "$0" "$@"

package require snack

snack::sound s

set f [open list.txt]
set list [read $f]
close $f

foreach file $list {
 s read $file
 if [catch {open [file rootname $file].ltas w} out] {
  error $out
 } else {
  foreach value [s dBPowerSpectrum -fftlength 512 -windowlength 512 \
   -windowtype Hanning] {
   puts $out $value
  }
 }
 close $out
}

exit
 

Mean pitch per speaker

The following script calculates the average pitch per speaker for a number of sound files each. The script looks for files that match the pattern d:/test/sp0?/sp0?.*.wav and computes the mean pitch for all voiced segments in all files for each of the speakers sp01, sp02, sp03, and sp04.

#!/bin/sh
# the next line restarts using wish \
exec wish8.4 "$0" "$@"

package require snack
snack::sound snd

foreach speaker {sp01 sp02 sp03 sp04} {
  set sumPitch 0.0
  set count 0
  foreach fn [lsort [glob D:/test/$speaker/$speaker.*.wav]] {
   snd read $fn
   foreach value [snd pitch] {
    if {$value != 0} {
     set sumPitch [expr $sumPitch + $value]
     incr count
    }
   }
  }
  puts "$sp: [expr $sumPitch / $count]"
}


Compiling scripts into stand-alone executables

When deploying an application that has been created using Tcl/Tk and Snack it can be convenient to create a stand-alone executable. This saves the end user the trouble of having to install Tcl/Tk and Snack first, before using your application. The tool wrap.tcl, which is included in the distribution, is used to compile a Tcl script together with Tcl/Tk and Snack into a self-contained binary executable.
On Unix you would type the following command in a terminal window

wrap.tcl app.tcl

Windows users simply run wrap.tcl and choose the name of the script to compile, in the dialog that pops up.
Running wrap.tcl will generate an application called app (app.exe on Windows). The resulting binary has no dependencies on Tcl/Tk or Snack at all and does not need any installation apart from the copying of the file itself. Most applications will fit on a diskette.

 The wrap.tcl script relies on the freewrap tool which needs to be installed first. You might have to edit wrap.tcl first in order to specify the path to freewrap.



A collection of example scripts

Snack home

Last up dated January 23, 2006