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.


2011–2012: Beginning

The program was originally developed in 2011–2012. Meant to have 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 I also wanted 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.

The design unfolded bottom-up, experimentally, into something very compact and simple – which also applies to the scripting language. I noted that all well-known alternatives differed greatly in the basic look and way of working with their languages, oriented around ideas fundamentally different from the half-formulated mental trail I explored. 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 make it truly suitable for writing music in. It had only become fit for experimenting with sound, not for composing music. Far greater expressiveness was hoped for than was straightforward to arrive at.

In hindsight, many changes were rushed or poorly done after the initial design was arrived at. The first four months or so brought most things of more lasting quality. The remaining ten were spent breaking and unbreaking things in various ways, and leaving extra debugging and clean-up work for later years.

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.

Various sketchy ideas for further development remain from this 2011–2012 period.

2011-01-21: Quick tests

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

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
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

"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.

2011-02-21: PM synthesis achieved

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 |

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>>

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.

2011-04-04: "sgensys is the Sound GENeration SYStem"

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.)

2011-06-04: Early feature plateau

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 >.

2012-04-01: All the rest, then at rest

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.

2013–2014: Stagnation

The 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 about the subtle issues and limitations of different design choices. I also became rather torn about what to spend my time on, and largely put it all aside for that reason too.

In 2013, Linux ALSA support was added to the old program, and some quick experimentation with redesigns in mind 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.

The old program design and language seemed like a dead end, but no substantial ideas for a rewrite developed. I had studied some theory, but couldn't recognize what it described in my old quirky work, and couldn't arrive at anything original through the theory. I really wanted something other than function calls and loops and such building blocks in the language to be made.

2017–2022: Cleaning up and reorienting

The project was revived on November 27, 2017, with a focus torn between three paths:

  1. Bugfixes, refactoring, etc.
  2. Deep-going redesign with the old language and features still the basis.
  3. Experimenting on and developing a new language.

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.)

2019-01: A new name – saugns

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). Further versions with varying changes to design and features, but mostly quite similar to use, continue throughout v0.3.x in 2019–2022.

2020-06 "mgensys": Almost starting over

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.

2021-07: Fiddling with DPW-ish oscillators

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.

2021-11: Extending numerical expressions

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.

2022-05: More command-line interoperability

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.

2022-06: Rethinking syntax symbols and their use

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).

2023+: Into a new groove

2023-01: Ready to rumble, or the value of noise

As 2022 drew to a close, I put out a final v0.3.12 clean-up release and changed focus to my work on an audio generator with a design I invented as I worked on it – R (Random segments oscillator), a rumbly 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.

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.

2023-03: Making waves, or at least the oscillator more interesting

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.

2023-08: Towards straightening out a redesign

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.