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

Part V/2: Templates

Sections

Language Elements:

template   map   with   preset

Introduction

Musical instruments that are capable of sounding a range of pitches often have a slightly different timbre quality for each pitch.

The simplest way to model this type of instrument in SAOL is to write one instr that creates sound for the entire pitch range of the instrument. The parameters of this instr specify the pitch and timbre of the sound, and the algorithm for sound generation changes the pitch and timbre of the instrument based on these parameters.

Sometimes, this parametric approach to modeling an instrument is not a good fit to the problem. If an instrument generates sound in very different ways when played in different registers, it may be easier to write several instrs, one for each pitch register.

Often, the instrument modeling problem falls between these two extremes. The code for different registers is too different to use a parametric approach, but so similar that if separate instrs are written for each register, most of the code in each instrument would be identical. Widespread code cloning is a bad software practice, since any bug in the cloned section needs to be fixed multiple times.

The SAOL template language feature is designed to solve the code cloning problem. It provides a way to specify a family of instrs that change in systematic ways. Early in the processing of SAOL program, a template is transformed into a set of instrs, which are then treated identically to normal SAOL instrs.

We begin this chapter with a very simple example of templates, to introduce the concept. We then show more complicated examples, to show the features of the language construct. In each example, we show the family of instruments a template creates.

 

A Simple Example

The panel on the right shows a simple example that uses templates. The top part of the panel is the template definition, which the programmer writes instead of creating several nearly-identical instruments. The bottom part of the panel shows the instrument definitions that the template specified: this is the code the programmer would write if the templates feature did not exist in SAOL.

The template definition shown in a trivial one: it creates two instrs, lower and upper, that differ only in their names. We start with this example to explain the basic syntax of the template construction.

template syntax

A template begins with the keyword template. This keyword is followed by a list of one or more the names of instruments that the template creates (in this case, the instrument names lower and upper). This list starts with < and ends with >, and the list members are separated by commas. Each name must be a valid instrument name, as described in Part II/1.

The name list is followed by the parameter definition for the family of instruments (in this case, (note)). Template parameters are defined in the same way as instr parameters (see Part II/1). It is not possible to define a template so that different instrs have different parameter lists.

Following the parameter list is the map construct. As described in this next section, the map construct lets the programmer specify how each instr in a template family is unique.

This example shows the simplest form of the map construct, that specifies that all family members are identical (even in the simple cloning case, a map construct is required). The null map construct consist of the map keyword, followed by a matched set of curly brackets ({ }), followed by the with keyword, followed by a second set of curly brackets ({ }).

The rest of a template definition looks like an instrument definition: curly braces surround a variable block followed by a code block.

This template example creates two identical instruments, upper and lower, as shown on the bottom part of the panel, at the start of execution of the SAOL program.

This template:


template <lower, upper> (note) 

map { } with { } 

{
  table t(harm,128,1);
  ksig e;


  // runs at k-rate

  e = 0.5*kline(0,1,1,1,0);

  // runs at a-rate

  output(e*oscil(t,cpsmidi(note)));

}

Expands to:


instr lower (note) {

  table t(harm,128,1);
  ksig e;


  e = 0.5*kline(0,1,1,1,0);

  output(e*oscil(t,cpsmidi(note)));

}


instr upper (note) {

  table t(harm,128,1);
  ksig e;


  e = 0.5*kline(0,1,1,1,0);

  output(e*oscil(t,cpsmidi(note)));

}

Template Variables

The example on the right panel shows how to use the map construction to specialize each family member of a template. The top part of the example shows the template definition, and the bottom part of the example shows the two instruments (lower and upper) the template creates.

The map construction works by defining a set of template variables, that may be used in the code block anywhere that an expression may be used. The map construction also defines, for each family member of the template, the actual SAOL substitution expression that should used in place of the template variable in that family member. By defining different substitution expressions for each family member, the programmer can specialize the functionality of each instrument created by the template.

The example on the right panel shows how to specify template variables and substitution expressions in a template. Template variables are listed inside the curly brackets following the map keyword. In this example, there are three template variables: env, tone, and vol.

The second part of the map construction specifies the expression to substitute for each template variable, for each instr created by the template. Substitution expressions are defined inside the curly braces following the with keyword.

A substitution expression definition consists of a list of expression lists. Each expression list is enclosed in angle bracket (<,>), and contains one expression for each template variable. There is one expression list for each instrument in the family, separated by commas.

In this example, the first expression list defines the substitutions for instrument lower. In other words, it specifies that the expression kline(0,1,1,1,0) be used in place of the template variable env, that the expression oscil(t,cpsmidi(note)) be used in place of the template variable tone, and the number 0.9 be used in place of the template variable vol. Likewise, the second expression list defines substitutions for family member upper.

The second part of the example shows the two instruments the template creates. Note that all template variables are gone, replaced by the appropriate expressions.

Compared to other SAOL language elements, templates are rather complicated. Keep these points in mind to avoid common template mistakes:

  • The with part of the map construction must have one expression list for each family member.
  • Each expression list in the with part of the map construction must have one expression for each template variable.
  • Template variables may be used in the code block anywhere an expression is legal. Note that in SAOL, variables are sometimes used in places where expressions are illegal: for example, the lval of an assignment statement.
  • Template variables are not declared in the variable declarations.

This template:


template <lower, upper> (note) 

map { env, tone, vol } with 

{ 
  < kline(0,1,1,1,0), 
    oscil(t,cpsmidi(note)),
    0.9 >,

  < kexpon(0.01,1,1,1,0.01), 
    buzz(cpsmidi(note), 0,0, 0.5),
    0.5 > 
} 


{

table t(harm,128,1);
ksig e;

// runs at k-rate

e = vol*env;

// runs at a-rate

output(e*tone);

}

Expands to:


instr lower (note) {

tone t(harm,128,1);
ksig e;

// runs at k-rate

e = 0.9*kline(0,1,1,1,0);

// runs at a-rate

output(e*oscil(t,cpsmidi(note)));

}


instr upper (note) {

tone t(harm,128,1);
ksig e;

// runs at k-rate

e = 0.5*kexpon(0.01,1,1,1,0.01);

// runs at a-rate

output(e*buzz(
       cpsmidi(note), 0,0, 0.5));

}

Preset Maplists

As described in Part III/2, the preset tag is used in instr definitions to define the MIDI preset numbers associated with the instrument. In this section, we show how to specify MIDI preset numbers for each instrument created by a template.

The right panel shows a template definition that uses the optional preset maplist construction to specify MIDI preset numbers. This construction begins with the keyword preset, and occurs after the template name definition list (in this example, <upper,lower>).

Following the preset keyword is a list of expression lists, one for each instrument in the template family. The first list declares the MIDI preset numbers for the first instrument, the second list the MIDI preset numbers for the second instrument, etc. Each expression list is enclosed in angle brackets (<,>) and separated by commas.

Remember these points when using the preset maplist construction

  • Unlike instr definitions, the preset token comes before the parameter list, and the preset expressions are separated by commas, not spaces.
  • Because each instrument in a template may have a different quantity of MIDI preset numbers, the length of each expression list may be different.
  • Only constant expressions are allowed in the preset maplist construction; global variables may not be used.
  • Two instruments may not use the same MIDI preset numbers.

This template:


template <lower, upper> 

preset <0,1>, <5+1> (note) 

map { env } with 

{ 
  < kline(0,1,1,1,0) >,

  < kexpon(0.01,1,1,1,0.01) > 
} 


{
  table t(harm,128,1);
  ksig e;


  // runs at k-rate

  e = 0.5*env;

  // runs at a-rate

  output(e*oscil(t,cpsmidi(note)));

}

Expands to:


instr lower preset 0 1 (note) {

  table t(harm,128,1);
  ksig e;


  e = 0.5*kline(0,1,1,1,0);

  output(e*oscil(t,cpsmidi(note)));

}


instr upper preset 6 (note) {

  table t(harm,128,1);
  ksig e;


  e = 0.5*kexpon(0.01,1,1,1,0.01);

  output(e*oscil(t,cpsmidi(note)));

}
Next: Part V/3: The Slib Library
 


Copyright 2000 John Lazzaro and John Wawrzynek.