Sound files and sound manipulation - Introduction to programming in ChucK - Programming for Musicians and Digital Artists: Creating music with ChucK (2015)

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

Part 1. Introduction to programming in ChucK

Chapter 4. Sound files and sound manipulation

This chapter covers

· Storing sound in computers

· Loading and playing sound files in ChucK using SndBuf

· Working with stereo files

· Modulo, a cool new, musically useful, arithmetic operator

· Making your own drum machine using samples

So far you’ve used only oscillators and noise to make your sounds and compositions. But there’s much more to music composition and production than just these sounds. There are countless types of sounds in the world and in music. In this chapter, we’ll show you how to use sound files in ChucK. Sound files, such as .wav, .aif, or other files you have on your computer or have seen on the internet, are sometimes called samples, for reasons you’ll see soon. Samples are a quick and effective way to build sonic and musical elements with lots of variety and realism. By using samples, you can draw from the huge collection of existing sounds that people have recorded and synthesized over the years.

In later chapters you’ll learn to synthesize your own sounds from scratch. But in this chapter, you’ll use samples to get to the point where you can build your own beat-rockin’ techno drum machine with many new programming skills. First, we’ll take a quick look at how sound is turned from waveforms in the air into digital numbers for computer storage and transmission, called sampling. Then we’ll dig into using samples in ChucK. We’ll also look at new ways to use arrays and also introduce a new math operator called modulo.

4.1. Sampling: turning sound into numbers

First, let’s talk a bit about sound. We define sound as the traveling of fluctuating air compressions through the air, from some source to our ears. In computer systems, sound is stored and played back in various digital formats as numbers. Although you don’t need to know the specifics of all this to use sound files in ChucK, it’s important to know some things as you load and play sound files and as you look forward to synthesizing sounds in the future.

To get sound into a format that your computer and ChucK can understand, sound is turned into a stream of numbers, called a digital signal. The process of turning a sound waveform into a digital signal is called analog-to-digital conversion. The device that does this is an analog-to-digital converter (ADC), which is essentially the opposite device of the digital-to-analog converter (DAC) that you’ve been using all along to get digital signals synthesized by ChucK out to your speakers or headphones. The ADC works by inspecting electronically the incoming waveform (this is called sampling) at regular intervals, called the sampling period (which is 1/sampling rate) and rounding the value to the nearest integer available to represent and store that amplitude. Figure 4.1 shows this process, zoomed into one “eee” period of our earlier “see” sound. We further zoom into the beginning of the waveform and show a few of the individual integers that represent the first few sample values.

Figure 4.1. Sampling of one period of an “eee” sound. Pressure at a microphone is turned into voltage, which is turned into a number by a process known as analog-to-digital conversion.

In the previous chapter, when we talked about arrays, we said that arrays can hold anything, and anything that’s in your computer is stored in an array. The process shown in figure 4.1 is doing exactly that, turning the values of a waveform into a sequence of numbers and storing those into an array. Here the indices (0, 1, 2, ...) represent increasing time, one count for each sample period (1/sampling rate). The levels corresponding to each point in time (sample) are integers in most sound file formats like .wav and .aiff, but ChucK converts them into floating point numbers in the range +1.0 to -1.0.

All of the waveforms you’ve seen, including the one in figure 4.1, require both positive and negative values because the line through the middle is zero. This is generally true for audio, which is the traveling through the air of compressions (air pressure higher than normal) and rarefactions (less than normal), so you need positive numbers for pressures higher than ambient air pressure and negative for lower pressures.

So, each number representing each value in time of a waveform is called a sample, and—perhaps confusingly—once a whole sound is turned into a stream of numbers, that collection of values, indexed in time, is also called a sample. When that collection of numbers is written out to be stored on disk, or as a link from a web page, or burned on a CD/DVD, it’s called a sound file. One more term you should know is that when a sound file is loaded into your computer or synthesizer, it’s sometimes also called a wavetable.

To play back audio, you need a DAC, which turns the numbers back into continuous waveform samples, usually voltages, in your speakers or headphones. You’ve used the dac object in ChucK a lot already in your compositions, to hook your oscillators and noise generators up to your speakers. ChucK, combined with your computer’s audio hardware, takes care of turning sound files back into the voltages needed to drive your headphones or speakers.

Sampling, word size, and sampling rate

Engineers need to consider many issues when creating and working with sound files and especially when designing hardware for ADCs and DACs. These include how much computer storage should be allotted for each individual sample value (called word size, typically 16 bits or 24 bits per sample) for music and often 8 bits for speech sounds. Also, they must choose how often the waveform is sampled (typically 44100 samples per second). These have implications for quality and memory/disk space. For example, higher sample rates and more bits per sample are generally better, up to a point. CD-quality sound, at 2 bytes per sample, 2 channels, and 44100 samples per second, consumes about 10 megabytes per minute.

For more information on sampling, sound file formats, and so on, see the references at the end of this chapter.

Now you know a little about how sound gets into your computer. Let’s dig in right now and play sound files using ChucK’s built-in unit generator called SndBuf.

4.2. SndBuf: loading and playing sound files in ChucK

SndBuf, shortened from sound buffer, is the built-in ChucK UGen that allows you to load sound files. To do this you must start out with a sound file or perhaps a folder full of sound files. We’ve provided you a series of sound files that you can use to start composing and that we’re using throughout this book, so all the examples work without you having to change anything. It’s also possible to add your own sound files and get things to work as you see fit. First you’ll be setting up a file structure on your computer so ChucK can find the sound files. Then you’ll learn lots of ways to load, play, and manipulate sound files using SndBuf.

4.2.1. Organizing your sound files

For you to start using sound files in ChucK, we first have to help you get your computer file structure set up so you can follow all the examples in this book, as well as adopt good habits for organizing your code, sounds, and songs.

As shown in figure 4.2, we recommend that you first create a chuck folder on your computer to save all your files. In this folder you also need to make a folder called audio that hosts all your samples. Included in the installed version of ChucK on your computer you’ll find a folder called audio. Copy and paste this folder to your new chuck folder so you can use those sound files as you’re reading through this chapter and the rest of the book.

Figure 4.2. Recommended file structure for your ChucK projects. Make a new folder called chuck. Save (or move) all of your .ck code files into it, and copy the audio folder with all of its contents into it also. Now ChucK can know where to look for things!

Now that you’ve set up a file structure on your computer that makes it easy to use sound files, you can use ChucK to try it out by using prerecorded samples of playing a drum located in the audio directory. Listing 4.1 shows just how to do that, using SndBuf, which is a new sound-making UGen that helps you load and play samples. Although you connect it to the dac just like you have with other UGens so far, when compared with our oscillator (SinOsc, TriOsc, and so on) and noise unit generators, there are a number of new things about SndBuf that you need to know in order to use it.

First, you need to tell SndBuf to load a sound file and from where, or else it won’t make any sound. You do that by assembling a complete file path and filename. You can think of this as an address on the file system of your computer, just as you have to use menu navigation to load documents, spreadsheets, or any other type of files on your computer into other programs. With ChucK, as long as you have things set up right (as shown in figure 4.2), you can load files from within ChucK programs using just ChucK code and without needing menus.

Type in the ChucK code of listing 4.1 and save it as UsingSndBuf.ck (or whatever you like; just remember what you named it) into the chuck directory you previously created. The code on line asks ChucK to return the complete file path to where your ChucK file resides. me is a keyword, and me.dir() returns the current directory where this ChucK file resides. That’s why it’s important to save your file somewhere in the chuck directory. You store the file path in a string called path to use later. In the next line you make a new string variable and initialize it with the name of the sound file you’re going to play, including the audio folder name where that sound file resides.

Then you make another string variable called filename to hold the name of the sound file you’re going to play. This name should include the audio folder holding your sound files. Note here that you use the + symbol in a new way, to attach the two strings named path and filenametogether to make a new string, which you store back into the filename variable. This is another cool feature of ChucK, where the plus sign can add numbers together or attach strings together automatically based on the data type in question.

Listing 4.1. Loading and playing sound files with SndBuf

Note

You can’t use the + sign to add a string to a float, even though the string might look like a number. So "123.7"+ 6.3 isn’t legal, but depending on whether you want the final result to be a string or a float, you could use Std.atof("123.7 ") + 6.3 to get a float. Remember that Std.atof()converts an ASCII string into a floating-point number. You could also use "123.7" + Std.ftoa(6.3) (float to ASCII) to get a string, although a somewhat nonsensical one, because it would have two decimal points in it.

Once you’ve assembled the complete path and filename, you can ChucK that to your SndBuf using the .read method , and it will automatically load the file from disk and store it in your SndBuf’s internal array. If things didn’t work out, such as the file couldn’t be found because the path wasn’t correct or you mistyped something in the filename, then ChucK will print an error to the Console window.

Sound file formats and file extensions

Among the many types of audio file formats, the most common are .wav (from wave or waveform) and .aif (from audio interchange file or format). SndBuf understands these and other file formats just fine. Just tell your SndBuf to .read it, and ChucK will figure out the rest, or it will give you an error if it can’t find the file or read the particular format. ChucK doesn’t yet read .mp3 compressed files, so if you try to load one, you’ll get a “Format not recognized” message in the Console Monitor window.

You can set the volume of your SndBuf using the .gain method. As you recall, every UGen in ChucK obeys the .gain method, so you can always use that to change volume. Finally, to make your SndBuf play, you use a new method called .pos (for position) to point the SndBuf’s internal pointer to the first sample, indexed zero, just like with any array. After that, you just ChucK some duration (in this case 1 second) to now, to allow time to pass and sound to be generated. SndBuf takes care of the rest, handing individual samples over to the dac for playback over your speakers or headphones.

Assuming you typed everything in correctly and all the files are in the right places, if you click the Add Shred button on the miniAudicle, you should hear a snare drum hit! Click that button a few times. Every time you do, you’ll hear that same sound. You could even perform this live as the snare drummer in a band. Clicking in time is somewhat difficult—just like hitting a drum at exactly the right time—but doable.

4.2.2. Looping (automatically repeating) your samples

As you’ve seen, the SndBuf object has a number of different methods to control sound. Remember that our oscillators had .gain and .freq methods, and you’ve seen that SndBuf has a .gain object as well, but it doesn’t have a .freq method. The reason for this is that because a SndBuf holds a recording, you have no idea what frequency, if any, that recording contains. You’ll see soon how to make the pitch of the sound of a SndBuf go up or down.

You’ve also seen that SndBuf also has a .pos method, which is used to set the location where you start playing the sound file, called the playhead. One way to think about this is to imagine your sound file as a vinyl record. The .pos method would be where you place the stylus on the record. Looking at the samples stored in the SndBuf of figure 4.3, note that each successive sample going rightward matches up exactly with the sample values of our “eee” waveform of figure 4.1. Another way to think about it is to remember that your sound file is stored in a big array, and the .posmethod tells you which array index to start at, as shown in figure 4.3.

Figure 4.3. The .pos method of SndBuf sets the point from which the sound will start playing. This is also called the playhead.

In figure 4.3, the zero position (with sample value zero) is the beginning of the sample, time zero in the sound file. The position labeled 1 (value 40) holds the wave sample one T (sampling period) later. Each position is the sample one T later, so on until the end of the sound file.

What if you wanted to play your SndBuf sample over and over again, maybe in a repeating loop? You can do that by repeatedly setting the playhead to zero using the .pos method. While you’re at it, you’re going to add other expressive features, such as a panning object, which you remember from chapter 2. As you’ve done in the past, you’ll place your sound control into a while loop, as shown in listing 4.2. Save this file to your ChucK directory once you’ve typed it in. In listing 4.2, you use random numbers for setting the volume/gain of your sound file , and you set a random panning position . As usual this will create a new volume and spatial position for every hit .

For even more musical variety, you’ll use one more method built into the SndBuf object. One of the most expressive aspects of working with sound files is getting to control the sample playback rate. Imagine again the record player: when a record plays faster than it was intended to, the sound is higher pitched. Likewise, when a record plays slower than normal, the sound is at a lower pitch. Through a single line of code, you can alter and experiment with this parameter to get a large variety of expression for your compositions. You do this using the .rate method, to set a random value from .2 to 1.8. The original playback speed is 1.0 . Above 1.0 will go faster and thus higher, and below 1.0 will go slower and thus sound lower.

You set a time to wait before repeating the loop. Here you wait 500::ms (1/2 second) between each new strike. You can make this number greater and see that your looping playback gets slower, make it less and hear your looper get faster.

Listing 4.2. Using a loop to repeatedly play a sound file

Controlling and stopping your loops with Replace Shred and Clear VM

Remember that to make a change, such as the range of the random numbers or the time you ChucK to now to control the timing of your looping sounds, you need to click the Replace Shred button to stop the old infinite loop and replace it with your new one. Because this is an infinite loop (while (true)), when you’re finished and ready to move on, you have to stop what’s running by clicking Clear VM.

4.2.3. Playing your samples backward

Because that sounded so incredibly cool, let’s not stop there in messing around with samples. What if you wanted to play a sample in reverse? This is a useful technique that can add a further expressive motif to your compositions, and it’s used for sound effects, special musical effects, movie sound design, and so on.

Listing 4.3 shows how to do this, using a different sound, a type of cymbal called a hi-hat. After first making a SndBuf and connecting it to the dac, you load your hi-hat sound file using me.dir() and other methods we’ve already covered .

To play a sound file backward, you need to do two things. The first is to set the playhead position at the end of the file, an array built into SndBuf. But how do you know how many samples are in your sound file? SndBuf provides a method called .samples(), which returns the number of samples in your sound file, and you store that in an integer variable called numSamples . Another way to look at this is that you’re asking SndBuf, how big is that array inside holding that sound file (similar to the .cap() method for actual arrays)? You first play the sound file once forward and at normal speed; note that you’re using numSamples here to advance the time by exactly the right amount . Using .samples(), you can now use that number as an upper limit with the .pos method to set where your playhead position starts , as shown in figure 4.4.

Figure 4.4. To play a sound backward, you set the playhead to point at the end of the array using the .pos method. You also have to set the rate to be negative, so you count down from the maximum number of samples to zero.

The second thing you must do to play a sound backward is to set .rate to go backward instead of forward. As you might have guessed, you do this by making the rate value negative -1.0 for reverse at the correct speed. Or you could use -.2 for slowly in reverse, -2.0 for double speed in reverse, and so on.

Listing 4.3. Playing a sound file backward

4.2.4. Managing multiple samples at a time

The final skill to learn in this section is how to play multiple SndBufs at the same time. There are two ways to do this, depending on how long your sounds are and what your compositional needs are. One way is to reload a single SndBuf with different sound files. Let’s say you have three different snare drum recordings, and you want to interchange them throughout a composition. You can do this by making an array of strings to store the file paths and names , as shown in listing 4.4. Now at any time you can access the paths to the sound files and load them into memory at any point during the program. As a simple example here, in an infinite loop, you generate a random number and use that to decide which file will load and play back .

Note here you’re doing something slightly different to play these files, in that you don’t explicitly set the .pos pointer to zero. Inside SndBuf, loading a file automatically sets the .pos pointer to zero and the .rate to 1.0. So all sound files automatically play once when they’re loaded, as long as time is advanced.

Listing 4.4. Playing different sound files with a single SndBuf

The method of listing 4.4 can work fine, but it can also cause clicks if other processes running on your computer get in the way of the files being loaded in time to play or if the files are too long. There’s a better way. Remember in chapter 3 we said that you can make arrays to hold any type of data? Here you exploit that by declaring an array of SndBufs instead and preloading them with different sound files , as shown in the following listing. This way, all sound files are loaded before you enter the main loop, and you can avoid clicking by not trying to load files while sound is playing.

Listing 4.5. Playing different sound files (method 2) using multiple SndBufs

You might have noticed that the sonic results of the code of listing 4.5 are the same as those of listing 4.4. This is common in programming, where, as with the for and while loops, you saw that there were multiple ways to achieve the same results. There are cases where the method of listing 4.5might not be desirable, however. One example might be if you’re using sound files that are very large (like whole songs, which we don’t recommend for now) or you’re running ChucK on a machine with little RAM. ChucK runs on some pretty small machines, such as really old Macs and PCs, and even the Raspberry Pi, a cheap and tiny Linux computer. In cases such as these, preloading all the files you might need ahead of time might be impossible. You could run out of memory before everything gets loaded. But you now know both methods and can choose which one to use!

We’ve looked at how to use Pan2 (listing 4.2) and how to accomplish hard panning—direct connection to left, center, or right, as shown in listing 4.5—to get stereo effects using SndBuf. But what about stereo files? No problem! Read on.

4.3. Stereo sound files and playback

The code we’ve looked at so far has treated sample files as single-channel, or mono (short for monaural, meaning “of one ear”), files. But you have two ears and usually listen to sound and music using two speakers or headphones with two ear cups/buds. Remember that you’ve already used aPan2 unit generator to move noise back and forth between your left and right speakers/headphones. But you’ll also want to be able to use two-channel, or stereo, sound files. Stereo sound files, in a sense, come pre-spatialized, so you don’t need to use Pan2 to place them in the spatial sound field. A well-produced stereo sound file will have a more authentic sense of space than can be provided by a mono sound file processed by Pan2. But it’s harder to change the spatialization after the file has been recorded, making dynamic panning adjustments in code more difficult.

Loading stereo sound files in ChucK is much the same as with mono files, but instead of using SndBuf, you use SndBuf2, as shown in listing 4.6. The addition of the 2 in the name indicates that the unit generator is stereo, that is, it has two output channels. Everything should look fairly standard by now, except for a new function/ method you use to obtain a duration for advancing time. The .length() method for SndBuf and SndBuf2 returns a duration that’s exactly equal to the time required to play that sound file. Thus, you can just use it to ask SndBuf2 how long to play and ChucK that immediately to now!

Listing 4.6. Loading and playing stereo sound files using SndBuf2

Note that you’re no longer using a Pan2 object to control spatialization because SndBuf2 produces a two-channel output. Connecting it to the dac automatically does the correct things (left to left, right to right, just like the two outputs of Pan2).

Note also that if you had connected your SndBuf2 to a Gain unit before going to dac, the result would have been different. When a two-channel output is connected to a single-channel input, the two channels are mixed together to match the input. This throws away any spatialization effects in the original stereo signal, so mixing the output of SndBuf2 generally defeats the purpose of using SndBuf2. If you want to accomplish that, you can load a stereo file into a regular SndBuf, and the two channels will be mixed together internally within the SndBuf object and stored in the built-in mono array.

But you can still use Gain UGens to control the stereo sound field in other ways, as shown in listing 4.7. Here you combine your knowledge of arrays and unit generators to create an array of UGens, one for each channel. Using an array of two Gain UGens, you create a stereo balance control, which is a way of adjusting the panning of a stereo signal. So how does this work?

After making a stereo SndBuf and loading up a stereo file , you make an array of two Gain UGens, to be used for left/right volume . You then connect the left channel of your SndBuf to the zeroeth Gain (bal[0]) and then to the dac.left channel . You do the same with dac.right andbal[1].

Listing 4.7. Stereo panning with stereo sound files using SndBuf2

Then you do something new with your SndBuf2, which you can also do with regular mono SndBuf. By ChucKing 1 to the .loop method, you tell SndBuf that you want it to play in a loop forever. Normally any SndBuf plays just once and sticks at the end, waiting for you to reset the play .pos(position) to zero. But by setting .loop to 1, you’re telling the innards of SndBuf that when the position pointer reaches the end of the array, and all samples have been played, it should reset to 0 automatically and start playing again. Note that you could also use this for backward playback (negative rates), and when the pointer counts down to 0, it automatically resets to the last sample and repeats playing backward forever.

Now to the stereo panning stuff. Inside the infinite loop, you set a random playback rate , and you create and set (randomly) a new variable called balance . Just as with Pan2, as you set your balance variable toward -1.0, the left channel becomes more prominent; if balance tends toward 1.0, it makes the right channel more prominent. Once balance is randomly set between -1 and 1, you do the math to turn that into gains, between 0 and 1, for controlling the left and right channels .

Try This

On paper, set balance to -1.0; then work through the math to arrive at leftGain and rightGain. Do the same for balance set to 0.0 and balance set to 1.0. See how moving balance from -1.0 to 1.0 causes the actual panning to move from left to right? Do this in ChucK code too so you can hear the difference as you change balance.

As you saw before when you preloaded an array of SndBuf UGens, arrays of UGens are a common technique for creating and manipulating multichannel synthesis patches in ChucK.

4.4. Example: making a drum machine

Now that you’ve seen ways to use sample playback in ChucK, let’s put it all together to make a rockin’ dance beat drum machine, with multiple sound files. Let’s start out with a kick drum (bass drum in a drum set, played with a pedal) and a snare drum, connected to the dac , via a mixerGain UGen we call master, as shown in listing 4.8. The Gain UGen object allows you to make a gain control anywhere in your audio chain, in this case before the output to the dac, so that you can control the volume of your entire composition now by changing master.gain. You might remember that all UGens obey the .gain method, but you can use a Gain UGen not only to enable gain control but also to provide a nice named patch point for mixing multiple sources and other functions that you’ll learn about later on.

Once it’s created and connected in your code, you need only connect additional new SndBufs to the master Gain UGen . This is because you’ve already connected that master Gain to the dac. After connecting your SndBufs, you can load them with the appropriate sound files . Then you enter an infinite loop and play the kick drum (0 => kick.pos) by itself on Beat 1 , and you play both drums on Beat 2 by ChucKing 0 to both kick.pos and snare.pos.

Listing 4.8. Making a drum machine with SndBufs in ChucK

4.4.1. Adding logic for different drums on different beats

Already you’re making music! But you can do oh so much better by using a for loop and some logical statements. Remember those? We told you they’d be increasingly important later. Change the previous while loop to that shown in the next listing. Also add a new tempo duration variable , and replace the while loop with one that uses a for loop and logical tests .

Listing 4.9. Improving the while loop of your drum machine

Note

You can change the if (beat==N) numbers to see how the overall pattern changes.

In listing 4.9 you use a for loop to play 16 beats at a time. Musicians and composers might call that a measure. You use logical operators and expressions such as if and || (or) to play the different drums on different beats within your measure; you play kick on some beats and snare on others . Try changing the numbers in the beat==# conditions, click the Replace Shred button, and see how your pattern changes. You may get pretty good at clicking the button in time, so that your drum pattern changes but the basic timing is preserved. In later chapters you’ll learn techniques for using ChucK’s strong sense and control of timing to accomplish this type of on-the-fly drumming exactly.

Loops and other control structures can be nested

Note that in listing 4.9, we’ve nested a for loop within a while loop, and we’ve done that in prior examples in other chapters. This is totally cool to do, of course, and is really powerful. You can nest for loops within for loops, while loops within while or for loops, ifs within ifs, ifs within elses, as deep as you’d like. As long as you keep your curly braces straight and aligned, which the miniAudicle greatly helps you with, you can tell where each block begins and ends.

4.4.2. Controlling when drums play using logic arrays

Use your knowledge of arrays to control when the drums play, as shown in listing 4.10. And add another drum sound , a hi-hat cymbal sound, while you’re at it. Here you change your for loop into a while loop; again, there are multiple ways to solve problems in ChucK. You define two arrays that contain logical values, either 1 or 0, to tell the drum whether to play or not play on that beat. So here the index into the array is the beat number. That makes your logic pretty simple; instead of all those || or conditions tied together, you read the logic variable from the array at the right beat index, and that tells you whether or not to play that drum. You use one array for kick and one for snare, while your new hi-hat sound plays on every beat. But it would be really easy to introduce a third hatHits[] array and subject that drum to its own conditional statement as well. Note that you again use the .cap() function to determine the size of one of your arrays.

Listing 4.10. Using arrays to further improve your drum machine

Try This

Add a hatHits array full of 1s and 0s and add the code to play the hihat, conditional on each entry of the array. Change the 1s and 0s in all the arrays to make your own drum patterns. Further, try changing the lengths of the patterns by changing the lengths of the arrays. Be sure to make them all the same length after you’ve made your changes, or you might get an “Array index out of bounds” error.

You’ve improved your drum machine by using logic and looping structures and by arranging logical variables into arrays, corresponding to drum patterns that your inner loop(s) can play automatically. But there’s more that you can do. Next, you’ll learn about a mathematical operator that can be extremely useful for making musical patterns.

4.5. A new math/music tool: the modulo operator

Say you wanted to play a particular drum only on every fourth beat, or every other beat, or every Nth beat for some arbitrary N? To give you another ChucK tool to make your drum machine—and compositions in general—even more flexible, we’re going to introduce a math operator calledmodulo. Denoted by the % symbol, modulo is a type of division. Unlike normal division, modulo returns the remainder of a long-division operation. For example, 9 % 4 would return the number 1, because 9 divided by 4 is 2 with a remainder of 1. Similarly 14 % 4 would return the number 2 (3 with a remainder of 2, but modulo returns only the remainder).

The program in listing 4.11 prints and “sonifies” your modulo numbers to help you better understand how the modulo operator works. Sonification is the display of data or process state through sound, which ChucK is really good for. Here you use a couple of click sounds (one high pitched, one low) loaded into SndBufs . You also define and assign a variable called MOD , which we’ll use (and you can change later on) to demonstrate the modulo operator.

A note on programming style

In listing 4.11, you define and assign an integer called MOD that you use for the rest of the program, without modifying it while the program runs. You can change the initial definition, which changes the sound, but MOD is said to be constant during any run of the program. It’s common in programming to name such constant variables using all capital letters. Thus MOD is the name we selected for that variable. In our last example of this chapter, we’ll make and use another constant variable called MAX_BEAT. Watch for it, and remember that you can do this in your own programs to make them more readable.

In a for loop that counts a variable called beat upward from zero , you play the higher-pitched click sound on every beat . But you play the lower-pitched click sound only under the condition where beat % MOD (in this case beat % 4, read “beat modulo four” or “beat mod four”) is equal to zero . This will be true on each MODth beat (every fourth beat in this case). Run this a few times, but change the number assigned to MOD, and see how the sonified result changes. Count along, and look at the results printed into the console.

Listing 4.11. Using the modulo operator

Listening to how this sounds, it should be clear that modulo could have musical uses; otherwise we wouldn’t have introduced it. Modulo can play an important part in building drum machines, sequencers (loops that play drums, notes, whatever), and compositions in general. So you have yet another tool to use for determining when drums or other things might play and also for computing cyclic indexes, like the ones you might need to read through arrays over and over.

4.6. Tying it all together: your coolest drum machine yet

Let’s tie together everything we’ve covered and make one last awesome drum machine example for this chapter. You’ll use a number of drum sounds via SndBufs, and you’ll do stereo panning using both Gains and the Pan2 object. The next listing shows the beginning of your program, where you set this all up.

Listing 4.12a. SndBufs and panning connections for your big drum machine

Once you have all of your drums declared, connected to gains, panning objects, and the dac, and once you’ve loaded all of your sound files, you need to declare variables that you’ll use in your main loop to control your drums. In the following listing you declare an array to logically control your cowbell strikes . Then you declare other global variables that all drums will use.

Listing 4.12b. Setting up variables for your big drum machine

In the main loop, shown in the following listing, of your big drum machine, you’re going to use a mixture of all of the techniques you’ve used so far: a loop, a beat counter, conditionals and logic, arrays, and the modulo operator.

Listing 4.12c. Main loop to actually play drum patterns

You use an array to control the cowbell strikes in listing 4.12b. You’ll use modulo to play kick drum on beats 0, 4, 8, and 12 and snare drum on beats 2, 6, 10, and 14 , but you play snare only during odd measures—more on that soon. To give your drum song compositional structure, you use more conditional logic (if statements), checking your measure number, which counts up once for each MAX_BEATS beats, to see if you should be playing other instruments. For example, once the measure number is greater than 1 you begin playing either cowbell strikes , as determined by the array , or random gain hi-hat strikes. Once the measure number is greater than 3, you add in your randomly panned handclaps, but only on beats 12–15 .

After advancing time by your duration tempo , so all those drums can sound, if you’ve triggered them by ChucKing 0 to .pos, you use modulo math to update your beat counter . By incrementing beat, and then taking that number modulo MAX_BEAT and storing that back into beat, you automatically reset beat to zero when it reaches MAX_BEAT. If the result of that modulo operation on beat is zero , then you increment your measure counter, so you can use that in your logic to give your song that increasing compositional tension , , .

4.7. Summary

Now you know how to make realistic sounds in ChucK, by using SndBuf to load and play back sound files, or samples. You learned how to load and play back sounds using the following:

· SndBuf’s .read method, along with me.dir(), to load a sound file from memory

· The .pos method to set your playback position

· The .rate method to control how fast and how far forward/backward a sound file plays back

· SndBuf2 for stereo files

· One or multiple SndBufs for multiple sound files

· The modulo math operator

And now that you’ve coded your first drum sequencer, your compositional coding can advance to an entirely new level of fun and expression. That was a fairly involved example, but it built on everything you’ve used so far in your journey of learning ChucK. The results were decidedly awesome. We encourage you to put together more items that you’ve learned so far and modify this expressive techno beat to solidify the knowledge you’ve gained up to now.

You might be wondering if there’s a way to factor out some of that code, some sort of shorthand or method of grouping things, to make your code both more readable and reusable. It turns out that’s where we’ll be going next. We’ve been talking about methods (functions) all along, such as when you set .gain or .freq or .pos. Next, you’re going to learn how to write your own functions to make your life easier and give your programs super-powers. See you in chapter 5.

For further reading

Ken Pohlmann, Principles of Digital Audio, 6th Edition (McGraw Hill, 2010).

Perry Cook, Real Sound Synthesis for Interactive Applications (A K Peters LTD, 2002).

Ken Steiglitz, A Digital Signal Processing Primer (Addison Wesley, 1996).

Robert Bristow-Johnson, “Wavetable Synthesis 101, A Fundamental Perspective,” AES 101 Conference, 1996. Available at http://musicdsp.org/files/Wavetable-101.pdf.