Objects and classes: making your own ChucK power tools - Now it gets really interesting - Programming for Musicians and Digital Artists: Creating music with ChucK (2015)

Programming for Musicians and Digital Artists: Creating music with ChucK (2015)

Part 2. Now it gets really interesting!

Chapter 9. Objects and classes: making your own ChucK power tools

This chapter covers

· Data encapsulation and access, member variables

· Behavior of objects, functions, and methods

· Public vs. private classes

· Inheritance, overloading, and polymorphism

· Musical applications of objects and classes

As you keep adding to your bag of ChucK tricks and tools, you’re able to be more expressive in your compositions, more succinct and efficient with your code, and generally more awesome as programmers and composers. In chapter 5, you learned how to make your own functions, opening up new capabilities for your programming, because you could reuse code and get a lot more work accomplished by calling functions with different arguments. In the previous chapter, you learned how to run programs and functions in parallel, again adding to your programming power and musical expressiveness. In this chapter, you’ll learn how to make your own objects and classes, which can be viewed as super-functions but even more powerful. When you’re through, you’ll have even more ways to organize, use, and reuse your ChucK code. And your music and art will be even better.

9.1. Object-oriented programming: objects and classes

In this chapter, we’ll will dive into the last major computer science topic in the book: OOP. The power that you’ll gain from learning about and how to make and use objects and classes can make your programs and compositions even more flexible, readable, and expressive. First, we need to briefly define some terms, and then you’ll be ready to learn about and use OOP in your programs.

A little OOP history

Beginning in the 1980s, a new notion called object-oriented programming began to revolutionize the computer world. Actually, OOP systems weren’t so new, dating back to Simula, ALGOL, Lisp, and other languages in the late 1960s, but the ideas were new to production programming (commonly used programs and applications). Languages such as Smalltalk, Object Lisp, and others began to popularize OOP in the 1980s, but OOP really caught on with the introduction and adoption of C++, Objective C, and Java during the 1990s. Inspired by many of these pioneering languages, ChucK is an objected-oriented language that borrows from the object systems of C++ and Java but with some differences. You’ll see in this chapter how powerful this philosophy, method, design, and style of programming can be, especially for music and media programming.

9.1.1. Objects in general

Say you want to put together a band, but in this case you’ll be using ChucK code, as you did in the previous chapter with your jazz band. You might hire players (program them in ChucK), give the players music or tell them the song you want to play, and conduct them or somehow get them started at the same time with the same tempo. From your perspective, each player is essentially an object. The guitar player, drummer, and flute player all bring with them their own instrument and expertise. Each knows how to play their own instrument, knows how to read music, and/or knows their part for the song of interest. As a contractor/producer, you don’t need all that special knowledge; you need to know only that each band member knows what they’re doing. For each player, the instrument is itself an object (or collection of objects in the case of a drum set). The flute player doesn’t have to know the physics of the materials that make up a flute, or the physics of the waves travelling inside, or the nonlinear fluid dynamics of the air jet they’re blowing. So the flute player is an object playing an object, getting instructions or messages and data from other objects (conductor, score, and other players).

Just as you’ve used functions to encapsulate calculations (behaviors), you can make software objects that do the same. But objects are bigger and better than functions, because they can have knowledge in the form of internal data that persists over time. Some object data can be private (as with the mysterious physics inside the flute), and some data can be public, accessible by all. Some things that objects do are also private (how the STK Flute model calculates each sample internally), whereas others are public, like the .freq and .gain methods you use to control pitch and volume. You’ll see later why you might want to make some things private and others public.

You’ve been using objects in ChucK, because all unit generators are objects. Each variable you declare is an object, just a simple one. An array is an object composed of other objects. So when you declare something like

[0,0,1,1,0,1,0,1] @=> int nums[];

you’re declaring an array object that holds integer variables inside.

When you make a new SinOsc, it has data inside it, including frequency, amplitude, how to interpret input (.sync), and the calculations to make the sine wave. Figure 9.1 depicts a SinOsc object that contains code for computing a sine wave (hidden from you, it just “works”) and variables controlling gain, frequency, and sync. You can change the variable values by using the .gain, .freq, and .sync methods. You can also ask the SinOsc object for the current value of .gain(), .freq(), and .sync().

Figure 9.1. A SinOsc object contains hidden code for computing a sine wave and variables that you can modify or retrieve using the functions provided by the object.

UGens are objects, and even the VM is an object, so you’ve already used lots of objects. But how do you make your own new types of objects? To do that, you have to know about classes.

9.1.2. Classes

In OOP, a class is a data type that encapsulates and abstracts data and behavior. This means that a class defines what an object will do and what it will contain, including variables and functions that you can call from outside to control and inspect the object. The class also defines any hidden code that the object will implement and/or variables that the object will contain.

When you want to use a SinOsc, you have to declare one, and you can declare and use as many as you like, as shown in figure 9.2. ChucK knows what a SinOsc is because the SinOsc class is already defined inside ChucK. When you declare SinOsc s1, ChucK creates a new one for you and associates it with the name s1 and similarly for SinOsc s2 and SinOsc s3. Each is a different copy of the basic SinOsc type, and you can modify its frequency, gain, and so on individually without changing the others. The individual SinOsc s1, s2, and s3 are instances of the basic SinOsc class. Declaring a new object and giving it a variable name is often called instantiation in OOP.

Figure 9.2. Each time you declare a SinOsc, you’re making a new instance of the SinOsc class. You can use them individually without confusion, because you give each instance a unique variable name.

When we introduced variables in chapter 1, you learned that you can declare multiple variables like float x, y, and z; each is an individual float variable that can have its own value. Similarly, you can declare SinOsc s[4], and each member of that array is a separate SinOsc object. In OOP we say that a declared object is an instance of a class, and you can make (declare) as many of those instances as you like. As shown in figure 9.2 s[3] is one instance of SinOsc, and x and y are instances of float.

Let’s take a specific example of a class that you’ve already used extensively, SndBuf. You can make as many instances of SndBuf as you like just by declaring them, but for now let’s just declare one SndBuf, myBuff.

Usually, the function you first use with a new SndBuf is myBuff.read(FILENAME), in this case causing this particular myBuff instance to load a sound file into its internal memory. When you call that function, SndBuf automatically allocates the amount of memory needed to store the sound file you wish to load. You don’t have to worry about that (as you do in the case of Delay objects, where you must tell them how much maximum delay you might need); the SndBuf class takes care of that automatically. In OOP, functions for accessing and modifying object data and causing computation within an object are called methods or member functions. Other SndBuf member functions include .rate and .loop.

You can use the built-in SndBuf member functions to change myBuff.rate to modify the speed and pitch of playback. You can set myBuff.loop to cause the sound to loop (set it to 1) or not (set it to zero). You can query myBuff to see how long the sound you’ve loaded is, either as an integer number of .samples() or as a duration using .length(). There are other member functions and variables built into SndBuf, specified within the class, and available to you via member functions called on any instance that you make.

Variables encapsulated within an object are sometimes called member variables. For example, member variables of SinOsc instances include .freq and .gain. There’s lots of other lingo to describe how cool classes and objects are, but rather than expand on those, we’ll show you how to make and use your own custom classes.

9.2. Writing your own classes

Your first class, called testData , in listing 9.1, will show how objects can contain both data and functions. testData contains int and float member variables and a function (a method or member function). You can access the data easily by name and use the function as you might expect. Both are attached to the name of the instance of the class by a dot, so in this case you make an instance called d and access the internal myInt by using d.myInt . You can invoke the internal class function by calling d.sum() . This should all look familiar, because you’ve been making lots of objects such as UGens and accessing their innards by using the dot, as in the case of s.freq() and s.gain for a SinOsc called s.

Listing 9.1. Making and using your first new class, testData

Now let’s do something musically useful with classes. In listing 9.2, we’ll refer to our ResonZ example from listing 6.10. You declare the same chain of Impulse through the ResonZ filter into dac , but you wrap it all in a class definition , with some methods to set the frequency , filter resonance (Q) , and volume . You also create a noteOn() function that behaves as any other instrument might, playing a note at the specified argument volume. The nice thing about encapsulating all this into a class is that you can then make any number of that type of object just as you make floats and ints and SinOscs, and use them without their internal variables (imp, filt) getting in the way of each other. You could even make an array of your new Simple objects.

Listing 9.2. Simple resonant pop class wraps UGens and functions for accessing them

Once your class is defined, you can make an instance of it and play it in an infinite loop , where you change the frequency randomly for each note .

You’ve now created your first class that makes and controls sound! This class contained UGen objects, including an Impulse and a ResonZ filter. You can declare as many instances of your Simple objects as you like, and each one will contain unique instances of the Impulse and ResonZ filters. Next, you’ll make your class definitions and functions even more flexible.

9.3. Overloading: different functions can share the same name

OOP allows you to overload functions, which means that functions can behave differently depending on the arguments. This allows you to interact with your classes using meaningful function names, and the class and object instance figures out what to do based on the argument. As an example of making a highly useful overloaded function, you might define some new functions for Simple, to set pitch either by MIDI note number (an int), frequency (a float), or even a musical note name (a string). Those three functions can all have the same function name (we’ll use.pitch()), but because their arguments are different, the class will know what to do.

ChucK figures out which one you want by looking at the argument you pass to the .pitch() method. If it’s a float, you use the .freq method you already have . If it’s an int, then the integer version is used , with the argument treated as a MIDI note number. If it’s a string , some logic and math on string characters is used to convert that into a MIDI note number , and then the int version of pitch is called by invoking the this keyword (meaning this object, this instance of the class). Try adding these three new pitch functions to Simple from listing 9.2, then calling it with different arguments. The code for this is shown in the following listing.

Listing 9.3. Add these overloaded pitch() functions to Simple

Once you’ve added these functions to your Simple class, you can test it by calling pitch using each method, passing it an int , a float , and a note name string .

Did you get an error when running your new Simple class?

When you tried to run your new Simple class example, you likely got an error from the miniAudicle. If it was a syntax error, as you so often get when first running any new code, you can fix the error and try again. But even with no syntax errors, you probably got an error that looked something like this:

[Simple.ck] :line(2): class/type 'Simple' is already defined in

namespace '[user]'

Read on, because the next section will explain this and help you fix it.

When defining your own classes, you can declare multiple functions that have the same name but can do different things depending on the type and number of arguments. The same is true inside ChucK for many operators. You’ve already been using lots of ChucK operators that are overloaded. The ChucK symbol => itself is overloaded, because you use it to connect unit generators (SinOsc s => dac), to assign values to variables (0.1 => float x), and to advance time (0.1 :: second => now). ChucK figures out by the context and the data types of the arguments and destinations what the ChucK operator should be doing. Another example of overloading is the plus sign. You can use it to add integers, floats, or even complex numbers, and it can also be used to concatenate strings ("Hello "+"World" => string helloworld). As we said, functions can be overloaded as well, and you’ve already seen a musically useful reason for that with your Simple.pitch() functions.

9.4. Public vs. private classes

That error you probably got when running your new Simple class has to do with defining new classes as public, which means that every new piece of ChucK code can have access to and use the newly defined class. Having classes defined as public is great, as long as you know that your class works just the way you want it and you don’t ever want to modify it. But for development, you need to be able to modify your class definitions, functions, and data on the fly. The problem the ChucK compiler ran into was that it already had a class defined called Simple, and running your new code told ChucK to define it again, which is illegal.

The easiest way to fix this is to rename Simple to Simple2 or something else, but that’s not really the best way, because class definitions will pile up quite quickly and become confusing. Another way to deal with the public name-collision issue is to declare the class as private rather thanpublic, meaning that only code within this file and shred, as you’re running it, can see it and have access to any new private class. So for now, go to the top of your Simple code, and change the first defining line to read as follows:

// Simple example of a class, made private

private class Simple {

// our Simple instrument patch

Impulse imp => ResonZ filt => dac;

// etc. etc. etc.

With Simple defined as private, you can now redefine, experiment, and fiddle with the new class all you like, even adding more of them on top of each other while the virtual machine continues running.

private classes are great, if you define and use them in the same .ck code file. But there are many reasons why you need and want public classes as well as private, and the next section will demonstrate just one of those reasons: communication between shreds via global (static) public variables.

9.4.1. Useful applications for public classes

Now that you know how to define classes and declare them as private so that you can incrementally improve them, you might wonder why you’d ever make a class public. Because they’re global and visible to all, public classes can be really useful. As an example, let’s define a public class that keeps track of tempo for an orchestra of players, as shown in the following listing. The job of the BPM class is to be a container for some related durations: quarterNote, eighthNote, and so on . The tempo() method takes a floating-point argument specifying the beats per minute , does a little math to figure out a base duration , and sets the remaining durations .

Listing 9.4. Public class BPM acting like a global conductor

To use the BPM class, you just need to make an instance of it , set the tempo , and then ask for the values you need (quarter, eighth, and so on) . This example uses a duration within a BPM object to step the frequency of a SinOsc upward from 400 to 800 Hz every quarterNote . If you run the code of listing 9.5 multiple times but change the tempo , you’ll hear the frequency step upward faster or slower.

9.4.2. The Clear VM button

Because you’re using a public class for BPM, once you’ve defined the class and run the code again, you’ll run into the same type of “already defined in namespace ‘[user]’” error message as you define and redefine the class. The way around this is to click the special Clear VM button, which causes ChucK to forget everything that’s been defined previously. You might want to redefine one class while leaving all others in place, but this isn’t possible. But very soon we’ll show you a means of organizing your code that makes all this much easier to deal with.

9.4.3. Static variables

Our example of using BPM to step a SinOsc frequency upward is fine as an example, but you could have done that by just defining and using the quarter note duration. The true power of BPM as a public class is that many objects and functions can access it at the same time. To really take advantage of that, you need to make a little change to your BPM class definition in order to make its variables visible to other shreds of ChucK code, so everyone in your band can see the tempo values from everywhere. The way to do that is to declare the note duration values as static , as shown in the following listing, meaning that those particular variables are shared and accessible across all instances of the class.

Listing 9.5. Redefining BPM’s variables as static

If you make two instances of BPM, in two different ChucK files, or even two BPM s within the same file, and modify one BPM instance, the other BPM is modified automatically, because they share the same static duration variables. We’ll use that in an example in the next two listings.

Listing 9.6. Make a BPM; setting tempo here affects all others

// This lives in file UseBPM.ck

SinOsc s => dac;

BPM t; // Define t Object of Class BPM

t.tempo(300); // set tempo in BPM

0.3 => s.gain;

400 => int freq;

while (freq < 800)

{

freq => s.freq;

t.quarterNote => now;

50 +=> freq;

}

Listing 9.7. Make another BPM, but static variables are shared

// Lives in another file, UseBPM2.ck

SinOsc s => dac.right;

BPM t2; // Define another BPM named t2

0.3 => s.gain;

800 => int freq;

while (freq > 400)

{

freq => s.freq;

t2.quarterNote => now;

50 -=> freq;

}

Save your BPM class definition in one ChucK file, BPM.ck. Save your first test program in a file called UseBPM.ck and your second test program in a file called UseBPM2.ck. Then you can add them in that order (BPM.ck, then UseBPM.c, and then UseBPM2.ck) and hear the first sine wave rising and the second one falling. The variables quarter-Note, eighthNote, and so on are now global, meaning that they’re visible from everywhere within ChucK.

You might ask, “But can’t I put all those in the same file and just run it?” As it is, the definitions of SinOsc s and int freq would collide, yielding an “already defined” error. You could change that, giving each a new name, but as the size of your orchestra grows, you’re increasingly going to need a way to keep things straight and put instruments, class definitions, data, and the like in separate files so you can reuse them (another strength of OOP). Also, because you now know how awesome concurrency can be, you might as well get used to thinking in that way: putting different things in their own files and using Machine.add() and other VM functions to control your programs and compositions.

9.5. Initialize.ck: an architecture for organizing your code

To help keep things straight, you use a special file called initialize.ck, whose job it is to be the first and only file you run (using the Add Shred button). Once you’ve crafted and saved your individual files (classes, score.ck, instrument files, and so on) initialize .ck can be the only file you open, and it adds everything to the ChucK VM. initialize.ck first adds any public classes and then adds the score.ck file, which in turn adds the other files. Figure 9.3 shows this architecture.

Figure 9.3. Suggested architecture for organizing code. Master file initialize.ck first adds any public classes and then adds score.ck, which then adds other files to make up the composition.

Just as with sounds and other files in previous chapters, the use of

me.dir()+"/FILENAME.ck" lets that run for 4 seconds,

allows ChucK to find your files, as long as they’re in the same directory as initialize.ck. As shown in listing 9.8, initialize.ck first adds your public BPM class and then your two test files, UseBPM.ck and UseBPM2.ck . Note that they’re perfectly in sync, with one sine wave stepping upward and the other stepping downward. This is another way to start two or more independent instruments playing together at the same time. If you Machine.add() them together, they start together.

Listing 9.8. Initialize.ck file serves as the master program

After waiting a bit, initialize.ck adds one more file, myScore.ck , which contains an endless loop that continuously adds UseBPM2.ck and a new file, UseBPM3.ck , as shown in the following listing.

Listing 9.9. MyScore.ck file adds lots of other

UseBPM3.ck, shown in listing 9.10, modifies the tempo randomly each time it’s added , so any other files that are still running (UseBPM2 or UseBPM3) will change tempo as well. UseBPM3 also sweeps its own sine wave downward .

Running initialize.ck, you should hear lots of sine waves sweeping downward at different rates, because it first runs UseBPM.ck and UseBPM2.ck and then adds myScore.ck after 4 seconds have passed. myScore.ck in turn runs UseBPM2.ck and UseBPM3.ck in an endless loop. Watching the VM monitor window, you’ll see all of the new shreds being added and removing themselves after their individual sines have swept.

Listing 9.10. UseBPM3.ck randomly sets BPM tempo and sweeps another sine

This example shows how you could use public classes and global static variables to communicate and synchronize between shreds, but because BPM is about tempo, you should do something more rhythmic, don’t you think?

9.6. Conducting a drum pattern using a time-varying BPM

Let’s put the BPM class to even more musical use, by constructing a drum pattern using different sounds controlled by different files, as shown in listing 9.11. Because of the flexibility of ChucK, and by crafting your individual programs well, you can easily adapt these to use BPM to control tempo globally. You’ll use the very same BPM class definition you made and used previously. This example will also modify BPM on the fly as all of the drums are playing, using it as a global conductor, controlled by your score.ck file. You should put all these files in their own directory, named something like DrumBPM, at the same level as the /audio directory.

You need percussion instruments, so you’ll start out with a kick drum. This file uses a SndBuf loaded with a kick drum sound file . Then you define a BPM object , but you don’t need to set any parameters for it because that will all happen globally, controlled by your score.ck file (you’ll make that later). The kick infinitely plays a four-beat pattern , sounding each beat and refreshing its quarter-note duration at the beginning of each bar . You’ll be writing each instrument (player) so that it does its thing for a four-beat bar and then refreshes its local duration from the global BPM before starting the bar pattern over.

Listing 9.11. kick.ck kick drum file, conducted by BPM

To add more instrument players, snare.ck (listing 9.12) and cowbell.ck (listing 9.13) look pretty much like kick.ck, except they play different patterns to make things interesting. Snare (listing 9.12) plays on off beats (beats two and four), with an extra eighth note for accent. Tempo is updated at the beginning of each measure pattern . The off-beat playing pattern is accomplished by first advancing time by a quarter note , then playing a snare hit , waiting two quarter notes , and then playing another hit . You then advance time by a sixteenth note , play a hit , and then make up the remaining time (three sixteenth notes) .

Listing 9.12. snare.ck plays on beats 2 and 4 and more, conducted by global BPM

Cowbell (listing 9.13) plays only on the last eighth note of the whole four-beat pattern, by first updating tempo from BPM , then using a for loop of eighth-note time advances , and only playing on the seventh (remember, you start counting at zero) eighth note .

Listing 9.13. cowbell.ck plays only on last eighth note of measures, conducted by BPM

The hihat.ck player is similar, as shown in the following listing, but it plays all eighths except for the last one (when the cowbell plays). Note that the code is nearly identical to cowbell, except the logical condition check is changed from if (beat == 7) for the cowbell to if (beat != 7) for the hi-hat . What a difference one character can make!!

Listing 9.14. hihat.ck plays on all eighth notes except the last, synced to BPM

The hand clapper clap.ck program, shown in the next listing, randomly claps on sixteenth notes .

Listing 9.15. clap.ck claps hands on random sixteenth notes, synced to BPM

So now you’ve created files to control kick, snare, hi-hat, cowbell, and hand claps. You still need a score file to add all of these into the VM and, most importantly, to control tempo using BPM , as shown in the next listing. Your song starts out by gradually adding in each drum player file . Then you change the tempo to 80 BPM , then 160 BPM , and then gradually decrease the tempo until it reaches 60 BPM . Finally, you remove all the instruments .

Listing 9.16. score.ck controls individual players and tempo via BPM

Finally, you need to create an initialize.ck file to load/declare the BPM class and load your score file to kick off the music , as shown in the next listing.

Listing 9.17. Initialize.ck adds BPM.ck class and then turns it all over to score.ck

And that’s it—a whole orchestra of individual percussionists, all controlled in perfect sync by BPM objects via common shared static variables! Having classes and objects makes it amazingly flexible to share and modify lots of information globally (using static variables), as you’ve done here with the BPM class.

9.7. Making new classes from existing classes

Possibly the most powerful aspect of classes and objects is the ability to model a new class after an existing one, adding to it or modifying how it behaves. In OOP, this is called inheritance, meaning that you can make classes that inherit from others. Just as human children have some features that look and/or act like those of a parent but also have their own behaviors, in ChucK programming so can a child class do whatever a parent class can do but also add to it or change some behaviors altogether.

9.7.1. Inheritance: modeling and modifying parents

As a simple example, you’ll make a clarinet that knows a little more about playing notes than just the regular built-in Clarinet UGen. This is shown in figure 9.4, where the subclass MyClarinet inherits features (functions, data, and behavior) from its Clarinet parent.

Figure 9.4. MyClarinet subclass inherits from parent class Clarinet. All functions, variables, and the like automatically become part of MyClarinet, and you can add your own functions such as a new noteOn that takes two arguments, MIDI note and volume.

All functions that are already defined in the parent Clarinet class are available to the child MyClarinet class, but new functions can be added (and parent methods can be overloaded as well) in the MyClarinet definition, as shown in the next listing.

Listing 9.18. A smarter MyClarinet that Inherits from Clarinet

Note in listing 9.18 that you use a new word extends and the class (Clarinet) that you wish to extend or inherit from . The MyClarinet class is a child of Clarinet, and Clarinet is a parent of the new class. In the class definition you define a new noteOn function/method that accepts two arguments, a MIDI note number and a velocity/volume . These are then used to call the existing Clarinet .freq and .noteOn methods using the this keyword.

Note

We’ve been talking about parent and child classes, but you’ll find that if you learn another language, like Java or C++, you might hear the terms superclass used for parent and subclass used for child.

You’re also exploiting overloading here, with two noteOn methods that take different numbers of arguments. When you call myclar.noteOn() with two arguments , ChucK knows you want to use your new MyClarinet function. Then when you call myclar.noteOn() with only one argument , ChucK looks up and uses the superclass Clarinet noteOn method. All other things that the Clarinet parent knows how to do you don’t have to rewrite; they’re inherited automatically. This is the beauty of inheritance, because you can use the built-in functionality of existing classes but customize them by adding your own data and methods.

Exercise: make an even smarter instrument

Add more new functions to MyClarinet, such as a noteOn that responds to a string note name and a float volume. Next, make a smarter subclass of any instrument of your choice. Give it new functions to understand more things that you want to do. Try overwriting at least one function that the instrument/ class already has and add new functions you create yourself. For example, you could make a flute player that sweeps each note upward slightly as it’s being played. You could do this by declaring a noteOn function that sweeps the pitch up from 0.9 times a target frequency until it reaches the target. (Note: you’d have to advance time in that function.)

Clearly, inheritance is a super-powerful aspect of OOP, allowing you to use classes that already exist or that you (or other programmers) have created and then modified in small or large ways, without having to type and implement all of the parts of the original class that you want to keep.

9.7.2. Polymorphism: managing many children

Now that you know about inheritance, you can also use it to group things and control them under a common base class. You did this in chapter 5 (listing 5.7) when you defined your swell() function to work on any UGen (listing reproduced here). Because all unit generators, whose parent class is called UGen, obey the .gain() method, you can pass absolutely any UGen into that swell function and it will work just fine. That’s called polymorphism, but it’s actually a very simple version of it.

Listing 5.7. (Repeated) Using a swell function to ramp oscillator volume up and down

// swell function, operates on any type of UGen

fun void swell(UGen osc, float begin, float end, float grain){

float val;

// swell up volume

for (begin => val; val < end; grain +=> val)

{

val => osc.gain;

0.01 :: second => now;

}

// swell down volume

while (val > begin)

{

val => osc.gain;

grain -=> val;

0.01:: second => now;

}

}

For another example of polymorphism, you can make an array of objects that inherit from the existing StkInstrument class and fill that array with instances of various subclasses, to make them easier to access and control. Figure 9.5 shows the relationship of the StkInstrument superclass with some of its children. In this version of polymorphism, each instrument might interpret .noteOn, .freq, and the like in a different way based on how it works, but you can refer to them all using the same array.

Figure 9.5. Sitar, Mandolin, Clarinet, BlowBotl, and all other StkInstrument UGens inherit from a parent class StkInstrument.

Let’s use this in a simple example in listing 9.19. Here you make an array of four StkInstruments and fill that array with four different types of instruments. When you drop into the main test loop, you don’t have to know which instrument you’re playing, because they all obey .freq, .noteOn, and .noteOff. The individual instruments (and ChucK) figure it all out, through polymorphism, because every one of those instruments has built-in .freq, .noteOn, and .noteOff methods. Inside, each does something different with each function, like setting the length of a delay line to model a string or tube for Mandolin or Clarinet versus setting the center frequency of a filter for BlowBotl. Similarly, .noteOn plucks a string in Mandolin or Sitar but triggers an internal ADSR for Clarinet or BlowBotl. Polymorphism covers it all!

Listing 9.19. Polymorphism with an array of StkInstruments

// Example of polymorphism and base class use

// make an array of generic instruments

StkInstrument inst[4];

// make each instrument a different type

Sitar inst0 @=> inst[0] => dac;

Mandolin inst1 @=> inst[1] => dac;

Clarinet inst2 @=> inst[2] => dac;

BlowBotl inst3 @=> inst[3] => dac;

// take advantage of polymorphism to play them all

for (0 => int i; i < 4; i++) {

500.0 - (i*100.0) => inst[i].freq;

1 => inst[i].noteOn;

second => now;

1 => inst[i].noteOff;

}

You can do even more with inheritance and polymorphism, but both of those can quickly become pretty advanced programming topics. At least you’re now familiar with pretty much all of the possibilities (and buzzwords) of OOP. As you program more and more in ChucK, you might now easily think of cases where making your own classes, either from scratch or basing them on existing classes, will help you be more expressive and efficient with your code. Let’s finish up by building and using a smart Mandolin player class.

9.8. Example: building a smart mandolin player

You’ve worked through much of the magic and power of OOP, classes, and objects. You know something about inheritance, overloading, and polymorphism, but there’s a whole lot more that you can do with classes. You’ll be using them for the rest of the book, and you’ll see many more examples of their expressive capabilities.

For now, we’ll conclude by extending our idea of making bands out of smart instrument players, by building a somewhat smart mandolin instrument and player class called MandoPlayer , as shown in the following listing. This class bundles four Mandolin UGens in an array , one for each string pair on a real mandolin (a mandolin has four pairs of strings; each string pair is tuned to the same note to give a richer sound than a single string). Each mandolin string pair is then connected to a single reverb and to the dac.

Listing 9.20. Smart mandolin instrument and player class

You then provide functions to set all four strings at once. The .freqs() function sets the string notes with four floating-point frequencies . The .notes() function expects four integer MIDI note numbers and uses Std.mtof() to set the individual string frequencies. Because thisMandoPlayer is more than just an instrument, you give it even more smarts by creating a function that knows the names of a few common mandolin chords . This function expects a string specifying which of the standard G, C, and D chords to use .

You then give MandoPlayer the knowledge and technique to perform gestures that are common to stringed instrument playing. The roll() function accepts a string argument specifying a chord and a duration specifying the rate (time spacing between playing each note). This function takes advantage of the this keyword and the MandoPlayer.chord() function to set the notes of each string pair . Then it uses a for loop to play the notes one at a time, delaying by the rate between each note . Another musical name for what the roll function accomplishes isarpeggiation.

With a .strum() function , you also give the MandoPlayer the ability to perform tremolo strumming, which is the rapid repeating of a single note, a very common mandolin playing technique. The function takes an integer MIDI note number and a total duration for which to strum. First, you use a bit of logic to sort out which is the correct string to play the note on . This is a subtle but important feature, because any strings still ringing from a previous note or chord will continue to ring. You then use the this keyword to set the frequency of the correct string , and you do math on time and duration to determine when the function should stop strumming . Then you drop into a while loop and with somewhat random volume play repeated notes for somewhat random durations . Building in this randomness, combined with the mandolin being a physical model, makes it sound more like a human player rather than a robotic computer program.

Finally, you define one more function to set the damping for all four string pairs at once . This completes the definition of the MandoPlayer class .

You can now put your MandoPlayer through its paces, testing all of the smart functions you’ve created, as shown in listing 9.21. First, you make a new MandoPlayer . Then you declare chords and durations for the song you’re going to play . You also define strumming notes for later on in the song . To play your song, you first use a while loop to roll through all of the chords in your song array . You then strum through your strumming note array . You end by setting the damping to 1.0, meaning that the strings will ring for a very long time , and roll a G chord , letting that ring for 2 seconds. Finally, you damp the strings as a player might do at the end of a song .

Listing 9.21. MandoScore.ck makes and plays your new MandoPlayer

You could just run this score, provided that you remember to run the class definition of MandoPlayer.ck once (and only once) beforehand. But to keep with your new practice of keeping your code organized, you’ll make a simple initialize.ck file that uses Machine.add() to first add the class definition and then add MandoScore.ck to start the music, as shown in the next listing.

Listing 9.22. initialize.ck adds MandoPlayer.ck class and then MandoScore.ck score

Public Classes and Clear Vm

Remember that you might get occasional errors because you’re trying to redefine a public class that has already been defined. This is easily fixed by clicking the Clear VM button.

9.9. Summary

Your ChucK toolset keeps expanding, and now you know how to build your own tools in the form of classes. Object-oriented programming systems are a super-powerful means to organize and extend your code and to express yourself in really new ways. All this comes from knowing the following:

· Classes are code that encapsulate data and behaviors (functions). They can be either public (visible to all) or private (only locally visible).

· Objects are instances of classes. You declare them like variables.

· You can overload functions of the same name to do different things.

· You can use static variables to make them globally accessible.

· Inheritance lets you make new classes from a parent class by declaring extends.

· Polymorphism can be a powerful tool for your programming, allowing you to make and control multiple children of a single superclass through inheritance.

You’ve seen how shreds can communicate with each other via the use of public classes with static variables, but in the next chapter you’ll look at events, which will let your ChucK code and programs be aware of many new things. You’ll learn how to get events from the computer keyboard and how to listen for and send messages between shreds. Armed with that knowledge, you’ll be able to make your programs more responsive to each other and the outside world.