Introduction

A great deal of work has gone into the development of Csound5, and with it a number of new features which are useful for users both composing with Csound and developing with it.  Amongst the many new opcodes that have been added to Csound5 are the "chn" set of opcodes. These opcodes are able to be used as a way of communicating between a host application and the Csound engine, but they can also be very useful within Csound alone. Acting as a global string-indexed map, the use of the chn opcodes allows for new techniques in approaching Csound instrument design.

The focus of this article will be to analyze traditional Csound instrument design to show how it puts limitations on reusability of instruments, then to take a look at the chnset and chnget opcodes to see how we can use these to get past these limitations to create reuable, encapsulated instruments.

I. Csound Instrument Dependencies

Csound instrument's often have dependencies: things which exist outside the instrument code body but which the instrument depends on.  Sometimes this is intentional as part of the instrument design and the way the instrument is meant to be used.  Other times, dependencies are left outside of the instrument body because that used to be the only way to do it.

The most common dependency which an instrument relies on are those of ftables created either in the SCO using f-statements or created in the ORC using ftgen statements. There are other types of dependencies as well, such as global variables, but for the scope of this article we will only look at how to deal with ftable dependencies, as the techniques for working with ftable dependencies may also apply to other dependencies.

We'll start off with a simple instrument that depends on an ftable, examine the benefits and drawbacks of coding in this style, and then take a couple other views on how create the same basic instrument but coded in different ways, each time handling the dependency in a new way.  In the end, we'll take a look at the techniques introduced and look at things to be aware about when using these techniques.

II. Using f-statement table

Let's take a look at a very basic Csound instrument and its dependency:

<CsoundSynthesizer>

<CsInstruments>
sr=44100
ksmps=1
nchnls=2

instr 1 ; Sine Oscilator Instrument

iamp = ampdb(p4)
ipch = cpspch(p5)
itable = 1 ; sine

aout oscil3 1, ipch, itable

aout = aout * iamp

outs aout, aout

endin

</CsInstruments>

<CsScore>

f1 0 65537 10 1 ; sine wave

i1 0 1 70 8.00
i1 + . . 8.01
i1 + . . 8.02
i1 + . . 8.01
i1 + 4 . 8.00

</CsScore>

<CsoundSynthesizer>

The above example contains an instrument that depends on an ftable .  The ftable is declared in the SCO section of the CSD.  Note: The ftable is declared outside of the instrument's code. 

This method of Csound instrument design is not unusual and in fact is probably the most common style found in Csound code. Historically, this was the only way to do it until the ftgen opcode was created. In terms of reusability, the above offers:

However, there are some drawbacks to this:

The ability to share ftables is very useful for making efficient instruments, but defining ftables with f-statements can make it difficult to make easily reusable instruments.  

III. Using ftgen table

The following example shows another way of creating the same instrument, but using ftgen statements in the ORC instead of an f-statement in the SCO:

<CsoundSynthesizer>

<CsInstruments>
sr=44100
ksmps=1
nchnls=2

gi_sine ftgen 0, 0, 65537, 10, 1 ; sine wave

instr 1 ; Sine Oscilator Instrument

iamp = ampdb(p4)
ipch = cpspch(p5)
itable = gi_sine ; sine

aout oscil3 1, ipch, gi_sine

aout = aout * iamp

outs aout, aout

endin

</CsInstruments>

<CsScore>

i1 0 1 70 8.00
i1 + . . 8.01
i1 + . . 8.02
i1 + . . 8.01
i1 + 4 . 8.00

</CsScore>

<CsoundSynthesizer>

Using an ftgen statement instead of a SCO f-statement gives us some flexibilty.  The table is auto-assigned a number and that number is stored in a global variable.  The global variable can be given an easy to remember name that signifies what that ftable is.  In this case, now instead of referring to ftable 1 in the instrument code we are referring to ftable gi_sine.  

With this method, we still maintain the advantage of being able to share ftables between instruments, as was the case with the f-statement table instrument in section II. With the above, we also gain:

The above method gives us a number of advantages in making it easier to manage our instrument code as well as making it easier to reuse our instruments in other projects.  However, there are still some drawbacks:

IV. Using ftgen with chnset/chnget

With Csound5 there are many new opcodes and with them they have openned new techniques for coding instruments.  For the purpose of creating a completely encapsulated instrument we'll be using the chnset and chnget opcodes. The chnset and chnget opcodes allow for accessing a global string-indexed software bus whose purpose as a bus is to communicate values to and from a host which also reads the bus. However, these opcodes can also be used in a much different way.

In programmer terms, the bus that backs the chnset/chnget opcodes is a global map. In C++, this might be defined as map<string,float>. In layman terms, what this allows is us to look at the bus and get values for a given string as well as set what that string means.

For example:

itablenum 	chnget	"table_sine"
chnset 20, "table_sine"

Here, in the first line, we're looking to see what the current value of "table_sine" is on the bus. If it has not been defined before, the value will default to zero. (We will be using this feature shortly to determine if a table has been defined). The second line sets the value of "table_sine" to 20. If you were to call chnget again after the chnset line, as in the following:

			chnset 	20, "table_sine"
itablenum chnget "table_sine"

itablenum would be set to the value 20.

With the chnget and chnset opcode as well using the ftgen opcode, we have everything we need to create a completely encapsulated instrument. To do so, the basic technique will be:

  1. Check to see if the table we are depending on is defined. 
  2. If it is already defined, use pre-existing table.
  3. If it is not defined, create the table and use.
Modifying the instrument from Section III, we now have:

<CsoundSynthesizer>

<CsInstruments>
sr=44100
ksmps=1
nchnls=2


instr 1 ; Sine Oscilator Instrument

iamp = ampdb(p4)
ipch = cpspch(p5)

itable chnget "table_sine"

if (itable == 0) then
itable ftgen 0, 0, 65537, 10, 1 ; sine wave
chnset itable, "table_sine"
endif

print itable

aout oscil3 1, ipch, itable

aout = aout * iamp

outs aout, aout

endin

</CsInstruments>

<CsScore>

i1 0 1 70 8.00
i1 + . . 8.01
i1 + . . 8.02
i1 + . . 8.01
i1 + 4 . 8.00

</CsScore>

<CsoundSynthesizer>

Notice now that everything we need for the instrument is contained within the instrument body itself. You are now able to take the instrument and easily copy and paste it into another project or share it with others knowing that there is a good chance it will be able to just be added to a project and work right out of the box.

When running the CSD, you'll find from the print statement will alway prints the same value for itable, telling us that itable has been set only once, and therefore the ftable create with ftgen has only been created once.

Tips On Usage

Acknowledgements

I'd like to thank Istvan Varga for implementing the named software bus and creating the chn family of opcodes, without which this article and the techniques discussed would not have been possible.