Introduction

This article introduces the new signal flow graphopcodes in Csound, and demonstrates several ways of using them,including routing through effects chains for mastering, and creatinga library of re-usable instrument patches.

I created these opcodes because I was not happywith existing means of routing signals in Csound: the zakopcodes, the numbered control busses, the named control busses, thetable opcodes, the mixer opcodes (also created by me), and plain oldglobal variables.

I was unhappy not because these other solutions donot work – they all work – but because of myexposure to Buzz, the freeware tracker[1].Although I have never completed a piece of music in Buzz, the minuteI began playing around with it, I had that "ah-ha"sensation, "this is a really good piece of software. This is theway this kind of software should work." In Buzz, the user putsdown "machines" on a canvas and connects them with lines,which represent signal flow. The machines can be "effects"or "generators" and they can be connected in any mannerdesired – as long as there are no feedback loops. (The notesand other events that drive the patch are stored in "tracks,"and normally there is one track per machine.)

Figure 1. Buzz Signal Flow Graph.

Technicallythis is known as a “signal flow graph”, which is anothername for “synchronous data flow language." Mathematicallyspeaking, it is a “directed acyclical graph”. I was veryimpressed with the sound design and complexity of various exampleBuzz songs. I was even more impressed at how easy it was to edit andchange the arrangement of instruments and the effects routing in theexamples, while they were playing, in comparison with working inCubase, with which I was much more familiar.

It became obvious to me that signal flow graphsare far more flexible than mixers with channel strips, busses,submasters, and masters. I was not surprised to find many otherexamples of music software using signal flow graphs, includingMax/MSP[2]and Pure Data[3],the many Buzz clones[4],Linux synthesizers such as gAlan[5]and SpiralSynthModular[6],the nice Analog Box[7]program on Windows, the Linux audio routing system Jack[8],and so on.

Still, Buzz still turns out to be easier to usefor audio routing than any of these, as Buzz only has one kind ofwire, and Buzz machines accept any number of connections.

Frankly, I do not know why the mixer paradigmcontinues to dominate commercial studio software. Perhaps it is justtradition. Perhaps it saves time for producers in a hurry, though Ireally doubt that.

Csound internally does not use a puresignal flow graph design, although it probably should be changed todo so. The signal flow graph model is in fact used by Csoundinstrument definitions; the connections between nodes in the graphare created by variables, which serve as the wires that connect theoutputs of source opcodes to the inputs of sink opcodes. Butglobally, in the Csound orchestra, each instrument definitionfunctions like a channel strip in a mixer that feeds, usually,directly into the master outputs. This is partly because krate andarate opcodes cannot be used in the orchestra header, and partlybecause Csound would not know what to do with automatically allocatedinstances of instrument definitions.

All the various signal routing facilities inCsound have been created to make this default routing scheme moreflexible.

The signal flow graph opcodes are designed tobring the signal flow graph model of sound processing up into theCsound orchestra header, where it can be used to arrange instrumentdefinitions that feed into each other in any way that does notinvolve feedback, and will handle multiple instances of the sameinstrument. I have also designed the system of opcodes in such a wayas to make it easy for instruments to be defined in files that canthen be #included in amaster orchestra file.

I. Example of Use

It is probably best to begin with an example,which I adapted from the Csound manual:

<CsoundSynthesizer> <CsOptions> -Wfo SignalFlowGraph1.wav </CsOptions> <CsInstruments> ; Initialize the global variables. sr 	= 44100 ksmps 	= 100 nchnls = 2 ; Connect up instruments and effects to create the signal flow graph. connect "SimpleSine",   	"leftout",     "Reverberator",     	"leftin" connect "SimpleSine",   	"rightout",    "Reverberator",     	"rightin" connect "Moogy",        	"leftout",     "Reverberator",     	"leftin" connect "Moogy",        	"rightout",    "Reverberator",     	"rightin" connect "Reverberator", 	"leftout",     "Compressor",       	"leftin" connect "Reverberator", 	"rightout",    "Compressor",       	"rightin" connect "Compressor",   	"leftout",     "Soundfile",       	"leftin" connect "Compressor",   	"rightout",    "Soundfile",       	"rightin" ; Turn on the "effect" units in the signal flow graph. alwayson "Reverberator", 0.91, 12000 alwayson "Compressor" alwayson "Soundfile" ; Define instruments and effects in order of signal flow. 			instr SimpleSine                 	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 			; Default values:   p1  p2  p3  p4  p5  p6  p7  p8  p9  p10 			pset			0,  0,  10, 0,  0,  0,  0.5 iattack		=			0.015 idecay			=			0.07 isustain		=			p3 irelease		=			0.3 p3			=			iattack + idecay + isustain + irelease adamping		linsegr		0.0, iattack, 1.0, idecay + isustain, 1.0, \						irelease, 0.0 iHz 			= 			cpsmidinn(p4)                 	; Rescale MIDI velocity range to a musically usable range of dB. iamplitude 		= 			ampdb(p5 / 127 * 15.0 + 60.0) 			; Use ftgenonce instead of ftgen, ftgentmp, or f statement. icosine		ftgenonce 		0, 0, 65537, 11, 1 aoscili		oscili 		iamplitude, iHz, icosine aadsr 			madsr 			iattack, idecay, 0.6, irelease asignal 		= 			aoscili * aadsr aleft, aright	pan2	asignal, p7 			; Stereo audio output to be routed in the orchestra header. 			outleta 		"leftout", aleft 			outleta 		"rightout", aright 			endin 			instr Moogy                 	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 			; Default values:   p1  p2  p3  p4  p5  p6  p7  p8  p9  p10 			pset			0,  0,  10, 0,  0,  0,  0.5 iattack		=			0.003 isustain		=			p3 irelease		=			0.05 p3			=			iattack + isustain + irelease adamping		linsegr		0.0, iattack, 1.0, isustain, 1.0, irelease, 0.0 iHz 			= 			cpsmidinn(p4)                 	; Rescale MIDI velocity range to a musically usable range of dB. iamplitude 		= 			ampdb(p5 / 127 * 20.0 + 60.0) 			print 			iHz, iamplitude 			; Use ftgenonce instead of ftgen, ftgentmp, or f statement. isine 			ftgenonce 		0, 0, 65537, 10, 1 asignal 		vco 			iamplitude, iHz, 1, 0.5, isine kfco 			line 			2000, p3, 200 krez 			= 			0.8 asignal 		moogvcf 		asignal, kfco, krez, 100000 asignal		=			asignal * adamping aleft, aright	pan2	asignal, p7 			; Stereo audio output to be routed in the orchestra header. 			outleta 		"leftout", aleft 			outleta 		"rightout", aright 			endin 			instr Reverberator                 	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 			; Stereo input. aleftin 		inleta 		"leftin" arightin 		inleta 		"rightin" idelay 		= 			p4 icutoff 		= 			p5 aleft, aright 	reverbsc 	    	aleftin, arightin, idelay, icutoff 			; Stereo output. 			outleta 	    	"leftout", aleft 			outleta 	    	"rightout", aright 			endin 			instr Compressor                 	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 			; Stereo input. aleftin 		inleta 		"leftin" arightin 		inleta 		"rightin" kthreshold 		= 		      	25000 icomp1 		= 		       0.5 icomp2 		= 		       0.763 irtime 		= 		       0.1 iftime 		= 		       0.1 aleftout 		dam 	aleftin, kthreshold, icomp1, icomp2, irtime, iftime arightout 		dam 	arightin, kthreshold, icomp1, icomp2, irtime, iftime 			; Stereo output. 			outleta 	      	"leftout", aleftout 			outleta 	     	"rightout", arightout 			endin 			instr Soundfile                 	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 			; Stereo input. aleftin 		inleta 		"leftin" arightin 		inleta 		"rightin" 			outs 		      	aleftin, arightin 			endin </CsInstruments> <CsScore> ; It is not necessary to activate "effects" or create f-tables in the score! ; Overlapping notes create new instances of instruments with proper connections. i "SimpleSine" 1 5 60 85 i "SimpleSine" 2 5 64 80 i "Moogy" 3 5 67 75 i "Moogy" 4 5 71 70 e 1 </CsScore> </CsoundSynthesizer> 

This example is fairly self-explanatory, but I will continue with further explanation.

The connectopcode declares that an outlet in one instrblock is connected to an inlet in another instrblock. This opcode is used first in the orchestra to establish thetopology of the signal flow graph. For any given connection, alloutlets and inlets must carry the same type of signal.

Inside the instrument definitions, the inlet andoutlet opcodes receive and send signals of the appropriate type fromthe outlets and inlets, respectively, to which they have beenconnected. There may be any number of inlets feeding an outlet, andany number of outlets feeding an inlet. In fact, connections arecopied when new instances of instruments are allocated by Csound. Forexample, when the SimpleSineinstrument is copied to begin playing the second note in thescore, the second copy of the instrument also gets copies of theconnections to the Reverberatoreffect. Inlets and outlets work with arate, krate, or fsigvariables such as inletaand outleta, inletkand outletk, and inletfand outletf.

An instrument such as SimpleSineor Moogy in the examplenormally has only outlets, which are used for audio output instead ofthe regular opcodes such as outsor outc. An "effect"such as Reverberatornormally has inlet opcodes at the beginning of the instrumentdefinition, and outlet opcodes at the end (although in fact they cango anywhere in the code).

The key to the signal flow graph concept isthat inlets and outlets are abstract. They have no meaning untilinstrument definitions are hooked up with the connectopcode.

Note the alwaysonopcodes in the orchestra header. These can be used to activate aninstrument from the orchestra header instead of from the score. Tome, this seems like a more natural way of defining "effects"in Csound. The alwaysonopcode functions just like an istatement in the score, even accepting pfields just as istatements do, but it turns on the instrument at the beginning ofperformance, and the instrument stays on until the end ofperformance.

Finally, note the ftgenonceopcode inside the instrument definitions. These are used in place offunction table statements in the score. The function table isgenerated once by the opcode and reused by all copies of the opcodein different instances of the instrument. If any of the arguments tothe ftgenonce opcodechange, the table is regenerated.

Again, with the inlet, outlet, and ftgenonceopcodes, instrument definitions become completely self-contained, andtherefore completely abstracted from the score.

The next section shows how such instruments can bedefined outside an orchestra and then combined in variousarrangements and mixing topologies by different master orchestras.

II. Patch Libraries

Suppose we want to develop a consistent schema formaintaining a large library of patches. Rather than having a separatecopy of each instrument definition in the orchestra for each newpiece, we will create one central definition of each instrumentwhich can then be shared by any number of pieces.

Whenever we want to use an instrument in a newpiece, we can simply #includethat patch. Whenever we refine an instrument definition for a newpiece, we can go back and simply rerender all older pieces that usethat patch to obtain the improved sound. Whenever we want to changethe arrangement of instruments or the order or type of effectsprocessing for a composition, we can simply edit the orchestraheader.

To implement the approach outlined above we need to:

  1. Use the ftgenonce opcode for all function table definitions in our instruments.

  2. Use only the inlet and outlet opcodes for all signal routing to and from our instruments and effects.

  3. Use one consistent scheme of pfields for all instrument definitions.

  4. Place all instrument definitions in separate patch files that contain no orchestra header code.

  5. In each actual composition, in the orchestra header, use the connect opcode to establish the signal flow routing, #include all required instruments from separate patch files in the order of processing, and use the alwayson opcode to turn on and configure all “effects.”

The following scheme of pfields is used in allexamples below:

  1. Instrument number (Csound requirement).

  2. Time (Csound requirement).

  3. Duration (Csound requirement).

  4. Pitch as MIDI key number (60 is middle C).

  5. Loudness as MIDI velocity number (0 is silence, 127 is loudest).

  6. Phase (could be used e.g. to specify the phase of a grain, not used here).

  7. X coordinate of spatial location (i.e. stereo pan).

Note that additional pfields, with meaningsspecific to a given instrument definition, may also be used, so longas they are not sent to an instrument for which they are notappropriate.

Instrument Library

First, all the existing instrumentdefinitions will be taken out of our example .csdand then each instrument definition will be placed into its own separate patchlibrary file (.ins forinstrument), such as below in the SimpleSine.ins:

			instr SimpleSine                 	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 			; Default values:   p1  p2  p3  p4  p5  p6  p7  p8  p9  p10 			pset			0,  0,  10, 0,  0,  0,  0.5 iattack		=			0.015 idecay			=			0.07 isustain		=			p3 irelease		=			0.3 p3			=			iattack + idecay + isustain + irelease adamping		linsegr		0.0, iattack, 1.0, idecay + isustain, 1.0, \					irelease, 0.0 iHz 			= 			cpsmidinn(p4)                 	; Rescale MIDI velocity range to a musically usable range of dB. iamplitude 		= 			ampdb(p5 / 127 * 15.0 + 60.0) 			; Use ftgenonce instead of ftgen, ftgentmp, or f statement. icosine		ftgenonce 		0, 0, 65537, 11, 1 aoscili		oscili 		iamplitude, iHz, icosine aadsr 			madsr 			iattack, idecay, 0.6, irelease asignal 		= 			aoscili * aadsr aleft, aright	pan2	asignal, p7 			; Stereo audio output to be routed in the orchestra header. 			outleta 		"leftout", aleft 			outleta 		"rightout", aright 			endin 

We will also create a patchlibrary file for the followingPluckedStringGogins.ins:

                    instr PluckedStringGogins                    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iattack		=			0.002isustain		=			p3irelease		=			0.05p3				=			iattack + isustain + ireleaseadamping		linsegr		0.0, iattack, 1.0, isustain, 1.0, irelease, 0.0iHz 				= 			cpsmidinn(p4) iamplitude 		= 			ampdb(p5) 				print 			iHz, iamplitude aenvelope           transeg            1.0, p3, -3.0, 0.1				; Use ftgenonce instead of ftgen, ftgentmp, or f statement. isine 			ftgenonce 		0, 0, 4096, 10, 1 aexcite             poscil             1.0, 1, isineasignal1		wgpluck2 		0.1, 1.0, iHz,         0.25, 0.22asignal2		wgpluck2 		0.1, 1.0, iHz * 1.003, 0.20, 0.223asignal3		wgpluck2 		0.1, 1.0, iHz * 0.997, 0.23, 0.224apluckout           =                  (asignal1 + asignal2 + asignal3) * aenvelopealeft, aright		pan2			apluckout * iamplitude * adamping, p7				outleta		"rightout", aright				outleta		"leftout", aleft                    endin

The reason for putting each instrument definition into its own fileis to enable the easy rearrangement of pieces. The instrumentdefinitions are named, but when Csound runs it also assigns a numberto each named instrument. These numbers are generated in the orderin which the instruments are defined. Therefore, if the score uses numbers toindicate instruments, the arrangement of instruments can be changedby changing the order in which the patch files are included in themaster .csd file.

For example:

#include "SimpleSine.ins"#include "PluckedStringGogins.ins"

assigns SimpleSine toinstrument 1, and PluckedStringGoginsto instrument 2; whereas:

#include "PluckedStringGogins.ins"#include "SimpleSine.ins"

assigns PluckedStringGoginsto instrument 1, and SimpleSineto instrument 2.

Effects Library

Next we can take all the existing effects definitionsout of our example .csd and put them into an independent file aswell, such as the Effects.ins below:

			instr Reverberator                 	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 			; Stereo input. aleftin 		inleta 		"leftin" arightin 		inleta 		"rightin" idelay 		= 			p4 icutoff 		= 			p5 aleft, aright 	reverbsc 	    	aleftin, arightin, idelay, icutoff 			; Stereo output. 			outleta 	    	"leftout", aleft 			outleta 	    	"rightout", arigh 			endin 			instr Compressor                 	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 			; Stereo input. aleftin 		inleta 		"leftin" arightin 		inleta 		"rightin" kthreshold 		= 		      	25000 icomp1 		= 		       0.5 icomp2 		= 		       0.763 irtime 		= 		       0.1 iftime 		= 		       0.1 aleftout 		dam 	   aleftin, kthreshold, icomp1, icomp2, irtime, iftime arightout 		dam 	   arightin, kthreshold, icomp1, icomp2, irtime, iftime 			; Stereo output. 			outleta 	      	"leftout", aleftout 			outleta 	     	"rightout", arightout 			endin 			instr Soundfile                 	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 			; Stereo input. aleftin 		inleta 		"leftin" arightin 		inleta 		"rightin" 			outs 		      	aleftin, arightin 			endin 

It is not so important to put each effect into its own independentpatch file, although that certainly is possible, because the effectsare controlled solely by the alwaysonand connect opcodes in theorchestra header, and the number assigned by Csound to the effect isnot so useful.

Using Libraries in Master Orchestras

Now we can write a new master orchestra file touse these library files (SignalFlowGraph2.csd):

<CsoundSynthesizer> <CsOptions> -Wfo SignalFlowGraph2.wav </CsOptions> <CsInstruments> ; Initialize the global variables. sr 		= 44100 ksmps 	= 100 nchnls = 2 ; Connect up instruments and effects to create the signal flow graph. connect "SimpleSine",   		"leftout",     "Reverberator",     	"leftin" connect "SimpleSine",   		"rightout",    "Reverberator",     	"rightin" connect "Moogy",        		"leftout",     "Reverberator",     	"leftin" connect "Moogy",        		"rightout",    "Reverberator",     	"rightin" connect "PluckedStringGogins",  "leftout",     "Compressor",     	"leftin" connect "PluckedStringGogins",  "rightout",    "Compressor",     	"rightin" connect "Reverberator", 		"leftout",     "Compressor",       	"leftin" connect "Reverberator", 		"rightout",    "Compressor",       	"rightin" connect "Compressor",   		"leftout",     "Soundfile",       	"leftin" connect "Compressor",   		"rightout",    "Soundfile",       	"rightin" ; Turn on the "effect" units in the signal flow graph. alwayson "Reverberator", 0.88, 12000 alwayson "Compressor" alwayson "Soundfile" ; Define instruments and effects in order of signal flow.#include "SimpleSine.ins"#include "Moogy.ins"#include "PluckedStringGogins.ins"#include "Effects.ins"</CsInstruments> <CsScore> ; It is not necessary to activate "effects" or create f-tables in the score! ; Overlapping notes create new instances of instruments with proper connections. i "SimpleSine" 1 5 60 85 i "SimpleSine" 2 5 64 80 i "PluckedStringGogins" 3 5 67 75 i "Moogy" 4 5 71 70 i "PluckedStringGogins" 5 5 71 79 e 1 </CsScore> </CsoundSynthesizer> 

In this case, note that the plucked string soundis not routed through the Reverberatoreffect, but directly to the Compressor.Note also that the Reverberatordelay time has been reduced.

The whole point of this exercise is that now thesame instruments and effects can be used again without change to realize acompletely different arrangement and score such as in SignalFlowGraph3.csd,in this case a Csound rendering of J.S. Bach's Passacaglia andFugue in C# Minor (BWV 582):

<CsoundSynthesizer> <CsOptions> --midi-key=4 --midi-velocity=5 -F bwv582.mid -WRfo SignalFlowGraph3.wav </CsOptions> <CsInstruments> ; Initialize the global variables. sr 		= 44100 ksmps 		= 100 nchnls = 2  ; Connect up instruments and effects to create the signal flow graph. connect "PluckedStringGogins",  "leftout",     "Reverberator",     	"leftin" connect "PluckedStringGogins",  "rightout",    "Reverberator",     	"rightin" connect "Reverberator", 	    "leftout",     "Compressor",       	"leftin" connect "Reverberator", 	    "rightout",    "Compressor",       	"rightin" connect "Compressor",   	    "leftout",     "Soundfile",       	"leftin" connect "Compressor",   	    "rightout",    "Soundfile",       	"rightin" ; Turn on the "effect" units in the signal flow graph. alwayson "Reverberator", 0.8, 15000 alwayson "Compressor" alwayson "Soundfile" ; Define instruments and effects in order of signal flow. #include "PluckedStringGogins.ins" #include "Effects.ins" </CsInstruments> <CsScore> ; It is not necessary to activate "effects" or create f-tables in the score! ; Overlapping notes create new instances of instruments with proper connections. f 0 690 </CsScore> </CsoundSynthesizer> 

Download

Download the example files here.

References

[1] Buzzmachines.com. http://www.buzzmachines.com(15 April 2010)

     Jeskola.http://www.jeskola.net(15 April 2010)

[2] Cycling '74. http://cycling74.com/products/maxmspjitter(15 April 2010)

[3] Pure Data -- Community Site. http://puredata.info(15 April 2010)

[4] Buzztard Related Software Packages. http://www.buzztard.org/index.php/Related_Software_Packages(15 April 2010)

[5] gAlan. The Graphical Audio Language. http://galan.sourceforge.net(15 April 2010)

[6] SpiralSynthModular. http://www.pawfal.org/Software/SSM(15 April 2010)

[7] Analog Box 2. http://www.andyware.com/abox2(15 April 2010)

[8] Jack Audio Connection Kit. http://jackaudio.org(15 April 2010)