SAU language overview

An overview of how to use the SAU language provided by the saugns program. For how to use saugns command-line options to do various things with scripts, instead see the usage page. Syntax changes, feature additions, and other tweaks to the language between versions are listed on the changes page.

The below is meant to strike a balance between simple explanation, the big picture, and a reference for details of the language features. The examples page gathers examples with rendered audio files, which may be pointed to in the text. A concise language reference which mainly describes and lists details can be found in the README.SAU file.

Contents

Nature of the language

SAU (Scriptable AUdio) is a simple and non-Turing-complete language for mathematical audio synthesis, without support for the use of pre-recorded samples.

The core idea of the language is that of time-ordered steps for configuring audio generation: add an oscillator, then later, change a parameter and extend play duration, etc. A script is basically a list of such timed instructions. Language constructs also offer more flexible arrangement of steps to take than a bare flat list of instructions and forward time movement.

The syntax is a bit terse and unusual. It uses one-character keywords with or without an argument, followed by zero or more parameters with arguments. Each main keyword provides an action, either at run time (like an object constructor in other languages, here used for signal generators), or at parse time (like a global script setting).

The keywords that are type names for signal generators include:

See just below for the basics of generating audio using them. The next main section goes into modulation features, which are a main way to do more and have been described in a little more hands-on manner. Thereafter comes all the rest, in values and expressions. That includes timing logic and parameters, which together with the generator types and modulation options include the core features of the language.

Generating audio

The examples page has more in-depth examples.

To generate a single pure tone in the SAU language:

Wsin f440 p0 a1.0 t1

In this case, frequency is set to 440 Hz, phase to 0% of the wave cycle, and amplitude to 1.0 (0dB, no gain change). The time duration will be 1 second. Default values can also be used, by leaving out one or more of the parts after Wsin; the above values match those in a script consisting only of Wsin, but more generally default values are context-sensitive (especially handling of time lengths).

The W above is the name of the wave oscillator type, and written to add an instance of it – immediately followed by a wave type name, in this case sin, or none to use sin as the default (though there's more; see the wave type table). Such an oscillator is also called an "FM operator" in FM synth terminology, especially when it's a sine wave oscillator (though other kinds of audio generators can also be put to the same kinds of use).

Numbers used above are simple examples of numerical expressions. Positive numbers are the simplest to write. Fancier things are also possible, including randomized values. Phase values are treated specially to be simple to write. There's also a syntax for writing frequency values as notes.

If several audio generators like the above example are used (with or without different values), then you'll get the result of all playing at once – unless you use timing syntax to arrange them in time. The mixing amplitude per script will be scaled automatically so that (if you don't increase amplitude for generator(s)), it will fit with no clipping.

More precisely, when a script has several top-level or carrier audio generators playing at the same time, the overall amplitude for everything is scaled down throughout the script by the maximum number of audio generators playing at once. This adjustment can be disabled, using a manual adjustment instead – or in addition.

Dynamic changes

The above example, and various others, begin and end with a little click – this can be avoided by fading in and out the audio. The easiest way to do that is by using envelope parameters. Below you see a.e[a0.01 r0.01] added, which sets a linear fade-in and a fade-out time of 0.01 seconds. This can be done once per audio generator used. (Envelopes have more parameters as well, and can shape sounds further.)

Wsin f440 p0 a1.0 a.e[a0.01 r0.01] t1

Various parameters support dynamic changes to their values. Other ways to vary things include plugging in modulators using modulator lists. There's also the one-off value sweeps that can be used to move a parameter's value once – unlike the repeating pattern you get with envelopes when reusing an audio generator that has them, playing several sounds in a sequence.

These features for dynamic changes can all be combined, and where envelopes are available for a parameter, sweeps and modulation with value ranges also are, and vice-versa. An envelope uses a range of values with two bounds, moving the result between the two bounds using an internally generated signal. Value range modulation can also be used to pick values between bounds using modulators – if this is done together with using an envelope, the envelope is applied on top of that.

Using modulation

Audio signal generators can be used in a nested way in scripts, to apply modulation of some type. This places them in some particular mathematical relationship, the modulation type amounting to a kind of operation that links them.

The most central modulation type in SAU is arguably PM – commonly called "FM synthesis" in commercial synthesizers (though there's also "real" FM, more on that further below). As in this example which nests wave oscillators.

MP3
// Generate 10 seconds of "engine rumble"
Wsin f137 t10 p[
	Wsin f32 p[
		Wsin f42
	]
]

The oscillators with frequency 32 Hz and 42 Hz are listed beneath the main one as modulators, linked in a chain which ends at the carrier (with frequency 137 Hz), and play for the same time (10 seconds) – the default time for a modulator is auto-fit, to the carrier time used, but a shorter time can also be used. (The p is for phase, and PM means adding modulator amplitudes to the phase.) Above, the amplitudes are all left at the default 1.0; those of modulators determine what is often called the modulation index or "depth" of modulation. (For PM, a modulator's output of ± 1.0 is scaled to ± 50% of a cycle, or ± π in radians.) The first line is just a comment.

For a modulator of any type, frequency can also be set relative to the carrier using the r (relative frequency) parameter instead of the usual f (frequency) parameter – and the default for a modulator is r1, the multiplier 1.0, keeping in tune with the frequency setting of the carrier. Whichever option (f or r) was most recently used to set the frequency will be used to get it. More specifically, a value set to r will be multiplied by the frequency of the closest carrier in the chain for the modulator. For example, a modulator with r4/3 will maintain a frequency 4/3 times the frequency of its carrier, i.e. a 4:3 modulator:carrier frequency ratio. Changing the f42 in the PM example above to that gives a somewhat different sound.

Frequency and amplitude can also be given dynamic values using modulator outputs. This includes "real FM", amplitude modulation (AM), and ring modulation (RM).

Sweeping parameters like amplitude or frequency is a little like using modulation features, except that the source of the change is not a generator object, but simply a one-off timed trajectory. Modulation can also become more interesting when that's done to parameters altering the result, changing what a modulator does over time.

Finally, the result of PM and of other types of modulation may differ – sometimes not audibly, sometimes very audibly – on changing the phase for a carrier or a modulator (with a number after a p), as it affects how the waveforms produced by the two line up and in turn interact through the type of modulation.

Frequency and phase modulation and their varieties

Mathematically closely related, FM and PM are a little different, and often confused (especially as "FM" in commerical synths is most often PM). FM applies to the frequency parameter (f or r), while PM applies to the phase parameter (p). They produce strongly related but not identical results, and can both be useful. Here's some more on their nature and use in general.

FM and PM are most commonly used with sine wave oscillators, but can also use different waveshapes – in any case building richer sounds and frequency spectrums from simpler ones. Adjusting the strength of the modulation plays a role analogous to that of adjusting a filter in subtractive synthesis (where instead the starting point is a rich frequency spectrum which is reduced). More mellow waveforms can be used more flexibly for modulation without creating an overly harsh or noisy result. Extreme settings are allowed, including making frequency go between positive and negative (called through-zero behavior; negative frequency means the waveform changes backwards) or having the phase jump around in basically any way (noisy as that can be). An audio generator which can be used for FM or PM is called an "FM operator" on many synths; here the full-fledged ones include both W and R.

When using sine wave oscillators (Wsin), FM and PM behave similarly – until more levels of carrier-modulator linkages are used. FM and PM then produce different waveshapes unless the input is different to match the difference between FM and PM. If the modulator uses a different waveform than sine or cosine, then this is also the case with just a pair of oscillators. Using PM, a triangle wave modulator has the kind of effect which a square wave modulator has with FM. To understand this, it helps to think of how the phase and frequency are related...

If a square wave FM modulator is used, then as a result frequency is either lower (phase moving slower) or higher (phase moving quicker). With a triangle wave PM modulator, when it rises, the phase of the carrier keeps getting pushed ahead more than usual at a steady pace until the peak of the triangle is reached, and then when the triangle falls, the carrier phase is instead held back – with the same result as if jumping between two frequencies, as there ends up being two speeds of movement for the phase. (By contrast, a square wave PM modulator would cause the carrier waveshape to jump back and forth between different phase positions, i.e. parts of the waveform. And a triangle wave FM modulator sweeps the frequency linearly up and down over and over again, instead of making it jump up and down all at once.)

With FM, it becomes important whether or not a signal has any DC offset – because the average value of the modulator will change the average frequency of the carrier, up if positive, down if negative. And when chaining several levels of carrier-modulator oscillators, the result is almost always an assymetric waveshape which usually has DC offset. So using FM in a more elaborate way can make the general pitch of a sound drift around. This is not the case with PM – and that's why PM is so often favored – because DC in the modulator is then merely the same thing as adding some value to the phase, rather than to the frequency.

Mathematically, adding something to the frequency is like adding the integrated version of the signal to the phase. This also matters when noise is injected through modulation – white noise turns into the more bassy red noise, a.k.a. brown noise, if added through FM, but remains white noise when added through PM. That's because integration of a signal tilts the frequency spectrum 6 dB per octave in the bassy direction, which is the difference between those noise colors, and phase is integrated in relation to the frequency.

Signal flow in the W oscillator and similar

FM/PM (angle modulation) is added to frequency and/or phase.

PD (phase distortion) synthesis can use a stage right after.

Frequency -> Integrator -> Phase -> PD -> Waveform lookup

Related techniques include phase distortion synthesis, which similarly changes the phase values prior to the oscillator producing the waveform using them. This is not to be confused with the concept of phase distortion in signal analysis (which has to do with changing time relationships between different frequencies only, rather than creating richer frequency spectrums by bending the shape of waveform cycles). As implemented by Casio in their classic synthesizers, mathematically PD synthesis is merely a different way to do PM, though the manner in which it's used (angular modulators rather than sinuous ones, and the set of parameters offered) gives results different from Yamaha's PM synthesizers.

By shaping the phase signal used by a digital oscillator, both "phase distortions" like those implemented by Casio and other ones are possible. Other possibilities include a limited form of pulsar synthesis. In both cases, the modulator types available for PM usually can't bring about the same result, especially when PD is done on top of FM and/or PM. The set of PD options in SAU is described further below.

Phase self-modulation, a.k.a. "feedback FM"

There's another type of PM which involves a feedback loop, prior amplitudes shifting the phase resulting in the current amplitude. This results in a different frequency spectrum than does ordinary PM, and the two have been combined since the early 1980s in "FM synthesizers" as made by Yamaha. However, the commonly used name for phase self-modulation is "feedback FM" since that's what Yamaha labeled it. (A similar feedback mechanism truly affecting frequency instead – not currently provided in SAU – would give a different result, and has been labeled "loopback FM" in implementations.) It can be used both with constant and varying (value range modulated) values set to p.a, as further described below. (Alternatively, values can be set to p[a].)

Characteristics. Self-PM sawtooth-ifies the waveform and adds sawtooth-like harmonics. A positive multiplier for the feedback makes it contract the rising part of the wave while expanding the falling part of it, while negative multipliers do the converse. In either case the result is a sawtooth-ish wave, whether falling (positive multiplier) or rising (negative multiplier). However, there's a limit to how sharp the sawtooth can get, and how strong a feedback intensity can be used, before noise and chaos results. When the feedback intensity is great, ringing appears in the waveform, and eventually it becomes noisy; still more and it sounds glitchy, and still more and it transforms into white noise eventually.

The p.a phase amplitude feedback parameter controls self-PM. For a sine wave as the underlying waveform, a value of ± 2⁄3 is close to a maximum for a bright yet clean sound, while ± 1.0 gives some audible sample-rate dependent ringing; values can be greater as well. Compared to classic synthesizers, 0.5 corresponds to level 5 in Yamaha's chips. Filtering differs, so 1.0 sounds cleaner than the Yamaha level 6 it otherwise corresponds to. A value of 1.0 also corresponds to π radians (meaning ± π feedback amplitude internally), if you look at the mathematics of this kind of synthesis. (If you want to match other values of Yamaha's 1–7 scale, multiply by 2 to go up and divide by 2 to go down, repeatedly. But you may need an extra 1.5–2.0 factor boost if you truly want a similarly noisy sound to result for high levels given the filtering difference.)

Traditional uses are simple, like setting a fixed low to moderate value (often a small fraction) for an oscillator. When used together with ordinary PM, usually it's the innermost PM modulators that have some level of feedback set, not the PM carriers; this can sometimes work as a substitute for a longer chain of nested PM modulators, as when producing a sawtooth-ish result. (Often two levels are used for that too, though, as this allows for a sawtooth with sharper ends.) Whatever shape of waveform may generally arise from a PM modulator-carrier frequency ratio, increasing the modulator's self-modulation level sharpens it and brightens that spectrum, also making for example squarish waves more squarish.

Not only sine waves can have self-modulation applied to them; all the wave types possible using the W and R oscillators can – but the result may end up looking more like a deformed sawtooth, and strong ripples and noise may happen at lower feedback levels than with sine waves (this is more so the stronger the harmonics of the underlying waveform used).

Dynamic levels. Both sweeping of the p.a value and the full set of value range modulation options can be used to vary the value used for the parameter. Below a modulator is used to add a ± 1⁄2 amplitude signal to a 1⁄2 value set to the parameter, LFO-ing the intensity.

WAV
Wsin p.a1/2[Wsin p-1/4 f1 a1/2] t3

To change the intensity of self-PM as its own kind of modulation of a modulation may sound similar to tweaking the filter of a subtractive synthesizer's sawtooth oscillator.

Frequency-scaled PM

There's two different ways to connect a PM (phase modulation) input to a carrier, normal PM (p[...]) as described above, and the similarly-used frequency-scaled PM (p.f[...]) which multiplies modulator amplitudes by the carrier frequency (scaled down so that 632.45... Hz, the geometric mean of the 20–20000 Hz human hearing range, makes level "normal"). The best PM type to use depends on the intended sound as the carrier pitch varies. Both modulator types can be linked to a carrier, so the choice can be made per modulator. Both modulator lists can be set either at once (p[...].f[...]), or independently (p[...] p.f[...]).

Changing the frequency of a carrier changes how fast its phase moves independently of any PM. When the PM signal also remains the same while frequency is changed, this changes the proportion of the two sources of phase movement for the carrier. For example, this affects how a vibrato effect from PM will sound as the carrier plays at different pitches; at higher pitch, e.g. twice the frequency, the PM has only half the impact relative to it. Frequency-scaled PM makes a constant "impact" by multiplying the carrier frequency into the PM signal.

However, when modulator frequency is set relative (r) to a carrier as a multiplier for its frequency, it's normal PM that sounds more equal in intensity as the carrier pitch varies, not frequency-scaled. A change in frequency, just like a change in amplitude, will change how much energy (in terms of physics) there is to a signal; to change both the same amount will change signal energy by the squared amount. With both combined higher-pitched sounds become more intense along with the modulator signal.

When comparing normal and frequency-scaled PM, keep in mind that changes in modulator amplitude can counter-intuitively affect the color of the sound, just like changes in the phase offsets for carrier relative to modulator can. To ease comparing and "tuning" sounds, the named constant mf can be used for the 632.45... Hz number when setting parameter values. For example, if a carrier has a frequency of 100 Hz, then setting the f-PM modulator amplitude with a(mf/100) brings the same result as for a normal PM modulator with a1.0. Such a setting can be used for all generators in a modulator list at once, by using the S a option at the beginning of it.

Phase distortion synthesis

Phase distortion synthesis is mainly associated with Casio-style synthesizers, from which the name comes. Mathematically, Casio's PD synthesis is merely a different way to do PM, though the angular modulator shapes and parameter choices gave very distinct results. As implemented in SAU, PD includes both some Casio-style (p.x and p.y) and some other options, which can all be used together. A separate article describes these options and related concepts in more technical detail.

Each option below offers a way to distort the waveform made by a R or W oscillator by distorting the phase signal driving the oscillator. (These options can also be written inside p[...] instead of with a p.-prefix each.) For a non-fluctuating distortion, a constant value other than the default can be entered. Value range options can also be used for each option and its subparameters .f and .p. The distortions are applied one after the other in the below listed, fixed order; this is after any FM and main PM input, and before any self-PM done during waveform lookup. This sequence was chosen to give the most intuitive and useful result when operations are combined.

p.c
Cycle length or "zoom out" PD; the default value 1.0 does nothing. Values above 1 "zoom out", e.g. 2 will double the cycle length, so the old shape then takes up the first half with horizontal line padding after (the line having the cycle boundary amplitude). With a value below 1, the effect is to "zoom in", such as reducing a sine to the first quarter with 1/4. Negative values work like positive ones, but also flip the direction of the waveform, in the same way as changing the sign of the frequency used does.

Amounts to a basic form of Pulsar Synthesis; waveforms for "zoom in" differ, and further options, for PS-specific waveform shaping, are missing.

Using modulators with audible frequency for this value will often sound somewhat like PM but bright and buzzier.
p.d
Duty cycle or "zoom in" PD; the default value of 1.0 does nothing, values close to 0 "zoom out" and above 1 values "zoom in". This is the multiplicative inverse of p.c, but both are set and applied independently. It's the PulWM of Pulsar Synthesis. Compared to p.c, this is the option allowing a full "zoom out", instead of a full "zoom in". When sweeping or modulating one of them, the part of the range close to 0 is that which will audibly come and go quickly.

Using modulators with audible frequency for this value will sound somewhat like doing it with p.c, except for being both more deep and brightly buzzy. It has the richest sound on being modulated compared to the other PD types.
p.h
Hold for cycle portion phase distortion; the default value 0.0 does nothing. A positive value like 1/4 is used to extend a horizontal line from the beginning, overwriting e.g. 1/4 of the cycle. A negative value, by contrast, extends the line from the end backward, so that -1/4 overwrites the last 1/4 of the cycle.

Using modulators with audible frequency for this value will often sound a little like PM, but much thinner and sharper – and large amplitudes result in silence.
p.x
An x-axis half-cycle length PWM-like PD. Generalized PWM (pulse-width modulation) in this way makes pulse waves out of square waves. It makes other wave types take on related shapes too, stretching/squeezing the waveform around its "center" in a cycle. The default value 1/2 does nothing. 0 shrinks to nothing the 1st waveform half, 1 the 2nd; values beyond these bounds are allowed, creating different distorted shapes.

Using modulators with audible frequency for this value will often sound somewhat like PM but warmer, fat, and buzzy. This and p.d both tend to sound deep when modulated, but this sounds less bright.
p.y
Inverse of the x-axis half-cycle lenght PWM-like PD, p.x. Where PWM changes length proportions of halves of a waveform while preserving their shapes, instead this distortion deforms one half, by reducing change in phase inside it, the other half being accelerated and displaced to compensate. Default 1/2, meaning no change, with 0 and 1 extremes – with values allowed beyond as well, creating different distorted shapes.

The effect is identical to that of using a PM modulator which is a triangle wave (especially with frequency ratio 1:1). Using modulators with audible frequency for this value corresponds to PM nested a level deeper, under such a modulator. It will often sound somewhat like less-nested PM but brighter, yet smooth.

Each of these above-listed options has the following subparameters.

.f
Subfrequency, default 1. For p.c and p.d, this is a frequency multiplier for the non-padding part of the waveform, applied independently for each cycle. For the other PD types, it instead subdivides the cycle into a number of regions, the distortion being applied to each; using an even integer for the value will make the distortion produce odd harmonics only.

Non-integers cause discontinuities.
.p
Phase offset for the cycle boundary position. This rotates where the distortion is applied. See Cycle position values for more. (This parameter differs in that values reaching integer multiples of 1.0 shift seed position for the R oscillator, much like strong PM inputs.)

Phase self-modulation (the p.a option) is worth mentioning here as well, as it is similarly an option that warps the waveform and does so by bending the phase signal in the oscillator. It can be used in the same places as the PD options above, and can be combined with them (being applied last).

The way the PD and modulation options are applied in order, results of using several tend to be straightforward. If using the "zoom in/out" p.c and/or p.d distortions (which are applied before other PDs), the "zoom out" proportion will be preserved if other PD is done after (the later PD distorting the non-padding part of the waveform). If self-PM (always applied last) is used, it will bend whatever waveform is arrived at beforehand in order to slant it piecewise like a sawtooth.

While there's much more to possible uses, these controls can be used for trivial waveform variations as in the example PD waveform recipes.

Amplitude/ring modulation and FM

Amplitude (a) and frequency (f and relative frequency r) – and various other parameters too – support modulation of the parameter values in the same ways, by adding modulator outputs to a value as described here, and/or by mapping modulator outputs to a range with two boundaries. For amplitude, whether the result is called amplitude modulation (AM), or ring modulation (RM), depends on how carrier and modulator amplitude are set up relative to one another. For frequency modulation, the result is however always the "real FM" related to yet distinct from PM, whenever modulation happens.

Modulator outputs can be used added to the main value for a parameter; this works basically similar to using the main option for PM described above, except that phase changes in an oscillator (with time and frequency) while other parameters (amplitude multiplier, frequency setting, etc.) don't "wander" unless swept. The modulators to use are written within [] in a modulator list, their output amplitudes summed and the sum used added to the main value.

With a carrier amplitude setting at 0, as in a0[...], amplitude fully depends on the modulator output, becoming the mathematical product of the two. This is called ring modulation. With a non-zero carrier amplitude setting, the result of adding the modulator output to it is instead amplitude modulation of some depth.

For example, here's a softer square-wave like beep made using ring modulation with two different wave types (it ends up having a base frequency of 220 Hz, half of the carrier's 440 Hz frequency default).

MP3
Whsi a0[Wsin r1/2]

For FM, usually frequencies are set to something non-zero, but sometimes a frequency value of 0 can be interesting. Then the only thing "driving" the carrier, so that it produces a non-constant result, is the modulator(s) – forwards when frequency becomes positive, backwards when it becomes negative. With FM, unlike PM (which can be used to a similar effect), a much larger amplitude may however be needed to have enough impact on the carrier. Numerical expressions make it easy to e.g. try out small-ish powers of 10 as the amplitude of a modulator. Below a simplistic imitation of an electric guitar sound (sounding almost like a siren) is made this way, with the help of some value sweeps. (The last sweep removes the click when the sound ends.)

MP3
Wsin f0[
	Wsin p1/3 f44[g440*pi llog] a10^met(3) t2
	; f[g44]
] t4; a[g0] t0.01

When there's a list with several modulators in it, it may be convenient to set a multiplier for all of their amplitudes. This can be done using the S a option for amplitude, inside the [] list at its beginning; doing so only affects the current list level and scope, except when the above type of amplitude or ring modulation is used – otherwise further nested sublists are untouched by the amplitude setting, for the sake of e.g. preserving timbre when using it to adjust volume.

Note that modulator lists for the f and r options are shared and identical.

Using the separate channel mixing parameter c, a stereo effect panning-AM is also available. (Adding a modulator on top of its center-balance value of 0.0 makes for classic AM in opposed, complementary patterns for the left and right channels – an effect that disappears on mono downmix.)

Modulation with value ranges

For amplitude (a), frequency (f or r), and various other parameters all support modulation with subparameters for value range mapping. The text for each parameter supporting this refers to this section.

For every parameter supporting this, the result of modulation is used in place of the main value for the parameter. A second value for the parameter can be set, defining the other end of a range (with the main value defining the first), with an extra list of modulators used to select values in this range. There's two syntax variations for this, the short form used below, and a long form with more options described after. It can also be combined with the other modulation style which just adds the modulator outputs to the main value (or to the result of range mapping if that's also done), described above; it's supported everywhere that value range modulation is, and not only for AM & FM.

The .r range subparameter is used by itself in the short form syntax, values for it supplying the second value and the modulator list needed for using a value range (instead of having a leading .. range as marks a long form expression). For example, an expression like f250.r(250 * 2)[...] sets up the use of whichever modulators are placed within the [] to move frequency between 250 Hz and 500 Hz. The second value defaults to 0.0 if left out, and is only used for this kind of modulation; it can also be swept like the main value, by using the same syntax in the place for the number.

Especially when several modulators are used for .r, the handling of signals from the modulators is different from how it's done for other modulator lists. The modulator outputs are multiplied together in a weighed way (towards the bottom rather than center) rather than added to one another. If a negative amplitude setting is used for a modulator, its signal will have the amplitude range top and bottom flipped before the scaling and multiplying into the combined product. When several modulator outputs are multiplied together, the weighing adds more bias towards the lower end of the range as more signals are combined – broader valleys and narrower peaks, much like in many envelope shaping signals.

Note that changing amplitude settings for the modulators to something other than ± 1.0 for each, while allowed, will effectively change the range set outside the modulators to another. Internally, the signal from each modulator is kept in a range going from 0.0 to a higher bound determined by the amplitude setting. 0.0 is mapped to the main value outside, while 1.0 is mapped to the second value outside, so if the higher bound internally doesn't match 1.0, the range will be bigger or smaller in that way as a result.

A simple LFO FM example, where frequency is varied between 250 Hz and 500 Hz using a 0.1 Hz sine wave.

Wsin f250.r500[Wsin f0.1] t10

A simple AM example, where amplitude is varied between 1/4 and full, using a half-rectified sine wave with 1/5 of the carrier's frequency.

Wsin f200 a1/4.r4/4[Whsi r1/5] t5

To use this for classic 100% modulation depth AM, one of the bounds should instead be 0.0 (like the default for the second value is); while for classic RM, the two bounds should instead have the same magnitude, but with the opposite sign.

If this type of modulation is used, it is done first; the other one from which the modulator outputs are simply added to a parameter's level, if also used, will be applied afterwards.

Long-form syntax for modulators added to each endpoint

The long form version of value range modulation syntax makes it possible to add modulation to one or both endpoints of the range individually. That's in addition to the modulators that just add something to the final result when combined with value range modulation, which go with the main parameter in the short form syntax.

Here's a comparison of the two syntaxes. The long-form syntax in full has the parts value[A]..value[B].r[C].a[D] – where A, B, C, and D are modulator list contents. It allows modulators from A and B, whose outputs are added to the endpoints (first value, second value) prior to using range mapping modulators (C) to pick values in the range, in addition to modulators with outputs added after (D). The short-form syntax by contrast looks like value[D].rvalue[C] – it places the last part first, and reduces the subparameters to just one.

The use of the .. subparameter not only separates the main value and the second value, but it also changes the meaning of any modulator list that goes before. If .. is not written, the leading part is interpreted in the short form way, while if .. is added it is given the meaning belonging to the long form. Everything else part of the long form can be left out. The parts of the subparameter chain if used must be written in order (no prior names can be left out), but there's no need to write more after what you're using. Each subparameter name can be given its values, but can also be written without any values as with named parameters in general.

Distortion of nested AM input under range-mapping .r[]

Nesting AM use further levels inside of .r range mapping modulation can bring distortion depending on the parameters. When AM is set up for the a parameter for a generator inside .r[...], then the final input sent to a may interact so as to become half-wave rectified, full-wave rectified, or some mixture (which may vary over time if the AM carrier does). This specifically happens if the inner AM signal is out of range for a clean result. This is unique to .r[...]; nested AM inside the other modulator lists doesn't have this behavior.

Mathematically, this all comes from the expression y=(x*a + abs(a))/2, where x is the signal value to be modulated (-1 ≤ x ≤ +1) and a is the amplitude modifier (the result of everything attached to the a parameter, which contains AM modulator input). This expression describes how range mapping modulator signals are scaled to 0 ≤ y ≤ abs(a) before mixing by multiplication. (Outside of range mapping lists, all other signals simply use the expression x*a and are mixed by summing.)

MP3

This only colors the sound when a in the expression above is neither constant nor varying slowly like an LFO, but varying faster. Here's an audio example of full distortion of a test-tone (440 Hz) sine in this manner, the shape of the distortion oscillating at 1 Hz with the AM carrier. (If the outer A-1.r+1[...] is removed, you instead only have ring-modulation by a 1 Hz wave.)

A-1.r+1[W f1 a-1..+1.r[W f440]] t8
MP3

Reversing the two frequencies used results in a clean sound – unlike above it sounds identical in this case to if the outer A-1.r+1[...] is removed, i.e. the same with and without distortion. The difference in the waveform is simply that when amplitude drops, the version using the "weird" mixing has it drop to the bottom, while the version without has it drop to the center – a subsonic difference.

A-1.r+1[W f440 a-1..+1.r[W f1]] t8

Finally, the question of distortion disappears with a change to the range of values for the inner a, making the values always positive. All of the following three variations make identical waveforms – clean-sounding, and compared to the clean audio above, the tremolo is at half speed and ramping up and down sinuously instead of with a "sine parabola" shape.

Bottom-clipping. A plain half-wave rectifier can be made in two steps, using the A amplitude or DC offset generator. A-1.r+1[...] works like a wrapper around the "..." part which does not change the waveform at all. But then adding A0[...] as an inner wrapper results in a half-wave rectiified "..." signal. Doing so, one may also wish to adjust the outer amplitude bounds if the peak amplitude is not meant to be centered and doubled. For example, A0.r1[A0[R f440]] will use the R oscillator to generate a smooth test-tone rumble and half-wave rectify it. The clipping point and extent can also be shifted by tweaking the inner values, but if you want to avoid needing to do some arithmetic to get it right, using value range modulation for the inner AM too is easier – an inner A-1.r+1[...] will have the same effect as an inner A0[...] for one innermost modulator. Changing the lower bound here for the inner AM will change how much the waveform is pushed down and clipped off – nothing for 0.0, half for -1.0, and so on. If you wish to modulate this parameter, a modulator can be set for specifically the lower bound using the long-form syntax.

To understand how this bottom-clipping works, it helps to consider A as a 0 Hz signal generator with amplitude scaling applied to it. The signal 1.0 repeated is scaled with the amplitude value and/or AM modulators. Range mapping modulation internally yields a 0.0–1.0 signal range for each modulator, and if the amplitude set for a modulator is negative, the top and bottom of this range is flipped (just like a negative amplitude multiplier generally flips the top and bottom of a waveform). Flipping the 1.0 constant here yields 0.0, which thus becomes the output from A inside range mapping modulation whenever the amplitude set is negative (0.0 being what's mapped to the main value outside). Exactly the same thing happens for a 0 Hz oscillator stuck at a 1.0 amplitude peak, with the same amplitude options set to the a parameter (for example Wsin f0 p1/4 a0[...]) in place of A0[...]. Meanwhile, a 0 Hz oscillator stuck at its bottom would instead clip off positive AM signal while flipping and passing through negative AM signal. And, finally, one stuck at its middle amplitude would full-wave rectify the AM input instead.

Pulsar synthesis

Pulsar synthesis is a less-known form of synthesis that alters the shape of wave cycles in one take on generalizing PWM. Developed by Curtis Roads et al., it often combines several mechanisms to vary the resulting signal. SAU does not support all options found in more full-fledged pulsar synthesis implementations, but it does provide some, and allows PS to be combined with various other modulation and distortion options. The SAU approach to PS implements it as a variation on phase distortion synthesis, phaseshaping in the form of scaling and clamping a phase signal. (The technical picture and pulsar synthesis are described in more detail in a separate article.)

The main concepts in PS revolve around how it divides a wave cycle into a pulsaret – a part of the waveform cycle which is rescaled – and a silence segment which pads the waveform, taking up the space if the former is shrunk. In SAU the silent part is called the padding part or padding line, as it may or may not be consistent from cycle to cycle depending on carrier waveform type. Using the R oscillator will (except in Perlin noise mode) produce a waveform where the padding fluctuates like a (usually random) half-square wave – which sounds similar to, but more rich and noisy than, the silent part being zero or DC. Using the W oscillator, the padding is zero or DC. Regardless, increasing the pulsaret size instead of decreasing it (i.e. a duty cycle above 100%) will cut off the part that doesn't fit in a cycle.

To use PS, the main options are the p.c and p.d "zoom in/out" PD options. The first of these is used as a multiplier for what fits in a cycle length, while the second offers a divisor instead (matching a PulWM duty cycle control). The multiplier form p.c is more convenient for simply changing the frequency of the pulsaret while preserving the base frequency. Using p.d instead allows an infinite zoom-out with the value 0, while zooming in and losing part of the waveform with values above 1. (Both can also be used at once, p.c being applied before p.d.) When modulating the level across a range of values, using p.c makes the zoom-out end proportionally wide in time, while p.d makes it proportionally narrow in time.

Uses of PS often use more drastic parameter values, turning the pulsaret into a small audio "grain" surrounded by padding. A low frequency f value combined with a high pulsaret frequency (by using a large multiplier for it), can make a bright, rhythmic clicking sound. The range of variation includes both uses for timbre and for rhythm. Another way to vary the result is to change the number of cycles of the original waveform placed in the pulsaret – this can be done using the .f subparameter for p.c or p.d, for example p.c.f2 will change how p.c is applied so that the pulsaret has 2 cycles squeezed into it.

Missing from PS features in SAU are proper pulsaret envelopes especially. More full-featured PS implementations allow an amplitude-shaping window function to be applied to the pulsaret, while the results of the above options are limited to using a rectangular envelope or window only. Another feature, pulsar masking, is however possible to approach by combining PS with AM – adding and using amplitude modulators to sometimes silence the signal.

These controls can also be used for trivial waveform variations, as in the example PD waveform recipes.

Values and expressions

There's several kinds of values and expressions that look very different in SAU. Most basic to any script that really does anything (produces audio) is the adding of objects of a named type – specifically using the types of signal generators. The use of such a type name, as in the most basic examples of generating audio, adds a value (the generator object) and is an expression, which can also contain subexpressions like parameter assignments and their subexpressions in turn.

A assignment consists of a name followed by a value – for parameter assignments which have one-letter names, without any symbol in-between. It is a kind of expression with two subparts, the name part and the value part (where the value part may have further subparts). There are various types of value:

Comments are text which is ignored, treated the same way as whitespace is; several comment styles are supported.

Signal generator types

Generator type names are a single uppercase letter each, like W and R for the varieties of oscillators (generators with frequency parameters). There's also the pseudo-type S, which is used as if it were some oscillator type, but has the effect of changing default values and other script options instead of adding any object.

For generator types which use random number generation, like N and R, each new instance created in a script will be given a different default starting point or seed for producing random numbers. The default seed is based on the $seed magic variable and a random number sequence derived from it. For each instance, the seed can be overriden by setting the s seed parameter, which accepts a number which is used modulo 1.0 as a percentage of the state space.

The seed sequence is deterministic unless changed to be based on system time, as can be done using the time() mathematical function.

When $seed is set, it also seeds the rand() function. Using rand(), and adding instances of seedable types like R, however involve two separate random number sequences derived from $seed, which do not affect the other.

Script S options for default values and more

The capital letter S can be used to access and change options for default values which apply in part of the script, and more. Some settings can only be accessed through this option. Apart from that, this can be handy as a way to change a series of new values for e.g. oscillators; the same lowercase names are used after the S to access settings as would be used for the oscillator.

For example, to use a default f frequency value of 100 Hz for new oscillator-like sound generators, then S f100 would make that apply afterwards. Or to downscale the amplitudes for a series of audio generators (more below on that), S a1/16 could for example be used.

Changes made in this way apply in the current [] list scope, and also in lists nested more deeply – but not outside of the current list if it was done in a list. The amplitude setting is however special in that it has independent multipliers at each level of lists used for modulators, so that volume control does not inadvertently affect timbre and more by changing modulator amplitudes (with the exception of AM/RM modulators, which are adjusted in order for them to have the expected impact on the carrier amplitude).

a
Multiplier for amplitude a values after, in the current scope of [] nesting. Starts at 1.0. The multiplier also applies to the second value for value range modulation, and to the multiplier inside any AM modulator list except the range-mapping .r[] list. It does not apply to other nested lists for modulators, that is modulators for other paramters; their multipliers apply unchanged.
.m
Script-wide gain mix control (multiplier). If used, this disables automatic down-scaling of amplitude by the number of voices (carrier audio generators simultaneously producing signals), for manual control instead. Can only be set in the outermost scope.
c
Default channel mixing c value. Starts at 0.0, i.e. C (center). Useful as a main way of setting the parameter value.
f
Default frequency f value, in Hz. Starts at 440.
.k
Key selection for f values using note syntax, default C4. For a just intonation (JI), the note for this key will use the first ratio in the scale. Also changes the default and relative octave. Can be C, D, E, F, G, A, or B, with or without one of b or f (flat), s (sharp), d (half-flat), z (half-sharp), v (flat-and-a-half), or k (sharp-and-a-half), w (double-flat), or x (double-sharp) – and/or an octave number (0–10) to move the default from a 4–5 range to one of the number to the number plus one.
.n
A4 tuning frequency in Hz for f values using note syntax. Starts at 440. For example, use S f.n432 for 432 Hz.
.s
Tuning system, either e (24-EDO, default), p (Pythagorean JI), c (classic 5-limit JI), or j (SAU 7-limit JI).
r
Default relative frequency r value, a modulator:carrier ratio. Starts at 1 (1/1, a "1:1" ratio).
t
Default short definite time t value, in seconds. Default times may be longer (and occasionally shorter) depending on the context. Starts at 1.0.

A – Amplitude generator

Amplitude generator A generates sweepable amplitude offsets, i.e. DC offsets. For convenience the amplitude a parameter, otherwise set as a separate parameter after the type name, has its values read right after A (e.g. A1.0 is the same as A a1.0). The current value is output as an offset.

It can also be used for mixing, as A0[...] produces the sum of the AM modulators and nothing else. This can be used to pan those sounds in one go. Modulation with value ranges can also be used to multiply AM inputs. Instances allow greater mixing flexibility inside modulator lists of either type, belonging to any object. Using A also makes it easy to use a quirk of nested range mapping AM for distortion.

Shares the basic amplitude a, channel mixing c, and time t parameters with all generators.

N – Noise generator

Noise types for N
NoiseDescription
wh Uniform white noise.
gw Gaussian white noise, soft-saturated.
bw Binary white noise.
tw Ternary smooth white noise. Every other sample is zero.
re Red/brown noise based on uniform white noise.
vi Violet noise based on uniform white noise.
bv Binary violet noise. Differentiated tw noise.

The noise generator N is the simpler, and by itself less musical, relative of the R oscillator. It simply creates a full noise spectrum with some chosen color and distribution. Unlike R, it has no frequency or phase parameters, hence there's no pitch. It produces a signal at the maximum rate for the sample rate, and noise colors other than white may sound different at different sample rates for that reason.

Plain noise can be interesting to add as a modulator to change another sound. Adding it to amplitude, frequency, or phase each has a different effect. AM/RM just adds noise to the modulated signal, scaled according to the latter; ring modulation makes a more thin sound than amplitude modulation. For FM and PM, the resulting color differs – adding white noise to an f input will lead to less-harsh red/brown noise coloring the sound, because frequency is integrated in order to produce phase in an oscillator. Adding violet noise to f, on the other hand, will lead to a white noise coloration for the same reason. For phase p on the other hand, the result sounds more like AM, but it results from adding jitter to the waveform which keeps the peak amplitude unchanged.

Non-white noise from N has a sample-rate dependent sound. Other means are needed if you want it the same at different sample rates. For violet noise and similar, that can be done using the R oscillator with mode mv, or 1D Perlin noise with mode mp; for the latter the spectrum peaks at the chosen base frequency set to f, instead of a little below.

Special N parameters

A noise type optionally follows the N, with wh used as the default if none. A lowercase n can be used to change the noise type later, e.g. Nwh t1; nre.

For convenience, N allows values for the amplitude parameter a written just after N, similarly to how that works for the A-generator – with the difference that a number for amplitude must be set apart from the leading label for noise type by a mathematical symbol, e.g. by placing parentheses around the number. For example, Nwh(1/2) is equivalent to Nwh a1/2, and N(0)[R] is equivalent to N a0[R].

R – Rumble/random line segments oscillator

Line types for sweeps and for R
LineDescription
cos Half cosine (S-curve) trajectory over time.
lin Linear trajectory over time.
sah Sample and hold until time (then jump to goal).
exp Steep exp(x)-1-like increase or decrease.
log Steep log(x+1)-like increase or decrease.
xpe Exponential envelope shape (saturate or decay).
lge Logarithmic envelope shape (saturate or decay).
sqe Square polynomial envelope (saturate or decay).
cub Cubic polynomial segment (-1 to +1) trajectory.
Spiky ends, the opposite of a sinuous shape.
smo Smoothstep (degree 5). A sinuous curve adding
mild odd harmonics like a soft-clipped cos.
Traditionally used for Perlin noise.
ncl Noise camel line; softer, two noise bulges.
nhl Noise hump line; harder, one broad noise bulge.
uwh Uniform random white noise in start–goal range.

The rumble oscillator (a.k.a. random line segments oscillator) R can produce several kinds of audio value noise as well as 1D Perlin noise. While the parameters are mostly like those of a normal oscillator – e.g. almost everything written after Wsin can also be written after Rcos – the resulting frequency spectrums are very different, and various amplitude fluctuation patterns can be created. It can be used by itself or, typically, combined with wave oscillators in order to produce more complex sounds and soundscapes.

Invented for this language, the underlying design of R adds several independent ways to vary what's done and the result; it's a fairly flexible building block which can be made to do a lot. By default, however, it uses uniform white noise as an underlying function, sampled at a limited frequency (two values per "cycle", e.g. 880 per second at 440 Hz), the pseudo-random values connected by the sinuous cos S-curve line type.

When white noise is used, then unless the frequency set is extremely high (e.g. the maximum, half the sample rate), the spectrum which results actually begins to roll off gently before the frequency used, reaching close to the peak level at half the base frequency, and before that extending backwards flatly across fractions of the frequency all the way down to 0 Hz. In other words, white noise modes make this a rumble oscillator – though the low-frequency content has its energy diluted more when higher frequencies are used, as the intensity is spread across a larger frequency range.

The violet noise modes allow producing less bassy signals, the frequency parameter then also controlling the point below which frequencies are reduced in the spectrum. Contrasting with any noise mode is the fixed cycle mode mf, which additionally allows using an R instance like a naive oscillator, where the line type selected determines a resulting wave type – but a mix of that and white noise (or violet noise if mfv is used) can also be set by tweaking the shaping level.

1D Perlin noise is a different way to use the values from the underlying noise function, sounding a little like violet noise except more gentle, and placing the loudest frequency exactly at the base frequency. It morphs the line type and waveform in a way that sounds relatively similar, but looks different. (As a result, it also aligns the phase with a sine wave instead of with a cosine wave as the R modes otherwise do.) This can be combined with any other options, combining gracefully with different randomness functions and with violet noise modes for even more thin noise. (The zig-zag flip option z however gives a different result, producing a squarish shape instead.)

There's various ways to make sounds using R. It can be used at an audible frequency, or for LFO variation of other sounds, or a low-frequency R oscillator can be modulated into producing brighter sounds by another generator. (The last use-pattern makes the sounds rise and fall in the manner of pseudo-random pseudo-melodies.) There's some example scripts available to look at and listen to.

Special R parameters

A line type optionally follows the R, with cos used as the default if none. A lowercase l can be used to change the line type later, e.g. Rcos t1; llin.

For convenience, R allows values for phase p to be written just after R. (Similarly to how this works for the W oscillator.) This allows quick access to phase distortion synthesis options for tweaking the waveform, as well as PM. If a main phase number is provided, it must be written after a mathematical symbol, e.g. within parentheses, to distinguish it from any line type label. For example R[W] is equivalent to R p[W], and Rcos(-1/4)[W] is equivalent to Rcos p-1/4[W].

The nature of the randomness can be tweaked with the mode parameter m, changing how the pair of values connected by lines each cycle are arrived at. A mode string for m can have a letter (to select a noise function), a digit (a 0-9 shaping level), and/or extra flag letters, in any order. The default level is 9; roughly, each level above 0 halves what remains of the unshaped underlying randomness. The functions are...

u
Uniform random (default). Ignores the level setting.
g
Gaussian random, soft-saturated approximation. On average ~6 dB quieter. Ignores the level setting.
b
Binary random. Extreme levels, more repetitive runs.
t
Ternary smooth random. Never repeats twice in a row; cycles above or below zero, randomly flips polarity.
f
Fixed cycle. Plain naive oscillator at the top level; below it, mixed with randomness at reduced amplitude.
a
Additive recurrence low-discrepancy a.k.a. quasirandom sequence. Ignores level digit. Uses subparameter .a.

In addition to the function and level, these noise flags can be set.

h
Half-shape waveform. Use with lin for a decreasing sawtooth instead of a triangle wave; similarly changes the shape for all line types and randomness modes.
p
Perlin noise mode. This reshapes the waveform so it returns to 0 at each cycle boundary. This removes the lowest frequencies, and shifts the peak of the frequency spectrum to the base frequency. Can be combined with any noise function and other mode flags, including violet noise modes for extra-thin noise. Transforms binary noise into a smoother noise. The amplitude may drop for ternary smooth random mode t.
s
Square, then restore sign, of the start/goal values. Turns uniform value variation into uniform energy variation; somewhat quieter, and more tremulant. Doesn't affect b, t, nor f with level 9. Distorts v violet noise toward white, as if mixed.
v
Violet rather than white noise version of the function if available; for u, b, and f, missing for g, t, and a. Like high-pass filtering the lower end of the noise, 6 dB per octave.
z
Zig-zag flip. Swap ends of each half-cycle, adding an inharmonic waveform jaggedness unless using h, or f level 9; more difference from these adds larger sharp steps. Always flips the waveform top and bottom.

There also exists these subparameters used for specific modes:

.a
Multiplier constant used for additive recurrence mode, defaulting to "met(1)" (see mathematical functions). Only the fractional value part is significant and used. Note that values like 0.0 and 1.0 result in silence (or DC offset); using irrational numbers gives good results and random ones often work.
For example, to use the silver ratio instead, ma.amet(2).

More sawtooth-like waveforms (including a naive sawtooth from line lin) are possible by adding the flag h (half-shape) to the mode – it switches each wave cycle from using two lines (down or up) each the same horizontal length, to using just one (down or flat) followed by a vertical jump. While the waveform looks as expected, using this option can reduce the purity of randomness (and also makes violet random modes 6 dB bassier); this is mainly provided for LFO and for naive oscillator uses where that doesn't matter.

In place of mode h, it's possible to instead distort the waveform less "perfectly", towards a rounded sawtooth shape rather than a naive ideal sawtooth shape, using self-PM a.k.a. "feedback FM". This also allows gradual and modulated morphing of the waveform. This affects all waveforms, including even squarish and noise line types, in what sounds like a more or less sawtooth-ish direction. (The exception is that if it sounds like bright noise already, then it will remain so. Also, self-modulating very hard will produce "glitches" and noise, eventually drowning out harmonic sound.)

The zig-zag flip flag z for the mode is another jagged waveshape distortion option, with a result depending on the other options – more randomness means a more jagged waveshape, and with a thin and bright inharmonic result. For example, with line type lin, it causes a chaotic mixture between triangle, randomly directed sawtooth, and square-like shapes, with the default randomness mode. It can also combine with mode mb to produce a wave which randomly switches between segments of binary noise and the selected line oscillation after half-cycles, or mode mt for random square steps and a constant line oscillation reaching zero in-between them. Zig-zag mode may be most interesting for creating LFO patterns.

W – Wave oscillator

Wave types for W
WaveDescription
sin Sine. For cosine, set phase p to 1/4.
tri Triangle. Mellow odd-harmonics wave. Opposite of ean relative to par.
srs Square root of sine. (Mirrored for the negative half.) Medium-bright odd-harmonics wave. Opposite of cat relative to mto.
sqr Square. Bright odd-harmonics wave. Opposite of eto relative to saw.
ean Evenangle. Mellow even-harmonics wave. Opposite of tri relative to par. To begin at 0.0 amplitude, set phase p to 6/93.
cat Catear. Medium-bright even-harmonics wave. Opposite of srs relative to mto. To begin at 0.0 amplitude, set phase p to 1/16.
eto Eventooth. Bright even-harmonics wave. Opposite of sqr relative to saw.
par Parabola. (x^2, steep part up.) Mellow all-harmonics wave. Between tri and ean. To begin at 0.0 amplitude, set phase p to 9/87.
mto Mellowtooth. (Half-rectified srs, amplitude doubled.) Medium-bright all-harmonics wave. Between srs and cat. To begin at 0.0 amplitude, set phase p to 1/25.
saw Sawtooth. Bright all-harmonics wave. Decreasing slope; use negative amplitude or frequency (but not both) for increasing slope. Between sqr and eto.
hsi Half-rectified sine. (Amplitude doubled.) Like a somewhat louder ean, harmonics decreasing as fast. To begin at 0.0 amplitude, set phase p to 1/12.
spa Sine parabola. (First half, amplitude doubled.) Slightly cleaner than par. Mainly useful for modulation. To begin at 0.0 amplitude, set phase p to -1/12.

The wave oscillator W is the oldest and most-used signal generator – a pretty plain and flexible oscillator of the kind often used for FM synthesis – especially with the sine wave type, typically inserted by writing Wsin (or just W) in a script.

It produces a (weakly) anti-aliased signal by default, including for FM and PM, but can also be switched to naive oscillator mode (when more aliasing is actually wanted, or if needed for another reason, like the anti-aliasing interacting poorly with some uses of phase distortion synthesis).

Basic wave types are listed in the table. Beyond sin, 3 × 3 complementary wave types are provided, in terms of:

  1. The added harmonics – odd, even, or all.
  2. More mellow vs. bright – whether the higher harmonics are weaker or stronger.

Additionally, there's 2 more wave types listed after these main 10; the ones at the end don't fit as neatly into the main groups, but have their uses as well. You can listen to all the wave types on the examples wave type subpage. It also gives some examples of using options to vary pulse width and other things, to derive more wave types from the basic ones.

More interesting uses of W usually involve using modulation. Usually, modulation either brings a slow, periodic variation to a sound – or when faster, creates "brighter" or more intense sounds from "mellower" or less intense ones; the waveforms available allow for a lot of simple combinations. Many complex and more dynamic sounds can also be created by combining more wave oscillators. Phase self-modulation a.k.a. "feedback FM" is also possible.

Wave oscillators can also be combined with other kinds of generators, in order to produce a larger variety of results. This can be done not only to produce a richer sound, but also sometimes to constrain it; ring modulation by a sine wave can for example be used to brighten the rumbly noise produced by the R oscillator, and reduce the lowest-frequency content, in place of using a frequency filter (currently not a feature) to a somewhat similar end.

Special W parameters

A wave type optionally follows the W, with sin used as the default if none. A lowercase w can be used to change the wave type later, e.g. Wsin t1; wtri.

For convenience, W allows values for phase p to be written just after W. (Similarly to how that works for the R oscillator.) This allows quick access to PD synthesis options for tweaking the waveform, as well as PM. If a main phase number is provided, it must be written after a mathematical symbol, e.g. within parentheses, to distinguish it from any wave type label. For example W[W] is equivalent to W p[W], and Wsin(1/4)[W] is equivalent to Wsin p1/4[W].

The implementation type and behavior of the oscillator can switched using m (mode). For example, W mn adds an oscillator and switches it to be the naive variety. These modes are available.

a
Antiderivative anti-aliasing (default). Weakly anti-aliased result for any combination of wave type, FM, PM, and phase distortion syntheis – but may give unwanted results for some extreme PD waveforms (mainly half-rectifying or full-rectifying using PD). Dampens frequencies close to half the sample rate a little.
n
Naive. Gives the most classic "FM operator" behavior.

Timing

Unless the time position is changed, things in a script all have the same time placement, i.e. they begin or take effect at the same time. So playing several things at the same time is easy – just write one thing after the next – while there's several ways to arrange them differently than that.

A /number, with "number" a time in seconds, adds a delay to everything after; it's the global forward-shift option, added between two parts of a script. The time separator | is related and adjusts the delay added to everything after so it exactly matches the duration, or play time, remaining for things before it. (When both of these options are used together, the order of use is important.)

To generate two tones, separated in time, and also insert an extra 2.5 seconds of silence in-between them:

Wsin f440 t2
| /2.5
Wsin f220 t2

While those two timing modifiers apply to everything which follows after in a script, there's other options which only apply more locally, described below.

Modulator time handling

Modulators are linked to carriers, and limited to only running when carriers do. By default, if no time duration is set for a modulator, it will not be further limited – it will run when a carrier using it runs. This removes the need to match time lengths set for carriers and modulators in scripts when the modulator should be used all the time.

Each modulator can also have its own time duration in seconds, however, set the same way as for a carrier, and then will run for the shortest of the time lengths. (If the carrier time expires first, this will "suspend" the modulator, unless and until the carrier time is set to a new non-zero length.) The special "implicit" time, which is the default for modulators, can also be set manually as the non-number value i (implicit time) for modulators (but not for carriers).

The non-number value d can also be used to set the default time which would have been set for a carrier.

Envelope time handling

See also the envelope .e[] parameters.

An envelope has timed ADSR stages (attack, decay, sustain, and release), which rotate and each adjust the resulting level in sequence. It runs when activated and linked to some generator which also runs. It begins with the attack stage when t (the time parameter common to all audio generators) is set for the generator which has the envelope. When the envelope reaches the end of the release stage, the t time will have elapsed. In-between, the sustain stage takes up however much time you get, if any, when subtracting the ADR times from the time set to the generator t parameter.

If the envelope belongs to a modulator which has implicit time (ti, the default), then it will base its timing on the time set for the carrier instead. (That is, the nearest carrier, going outward from the modulator – however many levels of nesting until a definite t time is found.)

Note that the compound step ; substep separator timing syntax, if used, always implicitly sets a new t value for a generator after the end of the old, and therefore triggers a new use of envelopes. This makes it easy to make a sequence of sounds using envelopes. If instead you want to adjust a generator after a time without re-running envelopes, you can use an alternative timing syntax.

It is also possible to toggle envelope behavior using the envelope .e[] subparameters, making it behave differently, such as looping all stages except sustain. This turns an envelope into a periodic waveform generator instead, with the same timing behavior as a modulator with implicit time, the envelope time settings together determining the length of a cycle, their sum the inverse of the base frequency.

Compound steps for an object

The numberless ; substep separator splits and extends the duration of a step for some object into two parts, one placed directly after the other. Parameter changes written after it will take place just after the time duration of the preceding part, and the following part will in turn have a new time duration. It can be used any number of times in a row, timing only changing locally within the compound step built this way.

It's often the simplest way of arranging a series of timed changes for an object. The following example plays four tones in sequence, each for 1.5 seconds:

Wsin t1.5 f100; f200; f300; f400

Here all four time durations are 1.5 seconds, for 6 seconds in total, because the default time for the 2nd part is copied from the 1st, and that of the 3rd is copied from the 2nd, etc. That's the rule, though there is an exception for modulators; modulators generally have an automatically fit time length by default (also possible to set with the special time value i, implicit time), and this is by default the case for the last (but only the last) substep when ; is used, so that using it does not unexpectedly shorten the total default time for the modulator.

Silent gaps can also be inserted within a compound step, adding to the duration, using the ;number gapshift syntax described more generally in the next section. A ;number written just after a ; adds a local time delay "number" of seconds long inside the compound step, analogous to using the more global | and /number-syntaxes together. For example, to add a 0.5 second silent gap between each change of tone in the current example:

Wsin t1.5 f100;;0.5 f200;;0.5 f300;;0.5 f400

Gapshifts & silent time padding

The gapshift ;number-syntax looks somewhat like the more global /number-syntax, similarly allowing time in seconds to be entered as a delay time – but only for the current step for some object. It behaves much like the compound step numberless ; and is another way to split a step and forward-shift the later part in time. The main use is to move a (sub-)step forward and leave a silent gap at the old position, but it can also extend the duration without leaving a silent gap.

Using ;number always resets time for the new part each use if a new t value is not provided. Unlike on the use of /number, a long shift doesn't simply move past a short time expired; if it moves past sound to silence, the silence may also be followed by sound with a new play time added.

It's possible to use only ;number in place of the numberless ;, but it may be more messy. For example, with two oscillators inserted at the same time, for the first of them. Time should be explicitly set before the ;1 is used, thereafter play time for the first oscillator will extend, rather than move; it will play for 3 seconds rather than 2, with the time reset to the previous value, 2 seconds, after 1 second.

Wsin f440 t2 ;1 f220
Wsin f110

Note that the setting of a time value explicitly for the first oscillator above, before the ;1, is important; otherwise the peculiar behavior is to insert a pause or "rest" by making default time 0 before the ;number while after it, the old default time is copied if a new time isn't set there. Changing the order to ;1 t2, the first 1 second will be blank for the first oscillator, and it will only play the last 2 seconds with 220 Hz (never with 440 Hz). Another example, where a tone plays after 1 second (for the usual default time) follows:

Wsin ;1 f880

Here nothing is missing and the delay is intended. Such use of silent time padding may mainly be interesting inside nested lists (to make another modulator start to play after a delay, say) when used by itself.

There's also another way to control the behavior of moving vs. extending, to disable or adjust the proportion of silent padding. When several ;number are used in series – with no numberless ; or other timing modifier in-between – then only the first can zero the time before it. So, for example, ;0 ;1 will never move more than 0 seconds, then will extend by 1 second.

Combined with nesting

The flow of time and the nesting of scopes are like two dimensions in which things are arranged – which corresponds to how the text in a script looks. When objects are placed inside of lists for nesting, as when adding modulators, the same time placement is used for the contents of the list as for where the list is assigned. In turn, inside of a list, a step written for an object can have substeps (using ; and/or ;number as described above), making for timing offsets which then apply for further lists assigned there in particular within the list.

Currently, the global timing /number and | syntax is not allowed inside of modulator lists, only at the top scope (where they are also the most useful). A way to use them at the level of a subscope block may be added in the future, as an alternative to using only the compound step syntax (which only applies to the current individual object and that which is nested below).

Here's an example of both timing and nesting which builds up a richer and richer noise, using PM, second by second.

Wsin f400 t1; p[
	Wsin ;0 r(3/4) ;1 p[Wsin f500 a1/5; a1/4; a1/3; a1/2; a1/1]
	Wsin ;2 r(3/5)
	Wsin ;4 r(3/6) ;1 p[Wsin f300 a1/5; a1/4; a1/3; a1/2; a1/1]
	Wsin ;6 r(3/7)
	Wsin ;8 r(3/8) ;1 p[Wsin f100 a1/5; a1/4; a1/3; a1/2; a1/1]
] t10; f800 t4

A timed series of changes for an object can, of course, also include smoothly swept values. Here is a modification of the PM "engine rumble" example which produces something differently-sounding, morphing over time as the innermost oscillator has its frequency ratio swept towards a series of new values – along with a little silent gap in the middle.

MP3
Wsin f137 t11 p[
	Wsin f32 p[
		Wsin r50*1.0 r[g50*0.1]; r[g50*0.2]; r[g50*0.25] t2.0
		; t1.5; t0.0; ;1.0 r[g50*0.75 t1] t2; r[g50*0.0] t0.5
		; r[g50*2.0] t2.0
	]
]

Modulator lists

The various modulation options have the modulator list in common. Within [], written after the name of a parameter that supports it, signal generators can be included for use with that parameter. For example, for PM the phase parameter p is assigned a list as in p[...]. It works the same for other parameters such as a (for AM/RM) and f (for FM), and subparameters such as a.r, etc.

Assigning a list to a parameter will append the new list to any old one, expanding it rather than replacing the old items. Thus p[] changes nothing. To clear old items when setting a list, add - before the [, as in p-[] (which removes all PM modulators).

Lists can be assigned together with other values (numbers) for various parameters. For the whole assignment expression beginning with the parameter name, whitespace can only be placed inside list brackets (or inside parentheses for any numerical expressions).

Value sweep for a parameter can use the same list as one which contains modulators; the modulators simply need to be listed after any sweep subparameters (see section) which head the list. Apart from for sweeps, there's also other kinds of heading subparameters as well, which may be available depending on where the list is set, like for p.

It's also possible to write multiple lists directly after one another when setting to a parameter (with or without the one leading -). These lists will be joined into one, meaning that [X][Y] for some contents "X" and "Y" is the same as [X Y]. This also allows placing heading subparameters in a later list.

Value sweeps

To sweep a parameter which supports value sweep subparameters towards a goal value – the ordinary value being the start for a trajectory – following the ordinary value or by itself, a set of value sweep subparameters can be given values at the start of a [] list. (Any modulators added in a modulator list must go after if the same list is used for both.) This is supported for all parameters which support modulation with value ranges, and vice-versa.

A main parameter (like a for amplitude) can be assigned to several times in a row to build up changes for different kinds of values for it, so sweep subparameters can be set either all at the same time or separately; writing a0 a[g1] a[t1] or a0[g1 t1] gives the same result. It's also possible to set the ordinary value inside the [] instead, by using the name v for it to treat it as another sweep subparameter.

For example, the following tone begins at 20 Hz and rises exponentially to 20000 Hz, over 10 seconds:

Wsin f[v20 g20000 lexp] t10 a0.25

Here, l is a subparameter which changes the line shape (the default being lin, the linear shape). Note that the line shapes are not the same as any well-known mathematical functions with the same names. Each fills in points between the beginning (ordinary number) and the end value (g) in some way.

The sweep subparameters are as follows. The default values often allow two or three of them to be left out.

g
Goal (go-to) value, assigned to the parameter after time. This value has no default and must be provided. If changed again before the full time, the current point reached on the previous trajectory will be used to change the start value.
l
Line fill shape (default lin, or the previous shape if any). The exp and log shapes use ear-tuned polynomial approximations with definite beginnings and ends, designed to sound natural for frequency sweeping, and symmetric one to the other. The xpe shape increases like log and decreases like exp, much like a capacitor charges and discharges, natural-sounding for an envelope; and lge increases like exp and decreases like log. For a less-steep alternative to xpe, sqe can be used. The cos shape sounds similar to lin for a sweep, except it has a smoothly curved start and stop, and a steeper middle.
t
Time to reach goal (default is the external t duration, or the remaining previous time, if any, for this parameter). If longer than the active time for the object which has the swept parameter, the trajectory will be left unfinished.
v
Start (state) value, the ordinary parameter value. It can alternatively be set here after a v, if not set before the enclosing [].

Envelope parameters in .e lists

Envelope subparameters for ADSR etc. can be set inside an envelope .e[] list, where .e is in turn a main envelope subparameter for parameters. Every parameter which supports value sweeps and modulation with value ranges also supports this, and vice-versa. Envelopes shape values in a repeating pattern, according to their settings and their timing logic. These features can all be combined and used at the same time; envelopes are applied on top of the other means of varying values dynamically.

Where parameters take a main value, the .e subparameter takes a second value, much like that for the .r value ranges short-form syntax, but separate. The envelope maps the main parameter to a range between what you'd have without the envelope, and the second value for the envelope. (Like the .r short-form syntax, the second value can be swept inside .e[], but modulators in that list are summed to mix them with the second value, more like how the ..[].r long-form syntax list works. Again, the .e and .r second values are separate, and .e runs after .r range-mapping.)

Note that any modulators directly set for the main value of a parameter (for example amplitude a[]) correspond to the value range modulation final mix addition modulator list (alias a...r.a[] if you use the long-form syntax). These will be mixed in after the envelope runs, not before. However, any set for a[].., a main value with long-form value range .. after, apply before the envelope is used.

The envelope treats the second value as the internal "low" level before attack and after release (usually the "bottom" for an amplitude envelope) and the main value as the "high" level between attack and decay. (This is the other way around compared to .r.)

The ADSR parameters are as follows.

a
Attack time (default 0 seconds). If above zero, the result will "fade in" towards the main value.
d
Decay time (default 0 seconds). If above zero, the result will take time moving to the sustain level (or staying there if it's 1.0),
s
Sustain level (default 1.0). If not equal to one, the decay stage will change the level. 1.0 is mapped to the main value and 0.0 to the second value.

Values outside the range of 0.0 to 1.0 are allowed. If below 0.0, the level goes beyond the second value, and the release then returns to it the other way around. If above 1.0, the level goes beyond the main value, like a second attack.
r
Release time (default 0 seconds). If above zero, the result will "fade out" from the sustain level to the second value.

The special non-number literal s can also be set, to "stretch" the release to fill all available time, making it replace and so remove the S (sustain) stage. The sustain setting then still applies for setting the level from which the release stage starts.
e
Envelope settings. A letter or digit can be set to toggle behavior for the envelope.
0
Off. The envelope does nothing.
c
Clamp stage times (default). Each time the envelope is triggered, adjust stages to fit their combined duration to the linked generator time t value. This lets the envelope always finish the release.

Release time is reduced first, then the times of earlier stages if still needed.
l
Loop envelope. This changes the behavior into that of a periodic waveform generator. Attack immediately follows release, and there's no sustain time. In terms of timing, the envelope runs like a modulator.
t
Truncate envelope trajectory. If the generator t time linked to the envelope is too short for all stages, cut off the envelope trajectory. Release and maybe earlier stages may go unfinished before any retriggering and abrupt return to the attack stage.
The following e subparameters exist.
.l
Set line type for every stage at once. If set at the same time as the line type for a specific stage, the stage-specific setting overrides this.

Each of the a, d, and r parameters all have the following subparameters.

.l
Line fill shape (default lin); see "Line types".

PD & self-PM in p lists

The main phase p parameter doesn't support value sweeps, leaving the heading part of its modulator lists available for another use. For convenience, the various phase distortion synthesis options as well as the self-PM option can be accessed inside the list (p[...]) prior to writing any modulators it may contain, as well as outside (where each option requires a p.-prefix). For example, p[c2.f2 d2] is equivalent to p.c2.f2 p.d2.

Given the short-hand syntax available for both the W oscillator and the R oscillator, allowing writing p values directly after the W or R, it's also possible to place PD & self-PM options inside of just W[...] or R[...]. This makes for the shortest way to use such options to tweak the waveform.

Numerical expressions

Each number can be written with or without a decimal point. If a decimal point is used, a leading 0 can be left out, as in .25. After a decimal point, at least one digit must always be written.

Operator precedence
SymbolsDescription
1 ^ To the power of (right-associative)
2 * / % Multiplication, division, remainder
3 + - Addition, subtraction (not as sign)

Number signs and arithmetic operation symbols can be used in infix expressions, together with numbers and named constants, variables, and functions. The rules are fairly simple and conventional, including precedence as in the table shown. Nested parentheses can be used freely. Parentheses also allow shorthand multiplication (leaving out a * between two parts), e.g. 2(3) and (2)3 both give 6.

Unless a numerical expression is written within parentheses, it cannot contain any whitespace, as it ends the expression. For example, -1 is fine, but - 1 is a dangling minus followed by a dangling number 1, if not inside parentheses as (- 1). The ability to write any expression, sans whitespace, without any surrounding parentheses is for convenience. For example, writing rational numbers with a division, e.g. 1/2, is often useful in scripts and this keeps it short.

Some parameters support named constants only available under those parameter names, as with frequencies as notes; unlike other constant names, they use capital letters, with or without anything more to go along with them.

Mathematical functions and constants

A set of mathematical functions are supported in expressions for all parameters, whether or not surrounding parentheses are used. Writing name(value) gives the result of applying the function name to the value. A few functions give a value without being provided any, like rand(), which returns a new pseudo-random value from 0.0 to 1.0 each time it is called. Listed here are also constants which can be used in any expression, unlike those with upper-case names specific to some types of expressions.

abs(x)
Absolute value of x.
arbf(x)
Additive recurrence base frequency. Returns a multiplier from -1 to +1 for how much the pitch will change for an R instance when x is set as the value for R ma.a. May be negative, corresponding to direction in a sawtooth-like wave which rises rather than falls; negative frequencies are supported.
arhf(x)
Additive recurrence higher frequency. Returns a multiplier from -2 to -1 or +1 to +2, locating the first frequency above the unshifted base frequency, with a value which mirrors arbf(x) around ± 1.0 with the same sign. (This upper frequency can be important for perception of brightness or even pitch for some sounds.)
cos(x)
Cosine of x.
exp(x)
Base-e exponential value of x.
log(x)
Natural logarithmic value of x.
met(x)
Metallic value, e.g. met(1) gives the golden ratio. Positive integers give the series of metallic ratios. Other values are also allowed: fractional, 0 giving 1 and negative (gives how much the positive value would be increased, approaching zero further from zero). Note that met(-x) is also equal to (1/met(x)).
mf
632.45... Geometric mean of 20 and 20000. Middle frequency in Hz of nominal human hearing range.
pi
3.1415...
rand()
Pseudo-random number in range 0-1. The PRNG is seeded using the magic variable, $seed. The value sequence from a series of calls restarts each new script.
rint(x)
Round value x to the nearest integer. Halfway cases are rounded to the nearest even integer.
sin(x)
Sine of x.
sgn(x)
The sign of x as ± 1 or 0. (The sign bit is also preserved for 0.)
sqrt(x)
Square root of x.
time()
Get a system timestamp number changed each second. It can be used for seeding in a randomized script. (Note that the exact value is platform-dependent.) If disabled (deterministic mode), instead gives 0.

A musically interesting function for frequency ratios and some other uses is met(x), which produces the metallic means/ratios/constants (the Wikipedia article has the formula used); for example, the golden ratio value of 1.618... is the result of met(1). For FM and PM, more irrational numbers creates more complex frequency spectrums, and a PM modulator oscillator can have its r parameter set with e.g. rmet(1). This is also used for additive recurrence oscillators (R with mode ma), the default multiplier using the golden ratio.

Channel mixing and panning

SAU supports stereo audio, but audio generators pass mono signals between one another. Objects which are not used as modulators, whose output is mixed into the final output, have an extra channel mixing parameter c which defaults to centered mixing (0.0), and can be changed to pan sounds, with -1.0 as hard left and 1.0 as hard right. The shorthand constants L for hard left, C for center, and R for hard right can alternatively be used in expressions for this particular parameter.

For example, to play a tone starting at the left and moving linearly to the right over 3 seconds using a value sweep:

Wsin f440 cL[gR t3] t3

The inner t3 above is actually optional, since the outer sets its default to that.

Modulators can also be added within c[] or related subparameters, like with other modulation with value ranges. The resulting panning-AM works like a variant of amplitude modulation that affects stereo placement – the effect disappears if the signal is later downmixed to mono.

It's possible to pan harder than hard left and hard right; going "too far" in either direction simply amplifies what's added to that channel while giving what's added to the other a negative amplitude.

If several sounds are to be panned in the same way, for the main number it's possible to set a constant initial c value for all of them, by using the S c option for channel mixing prior to adding the sound generators. For example, writing S cL before adding some top-level audio generators will make c default to L for all of them. To pan several signals using modulation or dynamic values instead, they can be summed into an A generator used as a wrapper, which is in turn panned.

Frequency values as notes

Values for a frequency parameter can be written using named constants for notes in place of raw numbers for frequency in Hz. (This goes both for a main value and sweep subparameter values.) These can use either one of two naming schemes: MIDI note numbers (M0 to M127 where M69 by default is 440 Hz), or C-D-E-F-G-A-B notes (further described below) placed in some octave from 0 to 10.

The values of these named constants can be changed by tweaking the current settings under S f: S f.n for the A4 tuning frequency (default 440 Hz); S f.s for which tuning system to use; and S f.k for key selection, which rotates justly intoned scale ratios to match the first with the key note.

By default, notes use the 24-tone equal temperament or equal divisions of the octave (24-EDO) system, a superset of 12-EDO. There are also three justly intoned systems (Pythagorean JI, classic 5-limit JI, SAU 7-limit JI), in all of which each of the 7 notes have a natural, 3 flat, and 3 sharp variations, all of them unique.

MIDI note numbers

MIDI note numbers (written with a leading M) range from 0–127, with 69 mapped to the A4 tuning frequency (Wikipedia has a table). Every 12 numbers a new octave begins. Thus for equal temperament, flat and sharp notes are simply numbered – up to the limit of the 12-tone scale, beyond which microtonal variations need to use the syntax used for other named notes (see below), though the suffixes can also be combined with MIDI numbered notes. Perhaps most useful is that a quartertone d (half-flat) or z (half-sharp) suffix can be added to go down or up half a MIDI number, respectively (M69z is note 69 1⁄2).

For just intonation (JI), any MIDI note number which doesn't correspond to some natural C-D-E-F-G-A-B note (i.e. 1, 3, 6, 8, and 10, for some note number modulo 12) is given a value exactly between the surrounding two. This will differ both from the note corresponding to the flat from the number above, and the different sharp note from the number below, unlike in equal temperament where the three are the same.

C-D-E-F-G-A-B named notes

Each named note is written with a C, D, E, F, G, A, or B. As a first optional suffix, a b or f (flat) or s (sharp) can be added – or a quartertone alternative: d (half-flat), z (half-sharp), v (flat-and-a-half), or k (sharp-and-a-half). There's also w (double-flat) and x (double-sharp). The flats and sharps differ for EDO and the three JI systems – making a smaller difference in the JI systems than for EDO, except for the Pythagorean JI which very slighly exaggerates it instead.

An octave number (0–10) can then be added, e.g. A5 matches twice the tuning frequency, Ad5 a little below that. With no number, the octave for the note will be relative to the key setting (S f.k), and by default 4–5, so that the note for the selected key is also the lowest using the low default octave. To access more octaves from a relative position, add arithmetic (e.g. for A, A*2 is raised an octave and A/2 is lowered an octave).

Another means of microtonal variation is a subnote prefix for "inner octave" placement, optionally added at the very beginning: c, d, e, f, g, a, or b. (It can be combined with any other options.) The result is moving the tone, part of the distance from the diatonic note used to that above it. I.e., cC is the same as only C, but dC moves up one subnote step towards D, eC another step, and so on. (What about eCs? It likewise moves two subnote steps from Cs towards Ds, as the s is applied separately.) If the diatonic part of the key selected is not C, this small letter scale rotates with it. In just intonation, the frequency increases apply rational fractions.

With the default EDO system and A4 tuning, note frequencies match the most common in conventional scientific pitch notation. The notation when including octave numbers is an ASCII variation on that (no subscripts for octaves, b instead of ♭, and s instead of ♯), with extensions. To reach octaves beyond 0–10 (C 16.4 Hz to 16.7 kHz), either combine the note with arithmetic, or do that to the tuning frequency instead (e.g. make it 220 or 880 Hz) to shift all octaves.

Cycle position values (phase & seed)

Phase parameter p values are treated in a special way, used modulo 1.0 so that there's no difference between 0.1, 1.1, and -0.9, for example – all of those values representing 10% of a wave cycle. Angles are often written as simple fractions, e.g. using p1/4 to turn sine into cosine. The unit used is the percentage of a wave cycle, intended to be more concise than values in radians, which require a multiplier of 2*pi for the same result, and nearly always include pi. (Note however that this unit differs from that of modulator amplitudes used for phase (a inside p[...] etc.) – where 1.0 corresponds to only pi in radians, as the range ± 1.0 is mapped to a full wave cycle.)

Seed parameter s values work the same way modulo 1.0, but here the resulting percentage is that of the state space (which wraps around analogously to phase in a wave).

The named constant G can also be used for the golden angle as cycle percentage in expressions for these parameters. For example, G*2 provides the 2nd leaf-around-a-stem angle, with any number n in place of the 2 providing the nth.

Numerical variables

Following $name=, an expression can be written which will have its value stored in the variable name. The name is a case-sensitive string of alphanumeric characters and/or underscores. To use the value in a later numerical expression, write $name; the leading $ sets variables apart from other numerical names. Unlike other symbols which take a numerical argument, whitespace is allowed both before and after the = (but parentheses are still needed to use whitespace within the numerical expression).

After a variable has been made to hold a number, it can be used in a new expression assigning it a new value based on the old, for example $name=$name*2. The value from such a reference on the right-hand side of the = is always the previous value.

Numerical expressions for some named parameters can use context-sensitive constants; to allow such when assigning a variable, one of the below parameter namespace names can be added after the =. Between it and any number or mathematical name after must be whitespace and/or a mathematical symbol, to keep names apart. For example, $freq=f A4 has the frequency value of the note A4.

c
Channel mixing values
f
Frequencies as notes
p
Cycle position values (phase)
s
Cycle position values (seed)

Non-overriding assignments

Scripts can receive named values via command-line arguments setting numerical variables. The passing of such values can be treated as either optional or required by a script, and fallback values can be included in the script.

To only assign to a variable if it didn't hold a number, add a ? as in $name?=. Writing the first assignment of a variable in such a way will silently allow choosing whether or not to override the value via command-line argument. Doing this near the top of a script is recommended for optionally passed values.

If passing a value is meant to be required, then this can be treated as either a hard or a soft requirement. For a hard requirement, $?name will warn and stop the script from running after parsing if the value wasn't set. For a soft requirement, the syntax can be combined with an assignment as in $?name= for a combination of providing a fallback value, and warning if the value wasn't set beforehand; the script will still run after the warning, if not stopped by something else.

Any of these three approaches allow clean handling of missing arguments. By contrast, not using any of them and relying on a value being passed will, when it's not passed, result in other warnings or errors in the script when it tries to use undefined variables later on.

Magic variables

Magic variables
NameDescription
$seed Set to reset the rand() value sequence; defaults to 0 if no value was passed to the script. Does not hold a number when checked unless a value was passed or set, allowing non-overriding assignments, like $seed?=1. Keeps the last value set; $seed=$seed later resets. (Every bit counts; different expressions for the same number, with e.g. rounding may give different seeds.)

Built-in magic variables exist that perform a procedure when set, beyond holding a value. Like other variables, the initial values can be changed by passing options to a script to assign variables.

These special variables are related to those mathematical functions that are stateful, and in the case of $seed and rand() and time(), they're meant to be used together.

Labels for objects

The declaration of an object can be prefixed by 'name  to label the object name. Each name written is a case-sensitive string with alphanumeric characters and/or underscores, as with variables in general. Once labeled, the object can be referred back to by writing @name at any later point in the script; adding such a reference to the object does not automatically set a new time duration for it. (A new time value is set if any changes made to parameters include explicitly setting t (time), or if a step-splitting timing modifier is used.)

Note that a @name reference placed in a nesting scope different from the original (i.e. outside a list, or in a new list, etc.) does not move the object into the new nesting scope. It will not be added to, nor removed from, any list by being referenced anywhere. The time scope is however new and of the reference.

For example, the modulator used in this PM example is labeled name, and is then accessed using its label in order to change its frequency relative to the carrier at one-second intervals:

Wsin f500 t5 p[
	'name Wsin r1/1
]

/1
@name r1/2
/1
@name r1/3
/1
@name r1/4
/1
@name r1/5

Here the timing would also change for anything written afterwards (in a longer script) with every /1. The timing section describes more means of placing changes in time. The numberless ;-separator is often a neater alternative to label referencing, but can also be combined with it. (Here it uses the t value of each preceding part to only locally delay the substep which follows it.)

/1
@name r1/2 t1
; r1/3 t1
; r1/4 t1
; r1/5

In some cases, it's shorter and simpler to use the numbered form of the ;-separator, called a gapshift. Like the first example, this skips the use of t to set how long to wait between parts. Each /1 is replaced by a ;1 for a continuing @name reference. A gapshift combines with such references without making the first part silent, because the initial use of @name never automatically sets a new time duration, unlike later substeps, and unlike freshly added objects.

/1
@name r1/2 ;1 r1/3 ;1 r1/4 ;1 r1/5

Comment syntax

Several comment styles exist: