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

# Part V/1: Debugging SAOL Programs

## Sections Introduction. Reported Errors. Sfront and sa.c error printouts. Program Crashes. Using gdb to debug crashes. Finding Runtime Errors. Simplify programs to simplify debugging. `printf`. Introduces an sfront extension to SAOL, the printf statement. I-rate Methods. Using printf to debug note-instantiation issues. K-rate Methods Techniques for tracking control-rate variables. A-rate Methods Data reduction techniques for audio-rate debugging | ## 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: Sfront runs and reports error or warning messages about your program. The SAOL program must be fixed to eliminate these messages. 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: Sfront never completes. Sfront crashes. Sfront printing an Internal compiler error message. 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: Recompile the `sa.c` program, using the `-g` option of `gcc` Run `gdb` on the program, as shown on the right panel. 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: Change monitoring. Look at key k-rate variables every cycle, but only print if the value has significantly changed. 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. 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.