From The MPEG-4 Structured Audio Book by John Lazzaro and John Wawrzynek.

Part V/1: Debugging SAOL Programs

Sections

Language Elements:

printf  

Introduction

A popular staple of afternoon television is the cooking show. At the start of the show, a complex meal is planned, and by the end of the show, a perfect dinner pops out of the kitchen.

The examples shown in this book have a similar flavor. Code is shown on the right-hand panel, which always works to produce the desired audio output.

Like TV cooking shows, the book examples don't show the real development process: syntax errors, compiler bugs, program crashes, and bad-sounding audio.

In this chapter, we describe common ways that SAOL programs fail to work, and techniques to use to find and fix the problems.

The right panel shows a list of the major traps in the development cycle. In the sections of this chapter, we describe how to approach these problems.

In this chapter, we use the sfront SAOL-to-C translator, running under Linux, to demonstrate debugging. However, even if you use another SAOL development systems, the concepts in this chapter should help you debug your programs.

Errors: Where, When, and What


Before execution:

  • sfront reports SAOL Syntax Error.
  • sfront crashes or hangs up.
  • gcc fails to compile sa.c.

During execution:

  • Program ends, prints "Runtime Error" banner.
  • Program crashes, with Linux OS error.
  • Program runs forever.

After execution:

  • Audio sounds totally wrong.
  • Audio almost sounds correct.
  • Audio ends too soon or too late.

Reported Errors

Many SAOL programming errors are detected automatically and reported to the user. Depending on the error, sfront may report a compile-time error, or the C program sfront creates may terminate and report an error.

The right panel shows one kind of sfront compile-time error, a syntax error (in this case, a missing semicolon).

Since sfront can't understand the SAOL program without the semicolon, its error message doesn't describe the problem exactly. However, it does report a line number and filename, which (usually) is sufficient to debug the problem.

Syntax Error Report


SAOL fragment:

instr tremelo ( ) {
   asig count    // lacks ';'
   ksig kinit;


sfront reports:

Syntax error during 
-saolfile parsing.

Error occurred near line
28 in file min.saol:

instr tremelo ( ) {
   asig count
   ksig kinit;
   ksig halfperiod;

Ending sfront.

A second type of compile-time error is a type mismatch error. The right panel shows an example of a type mismatch (a table used in a signal expression).

Because sfront successfully understood the syntax of the program, it was able to print a meaningful error message.

Whenever you run sfront, your session should end in one of the following ways:

  1. Sfront runs and reports error or warning messages about your program. The SAOL program must be fixed to eliminate these messages.
  2. Sfront runs without reporting an error. In this case, the C file it creates should compile and produce an runnable executable.

However, if your session ends in one of the following ways:

  1. Sfront never completes.
  2. Sfront crashes.
  3. Sfront printing an Internal compiler error message.
  4. The C file sfront produces won't compile.
you've stumbled across a bug in sfront itself, which should be sent in as a bug report.

Type Error Report


SAOL fragment:

instr square ( ) {
   table delta(harm, 128, 1);

   delta = delta + 1; // type error
   

sfront reports:

Error: Table(map) delta 
used inappropriately.

Error occured near line
56 in file min.saol:

   table delta(harm, 128, 1);

   delta = delta + 1;
   

Ending sfront.

When sfront creates a C program, it includes error checking code for certain types of errors. If this error checking code is tripped, a run-time error report is printed and the program exits.

The right panel shows an example of run-time error checking. In this case, a table is defined, using the sample wavetable generator. However, the file specified doesn't actually exist.

During execution, the program tries to open the file and fails; it then prints the run-time error shown and exits.

Reported errors are the simplest type of run-time error to catch and fix. To help the programmer write good code, the MP4-SA standard mandates that decoders catch report many kinds of real-time errors checks.

Eventually, sfront will implement all of these error checks. At the present time, however, sfront implements many but not all required checks.

Specifically, sfront does not check to see if the index value of an array, oparray, or tablemap is out of range. In addition, CPU intensive checks on certain core opcodes, such as checking the range of the index parameter to tableread and tablewrite, are only performed if sfront is run with the -isocheck flag.

If your program does one of these illegal operations, it won't produce a run-time error message. Instead, the program might crash, or it may run forever, or (most likely) the sound created might not be what it should.

Runtime Error Report


SAOL fragment:

// file doesn't actually exist

global { 
  table oneclap(sample, -1,
               "samp_2.aif"); 



Execution trace:

gcc -O3 sa.c -lm  -o sa
./sa 



Runtime Error.
Location: File claps.saol 
          near line 10.

While executing: table.
Potential problem: Samplefile
           not found (aif(f)).

Exiting program.

Program Crashes

In many ways, a program that crashes is easier to debug than one that silently does the wrong thing.

In the future, sfront will include modes for reporting exactly where and how a program crashes. For now, the easiest way to debug a program crash under Linux is to follow these steps:

  1. Recompile the sa.c program, using the -g option of gcc
  2. Run gdb on the program, as shown on the right panel.
  3. The C code sfront generates embeds SAOL variable names into its C identifiers. Look at the debug printout gdb produces to pinpoint the error location.

It may be helpful to look at the sa.c file in this process, if you are comfortable with the C language.

The remainder of this chapter focuses on techniques for handling SAOL programs that don't crash, but which produce the wrong audio.

Running Gdb


[1] Run sfront without -except option

[2] Compile sa.c with debug switch:

gcc -g -O3 sa.c -lm  -o sa

[3] Run gdb:

gdb ./sa

GNU gdb 4.17.0.11 

(gdb) run

Starting program: ./sa 

Segmentation fault, in
arpsound2__sym_butter_gainlp3
(gdb) bt

#0  in arpsound2__sym_butter_gainlp3 ()
#1  in arpsound2_kpass ()
#2  in main_kpass ()
#3  in main ()

[3] Example what gdb prints in 
response to "bt" as shown above.
In this case, bug was in the 
kpass of instr arpsound2,
during a call to opcode 
butter_gainlp3.

Finding Runtime Errors

Finding the root cause of a run-time problem in a SAOL program may be a difficult task. Rate semantics may make even simple programs difficult to understand. Ancillary factors such as a complex SASL or MIDI track, real-time control, or audio input can make matters even worse.

The first step in debugging a complex SAOL program is to simplify the program itself. Store a safety copy of the original program, and then try the following techniques to simplify the problem:

  • Simplify the score. Make a dummy SASL score with a few calls to each instr, that creates a few seconds of audio. Use this score instead of the real SASL, MIDI, or real-time control source for debug.
  • Simplify the audio chain. Eliminate all route and send statements from the global block, and just test each sound-generating instrument by itself. If these instrs work OK, test each effects instrument in isolation.
  • Simplify the rate structure. Once you've located the instrument with the problem, try replacing the i-rate and k-rate sections with constants, and just test the a-rate section. Then add the k-rate and i-rate back, to isolate the problem.
  • Use audio channels independently. Think of the two speaker channels as independent audio oscilloscopes, and send intermediate signals to each output independently. Use the balance control on your audio amplifier to listen to the channels in isolation.
  • Visually inspect audio output. Sometimes a picture is worth a thousand seconds of sound. Use your favorite visualization tool to look at the audio data in the output file. The -aout file.dat option produces an ASCII file format that can be inspected in a text editor, or graphed using the Chipmunk tool view.

The techniques can be useful in tracking down a problem to a section of SAOL code. Once the problem area is localized, a careful examination of the source code may reveal the error.

Other times, the only way to find the root cause of the problem is to print out variables over time and watch them evolve, as we describe in the next section.

 

printf

SAOL programs compiled under sfront can use the printf statement, described on the right panel, to print out SAOL scalar program variables. Note that printf is an sfront extension of SAOL, and is not part of the standard language.

The SAOL printf statement borrows its basic syntax from the ANSI C standard library function printf. The first argument is a format string, that contains the text to be printed out. When printed, the first instance of %f is replaced with the value of the second printf argument, the second instance of %f is replaced with the value of the third argument, ect.

The SAOL printf statement differs from the C printf function in several ways:

  • SAOL's printf is a statement, not an opcode. It can not be used in expressions, but must stand alone like an output() or extend() statement.
  • SAOL's printf has rate semantics. The rate of the printf statement is the rate of its fastest argument. The format string is considered to be i-rate.
  • SAOL's printf arguments must be scalar signal expressions or strings. Unindexed arrays with non-scalar width, tables, tablemaps, and opcodes that return non-scalar widths may not be used as printf arguments.

For simplicity, the right-panel description of printf format strings only show plain %f (for variables) and %s (for strings) directives. Since SAOL printf statements are implemented in the sa.c file as C printf functions, experienced C programs are free to use more complex format directives for floating-point numbers.

Printf statements write to stdout, while all error diagnostics will write to stderr. Printf works this way so that stdout may be redirected to a file or program, while error messages are still printed to the screen. The first shell command below redirects printf output to the file log, the second redirects prints to the text viewer more:

% ./sa > log
% ./sa | more

Its a good idea to use the sfront option -except when redirecting to a log file, so that a program crash does not delete the log file.

The most powerful part of the SAOL printf statement is its rate semantics. By careful choice of arguments, printing will occur at the i-rate, k-rate, or a-rate. In the following sections, we describe useful printf techniques at each rate.

Statement syntax


printf("fmt" [, arg1, arg2, ..]);

Where:

"fmt":

  is a (required) quoted string.
  it may contain directives 
  beginning with the % character.
  Most users will only need the
  %f character (for printing 
  expressions) and the %s character
  (for printing quoted strings).
  Use the \n characters for carriage
  return, the \t character for tab.
  Replace %f with %e to print using
  scientific notation, or %g to 
  adaptively choose normal or 
  scientific notation.

argk:

  are optional arguments. each 
  must be a scalar signal 
  expression or a quoted string.
  arg1's value is printed in 
  place of the first %f or %s,
  arg2's value is printed in place
  of the second %f or %s, ect. 
  expressions must match with %f's,
  and strings with %s. 

Note that printf is a statement, 
not an opcode. The rate of the
expression is the rate of the
fastest arguments, with strings
considered i-rate. Expressions
may be special-ops, in which
case the printf is a special-op.

Example:

instr foo(i) {
ksig k;
asig a;

i = 12;
k = 5;
a = 6;
printf("hello world\n");
printf("%s%f\n","start of instr",i);
printf("krate %f %f\n",i, k);
printf("arate %f\n",a); }

Prints out (from instantiation):

hello world
start of instr 12.000000
krate 12.000000 5.000000
arate 6.000000
arate 6.000000
[...]
arate 6.000000
arate 6.000000
krate 12.000000 5.000000
arate 6.000000
arate 6.000000
[...]
[...]

I-rate Debugging

I-rate printf statements run when instrument instances are created. In many cases, the underlying source of an a-rate or k-rate bug can be understood by examining values at the i-rate. Useful items to print include

  • Instrument parameters These values tie the instance to the SASL, MIDI, or SAOL instr statement that created it.
  • Table contents. Bugs in wavetable generation can be found by printing out table values using the tableread opcode.
  • Ivar standard names These values can chart the time course of instances. See the right panel for details.

Sometimes, bugs happen in the initialization process of the SAOL program, at time zero.

To track these problems down, try adding i-rate printf statements in effects instruments, the startup instrument, and in user-defined opcodes called in the global block.

Useful ivar standard names


ivar time 

 The time that the instance
 was created.

ivar dur

 The duration of the instance,
 or -1 if no duration.

ivar inchan

 For effects instances: the 
 number of input audio channels
 provided.

ivar outchan

 The number of audio output
 channels for the instrument.

ivar channel

 For MIDI instruments: the 
 extended channel number for
 the instance.

ivar preset

 For MIDI instruments: the 
 preset number for this 
 instance.

K-rate Debugging

If i-rate printf statements do not find a bug, it may be necessary to print out k-rate variable values.

A key problem with k-rate printf statements is the large amount of data produced. Since an unguarded k-rate printf generates data once per k-cucle, it may produce hundreds of lines of text per second.

To solve this problem, only use k-rate printf statements inside of if statements, whose guard values are chosen to be rarely true. Common guard techniques include:

  1. Change monitoring. Look at key k-rate variables every cycle, but only print if the value has significantly changed.
  2. Subsampling. Print out status one every 10, 50, or 100 cycles. Use the itime standard name (see right panel) to keep track of when to print.
  3. Release monitoring.Use the released standard name (see right panel) to print the value of k-rate variables at the final k-cycle of an instance.

An alternative approach is to print information on every k-cycle, and to direct this information to a file for examination with a text editor.

Useful ksig standard names


ksig itime 

 The elapsed time since the
 instance was created. 

ksig released

 Normally 0, but 1 if the
 instance is scheduled for
 termination at the end of
 the current execution cycle.

ksig MIDIctrl[128]
ksig MIDIbend
ksig MIDItouch

 For MIDI instances, monitors
 MIDI data from the channel that
 created it. For non-MIDI 
 instances in a SAOL program
 controlled by MIDI, monitors
 the MIDI master channel 
 (usually channel 0).

imports exports ksig params[128]

 Communicates with the control
 driver in an application-
 specific way; good for debug
 of new control drivers.

A-rate Debugging

The most difficult debugging sessions involve monitoring a-rate changes. In most cases, unguarded printf statements are not practical, since a short sound may produce one-hundred thousand lines of text!

As in k-rate debugging, enclosing all printf statements with if statements whose guards rarely execute is the key to successful debugging. Since a-rate problems often involve clicks or run-away waveforms, guard expressions often are tuned to look for these waveform characteristics.

Another technique involves the use of specialops, such as the rms opcode. This opcode examines its parameter at the a-rate, and returns the root-mean-square value of the waveform at the k-rate. A printf statement with a specialop expression prints out text at the k-rate.

  
Next: Part V/2: Templates
 

Copyright 2000 John Lazzaro and John Wawrzynek.