Additive Synth Legacy Code

Let’s set the way back machine to January 2001. This is around the time I took my first steps into designing an additive synthesizer. I’m not sure when user-defined opcodes were introduced, though there is a good chance they had not existed then. And if they did, I had no idea of their existence. Same goes for the event series of opcodes. In my legacy code, each overtone, along with supporting envelopes and transfer functions, were written explicitly. I was a perl junky at the time, so I wrote scripts that would generate the instruments for me.

The example I’m posting today is the legacy code from 2001. I did not change the code, except for converting tabs to spaces and placing the orc/sco pair into a csd.

Download: add_synth_legacy.csd

In my new additive synth, I’m employing a recursive user-defined opcode technique, which I first read about in Steven Yi’s Csound Journal articles Control Flow Part I and Part II.

If you look at part 2, Steven actually demos an additive synth, which is eerily similar to the core design of the one I’m in the process of making. Which means I either independently came up with a similar design, or more likely, I’m suffering from a bout of cryptomnesia. Either way, if you haven’t studied up on these two articles, then perhaps it’s time you make a weekend project out of it; They are pure gold.

Making Records

Not of the vinyl variety, but the computer science data structure.

Csound comes with a few ways of generating, storing and retrieving data, with function tables being the primary data structure. Data can also be placed into global variables and chn busses. In terms of data abstractions, there isn’t a whole lot of choice. However, custom data abstractions and structures can be built from within a Csound orchestra.

In my blog Simple Keys — Modularized Key Mapping, I toyed with storing multiple key bindings, frequencies, amplitudes and function table numbers within a single ftable by treating the ftable as if it were an array of records, albeit a fairly clumsy one. Over the weekend, I continued along this line and distilled it even further.

Download: simple_record.csd

So far, I’m very happy with the results. Though the implementation maybe a bit hackish, the user-interface isn’t bad. This new system of making records is composed of five user-defined opcodes: RecordType, RecordField, Record, RecordSet and RecordGet. I additionally wrote wrapper GEN instruments, so they could be accessed from the score.

The first two, RecordType and RecordField, are used to create record abstractions. RecordType creates a new record type, while RecordField appends data fields to the record type. A record type is built from an ftable, thus shares the function table number space. The following score snippet creates a record type as ftable 100, and appends three fields: amp, freq and ftable.

i $RecordType  0 1 100           ; Record type stored at fn 100
i $RecordField 0 1 100 "amp"     ; Append field "amp"
i $RecordField 0 1 100 "freq"    ; Append field "freq"
i $RecordField 0 1 100 "ftable"  ; Append field "ftable"

For a record type to be useful, an instance of it must be created with Record. A record is also an ftable, and requires its own unique function table index. The following line creates a record from record type 100, and stores it in ftable 200:

i $Record 0 1 200 100  ; Instantiate record 200 from type 100

Once a record is created, the values of each field can be set with RecordSet:

i $RecordSet 0 1 200 "amp"    0.5  ; Set field "amp"
i $RecordSet 0 1 200 "freq"   440  ; Set field "freq"
i $RecordSet 0 1 200 "ftable" 1    ; Set field "ftable"

That’s just one record created from a single record type. In the csd, you’ll see I create two more records from the same record type.


The Synth instrument is provided to show a simple example on how to use a record. Instead of passing amplitude, frequency and the function number of a stored single cycle wave as pfield data, a record number is passed to Synth with pfield 4. The user-defined opcode RecordGet is used to pull data from the record. The data is then passed to oscil.

instr $Synth
    irecord = p4  ; Record function number
    iamp RecordGet "amp", irecord        ; Get amplitude from record
    ifreq RecordGet "freq", irecord      ; Get frequency from record
    iftable RecordGet "ftable", irecord  ; Get ftable from record
    a1 oscil iamp, ifreq, iftable, -1
    out a1
i $Synth 0 1 200  ; Feed synth record 200

More on all of this later.