Ctcsound API Examples

These examples are based Steven Yi's Python "How to use the Csound API" files on https://github.com/csound/csoundAPI_examples

All of the examples assume having the ctcsound module imported which is the module containing the Python interface to the Csound API. This must be executed before the examples are run.

This adaptation of Steven's examples as a notebook using ctcsound has been written by Mitch Kaufman.


In [1]:
import ctcsound

Example 1 - Simple Compilation with Csound

This example is a barebones example for creating an instance of Csound, compiling a pre-existing CSD, calling "perform" to run Csound to completion, then reset. The first thing we do is import the ctcsound module, which is the module containing the Python interface to the Csound API.

All of the examples below must have the ctcsound module imported.


In [2]:
c = ctcsound.Csound() 
ret = c.compileCsd("test1.csd")
if ret == ctcsound.CSOUND_SUCCESS:
    c.start()
    c.perform()
c.reset()

Example 2 - Compilation with Csound without CSD

In this example, we move from using an external CSD file to embedding our Csound ORC and SCO code within our Python project. Besides allowing encapsulating the code within the same file, using the compileOrc() and compileSco() API calls is useful when the SCO or ORC are generated, or perhaps coming from another source, such as from a database or network.


In [3]:
# Defining our Csound ORC code within a multiline String
orc = """
sr=44100
ksmps=32
nchnls=2
0dbfs=1

instr 1 
aout vco2 0.5, 440
outs aout, aout
endin"""

# Defining our Csound SCO code 
sco = "i1 0 1"

#c = ctcsound.Csound()
c.setOption("-odac")  # Using SetOption() to configure Csound
                      # Note: use only one commandline flag at a time

c.compileOrc(orc)     # Compile the Csound Orchestra string
c.readScore(sco)      # Compile the Csound SCO String
c.start()  # When compiling from strings, this call is necessary before doing any performing
c.perform()  # Run Csound to completion
c.reset()

Example 3 - Using Our Own Performance Loop

In this example, we use a while loop to perform Csound one audio block at a time. This technique is important to know as it will allow us to do further processing safely at block boundaries. We will explore the technique further in later examples.

Note that c.performKsmps() and c.performBuffer() return False while the score is not finished. Here, a dot is displayed for each pass in the loop:


In [4]:
orc = """
sr=44100
ksmps=32
nchnls=2
0dbfs=1

instr 1 
aout vco2 0.5, 440
outs aout, aout
endin"""

# Our Score for our project
sco = "i1 0 1"


#c = ctcsound.Csound()    # create an instance of Csound
c.setOption("-odac")  # Set option for Csound
c.compileOrc(orc)     # Compile Orchestra from String
c.readScore(sco)      # Read in Score from String
c.start()             # When compiling from strings, this call is necessary before doing any performing

# The following is our main performance loop. We will perform one block of sound at a time 
# and continue to do so while it returns 0, which signifies to keep processing.  We will
# explore this loop technique in further examples.

while not c.performKsmps():
    print('.', end='')
print()
c.reset()


..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

Example 4 - Using Csound's Performance Thread

In this example, we use a CsoundPerformanceThread to run Csound in a native thread. Using a native thread is important to get the best runtime performance for the audio engine. It is especially important for languages such as Python that do not have true native threads and that use a Global Interpreter Lock. CsoundPerformanceThread has some convenient methods for handling events, but does not have features for doing regular processing at block boundaries. In general, use CsoundPerformanceThread when the only kinds of communication you are doing with Csound are through events, and not using channels.


In [5]:
# Our Orchestra for our project
orc = """
sr=44100
ksmps=32
nchnls=2
0dbfs=1

instr 1 
aout vco2 0.5, 440
outs aout, aout
endin"""

# Our Score for our project
sco = "i1 0 1"

#c = ctcsound.Csound() # create an instance of Csound
c.setOption("-odac")  # Set option for Csound
c.compileOrc(orc)     # Compile Orchestra from String
c.readScore(sco)      # Read in Score from String
c.start()             # When compiling from strings, this call is necessary before doing any performing

t = ctcsound.CsoundPerformanceThread(c.csound()) # Create a new CsoundPerformanceThread, passing in the Csound object
t.play()              # starts the thread, which is now running separately from the main thread. This 
                      # call is asynchronous and will immediately return back here to continue code
                      # execution.
t.join()              # Join will wait for the other thread to complete. If we did not call join(),
                      # after t.play() returns we would immediate move to the next line, c.stop(). 
                      # That would stop Csound without really giving it time to run. 

c.reset()

Example 5 - Generating Score

In this example, we will look at three techniques for generating our Score. First we need to define our orchestra for the project.


In [6]:
orc = """
sr=44100
ksmps=32
nchnls=2
0dbfs=1

instr 1 
ipch = cps2pch(p5, 12)
kenv linsegr 0, .05, 1, .05, .7, .4, 0
aout vco2 p4 * kenv, ipch 
aout moogladder aout, 2000, 0.25
outs aout, aout
endin"""

Static Score

The first is one we have already seen, which is to just write out the score by hand as a String.


In [7]:
sco = "i1 0 1 0.5 8.00"

#c = ctcsound.Csound()    # create an instance of Csound
c.setOption("-odac")  # Set option for Csound
c.compileOrc(orc)     # Compile Orchestra from String

c.readScore(sco)      # Read in Score from pre-written String

c.start()             # When compiling from strings, this call is necessary before doing any performing

# The following is our main performanceu loop. We will perform one block of sound at a time 
# and continue to do so while it returns 0, which signifies to keep processing.  

while (c.performKsmps() == 0):
  pass

c.reset()

Generating a Score String with a Loop

Knowing that we pass strings into Csound to pass note events, we can also generate the string. In the second example, sco2 starts as an empty string. Using a for-loop, we append to sco2 note strings using a string formatting string that has its replacement values replaced. The replace values are calculated using the i value, and the result is an ascending note line.


In [8]:
sco2 = ""
for i in range(13):
    sco2 += "i1 %g .25 0.5 8.%02g\n"%(i * .25,i)
print(sco2)

#c = ctcsound.Csound()    # create an instance of Csound
c.setOption("-odac")  # Set option for Csound
c.compileOrc(orc)     # Compile Orchestra from String

c.readScore(sco2)      # Read in Score from pre-written String

c.start()             # When compiling from strings, this call is necessary before doing any performing

# The following is our main performanceu loop. We will perform one block of sound at a time 
# and continue to do so while it returns 0, which signifies to keep processing.  

while (c.performKsmps() == 0):
  pass

c.reset()


i1 0 .25 0.5 8.00
i1 0.25 .25 0.5 8.01
i1 0.5 .25 0.5 8.02
i1 0.75 .25 0.5 8.03
i1 1 .25 0.5 8.04
i1 1.25 .25 0.5 8.05
i1 1.5 .25 0.5 8.06
i1 1.75 .25 0.5 8.07
i1 2 .25 0.5 8.08
i1 2.25 .25 0.5 8.09
i1 2.5 .25 0.5 8.10
i1 2.75 .25 0.5 8.11
i1 3 .25 0.5 8.12

Generating a Score Using an Intermediate Data Structure

In the final example, we are going to generate a list of lists. This example generates a score using a random function and then converts to String. The top-level list represents our score as a whole, and each sub-list within it represents the data for a single note. The main list is then processed in two ways: first, it processes each sub-list and joins the values together into a single note string; second, it joins each individual note string into a single, large score string, separated by newlines. The end result is a sequence of 13 notes with random pitches.

The final example represents a common pattern of development. For systems that employ some event-based model of music, it is common to use some kind of data structure to represent events. This may use some kind of common data structure like a list, or it may be represented by using a class and instances of that class.


In [9]:
from random import randint

vals = []           #initialize a list to hold lists of values 
for i in range(13): #populate that list
    vals.append([1, i * .25, .25, 0.5, "8.%02g"%(randint(0,15))])

# convert list of lists into a list of strings
vals = ["i" + " ".join(map(str,a)) for a in vals] 

# now convert that list of strings into a single string
sco3 = "\n".join(vals)

print('Here is the list of lists that was converted into a list of strings:')
print()
print(vals) 
print()
print('Here is the list of score events that was generated into a single string:')
print()
print(sco3)

#c = ctcsound.Csound()    # create an instance of Csound
c.setOption("-odac")  # Set option for Csound
c.compileOrc(orc)     # Compile Orchestra from String

c.readScore(sco3)      # Read in Score from pre-written String

c.start()             # When compiling from strings, this call is necessary before doing any performing

# The following is our main performanceu loop. We will perform one block of sound at a time 
# and continue to do so while it returns 0, which signifies to keep processing.  

while (c.performKsmps() == 0):
  pass

c.reset()


Here is the list of lists that was converted into a list of strings:

['i1 0.0 0.25 0.5 8.02', 'i1 0.25 0.25 0.5 8.15', 'i1 0.5 0.25 0.5 8.07', 'i1 0.75 0.25 0.5 8.11', 'i1 1.0 0.25 0.5 8.07', 'i1 1.25 0.25 0.5 8.10', 'i1 1.5 0.25 0.5 8.03', 'i1 1.75 0.25 0.5 8.14', 'i1 2.0 0.25 0.5 8.02', 'i1 2.25 0.25 0.5 8.10', 'i1 2.5 0.25 0.5 8.03', 'i1 2.75 0.25 0.5 8.01', 'i1 3.0 0.25 0.5 8.06']

Here is the list of score events that was generated into a single string:

i1 0.0 0.25 0.5 8.02
i1 0.25 0.25 0.5 8.15
i1 0.5 0.25 0.5 8.07
i1 0.75 0.25 0.5 8.11
i1 1.0 0.25 0.5 8.07
i1 1.25 0.25 0.5 8.10
i1 1.5 0.25 0.5 8.03
i1 1.75 0.25 0.5 8.14
i1 2.0 0.25 0.5 8.02
i1 2.25 0.25 0.5 8.10
i1 2.5 0.25 0.5 8.03
i1 2.75 0.25 0.5 8.01
i1 3.0 0.25 0.5 8.06

Example 6 - Further Example of Generating Score

This example continues on from Example 5, rewriting the example using a Class called Note. The note example has its str method implemented to generate a well-formatted Csound SCO note. This example also shows how a list of notes could be used multiple times.The first loop through we use the notes as-is, and during the second time we generate the notes again with the same properties except we alter the fifth p-field up 4 semitones.

Note: Altering a Notes values like this is alright for this example, but it is a destructive edit. Real world code might make copies of Notes or alter the score generation to maintain the original values.


In [10]:
from random import randint

def midi2pch(num):
    "Convert MIDI Note Numbers to Csound PCH format"
    return "%d.%02g" % (3 + (num / 12), num % 12)

class Note(object):
    def __init__(self, *args):
        self.pfields = list(args)

    def __str__(self):
        retVal = "i"
        for i in range(len(self.pfields)):
            if(i == 4):
                retVal += " " + midi2pch(self.pfields[i])
            else:
                retVal += " " + str(self.pfields[i])
        return retVal

# Our Orchestra for our project
orc = """
sr=44100
ksmps=32
nchnls=2
0dbfs=1

instr 1 
ipch = cps2pch(p5, 12)
kenv linsegr 0, .05, 1, .05, .7, .4, 0
aout vco2 p4 * kenv, ipch 
aout moogladder aout, 2000, 0.25
outs aout, aout
endin"""

#c = ctcsound.Csound()    # create an instance of Csound
c.setOption("-odac")  # Set option for Csound
c.compileOrc(orc)     # Compile Orchestra from String


notes = []           #initialize a list to hold lists of values 
for i in range(13): #populate that list
    notes.append( Note(1, i * .25, .25, 0.5, randint(60,75)) )

# now convert list of Note objects to string
sco = ""
for n in notes:
    sco += "%s\n"%n # this implicitly calls the __str__ method on the Note Class

# generate notes again transposed a Major 3rd up
for n in notes:
    n.pfields[4] += 4
    n.pfields[1] += .125
    sco += "%s\n"%n 

print('Here is the list of score events that was generated:')
print()
print(sco)

c.readScore(sco)     # Read in Score generated from notes 

c.start()             # When compiling from strings, this call is necessary before doing any performing

# The following is our main performance loop. We will perform one block of sound at a time 
# and continue to do so while it returns 0, which signifies to keep processing.  

while (c.performKsmps() == 0):
  pass

c.reset()


Here is the list of score events that was generated:

i 1 0.0 0.25 0.5 8.01
i 1 0.25 0.25 0.5 8.03
i 1 0.5 0.25 0.5 8.07
i 1 0.75 0.25 0.5 8.00
i 1 1.0 0.25 0.5 8.01
i 1 1.25 0.25 0.5 8.11
i 1 1.5 0.25 0.5 8.04
i 1 1.75 0.25 0.5 8.09
i 1 2.0 0.25 0.5 8.04
i 1 2.25 0.25 0.5 8.04
i 1 2.5 0.25 0.5 8.05
i 1 2.75 0.25 0.5 8.01
i 1 3.0 0.25 0.5 9.03
i 1 0.125 0.25 0.5 8.05
i 1 0.375 0.25 0.5 8.07
i 1 0.625 0.25 0.5 8.11
i 1 0.875 0.25 0.5 8.04
i 1 1.125 0.25 0.5 8.05
i 1 1.375 0.25 0.5 9.03
i 1 1.625 0.25 0.5 8.08
i 1 1.875 0.25 0.5 9.01
i 1 2.125 0.25 0.5 8.08
i 1 2.375 0.25 0.5 8.08
i 1 2.625 0.25 0.5 8.09
i 1 2.875 0.25 0.5 8.05
i 1 3.125 0.25 0.5 9.07

Example 7 - Communicating Continuous Values with Csound's Channel System

This example introduces using Csound's Channel System to communicate continuous control data (k-rate) from a host program to Csound. The first thing to note is the RandomLine class. It takes in a base value and a range in which to vary randomly. The reset functions calculates a new random target value (self.end), a random duration in which to run (self.dur, expressed as # of audio blocks to last in duration), and calculates the increment value to apply to the current value per audio-block. When the target is met, the Randomline will reset itself to a new target value and duration.

In this example, we use two RandomLine objects, one for amplitude and another for frequency. We start a Csound instrument instance that reads from two channels using the chnget opcode. In turn, we update the values to the channel from the host program. In this case, because we want to keep our values generating in sync with the audio engine, we use a while-loop instead of a CsoundPerformanceThread. To update the channel, we call the SetChannel method on the Csound object, passing a channel name and value.

Note: The getValue method on the RandomLine not only gets us the current value, but also advances the internal state by the increment and by decrementing the duration.


In [11]:
from random import randint, random

class RandomLine(object):
    def __init__(self, base, range):
        self.curVal = 0.0
        self.reset()
        self.base = base
        self.range = range

    def reset(self):
        self.dur = randint(256,512) 
        self.end = random() 
        self.increment = (self.end - self.curVal) / self.dur

    def getValue(self):
        self.dur -= 1
        if(self.dur < 0):
            self.reset()
        retVal = self.curVal
        self.curVal += self.increment
        return self.base + (self.range * retVal)

# Our Orchestra for our project
orc = """
sr=44100
ksmps=32
nchnls=2
0dbfs=1

instr 1 
kamp chnget "amp"
kfreq chnget "freq"
printk 0.5, kamp
printk 0.5, kfreq
aout vco2 kamp, kfreq
aout moogladder aout, 2000, 0.25
outs aout, aout
endin"""

#c = ctcsound.Csound()    # create an instance of Csound
c.setOption("-odac")  # Set option for Csound
c.setOption("-m7")  # Set option for Csound
c.compileOrc(orc)     # Compile Orchestra from String

sco = "i1 0 60\n"

c.readScore(sco)     # Read in Score generated from notes 
c.start()             # When compiling from strings, this call is necessary before doing any performing

# The following is our main performance loop. We will perform one block of sound at a time 
# and continue to do so while it returns 0, which signifies to keep processing.  

amp = RandomLine(.6, .2)    # create RandomLine for use with Amplitude
freq = RandomLine(400, 80)  # create RandomLine for use with Frequency 

c.setControlChannel("amp", amp.getValue())     # Initialize channel value before running Csound
c.setControlChannel("freq", freq.getValue())   # Initialize channel value before running Csound

print('Initial amp value is: ' + str(amp.getValue()))
print('Initial freq value is: ' + str(freq.getValue()))

while (c.performKsmps() == 0):
    c.setControlChannel("amp", amp.getValue())   # update channel value 
    c.setControlChannel("freq", freq.getValue()) # update channel value 

c.reset()


Initial amp value is: 0.6002198175047312
Initial freq value is: 400.0694066225055

Example 8 - More Efficient Channel Communications

This example builds on Example 7 by replacing the calls to SetChannel with using GetChannelPtr. In the Csound API, using SetChannel and GetChannel is great for quick work, but ultimately it is slower than pre-fetching the actual channel pointer. This is because Set/GetChannel operates by doing a lookup of the Channel Pointer, then setting or getting the value. This happens on each call. The alternative is to use channelPtr, which fetches the Channel Pointer and lets you directly set and get the value on the pointer. When a pointer is returned by a function of the API, ctcsound encapsulates this pointer in an ndarray (numpy array). Once the pointer is connected to the array, values can be written directly into the array through the channel.


In [12]:
from random import randint, random

class RandomLine(object):
    def __init__(self, base, range):
        self.curVal = 0.0
        self.reset()
        self.base = base
        self.range = range

    def reset(self):
        self.dur = randint(256,512) 
        self.end = random() 
        self.slope = (self.end - self.curVal) / self.dur

    def getValue(self):
        self.dur -= 1
        if(self.dur < 0):
            self.reset()
        retVal = self.curVal
        self.curVal += self.slope
        return self.base + (self.range * retVal)

# Our Orchestra for our project
orc = """
sr=44100
ksmps=32
nchnls=2
0dbfs=1

instr 1 
kamp chnget "amp"
kfreq chnget "freq"
printk 0.5, kamp
printk 0.5, kfreq
aout vco2 kamp, kfreq
aout moogladder aout, 2000, 0.25
outs aout, aout
endin"""

#c = ctcsound.Csound()    # create an instance of Csound
c.setOption("-odac")  # Set option for Csound
c.setOption("-m7")  # Set option for Csound
c.compileOrc(orc)     # Compile Orchestra from String

sco = "i1 0 60\n"

c.readScore(sco)     # Read in Score generated from notes 

c.start()             # When compiling from strings, this call is necessary before doing any performing

# The following calls return a tuple. The first value of the tuple is a numpy array
# encapsulating the Channel Pointer retrieved from Csound and the second
# value is an error message, if an error happened (here it is discarded with _).
ampChannel, _ = c.channelPtr("amp",
    ctcsound.CSOUND_CONTROL_CHANNEL | ctcsound.CSOUND_INPUT_CHANNEL)
freqChannel, _ = c.channelPtr("freq",
    ctcsound.CSOUND_CONTROL_CHANNEL | ctcsound.CSOUND_INPUT_CHANNEL)

amp = RandomLine(.4, .2)
freq = RandomLine(400, 80)

ampChannel[0] = amp.getValue()    # note we are now setting values in the ndarrays
freqChannel[0] = freq.getValue()

print('Initial amp value is: ' + str(amp.getValue()))
print('Initial freq value is: ' + str(freq.getValue()))

while (c.performKsmps() == 0):
    ampChannel[0] = amp.getValue()
    freqChannel[0] = freq.getValue()

c.reset()


Initial amp value is: 0.4001430261404641
Initial freq value is: 400.2219446265465

Example 9 - More Flexible and Efficient Channel Communications

This example continues on from Example 9 and just refactors the creation and setup of numpy arrays into a createChannel() function. This example illustrates some natural progression that might occur in your own API-based projects, and how you might simplify your own code.


In [13]:
from random import randint, random

class RandomLine(object):
    def __init__(self, base, range):
        self.curVal = 0.0
        self.reset()
        self.base = base
        self.range = range

    def reset(self):
        self.dur = randint(256,512) 
        self.end = random() 
        self.slope = (self.end - self.curVal) / self.dur

    def getValue(self):
        self.dur -= 1
        if(self.dur < 0):
            self.reset()
        retVal = self.curVal
        self.curVal += self.slope
        return self.base + (self.range * retVal)

# The following call return a tuple. The first value of the tuple is a numpy array
# encapsulating the Channel Pointer retrieved from Csound and the second
# value is an error message, if an error happened (here it is discarded with _).
def createChannel(channelName):
    chn, _ = c.channelPtr(channelName,
    ctcsound.CSOUND_CONTROL_CHANNEL | ctcsound.CSOUND_INPUT_CHANNEL)
    return chn

###############################

# Our Orchestra for our project
orc = """
sr=44100
ksmps=32
nchnls=2
0dbfs=1

instr 1 
kamp chnget "amp"
kfreq chnget "freq"
printk 0.5, kamp
printk 0.5, kfreq
aout vco2 kamp, kfreq
aout moogladder aout, 2000, 0.25
outs aout, aout
endin"""

#c = ctcsound.Csound()    # create an instance of Csound
c.setOption("-odac")  # Set option for Csound
c.setOption("-m7")  # Set option for Csound
c.compileOrc(orc)     # Compile Orchestra from String

sco = "i1 0 60\n"

c.readScore(sco)     # Read in Score generated from notes 

c.start()             # When compiling from strings, this call is necessary before doing any performing

ampChannel = createChannel("amp")   # uses utility method to create a channel and get numpy array to write to
freqChannel = createChannel("freq")

amp = RandomLine(.4, .2)
freq = RandomLine(400, 80)

ampChannel[0] = amp.getValue()
freqChannel[0] = freq.getValue()

while (c.performKsmps() == 0):
    ampChannel[0] = amp.getValue()
    freqChannel[0] = freq.getValue()

c.reset()

Example 10 - Even More Flexible and Efficient Channel Communications

This example continues on from Example 10 and introduces a ChannelUpdater object. The ChannelUpdater will create and store a numpy array that is wrapping a Csound Channel. Additionally, it will store and call an object that has a getValue() method to update values in the channel when update() is called.

This example continues the illustration of a progression of a project. Note that the process has changed a little bit where we now create a number of ChannelUpdater objects and store them in a list. The list is then iterated through for updating the channel with the latest values. In a real-world project, this kind of scenario occurs when there are n-number of items to update channels and one wants to have a flexible number that may even change dynamically at runtime.


In [14]:
from random import randint, random

class RandomLine(object):
    def __init__(self, base, range):
        self.curVal = 0.0
        self.reset()
        self.base = base
        self.range = range

    def reset(self):
        self.dur = randint(256,512) 
        self.end = random() 
        self.slope = (self.end - self.curVal) / self.dur

    def getValue(self):
        self.dur -= 1
        if(self.dur < 0):
            self.reset()
        retVal = self.curVal
        self.curVal += self.slope
        return self.base + (self.range * retVal)


def createChannel(channelName):
    chn, _ = c.channelPtr(channelName,
    ctcsound.CSOUND_CONTROL_CHANNEL | ctcsound.CSOUND_INPUT_CHANNEL)
    return chn

class ChannelUpdater(object):
    def __init__(self, channelName, updater):
        self.updater = updater
        self.channel = createChannel(channelName)

    def update(self):
        self.channel[0] = self.updater.getValue()

###############################

# Our Orchestra for our project
orc = """
sr=44100
ksmps=32
nchnls=2
0dbfs=1

instr 1 
kamp chnget "amp"
kfreq chnget "freq"
kres chnget "resonance"
printk 0.5, kamp
printk 0.5, kfreq
printk 0.5, kres
aout vco2 kamp, kfreq
aout moogladder aout, 2000, kres
outs aout, aout
endin"""

#c = ctcsound.Csound()    # create an instance of Csound
c.setOption("-odac")  # Set option for Csound
c.setOption("-m7")  # Set option for Csound
c.compileOrc(orc)     # Compile Orchestra from String

sco = "i1 0 60\n"

c.readScore(sco)     # Read in Score generated from notes 

c.start()             # When compiling from strings, this call is necessary before doing any performing

# Create a set of ChannelUpdaters
channels = [ChannelUpdater("amp", RandomLine(.4, .2)),
            ChannelUpdater("freq", RandomLine(400, 80)),
            ChannelUpdater("resonance", RandomLine(0.4, .3))]

# Initialize all Channel Values
for chn in channels:
    chn.update()

while (c.performKsmps() == 0):
    for chn in channels:   # update all channel values
        chn.update()

c.reset()

Example 11 - Graphical User Interfaces

This example demonstrates a minimal Graphical User Interface application. The setup of Csound and starting of the CsoundPerformanceThread is done in the global scripting space. Afterwards, a Tkinter GUI is created that has one button. The button's callback (the command action) routes to a function that just sends an event to Csound.

For this example, since there is no need to synchronize continous channel data changes with Csound, it is more efficient to use the CsoundPerformanceThread, as it is a native thread. We use the CsoundPerformanceThread's inputMessage() function to ensure that the message is processed in a thread-safe manner.


In [15]:
from tkinter import *
from random import randint, random

###############################
# Our Orchestra for our project

orc = """
sr=44100
ksmps=32
nchnls=2
0dbfs=1

instr 1 
kenv linsegr 0, .05, 1, .05, .9, .8, 0
aout vco2 p4 * kenv, p5
aout moogladder aout, 2000, p6
outs aout, aout
endin"""

#c = ctcsound.Csound()    # create an instance of Csound
c.setOption("-odac")  # Set option for Csound
c.setOption("-m7")  # Set option for Csound
c.compileOrc(orc)     # Compile Orchestra from String

c.start()             # When compiling from strings, this call is necessary before doing any performing

perfThread = ctcsound.CsoundPerformanceThread(c.csound())
perfThread.play()

class Application(Frame):

    def __init__(self,master=None):
        master.title("Csound API GUI Example")
        self.items = []
        self.notes = []
        Frame.__init__(self,master)
        self.pack()
        self.createUI()
        self.master.protocol("WM_DELETE_WINDOW", self.quit)

    def createUI(self):
        self.size = 600
        self.canvas = Canvas(self,height=self.size,width=self.size,bg="darkgray")
        self.canvas.pack()
        # create button and setup the playNote() callback
        self.button = Button(self.canvas, text='Play Note', command=self.playNote)    
        self.button.pack()

    def playNote(self):
        perfThread.inputMessage("i1 0 2 .5 400 .25")

    def quit(self):
        self.master.destroy()
        perfThread.stop()
        perfThread.join()


app = Application(Tk())
app.mainloop()
c.reset()

Example 12 - Graphical User Interfaces

This example demonstrates a slightly more advanced GUI example. It uses a slider to allow setting the value of the frequency that the notes initiated by the button will play at.

Note: the actual use of update() here is not thread-safe. In real-world usage, we would need to drive Csound from a loop calling PerformKsmps to ensure thread-safety. For this example, the updating generally works as there are few things demanding computation.


In [16]:
from tkinter import *
from random import randint, random

###############################

# Our Orchestra for our project
orc = """
sr=44100
ksmps=32
nchnls=2
0dbfs=1

gkpch chnexport "freq", 1

instr 1 
kpch port gkpch, 0.01, i(gkpch)
printk .5, gkpch
kenv linsegr 0, .05, 1, .05, .9, .8, 0
aout vco2 p4 * kenv, kpch
aout moogladder aout, 2000, .25 
outs aout, aout
endin"""

#c = ctcsound.Csound()    # create an instance of Csound
c.setOption("-odac")  # Set option for Csound
c.setOption("-m7")  # Set option for Csound
c.compileOrc(orc)     # Compile Orchestra from String

c.start()             # When compiling from strings, this call is necessary before doing any performing

perfThread = ctcsound.CsoundPerformanceThread(c.csound())
perfThread.play()


def createChannel(channelName):
    chn, _ = c.channelPtr(channelName,
    ctcsound.CSOUND_CONTROL_CHANNEL | ctcsound.CSOUND_INPUT_CHANNEL)
    return chn

class SliderWrapper(object):
    def __init__(self, csound, channelName, slider):
        self.slider = slider
        self.channel = createChannel(channelName)

    def update(self):
        self.channel[0] = self.slider.get()

class Application(Frame):

    def __init__(self,master=None):
        master.title("Csound API GUI Example")
        self.items = []
        self.notes = []
        Frame.__init__(self,master)
        self.pack()
        self.createUI()
        self.master.protocol("WM_DELETE_WINDOW", self.quit)

    def createUI(self):
        self.size = 600
        self.canvas = Canvas(self,height=self.size,width=self.size)
        self.canvas.pack()
        self.button = Button(self.canvas, text='Play Note', command=self.playNote)    
        self.button.pack()
        self.freqSlider = Scale(self.canvas,from_=80.0, to=600.0,command=self.setFreq,label="Freq")
        self.freqSlider.pack()
        self.freqUpdater = SliderWrapper(c, "freq", self.freqSlider)

    def playNote(self):
        perfThread.inputMessage("i1 0 2 .3")

    def setFreq(self, val):
        print(val)
        self.freqUpdater.update()

    def quit(self):
        self.master.destroy()
        perfThread.stop()
        perfThread.join()


app = Application(Tk())
app.mainloop()
c.stop
del c


81
88
111
126
149
156
172
179
195
202
218
225
233
241
248
256
271
286
294
302
309
317
325
332
348
355
378
386
394
409
424
439
447
462
470
478
485
493
501
508
516
524
531
539
531
501
493
478
455
432
409
394
371
348
332
309
294
279
271
248
241
233
218
202
195
187
179
172
164
156
149
141
134
149
156
172
187
202
218
225
248
264
286
302
317
332
340
355
363
355
340
332
317
309
302
294
279
271
264
248
241
233
225
218
210
202
195
187
179
172
164