Brief history of the saugns program, SAU language and project as a whole. For lists of changes between versions, the changes page may be better.
The program was originally developed in 2011–2012. Meant to have a bit of a "retro"-touch from the start, it was originally inspired by the "sound" of the 16-bit video game era, particularly Sega Genesis games and the FM synth side of the samples vs. FM divide – but not aimed at emulation, and more just as a personal experiment. I vaguely hoped to do something new in terms of a different language for music than any previous one – and deliberately didn't copy (or even look too closely at) any alternatives back then.
My earliest intuitive ideas were worked into a simple bottom-up design, different in the basic look and way of working with the languages compared to alternatives. But after a few months of working on it, and in large part arriving at the early design, it became clearer over time that much more work would be needed to approach my vision of a personal language suitable for writing music in. It had become a program for experimenting with sound, but clunky and tedious to write melodies and more complex things in. I dreamed of far greater expressiveness. Yet, I still found something neat worth having in what I'd done, and didn't want a more conventional alternative.
The program was first called "mgensys" and then renamed "sgensys". Two sgensys versions were released in 2012 at Gna!, with OSS system audio support and WAV file output support. Historical versions, including other old snapshots, are included in and precede newer work in the git repository, which can be found on Codeberg or Framagit. (The development style for new work has involved a whole lot of rebasing, but old snapshots prior to 2017 are included in the history. For newer work, older versions are preserved in the history of tags and snapshot branches.) The old, historical versions never had any known users other than myself, the original developer.
The first four months or so of development brought most of the key ideas and features of lasting value; thereafter I was facing growing challenges, as taking it all further involved greater complexity and bug-proneness.
Various sketchy ideas for further development remain from this 2011–2012 period.
The first snapshot is a quick test supporting playing the classic wave
types: sine, triangle, square, saw. The script syntax is different from
later variations, and uses D
to set default values,
W
to insert a wait time, E
to
wait until playing done (until end), and S
to insert a "sound":
# This is the first mgensys script made D a0.5 W t0.5 Ssqr f220 t1 Ssqr f110 t0.5 a.05 E Ssqr f440 t1.5 Ssqr f220 t0.5 a.05 E Ssqr f400 t0.5 E Ssqr f350 t0.5 E Ssqr f325 t1.5 E W t1 Ssqr f220 t1 E Ssqr f440 t1.5 E Ssqr f415 t0.5 E Ssqr f445 t0.5 E Ssqr f490 t1 E Ssqr f420 t1 E Ssqr f440 t1.5 E Q
A quick second version the same day changes the syntax, and switches
to the basic wave-table oscillator (one cycle per wave type) then kept.
In three longer-lived changes, S
sets a default value,
W
plays a wave of a given type,
and /
inserts a delay:
# This is the first mgensys script made S a0.5 /0.5 Wsin f220 t1 Wsin f110 t0.5 a.05 E Wsin f440 t1.5 Wsin f220 t0.5 a.05 E Wsin f400 t0.5 E Wsin f350 t0.5 E Wsin f325 t1.5 /1 E Wsin f220 t1 E Wsin f440 t1.5 E Wsin f415 t0.5 E Wsin f445 t0.5 E Wsin f490 t1 E Wsin f420 t1 E Wsin f440 t1.5 E Q
"Wait nodes" were replaced by timing calculations using a "wait before using this node" delay time in each sound-generating node. The sound generation processes as many nodes as it reaches before having to wait before taking on the latest new node (and in turn any after).
While the W
was for a number of years renamed to O
(for oscillator or operator), the S
was (and remains) used all along – with some tweaks to the semantics over time – for setting defaults and other script options. The Q
in these early scripts is an optional "quit" command later replaced by another syntax for similarly commenting out the rest of a file.
On 02-13, label assignment and referencing was added to the syntax, so
that sounds can be named and then changed further after a delay.
By 02-16, scope level handling was added and a modulator list syntax,
m<...>
, was added for grouping modulator sounds for carrier
sounds. (The syntax for waiting until the end of prior sounds was also
changed to |
.) Basically correctly-sounding PM finally appeared in
the 02-21 version, along with supporting relative frequencies for modulators.
Wsin f444 t2 m<Wsin r1.23456789> | 'a Wsin f444 t2 m<Wsin r(1/7.12)> | :a t2 m<Wsin r(1/2.255)> | :a f222 t2 | /2 Wsin f137 t1 m<Wsin f032 m<Wsin f42>> | Wsin f137 t1 m<Wsin f132 m<Wsin f42>> | Wsin f137 t1 m<Wsin f232 m<Wsin f42>> | Wsin f137 t1 m<Wsin f332 m<Wsin f42>> | Wsin f137 t1 m<Wsin f432 m<Wsin f42>> | Wsin f137 t1 m<Wsin f532 m<Wsin f42>> | Wsin f137 t1 m<Wsin f632 m<Wsin f42>> | Wsin f137 t1 m<Wsin f732 m<Wsin f42>> | Wsin f137 t.5 m<Wsin f832 m<Wsin f42>> | Wsin f137 t.5 m<Wsin f932 m<Wsin f42>> | Wsin f137 t2 m<Wsin f1032 m<Wsin f42>> Q
Actually, before PM was correctly done, by 02-16 I had accidentally hit upon frequency-amplified PM, except that it was not properly scaled. While I remember finding the sound a bit interesting, I didn't keep it as a feature, only a decade later adding it as such and coming up with a name for it.
AM/RM and FM support were added by 03-04, and the modulator list syntax was
also changed for PM then: a!{...}
for AM/RM, f!{...}
for FM, and m{...}
for PM. Soon after, PM syntax became
p!{...}
.
The 04-04 version changed sound generation to fill and use buffers or "blocks", instead of re-traversing the nodes on a sample by sample basis.
A joke about the program that never took off: "sgensys is the Sound GENeration SYStem. Throw out your fancy pre-recorded samples and replace them with terse scripts in a primitive language invoking the powers of FM synthesis." (Compare to "ed is the standard editor"; the only problem was/is the young age of my program.)
A series of quick design tweaks and feature additions led up to an early feature plateau. A few early features were added after 06-04, but most was completed by then, and most of what was striven for afterwards was left unfinished.
By 04-26, some fancy syntactic sugar was added in the form of the
;
-separator (the numberless form, still around as part of the compound step language feature) that allows
grouping a series of changes for the same sound in one place in a script,
even if other things also done in the script alternate with some of those
changes in the flow of time.
By 05-08, an early version of support for value sweeps was added (linear curve, and "exponential and logarithmic" curves using a polynomial I arrived at by ear, still used). That was just after support for writing frequencies as notes in a justly intoned C-major scale was added as a quick experiment – simply done, then largely unused for a dozen years afterwards.
A mixture of ideas were in the inconsistent syntax of early June.
The letter O
became used for an "operator" (oscillator) in a
long-lived change. But the PM syntax was also replaced with a new scheme,
basically meant to be developed further as a new modulator syntax in general,
but then dropped and that change undone. For an oscillator, a -
meant "link it to what follows", flattening the PM modulator list syntax.
(A line break marked the end of nesting using one or more -
in the absence of other nesting characters.) Furthermore, a scope nesting
<...>
syntax was meant to be combined with it,
to allow pushing/popping such scope in a more conventional way,
as in <-...>
where nesting ends after the >
.
All remaining syntax changes in the early versions were done by 01-23. That snapshot added support for letting modulator oscillators either have their time duration determined by the carrier (as earlier), or set so as to be limited to a shorter time.
WAV file output was added in the 02-10 snapshot. By 02-26, preparations to release it at Gna! were visibly done. Further fixes and little design tweaks mark the 03-05 release and the 04-01 release.
As far back as this, I opted for LGPL licensing for the program, with a view to implementing an audio format of sorts, and maybe later turning it into a library – so that if it becomes worthwhile, such a library could be used to add support for the format to other programs.
The whole project stagnated after a time of mostly theoretical focus. Old notes on possible future work had accumulated, with far more held in mind than written down, considering further features, design choices, etc. I also became rather torn about what to spend my time on, and largely put it all aside for that reason too.
The old program design and language seemed like a dead end, but no substantial ideas for a rewrite developed. I had read some theory, but didn't see the general "programming language design" concepts so clearly in my old quirky work. I also didn't manage then to use theory to go toward anything besides ideas of more conventional-looking languages. I really wanted something other than function calls and loops and such building blocks in the language to be made.
In 2013, Linux ALSA support was added to the old program, and some quick experimentation with redesigns in mind also remain from that year and 2014. Two 2014 snapshots in the version history capture the sketchy outline of a lexer (meant to go along with a new language with an undecided syntax), and before that, incomplete ideas for an intermediate redesign of the old system.
Thereafter I didn't touch the project for some years.
The project was revived on November 27, 2017, with a focus torn between three paths:
The first of these ended up given most of the focus, until early 2021.
The main exception is reworking the command-line interface, and expanding
its options. Strings can be evaluated with -e
, after using new
low-level code spun off from the 2014 test lexer to scan the script text.
Further, some smaller syntax changes made while still feeling torn on how
it should ultimately look, don't significantly extend features.
A new development style took form in 2018, with small clean-up changes git-rebased down as far as "cleanly possible" in the new work, logs kept, the early commits "growing" while maintained like little stable versions. Sometimes, later history was discarded or reworked in a little "restart"; with this, the history of the history was preserved in snapshot branches. (Reflections on this, understanding my oldest work and retracing thoughts after years away, and more, can be found on my blog.)
In January 2019, the project and program was
renamed to saugns and the
language to SAU (Scriptable AUdio). The first of the tagged versions is
v0.3.0, released in July 2019 with better audio mixing,
3 added wave types, changes and additions to value sweep line types (actually
called "ramp curves" in this old version), and automatic downscaling of
amplitude by the number of voices (easily added as voices were already counted).
In enabling amplitude a
parameters inside value range modulation (as it looked back then), I also inadvertently added a distortion effect.
Further versions with varying changes to design and features, but mostly
quite similar to use, continue throughout v0.3.x in 2019–2022.
In early 2020, following more bugfixing, for half a year I forked off the more simply designed early 2011 version from around the time the name changed from "mgensys". Some new ideas had begun to echo really old ones in the design of the program. I rediscovered the greater elegance of the earlier design, and set a goal to re-expand it for a fuller feature set. This new-old "mgensys" program didn't end up becoming saugns v0.4.0, instead I eventually decided to adjust the more mature saugns program towards similarly cleaner and simpler design.
That version (kept in the git branch old-dev_202006
) added a
plain white noise generator. I also thought of minor tweaks to the language,
planning to do more (array or list features, flexible nested timing syntax).
Having felt somewhat bored and aimless with the whole project I then left it all for half a year, rethinking what's meaningful for the program to be.
A redone oscillator using anti-aliasing through pre-integrated wave tables also came in v0.3.9, for a DPW-like result (6dB aliasing reduction per octave, for all waveforms and – somewhat unusually – also for FM and PM), following experimentation now in the old-dev_202109
git branch. A regression (ability to handle large PM amplitudes well) was fixed in v0.3.10b, using fancier look-up table interpolation. This type of oscillator offers a more modest aliasing reduction, so a higher sample rate is needed.
Very different further (re)designs are possible. For wave oscillators, maybe I'll later abandon the wavetable approach entirely. I remember some early ideas I had for more flexibly wavetype-morphing oscillators but not worked on yet. It may make for more interesting synthesis if implemented, and if it's made to work without terrible aliasing noise.
From very early versions, numerical expressions in the language work like a parse-time calculator with conventional infix syntax (except for some long-lived bugs and flaws). Fancier uses of this saw little use, as there's only so much to be done with basic arithmetic – until I finally added some named functions after a decade in v0.3.9 (and then later more in the way of stateful parse-time actions).
Half a year after this, I decided to try extending the extension, for more types of function – the most interesting additions being rand()
, seed(x)
, and time()
, which allow making both predictably and unpredictably randomized scripts. (Attaching state to the parse-time numerical processing seems an interesting way to add flexibility, which does not require adding any new syntax elements elsewhere.)
Another area worked on afterwards was timing syntax; early 2022, v0.3.10 fixed a big old 2011 bug for nested timing syntax, and tried to refine the syntax a little. And on varieties of modulation, v0.3.10b also added frequency-amplified PM.
In what more ways can the program be used? After checking out some live audio stream visualizing programs, I decided to add more options to make saugns easy to use with programs that accept audio over stdin in general. Extending that to piping non-raw audio, I ran into the problem with WAV files (the standard does not really support unknown lengths) and decided to solve it by implementing the AU format for piping use; while not as widely supported by modern *nix software in the early 2020s, when it does work, it does work flawlessly.
Writing more scripts and experimenting again with changing details of the syntax, in v0.3.11 I decided to reduce the number of special symbols used outside of numerical expressions a little and re-enable the pre-2018-cleanup feature of full numerical expressions without surrounding parentheses. (That was after thinking of a different idea for changing the syntax, preserved in the old-dev_202206
branch.) Details were tweaked again in smaller ways afterwards (v0.3.11b and later), a basic new naming scheme idea remaining, which should allow adding more named features to the language without conflicts or extra complexity.
I've concluded that adding more features is a great way to get thinking about design. I got stuck in premature striving for simplicity while cleaning up the program, keeping some things undergrown and preserving other things too much (when they could in broader hindsight be redone in a simpler way).
In 2023 I began adding new audio generation capabilities, mostly new audio generators. I focused largely on noise and randomness and its roles, developing interest in another range of sound and sound design alongside the old. New generators don't really change the design of the program, they just add new components which fit into the same overall mold.
As 2022 drew to a close, clean-up work (v0.3.12) gave way to work on an audio generator with a design I invented as I worked on it – R
(Rumble oscillator, a.k.a. random line segments oscillator), a value noise generator which is unusual in being usable as an FM and PM carrier, for use alongside the plain wave oscillator. The early work towards it, including making more underlying noise types including my own soft-saturated Gaussian noise approximation, is kept in the 2023-01 "mgensys" version in the old-dev_202301
git branch. It was integrated into saugns v0.4.0 soon after. More modes for using line functions differently went into v0.4.0c and v0.4.1 later that year. 1D Perlin noise support, which changes value noise into a type of gradient noise, was added September 2024 and released in v0.4.6.
The use of (often rumbly) value noise for shaping sound, both as carrier and as modulator, adds a great deal to what is possible to do with very short scripts. There's not likely to be many additions as basic which add so much to the capability and expressiveness of a program like this, without higher-level abstractions being involved. (Randomness expands the range of timbres. However, relying on randomness for rhythm or melody allows a quick so-so musicality difficult to then move beyond with merely more of the same.)
The simpler, plain noise generator N
took until v0.4.3 in April 2024 to be added. First appearing in the 2020 redesign experiment, it seemed less interesting. Yet it's sometimes useful as a complement or for generating test signals.
Sticking for now with the type of main wave oscillator from 2021, that design has one nice feature – it's easy to add more wave types – and the old set of waveforms seemed due for an update.
Inspired by Donald Tillman's suggestions for a waveform palette, I've added his "evenangle" and "eventooth" – and the "catear" I devised as something in-between – and more, to the wave types. Tillman proposes a grid of 3 × 2 waveforms – with added odd, even, or all harmonics, and either mellow or bright. I made an in-between medium-bright 3 types, for 3 × 3 waveforms. These form most of the 12 in v0.4.1. More or less mellow waveforms can be useful for modulation purposes; maybe more uses will come along in the longer term.
I "found" my added "mellowtooth" wave (half-rectified square root of sine, medium-bright all-harmonics wave) back in 2018, but the aliasing noise was so bad I didn't keep it – while the current oscillator is at least good enough to make such waveforms usable.
By the time of v0.4.1 in July following more changes, I'd also added more waveforms for the R
oscillator. Technically described further on my personal blog, the two mode switches h
(for sawtooth-like waveforms) and z
(for messier, inharmonic jagged waveforms) make for a wider range of randomized signals, which can be interesting as modulators.
For the rest of the year, on-and-off work on design changes eventually led to a clearer plan for work ahead. While several things were attempted and became "todo" items, it all started with some work on and ideas for lists.
Echoing the 2022 simplification of script syntax, I went a step further and removed the use of different types of brackets for the value sweep syntax and the modulator list syntax in v0.4.2, ahead of further list features. Merging the two syntaxes so that either or both can be placed within list (square) brackets just required making list assignments append to, rather than replace, the old items held for a parameter. That way, lists can be set to update sweep parameters for a generator without wiping its old modulators. (A new feature allows replacement on assignment with a -
before the list where necessary, which isn't often – only in a few of the scripts I've written.)
By the end of the year, in v0.4.2d, I'd changed focus to reworking the timing logic closer to the parser, and related parts of the design, so that more can be done without needing a separate pass after parsing, and before ultimate interpretation and audio rendering. This leads to undoing a very old design complication from early 2012, which made a middle-layer a full separate pass, then needed for voice allocation. The further ideas I developed near the end of the year however look a little different from those I had with the 2020 redesign experiment when I forked an earlier 2011 version.
In March 2024, I began working on another side-project, "ladder effect" distortion a la YM2612 with an aim to add it to saugns. As of June it was basically done – I drew upon an article by Aly James as a starting point and then refined it further, as described in my own article. But my focus has been split and plans for features grown larger, so this work has not been placed in a stable version of saugns yet. (It can be found in the ladderesque
git branch until then.)
Earlier, in May 2023, I had explored adding waveshaping options, that work remaining in the old-dev_202305
git branch. Such may in some form be added later; for now I find other areas interesting, and e.g. the "ladder effect" makes a more unusual distortion complementary to other things. Working on the latter also convinced me that adding frequency filters to the language in some more flexible yet simple form is worthwhile. (The ladder effect was implemented with filtering, but it made little sense to only filter it in isolation.)
In July 2024 I added phase self-modulation a.k.a. "feedback FM" (v0.4.5), a long-missing feature that I hadn't really bothered to look into earlier. It turned out straightforward to add it to both W
and R
. This loosely fits in the distortion category as well, as a waveform morphing option that adds sawtooth-ish harmonics.
November 2024 I looked more closely at how nested value range AM works, after adding the A
amplitude/DC offset generator for completeness. (It's in part meant for use with ladder effect distortion later, applying it to an A
object which sums other signals.) I realized that back in 2018 I'd implemented a quirky distortion effect, which A
as a wrapper made easier to reach for, including use to half-rectify signals. Playing with distortion led to making a new long-form syntax for value ranges (v0.4.8).
From November 2024 I worked on the features added in January 2025, 5×3 options for phase distortion synthesis, included somewhat limited forms of pulsar synthesis. This followed looking at some extra wave types found in Yamaha's OPL FM synth chips, and deciding not to add any such to my W
oscillator's wave types. but instead provide something more general and flexible. One thing led to more and then to exploring PD and pulsar synthesis.