The Supercollider Book - Scott Wilson, David Cottle & Nick Collins

March 25, 2017 | Author: Juan Carlos Mendex | Category: N/A
Share Embed Donate


Short Description

Download The Supercollider Book - Scott Wilson, David Cottle & Nick Collins...

Description

The SuperCollider Book

edited

by

Scott

Wilson, David Cottle, and Nick Collins

The MIT Press

Cambridge, Massachusetts London, England

Contents

Foreword ix James McCartney Introduction xiii Scott Wilson, David Cottle, and Nick Collins Tutorials

1 1 2 3 4

Advanced Tutorials 5 6 7 8

Beginner’s Tutorial 3 David Cottle The Unit Generator 55 Joshua Parmenter Composition with SuperCollider 81 Scott Wilson and Julio d’Escriván Ins and Outs: SuperCollider and External Devices 105 Stefan Kersten, Marije A. J. Baalman, and Till Bovermann 125 Programming in SuperCollider 127 Iannis Zannos Events and Patterns 179 Ron Kuivila Just-in-Time Programming 207 Julian Rohrhuber and Alberto de Campo Object Modeling 237 Alberto de Campo, Julian Rohrhuber, and Till Bovermann

vi

Contents

Platforms and GUI

271

9 10 11 12

Practical Applications 13

14 15 16 17 18

Mac OSX GUI 273 Jan Trützschler von Falkenstein SwingOSC 305 Hanns Holger Rutz SuperCollider on Windows 339 Christopher Frauenberger “Collision with the Penguin”: SuperCollider on Linux Stefan Kersten and Marije A. J. Baalman 379

Sonification and Auditory Display in SuperCollider 381 Alberto de Campo, Julian Rohrhuber, Till Bovermann, and Christopher Frauenberger Spatialization with SuperCollider 409 Marije A. J. Baalman and Scott Wilson Machine Listening in SuperCollider 439 Nick Collins Microsound 463 Alberto de Campo Alternative Tunings with SuperCollider 505 Fabrice Mogini Non-Real-Time Synthesis and Object-Oriented Composition 537 Brian Willkie and Joshua Parmenter

Projects and Perspectives 19 20

21 22 23

355

573

A Binaural Simulation of Varèse’s Poème Électronique 575 Stefan Kersten, Vincenzo Lombardo, Fabrizio Nunnari, and Andrea Valle High-Level Structures for Live Performance: dewdrop_lib and chucklib 589 James Harkins Interface Investigations 613 Thor Magnusson SuperCollider in Japan 629 Takeko Akamatsu Dialects, Constraints, and Systems within Systems 635 Julian Rohrhuber, Tom Hall, and Alberto de Campo

vii

Contents

Developer Topics 24 25 26

657 The SuperCollider Language Implementation Stefan Kersten Writing Unit Generator Plug-ins 691 Dan Stowell Inside scsynth 721 Ross Bencina

Appendix: Syntax of the SuperCollider Language Iannis Zannos Subject Index 745 Code Index 751

659

741

Foreword James McCartney

Why use a computer programming language for composing music? Specifically, why use SuperCollider? There are several very high-level language environments for audio besides SuperCollider, such as Common Music, Kyma, Nyquist, and Patchwork. These all demonstrate very interesting work in this area and are worth looking into. SuperCollider, though, has the unique combination of being free, well supported, and designed for real time. It is a language for describing sound processes. SuperCollider is very good at allowing one to create nice sounds with minimal effort, but more important, it allows one to represent musical concepts as objects, to transform them via functions or methods, to compose transformations into higherlevel building blocks, and to design interactions for manipulating music in real time, from the top-level structure of a piece down to the level of the waveform. You can build a library of classes and functions that become building blocks for your working style and in this way make a customized working environment. With SuperCollider, one can create many things: very long or infinitely long pieces, infinite variations of structure or surface detail, algorithmic mass production of synthesis voices, sonification of empirical data or mathematical formulas, to name a few. It has also been used as a vehicle for live coding and networked performances. Because of this openendedness, early on, I often felt it difficult to know how best to write the documentation. There were too many possible approaches and applications. Thus, I am pleased that there will now be a book on SuperCollider, and the best part of it for me is that I have not had to do much of the hard work to get it done. Since I made SuperCollider open source, it has taken on a life of its own and become a community-sustained project as opposed to being a project sustained by a single author. Many people have stepped up and volunteered to undertake tasks of documentation, porting to other operating systems, interfacing to hardware and software, writing new unit generators, extending the class library, maintaining a Web site, mailing lists, and a wiki, fixing bugs, and, finally, writing and editing the chapters of this book. All of these efforts have resulted in more features, better documentation, and a more complete, robust, and bugfree program.

x

James McCartney

SuperCollider came about as the latest in a series of software synthesis languages that I have written over the years. I have been interested in writing software to synthesize electronic music ever since I was in high school. At that time, I had written on a piece of notebook paper a set of imaginary subroutine calls in BASIC for implementing all of the common analog synthesizer modules. Of course, doing audio synthesis in BASIC on the hardware of that time was completely impractical, but the idea had become a goal of mine. When I graduated college, I went to have a look at E-mu in California and found that it was operating out of a two-story house. I figured that the synthesizer industry was a lot smaller than I had imagined and that I should rethink my career plans. The first software synthesizer I wrote was a graphical patching environment called Synfonix that generated samples for the Ensoniq Mirage sampling keyboard using a Macintosh computer. I attempted to sell this program but sold only two copies, one to Ivan Tcherepnin at Harvard and another to Mark Polishook. A business lesson I learned from this is not to sell a product that requires purchasers to already own two niche products. The intersection of two small markets is near zero. In 1990, I wrote a program called Synth-O-Matic that I used personally but never meant to distribute, even though one copy I gave to a friend got circulated. I created this program after learning CSound and deciding that I never wanted to actually have to use CSound’s assembly-language-like syntax. Synth-O-Matic had a more expression-oriented syntax for writing signal flow graphs and a graphical user interface for editing wave tables. I used this program on and off, but it was quite slow so I stopped using it, for the most part, in favor of hardware synthesizers. It wasn’t until the PowerPC came out that it became practical to do floating-point signal processing in real time on a personal computer. At the time I had been working on music for a modern dance piece, using Synth-O-Matic to do granular synthesis. It was taking a long time to generate the sound, and I was running behind schedule to get the piece done. On the day in March 1994 when the first PowerPC-based machine came out, I went and bought the fastest one. I recompiled my code, and it ran 32 times faster. I was then able to complete the piece on time. I noticed that my code was now running faster than real time, so I began working on a program designed to do real-time synthesis. Around this time I got a note in the mail from Curtis Roads, who had apparently gotten one of the circulating copies of Synth-O-Matic, encouraging me to further develop the program. So I took the Synth-O-Matic engine and combined it with the Pyrite scripting language object which I had written for MAX. This became SuperCollider version 1, which was released in March 1996. The first two orders were from John Bischoff and Chris Brown of The Hub. The name “SuperCollider” has an amusing origin. During the early 1990s I worked in the Astronomy Department of the University of Texas at Austin on the Hubble Space Telescope Astrometry Science Team, writing software for data analysis

xi

Foreword

and telescope observation planning. On the floors below my office was the Physics Department, some of the members of which were involved in the Superconducting Super Collider project. In 1993, Congress cut the funding for the project, and there were many glum faces around the building after that. I had been thinking about this merging, or “collision,” if you will, of a real-time synthesis engine with a high-level garbage collected language, and it seemed to me that it was an experiment that would likely fail, so I named it after the failed Big Science project of the day. Except that the experiment didn’t fail. To my surprise, it actually worked rather well. The version 1 language was dynamically typed, with a C-like syntax, closures borrowed from Scheme, and a limited amount of polymorphism. After using version 1 for a couple of years, and especially after a project on which I was invited by Iannis Zannos to work on a concert in an indoor swimming pool in Berlin, I realized that it had severe limitations on its ability to scale up to create large working environments. So I began working on version 2, which borrowed heavily from Smalltalk. It was for the most part the same as the language described in this book except for the synthesis engine and class library, which have changed a great deal. The goal of SuperCollider version 2 and beyond was to create a language for describing real-time interactive sound processes. I wanted to create a way to describe categories of sound processes that could be parameterized or customized. The main idea of SuperCollider is to algorithmically compose objects to create soundgenerating processes. Unit generators, a concept invented by Max Matthews for his Music N languages, are very much like objects and are a natural fit for a purely object-oriented Smalltalk-like language. In 2001 I was working on version 3 of SuperCollider, and because of the architecture of the server, it was looking like it really should be open source, so that anyone could modify it however they liked. I was (barely) making a living at the time selling SuperCollider, so the decision to give it away was a difficult one to make. But financially it was looking like I would need a “real” job soon, anyway. I was also worried that the period of self-employment on my résumé would begin looking suspect to potential employers. Ultimately, I did get a job, so I was able to open source the program. On the day that I made all of my previously copyright-protected programs free, my Web site experienced an eightfold increase in traffic. So obviously there was a lot of interest in an open source audio language and engine, especially one that is free. I hope that this book will enable and inspire the reader to apply this tool in useful and interesting ways. And I hope to hear and enjoy the results!

Introduction Scott Wilson, David Cottle, and Nick Collins

Welcome to The SuperCollider Book. We’re delighted to present a collection of tutorials, essays, and projects that highlight one of the most exciting and powerful audio environments. SuperCollider (SC to its friends) is a domain-specific programming language specialized for sound but with capabilities to rival any generalpurpose language. Though it is technically a blend of Smalltalk, C, and ideas from a number of other programming languages, many users simply accept SuperCollider as its own wonderful dialect, a superlative tool for real-time audio adventures. Indeed, for many artists, SuperCollider is the first programming language they learn, and they do so without fear because the results are immediately engaging; you can learn a little at a time in such a way that you hardly notice that you’re programming until it’s too late and you’re hooked! The potential applications in real-time interaction, installations, electroacoustic pieces, generative music, audiovisuals, and a host of other possibilities make up for any other qualms. On top of that, it’s free, powerful, and open source, and has one of the most supportive and diverse user and developer communities around. Pathways This book will be your companion to SuperCollider; some of you will already have experience and be itching to turn to the many and varied chapters further on from this point. We’ll let you follow the book in any order you choose! But we would like to take care to welcome any newcomers and point them straight in the direction of chapter 1, which provides a friendly introduction to the basics. For those on Windows or Linux it may be read together with chapter 11 or 12, respectively, which cover some of the cross-platform installation issues. From there we suggest beginners continue through until chapter 4, as this path will provide you with some basic skills and knowledge which can serve as a foundation for further learning. For more advanced users, we suggest you look at the more “topics”-oriented chapters which follow. These chapters aren’t designed to be read in any particular

xiv

Scott Wilson, David Cottle, and Nick Collins

order, so proceed with those of particular interest and relevance to you and your pursuits. Naturally we have referred to other chapters for clarification where necessary and have tried to avoid duplication of materials except where absolutely crucial for clarity. These “topics” chapters are divided into sections titled Advanced Tutorials, Platforms and GUI, and Practical Applications. They begin with chapter 5, “Programming in SuperCollider,” which provides a detailed overview of SuperCollider as a programming language. This may be of interest to beginners with a computer science background who’d rather approach SC from a language design and theory perspective than through the more user-friendly approach in chapter 1. Chapters on a variety of subjects follow, including sonification, spatialization, microsound, GUIs, machine listening, alternative tunings, and non-real-time synthesis. Following these chapters is a section for intermediate and advanced users titled Projects and Perspectives. The material therein provides examples of how SuperCollider has been used in the real world. These chapters also provide some philosophical insight into issues of language design and its implications (most specifically in chapter 23, “Dialects, Constraints, and Systems-Within-Systems”). This sort of intellectual pursuit has been an important part of SuperCollider’s development; SC is a language that self-consciously aims for good design, and to allow and encourage elegance, and even beauty, in the user’s code. Although this might seem a little abstract at first, we feel that this sets SC apart from other computer music environments and that as users advance, awareness of such things can improve their code. Finally, there is a section titled Developer Topics, which provides detailed “under the hood” information on SC. These chapters are for advanced users seeking a deeper understanding of the SC and its workings and for those wishing to extend SC, for instance, by writing custom unit generator plug-ins. Code Examples and Text Conventions Initially SuperCollider was Mac only, but as an open source project since 2001, it has widened its scope to cover all major platforms, with only minor differences between them. Most code in this book should run on all platforms with the same results, and we will note places where there are different mechanisms in place; most of the time, the code itself will already have taken account of any differences automatically. For instance, SC includes cross-platform GUI classes such as View, Slider, and Window. These will automatically redirect to the correct GUI implementation, either Cocoa (on Mac OSX; see chapter 9) or SwingOSC (on all platforms; see chapter 10). However, there are some differences in the programming editor environments (such as available menu items) and the keyboard shortcuts. You are referred

xv

Introduction

as well to the extensive Help system that comes with the SuperCollider application; a Help file on keyboard Shortcuts for the various platforms is prominently linked from the main Help page. Just to note, when you come across keyboard shortcuts in the text, they’ll appear like this: [enter] designates the “enter” (and not the “return”) key, [ctrl+a] means the control key plus the “a” key, and so on. Furthermore, all text appearing in the FRGH IRQW will almost always be valid SuperCollider code (very occasionally there may be exceptions for didactic purposes, such as here). You will also encounter some special SuperCollider terms (e.g., Synth, SynthDef, and Array) that aren’t in code font and are discussed in a friendly manner; this is because they are ubiquitous concepts and it would be exhausting to have them in the code font every time. You may also see them appearing with a capital letter (i.e., Synths), or all lower case (synths), depending again on how formal we are being. Anyway, if you’re new to SuperCollider, don’t worry about this at all; chapter 1 will start you on the righteous path, and you’ll soon be chatting about Synths and UGens like the rest of us. The Book Web Site This brings us to the accompanying Web site for the book (), which contains all the code reproduced within, ready to run, as well as download links to the application itself, its source code, and all sorts of third-party extras, extensions, libraries, and examples. A standardized version of SuperCollider is used for the book, SuperCollider 3.4, for which all book code should work without trouble. Of course, the reader may find it productive to download newer versions of SuperCollider as they become available, and it is our intention to provide updated versions of the example code where needed. Although we can make no hard promises, in this fast-paced world of operating system shifts, that the code in this book will remain eternally correct—the ongoing development and improvement of environments such as SuperCollider are a big part of what makes them so exciting— we’ve done our best to present you with a snapshot of SuperCollider that should retain a core validity in future years. Final Thoughts Please be careful with audio examples; there is of course the potential to make noises that can damage your hearing if you’re not sensible with volume levels. Until you become accustomed to the program, we suggest you start each example with the volume all the way down, and then slowly raise it to a comfortable level. (And if you’re not getting any sound, remember to check if you’ve left the monitors off or

xvi

Scott Wilson, David Cottle, and Nick Collins

the computer audio muted.) Some examples may use audio input and have the potential to feedback unless your monitoring arrangements are correct. The easiest way to deal with such examples is to monitor via headphones. We couldn’t possibly cover everything concerning SuperCollider, and there are many online resources to track down new developments and alternative viewpoints, including mailing lists, forums, and a host of artists, composers, technology developers, and SuperCollider maniacs with interesting pages. We have provided a few of the most important links (at the time of writing) below, but Wikigoopedigle, or whatever your contemporary equivalent is, will allow you to search out the current SuperCollider 15.7 as necessary. We’re sure you’ll have fun as you explore this compendium, and we’re also sure you’ll be inspired to some fantastic art and science as you go. Enjoy exploring the SuperCollider world first charted by James McCartney but since expanded immeasurably by everyone who partakes in this infinitely flexible open source project. Primary Web Resources Main community home page: Application download and project site: James McCartney’s home page: The venerable swiki site: Acknowledgments We owe a debt of gratitude to the chapter contributors and to the wider SuperCollider community, who have supported this project. SC’s community is one of its greatest strengths, and innumerable phone calls, e-mails, chats over cups of tea at the SC Symposium, and many other interactions, have contributed to making this book and SC itself stronger. We apologize to all who have put up with our insistent editing and acknowledge the great efforts of the developers to prepare a stable SuperCollider 3.4 version for this book. A thousand thank-yous: GR^GRPRDULJDWRJR]DLPDVKLWDSRVWOQ`

Thanks to all at MIT Press who have assisted in the handling of our proposal and manuscript for this book. Thank you to friends, family, and colleagues who had to deal with us while we were immersed in the lengthy task of organizing, editing, and assembling this book. Editing may seem like a solitary business, but from their perspective we’re quite sure it was a team effort!

xvii

Introduction

Many thanks to our students, who have served as guinea pigs for pedagogical approaches, tutorial materials, experimental developments, and harebrained ideas. Now back to your exercise wheels! Finally, the editors would like to thank each other for support during the period of gestation. At the time of writing, we’ve been working on this project for 2 years to bring the final manuscript to fruition. Though Scott and Nick have met on many occasions (and were co-organizers of the 2006 SuperCollider Symposium at the University of Birmingham), neither has ever met David in person (we sometimes wonder if he really exists!); but you should see the number of e-mails we’ve sent each other. For many months, in the heat of the project, David modified his routine to include a 3 A.M. e-mail check to keep up with those using GMT, which imparts a celestially imposed 8-hour advantage. In any case, although it has at times been exhausting, seeing this book through to fruition has been a pleasure, and we hope it brings you pleasure to read it and learn about SC.

3

Composition with SuperCollider Scott Wilson and Julio d’Escriván

3.1

Introduction The actual process of composing, and deciding how to go about it, can be one of the most difficult things about using SuperCollider. People often find it hard to make the jump from modifying simple examples to producing a full-scale piece. In contrast to Digital Audio Workstation (DAW) software such as Pro Tools, for example, SC doesn’t present the user with a single “preferred” way of working. This can be confusing, but it’s an inevitable side effect of the flexibility of SC, which allows for many different approaches to generating and assembling material. A brief and incomplete list of ways people might use SC for composition could include the following: Real-time interactive works with musicians Sound installations • Generating material for tape music composition (to be assembled later on a DAW), perhaps performed in real time • As a processing and synthesis tool kit for experimenting with sound • To get away from always using the same plug-ins • To create generative music programs • To create a composition or performance tool kit tailored to one’s own musical ideas. • •

All of these activities have different requirements and suggest different approaches. This chapter attempts to give the composer or sound artist some starting points for creative exploration. Naturally, we can’t hope to be anywhere near exhaustive, as the topic of the chapter is huge and in some senses encompasses all aspects of SC. Thus we’ll take a pragmatic approach, exploring both some abstract ideas and concrete applications, and referring you to other chapters in this book where they are relevant.

82

Scott Wilson and Julio d’Escriván

3.1.1 Coding for Flexibility The notion of making things that are flexible and reusable is something that we’ll keep in mind as we examine different ideas in this chapter. As an example, you might have some code that generates a finished sound file, possibly your entire piece. With a little planning and foresight, you might be able to change that code so that it can easily be customized on the fly in live performance, or be adapted to generate a new version to different specifications (quad instead of stereo, for instance). With this in mind, it may be useful to utilize environment variables which allow for global storage and are easily recalled. You’ll recall from chapter 1 that environment variables are preceded by a tilde (~). VRPHFRGHZHPD\ZDQWWRXVHODWHU aVRPHWKLQJ ^3XOVHDU  (QY*HQDU (QYSHUFGRQH$FWLRQ ` ZKHQWKHWLPHFRPHVMXVWFDOOLWE\LWVQDPHDQGSOD\LW aVRPHWKLQJSOD\

Since environment variables do not have the limited scope of normal variables, we’ll use them in this chapter for creating simple examples. Keep in mind, however, that in the final version of a piece there may be good reasons for structuring your code differently. 3.2

Control and Structure When deciding how to control and structure a piece, you need to consider both practical and aesthetic issues: Who is your piece for? Who is performing it? (Maybe you, maybe an SC Luddite . . .) What kind of flexibility (or expressiveness!) is musically meaningful in your context? Does pragmatism (i.e., maximum reliability) override aesthetic or other concerns (i.e., you’re a hard-core experimentalist, or you are on tenure track and need to do something technically impressive)? A fundamental part of designing a piece in SC is deciding how to control what happens when. How you do this depends upon your individual needs. You may have a simple list of events that need to happen at specific times, or a collection of things that can be triggered flexibly (for instance, from a GUI) in response to input from a performer, or algorithmically. Or you may need to combine multiple approaches. We use the term structure here when discussing this issue of how to control when and how things happen, but keep in mind that this could mean anything from the macro scale to the micro scale. In many cases in SC the mechanisms you use might be the same.

83

3

Composition with SuperCollider

3.2.1 Clocks, Routines, and Tasks Here’s a very simple example that shows you how to schedule something to happen at a given time. It makes use of the 6\VWHP&ORFN class. 6\VWHP&ORFNVFKHG ^IRRSRVWOQ` 

The first argument to the VFKHG message is a delay in seconds, and the second is a that will be evaluated after that delay. In this case the Function simply posts the word “foo,” but it could contain any valid SC code. If the last thing to be evaluated in the Function returns a number, SystemClock will reschedule the Function, using that value as the new delay time.

)XQFWLRQ

IRRUHSHDWVHYHU\VHFRQG 6\VWHP&ORFNVFKHG ^IRRSRVWOQ`  EDUUHSHDWVDWDUDQGRPGHOD\ 6\VWHP&ORFNVFKHG ^EDUSRVWOQUDQG`  FOHDUDOOVFKHGXOHGHYHQWV 6\VWHP&ORFNFOHDU

SystemClock has one important limitation: it cannot be used to schedule events which affect native GUI widgets on OSX. For this purpose another clock exists, called $SS&ORFN. Generally you can use it in the same way as SystemClock, but be aware that its timing is slightly less accurate. There is a shortcut for scheduling something on the AppClock immediately, which is to wrap it in a Function and call GHIHU on it. FDXVHVDQRSHUDWLRQFDQQRWEHFDOOHGIURPWKLV3URFHVVHUURU 6\VWHP&ORFNVFKHG ^6&:LQGRZQHZIURQW`  GHIHUUHVFKHGXOHV*8,FRGHRQWKH$SS&ORFNVRWKLVZRUNV 6\VWHP&ORFNVFKHG ^^6&:LQGRZQHZIURQW`GHIHU` 

GUI, by the way, is short for Graphical User Interface and refers to things such as windows, buttons, and sliders. This topic is covered in detail in chapters 9 and 10, so although we’ll see some GUI code in a few of the examples in this chapter, we won’t worry too much about the nitty-gritty details of it. Most of it should be pretty straightforward and intuitive, anyway, so for now, just move past any bits that aren’t clear and try to focus on the topics at hand. Another Clock subclass, 7HPSR&ORFN, provides the ability to schedule events according to beats rather than in seconds. Unlike the clocks we’ve looked at so far, you need to create an instance of TempoClock and send sched messages to it, rather than to the class. This is because you can have many instances of TempoClock, each with its own tempo, but there’s only one each of SystemClock and AppClock. By varying

84

Scott Wilson and Julio d’Escriván

a TempoClock’s tempo (in beats per second), you can change the speed. Here’s a simple example. W 7HPSR&ORFNQHZPDNHDQHZ7HPSR&ORFN WVFKHG ^+HOORSRVWOQ`  WWHPSR WZLFHDVIDVW WFOHDU

TempoClock also allows beat-based and bar-based scheduling, so it can be particularly useful when composing metric music. (See the TempoClock Help file for more details.) Now let’s take a look at Routines. A 5RXWLQH is like a Function that you can evaluate a bit at a time, and in fact you can use one almost anywhere you’d use a Function. Within a Routine, you use the yield method to return a value and pause execution. The next time you evaluate the Routine, it picks up where it left off. U 5RXWLQH ^ IRR\LHOG EDU\LHOG `  UYDOXHIRR UYDOXHEDU UYDOXHZH YHUHDFKHGWKHHQGVRLWUHWXUQVQLO

Routine has a commonly used synonym for YDOXH, which is QH[W. Although “next” might make more sense semantically with a Routine, “value” is sometimes preferable, for reasons we’ll explore below. Now here’s the really interesting thing: since a Routine can take the place of a Function, if you evaluate a Routine in a Clock, and yield a number, the Routine will be rescheduled, just as in the SystemClock example above. U 5RXWLQH ^ IRRSRVWOQ \LHOGUHVFKHGXOHDIWHUVHFRQG EDUSRVWOQ \LHOG IRREDUSRVWOQ `  6\VWHP&ORFNVFKHG U 

85

3

Composition with SuperCollider

)HUPDWD VERRW U 5RXWLQH ^  [ 6\QWK ?GHIDXOW>IUHTPLGLFSV@   ZDLW   [UHOHDVH    \ 6\QWK ?GHIDXOW>IUHTPLGLFSV@   :DLWLQJSRVWOQ  QLO\LHOGIHUPDWD   \UHOHDVH    ] 6\QWK ?GHIDXOW>IUHTPLGLFSV@   ZDLW  ]UHOHDVH `  GRWKLVWKHQZDLWIRUWKHIHUPDWD USOD\ IHHOWKHVZHHWWRQLF USOD\

Figure 3.1 A simple Routine illustrating a musical use of yield.

Figure 3.1 is a (slightly) more musical example that demonstrates a fermata of arbitrary length. This makes use of ZDLW, a synonym for \LHOG, and of Routine’s SOD\ method, which is a shortcut for scheduling it in a clock. By yielding nil at a certain point, the clock doesn’t reschedule, so you’ll need to call play again when you want to continue, thus “releasing” the fermata. Functions understand a message called IRUN, which is a commonly used shortcut for creating a Routine and playing it in a Clock. ^ VRPHWKLQJSRVWOQ ZDLW VRPHWKLQJHOVHSRVWOQ `IRUN

Figure 3.2 is a similar example with a simple GUI control. This time we’ll use a 7DVN, which you may remember from chapter 1. A Task works almost the same way

86

Scott Wilson and Julio d’Escriván

W 7DVN ^  ORRS ^ ORRSWKHZKROHWKLQJ   GR ^ GRWKLVWLPHV    [UHOHDVH      [ 6\QWK ?GHIDXOW>IUHTPLGLFSV@     ZDLW    [UHOHDVH      [ 6\QWK ?GHIDXOW>IUHTPLGLFSV@     ZDLW   `    , PZDLWLQJIRU\RXWRSUHVVUHVXPHSRVWOQ   QLO\LHOGIHUPDWD   [UHOHDVH     [ 6\QWK ?GHIDXOW>IUHTPLGLFSV@    ZDLW   [UHOHDVH  `  `  Z :LQGRZQHZ 7DVN([DPSOH5HFW  IURQW ZYLHZGHFRUDWRU )ORZ/D\RXW ZYLHZERXQGV  %XWWRQQHZ Z5HFW  VWDWHVB >>3OD\5HVXPH&RORUEODFN &RORUFOHDU@@  DFWLRQB ^WUHVXPH  `  %XWWRQQHZ Z5HFW  VWDWHVB >>3DXVH&RORUEODFN&RORUFOHDU@@  DFWLRQB ^WSDXVH`  %XWWRQQHZ Z5HFW  VWDWHVB >>)LQLVK&RORUEODFN&RORUFOHDU@@  DFWLRQB ^   WVWRS   [UHOHDVH     ZFORVH  ` 

Figure 3.2 Using Task so you can pause the sequence.

87

3

Composition with SuperCollider

that a Routine does, but is meant to be played only with a Clock. A Task provides some handy advantages, such as the ability to pause. As well, it prevents you from accidentally calling play twice. Try playing with the various buttons and see what happens. Note that the example above demonstrates both fixed scheduling and waiting for a trigger to continue. The trigger needn’t be from a GUI button; it can be almost anything, for instance, audio input. (See chapter 15.) By combining all of these resources, you can control events in time in pretty complicated ways. You can nest Tasks and Routines or combine fixed scheduling with triggers; in short, anything you like. Figure 3.3 is an example that adds varying tempo to the mix, as well as adding some random events. You can reset a Task or Routine by sending it the UHVHW message. UUHVHW

3.2.2 Other Ways of Controlling Time in SC There are 2 other notable methods of controlling sequences of events in SC: Patterns and the Score object. Patterns provide a high-level abstraction based on Streams of events and values. Since Patterns and Streams are discussed in chapter 6, we will not explore their workings in great detail at this point, but it is worth saying that Patterns often provide a convenient way to produce a Stream of values (or other objects), and that they can be usefully combined with the methods shown above. Figure 3.4 demonstrates two simple Patterns: 3VHT and 3[UDQG. Pseq specifies an ordered sequence of objects (here numbers used as durations of time between successive events) and a number of repetitions (in this case an infinite number, indicated by the special value LQI). Pxrand also has a list (used here as a collection of pitches), but instead of proceeding through it in order, a random element is selected each time. The “x” indicates that no individual value will be selected twice in a row. Patterns are like templates for producing Streams of values. In order to use a Pattern, it must be converted into a 6WUHDP, in this case using the DV6WUHDP message. Once you have a Stream, you can get values from it by using the QH[W or YDOXH messages, just as with a Routine. (In fact, as you may have guessed, a Routine is a type of Stream as well.) Patterns are powerful because they are “reusable,” and many Streams can be created from 1 Pattern template. (Chapter 6 will go into more detail regarding this.) As an aside, and returning to the idea of flexibility, the YDOXH message above demonstrates an opportunity for polymorphism, which is a fancy way of saying that different objects understand the same message.1 Since all objects understand “value” (most simply return themselves), you can substitute any object (a )XQFWLRQ, a

88

Scott Wilson and Julio d’Escriván

U 5RXWLQH ^  F 7HPSR&ORFNQHZPDNHD7HPSR&ORFN  VWDUWD ZREEO\ ORRS  W 7DVN ^   ORRS ^    [UHOHDVH      [ 6\QWK ?GHIDXOW>IUHTPLGLFSVDPS@     ZDLW    [UHOHDVH      [ 6\QWK ?GHIDXOW>IUHTPLGLFSVDPS@     UUDQG  ZDLWUDQGRPZDLWIURPWRVHFRQGV   `   `F XVHWKH7HPSR&ORFNWRSOD\WKLV7DVN  WVWDUW  QLO\LHOG   QRZDGGVRPHQRWHV  \ 6\QWK ?GHIDXOW>IUHTPLGLFSVDPS@   QLO\LHOG  \UHOHDVH    \ 6\QWK ?GHIDXOW>IUHTPLGLFSVDPS@   FWHPSR GRXEOHWLPH  QLO\LHOG  WVWRS\UHOHDVH  [UHOHDVH  VWRSWKH7DVNDQG6\QWKV `  UQH[WVWDUWORRS UQH[WILUVWQRWH UQH[WVHFRQGQRWHORRSJRHV GRXEOHWLPH

UQH[WVWRSORRSDQGIDGH

Figure 3.3 Nesting Tasks inside Routines.

89

3

Composition with SuperCollider

UDQGRPQRWHVIURPO\GLDQEVFDOH S 3[UDQG >@LQI DV6WUHDP RUGHUHGVHTXHQFHRIGXUDWLRQV T 3VHT >@LQI DV6WUHDP W 7DVN ^  ORRS ^   [UHOHDVH     [ 6\QWK ?GHIDXOW>IUHTSYDOXHPLGLFSV@    TYDOXHZDLW  `  `  WVWDUW WVWRS[UHOHDVH  

Figure 3.4 Using Patterns within a Task. 5RXWLQH,

a number, etc.) that will return an appropriate value for S or T in the example above. Since S and T are evaluated each time through the loop, it’s even possible to do this while the 7DVN is playing. (See figure 3.5.) Taking advantage of polymorphism in ways like this can provide great flexibility, and can be useful for anything from generic compositions to algorithmically variable compositions. The second method of controlling event sequences is the 6FRUH object. Score is essentially an ordered list of times and 26& commands. This takes the form of nested Arrays. That is, > >WLPH>FPG@@ >WLPH>FPG@@  @

As you’ll recall from chapter 2, OSC stands for Open Sound Control, which is the network protocol SC uses for communicating between language and server. What you probably didn’t realize is that it is possible to work with OSC messages directly, rather than through objects such as Synths. This is a rather large topic, so since the OSC messages which the server understands are outlined in the Server Command Reference Help file, we’ll just refer you there if you’d like to explore further. In any case, if you find over time that you prefer to work in “messaging style” rather than “object style,” you may find 6FRUH useful. Figure 3.6 provides a short example. Score also provides some handy functionality for non-real-time synthesis (see chapter 18).

90

Scott Wilson and Julio d’Escriván

S DFRQVWDQWQRWH T 3VHT >@LQI DV6WUHDPRUGHUHGVHTXHQFHRIGXUDWLRQV W 7DVN ^  ORRS ^   [UHOHDVH     [ 6\QWK ?GHIDXOW>IUHTSYDOXHPLGLFSV@    TYDOXHZDLW  `  `  WVWDUW QRZFKDQJHS S 3VHT >@LQI DV6WUHDPWRD3DWWHUQGRUHPL S ^UUDQG  `WRD)XQFWLRQUDQGRPQRWHVIURPD FKURPDWLFRFWDYH WVWRS[UHOHDVH  

Figure 3.5 Thanks to polymorphism, we can substitute objects that understand the same message.

6\QWK'HI 6FRUH6LQH^DUJIUHT  2XWDU   6LQ2VFDU IUHT  /LQHNU GRQH$FWLRQ ` DGG [ > DUJVIRUVBQHZDUHV\QWKGHIQRGH,'DGG$FWLRQWDUJHW,'V\QWKDUJV >>?VBQHZ?6FRUH6LQH?IUHT@@ >>?VBQHZ?6FRUH6LQH?IUHT@@ >>?VBQHZ?6FRUH6LQH?IUHT@@ >>?FBVHW@@GXPP\FRPPDQGWRPDUNHQGRI157V\QWKHVLVWLPH @ ] 6FRUH [  ]SOD\

Figure 3.6 Using “messaging style”: Score.

91

3

Composition with SuperCollider

KHUH VDV\QWKGHIWKDWDOORZVXVWRSOD\IURPDEXIIHUZLWKDIDGHRXW 6\QWK'HI SOD\EXI^DUJRXW EXIJDWH   2XWDU RXW   3OD\%XIDU EXI%XI5DWH6FDOHNU EXI ORRS    

/LQHQNU JDWHGRQH$FWLRQ UHOHDVHV\QWKZKHQIDGHGRQH  ` DGG ORDGDOOWKHSDWKVLQWKHVRXQGVIROGHULQWREXIIHUV aVRPH6RXQGV VRXQGV SDWK0DWFKFROOHFW^_SDWK_%XIIHUUHDG VSDWK ` QRZKHUH VWKHVFRUHVRWRVSHDN H[HFXWHWKHVHRQHOLQHDWDWLPH aQRZ3OD\LQJ 6\QWK SOD\EXI>EXIaVRPH6RXQGV>@@  aQRZ3OD\LQJUHOHDVHaQRZ3OD\LQJ 6\QWK SOD\EXI>EXIaVRPH6RXQGV>@@  aQRZ3OD\LQJUHOHDVHaQRZ3OD\LQJ 6\QWK SOD\EXI>EXIaVRPH6RXQGV>@@  aQRZ3OD\LQJUHOHDVH IUHHWKHEXIIHUPHPRU\ aVRPH6RXQGV%XIIHUHGGR BIUHH 

Figure 3.7 Executing one line at a time.

3.2.3 Cue Players Now let’s turn to a more concrete example. Triggering sound files, a common technique when combining live performers with a “tape” part, is easily achieved in SuperCollider. There are many approaches to the construction of cue players. These range from a list of individual lines of code that you evaluate one by one during a performance, to fully fledged GUIs that completely hide the code from the user. One question you need to ask is whether to play the sounds from RAM or stream them from hard disk. The former is convenient for short files, and the latter for substantial cues that you wouldn’t want to keep in RAM. There are several classes (both in the standard distribution of SuperCollider and within extensions by third-party developers) that help with these 2 alternatives. Here’s a very simple example which loads 2 files into RAM and plays them: aP\%XIIHU %XIIHUUHDG VVRXQGVDZONZDY ORDGDVRXQG aP\%XIIHUSOD\SOD\LWDQGQRWLFHLWZLOOUHOHDVHWKHQRGHDIWHU SOD\LQJ

Buffer’s play method is really just a convenience method, though, and we’ll probably want to do something fancier, such as fade in or out. Figure 3.7 presents an

92

Scott Wilson and Julio d’Escriván

6\QWK'HI SOD\EXI^DUJRXW EXIJDWH   2XWDU RXW   3OD\%XIDU EXI%XI5DWH6FDOHNU EXI ORRS   

/LQHQNU JDWHGRQH$FWLRQ     ZLWK GRQH$FWLRQ ZHUHOHDVHV\QWKZKHQIDGHLVGRQH ` DGG aVRPH6RXQGV VRXQGV SDWK0DWFKFROOHFW^_SDWK_%XIIHUUHDG VSDWK ` Q DFRXQWHU KHUH VRXU*8,FRGH Z :LQGRZQHZ 6LPSOH&XH3OD\HU5HFW  IURQW ZYLHZGHFRUDWRU )ORZ/D\RXW ZYLHZERXQGV  WKLVZLOOSOD\HDFKFXHLQWXUQ %XWWRQQHZ Z5HFW  VWDWHVB >>3OD\&XH&RORUEODFN &RORUFOHDU@@ DFWLRQB ^  LI QaVRPH6RXQGVVL]H^   LI Q ^aQRZ3OD\LQJUHOHDVH`    aQRZ3OD\LQJ 6\QWK SOD\EXI>EXIaVRPH6RXQGV>Q@@ Q Q  `  `  WKLVVHWVWKHFRXQWHUWRWKHILUVWFXH %XWWRQQHZ Z5HFW  VWDWHVB >>6WRS5HVHW&RORUEODFN &RORUFOHDU@@ DFWLRQB ^Q aQRZ3OD\LQJUHOHDVH`  IUHHWKHEXIIHUVZKHQWKHZLQGRZLVFORVHG ZRQ&ORVH ^aVRPH6RXQGVGR BIUHH `

Figure 3.8 Playing cues with a simple GUI.

example which uses multiple cues in a particular order, played by executing the code one line at a time. It uses the 3OD\%XI UGen, which you may remember from chapter 1. The middle 2 lines of the latter section of figure 3.7 consist of 2 statements, and thus do 2 things when you press the enter key to execute. You can of course have lines of many statements, which can all be executed at once. (Lines are separated by carriage returns; statements, by semicolons.) The “1 line at a time” approach is good when developing something for yourself or an SC-savvy user, but you might instead want something a little more elaborate or user-friendly. Figure 3.8 is a simple example with a GUI. SC also allows for streaming files in from disk using the 'LVN,Q and 9'LVN,Q UGens (the latter allows for variable-speed streaming). There are also a number of

93

3

Composition with SuperCollider

third-party extension classes that do things such as automating the required housekeeping (e.g., Fredrik Olofsson’s 5HG'LVN,Q6DPSOHU). The previous examples deal with mono files. For multichannel files (stereo being the most common case) it is simplest to deal with interleaved files.2 Sometimes, however, you may need to deal with multiple mono cues. Figure 3.9 shows how to sort them based on a folder containing subfolders of mono channels. 3.3

Generating Sound Material The process of composition deals as much with creating sounds as it does with ordering them. The ability to control sounds and audio processes at a low level can be great for finding your own compositional voice. Again, an exhaustive discussion of all of SuperCollider’s sound-generating capabilities would far exceed the scope of this chapter, so we’ll look at a few issues related to generating and capturing material in SC and give a concrete example of an approach you might want to adapt for your own purposes. As before, we will work here with sound files for the sake of convenience, but you should keep in mind that what we’re discussing could apply to more or less any synthesis or processing technique. 3.3.1

Recording

At some point you’re probably going to want to record SC’s output for the purpose of capturing a sound for further audio processing or “assembly” on a DAW, for documenting a performance, or for converting an entire piece to a distributable sound file format. To illustrate this, let’s make a sound by creating an effect that responds in an idiosyncratic way to the amplitude of an input file and then record the result. You may not find a commercial plug-in that will do this, but in SC, you should be able to do what you can imagine (more or less!). The 6HUYHU class provides easy automated recording facilities. Often, this is the simplest way to capture your sounds. (See figure 3.10.) After executing this, you should have a sound file in SC’s recordings folder (see the doc for platform-specific locations) labeled with the date and time SC began recording: SC_YYMMDD_HHMMSS.aif. 6HUYHU also provides handy buttons on the Server window (appearance or availability varies by platform) to prepare, stop, and start recording. On OSX it may look like this, or similar (see figure 3.11). The above example uses the default recording options. Using the methods SUHSDUH)RU5HFRUG SDWK , UHF&KDQQHOVB, UHF+HDGHU)RUPDWB, and UHF6DPSOH)RUPDWB, you can customize the recording process. The latter 3 methods must be called before SUHSDUH)RU5HFRUG. A common case is to change the sample format; the default is to

94

Scott Wilson and Julio d’Escriván

JDWKHUDOO\RXUIROGHUSDWKV WKLVZLOOSDWKPDWFKHDFKIROGHULQWKHFROOHFWLRQLHZHZLOOKDYHDFROOHFWLRQ RIFROOHFWLRQVRISDWKV aJURXS2ILQGLY&XH)ROGHUV VRXQGV SDWK0DWFKFROOHFW^_LWHP_ LWHPDV6\PERO  SDWK0DWFK` 3RVWaJURXS2ILQGLY&XH)ROGHUVVHHWKHPDOO FKHFNKRZPDQ\FXHV\RXZLOOKDYHLQWKHHQG aJURXS2ILQGLY&XH)ROGHUVVL]H DXWRPDWHWKHEXIIHULQJSURFHVVIRUDOOFXHV aEXIIHUHG&XHV aJURXS2ILQGLY&XH)ROGHUVFROOHFW^_LWHPL_LWHPFROOHFW^_SDWK_ %XIIHUUHDG VSDWK ``QRZDOORXUFXHILOHVDUHVLWWLQJLQWKHLUEXIIHUV aEXIIHUHG&XHV>@KHUHLVFXH VHHLWLQWKHSRVWZLQGRZ 3RVWaEXIIHUHG&XHV>@ SOD\WKHPDOOLQD*URXSXVLQJRXUSUHYLRXVV\QWKGHI ZHXVHELQGKHUHWRHQVXUHWKH\VWDUWVLPXOWDQHRXVO\ VELQG ^  aQRZ3OD\LQJ *URXSQHZ V DJURXSWRSXWDOOWKHFKDQQHOV\QWKVLQ  aEXIIHUHG&XHV>@GR ^_FXH_6\QWK SOD\EXI>EXIFXH@aQRZ3OD\LQJ ` `  IDGHWKHPRXWWRJHWKHUE\VHQGLQJDUHOHDVHPHVVDJHWRWKHJURXS aQRZ3OD\LQJUHOHDVH

Figure 3.9 Gathering up files for multichannel cues.

95

3

Composition with SuperCollider

VERRWPDNHVXUHWKHVHUYHULVUXQQLQJ ILUVWHYDOXDWHWKLVVHFWLRQ E %XIIHUUHDG VVRXQGVDZONZDY DVRXUFH VSUHSDUH)RU5HFRUGSUHSDUHWKHVHUYHUWRUHFRUG \RXPXVWGRWKLVILUVW  VLPXOWDQHRXVO\VWDUWWKHSURFHVVLQJDQGUHFRUGLQJ VELQG ^  KHUH VRXUIXQN\HIIHFW  [ ^YDUFROXPELDDPS   FROXPELD 3OD\%XIDU EORRS    DPS $PSOLWXGHDU FROXPELD  VWLFN\ DPSIROORZHU   2XWDU 5HVRQ]DU FROXPELDDPS ILOWHUIUHTIROORZVDPS   `SOD\ VUHFRUG `  VSDXVH5HFRUGLQJSDXVH VUHFRUGVWDUWDJDLQ VVWRS5HFRUGLQJVWRSUHFRUGLQJDQGFORVHWKHUHVXOWLQJVRXQGILOH

Figure 3.10 Recording the results of making sounds with SuperCollider.

Figure 3.11 A screen shot of a Server window.

96

Scott Wilson and Julio d’Escriván

record as 32-bit floating-point values. This has the advantage of tremendous dynamic range, which means you don’t have to worry about clipping and can normalize later, but it’s not compatible with all audio software. VUHF6DPSOH)RUPDWB LQW 

More elaborate recording can be realized, of course, by using the 'LVN2XW UGen. Server’s automatic functionality is in fact based on this. SC also has non-real-time synthesis capabilities, which may be useful for rendering CPU-intensive code. (See chapter 18.) 3.3.2

Thinking in the Abstract

Something that learners often find difficult to do is to stop thinking about exactly what they want to do at the moment, and instead consider whether the problem they’re dealing with has a general solution. Generalizing your code can be very powerful. Imagine that we want to make a sound that consists of 3 bands of resonated impulses. We might do something like this: ^ 5HVRQ]DU 'XVWDU    5HVRQ]DU 'XVWDU    5HVRQ]DU 'XVWDU    UHFLSURFDOVFDOHWRHQVXUH QRFOLSSLQJ `SOD\

Now, through a bit of careful thinking, we can abstract the problem from this concrete realization and come up with a more general solution: I  Q  ^ 0L[ILOO Q^_L_5HVRQ]DU 'XVWDU  I  L  `

QUHFLSURFDOVFDOHWRHQVXUHQRFOLSSLQJ `SOD\

This version has an equivalent result, but we’ve expressed it in terms of generalized instructions. It shows you how to construct a Synth consisting of resonated impulses tuned in whole-number ratios rather than as an exact arrangement of objects and connections, as you might do in a visual patching language such as Max/MSP. We’ve

97

3

Composition with SuperCollider

also used variables (f for frequency and n for number of resonators) to make our code easy to change. This is the great power of abstraction: by expressing something as a general solution, you can be much more flexible than if you think in terms of exact implementations. Now it happens that the example above is hardly shorter than the first, but look what we can do with it: I  Q  ^ 0L[ILOO Q^_L_5HVRQ]DU 'XVWDU  I  L  `

QUHFLSURFDOVFDOHWRHQVXUHQRFOLSSLQJ `SOD\

By changing I and Q we’re able to come up with a much more complex variant. Imagine what the hard-coded version would look like with 50 individual 5HVRQ] 8*HQV typed out by hand. In this case, not only is the code more flexible, it’s shorter; and because of that, it’s much easier to understand. It’s like the difference between saying “Make me 50 resonators” and saying “Make me a resonator. Make me a resonator. Make me a resonator. . . .” This way of thinking has potential applications in almost every aspect of SC, even GUI construction (see figure 3.12). 3.3.3

Gestures

For a long time, electroacoustic and electronic composition has been a rather “manual” process. This may account for the computer’s being used today as a virtual analog studio; many sequencer software GUIs attest to this way of thinking. However, as software has become more accessible, programming may in fact be replacing this virtual splicing approach. One of the main advantages of a computer language is generalization, or abstraction, as we have seen above. In the traditional “tape” music studio approach, the composer does not differentiate gesture from musical content. In fact, traditionally they amount to much the same thing in electronic music. But can a musical gesture exist independently of sound? In electronic music, gestures are, if you will, the morphology of the sound, a compendium of its behavior. Can we take sound material and examine it under another abstracted morphology? In ordinary musical terms this could mean a minor scale can be played in crescendo or diminuendo and remain a minor scale. In electroacoustic music this can happen, for example, when we modulate 1 sound with the

98

Scott Wilson and Julio d’Escriván

I  Q QXPEHURIUHVRQDWRUV W $UUD\ILOO Q^_L_ ^ 5HVRQ]DU 'XVWDU  I  L 

QUHFLSURFDOVFDOHWRHQVXUHQRFOLSSLQJ `SOD\ `  QRZPDNHD*8, DVFUROOLQJZLQGRZVRZHGRQ WUXQRXWRIVSDFH Z :LQGRZQHZ %XWWRQV5HFW  VFUROOWUXH  ZYLHZGHFRUDWRU )ORZ/D\RXWQHZ ZYLHZERXQGV DXWROD\RXWWKHZLGJHWV QGR ^_L_ %XWWRQQHZ Z5HFW  VWDWHVB > >)UHT I  L 2Q&RORUEODFN&RORUZKLWH@ >)UHT I  L 2II&RORUZKLWH&RORUEODFN@ @ DFWLRQB ^DUJEXWW W>L@UXQ EXWWYDOXH   `  `  ZIURQW

Figure 3.12 A variable number of resonators with an automatically created GUI.

spectrum of another. The shape of 1 sound is generalized and applied to another; we are accustomed to hearing this in signal-processing software. In this section we would like to show how SuperCollider can be used to create “empty gestures,” gestures that are not linked to any sound in particular. They are, in a sense, gestures waiting for a sound, abstractions of “how to deliver” the musical idea. First we will look at some snippets of code that we can reuse in different patches, and then we will look at some Routines we can call up as part of a “Routine of Routines” (i.e., a score, so to speak). If you prefer to work in a more traditional way, you can just run the Routines with different sounds each time, record them to hard disk, and then assemble or sample as usual in your preferred audio editing/sequencing software. However, an advantage of doing the larger-scale organization of your piece within SC is that since you are interpreting your code during the actual performance of your piece, you can add elements of variability to what is normally fixed

99

3

Composition with SuperCollider

at the time of playback. You can also add elements of chance to your piece without necessarily venturing fully into algorithmic composition. (Naturally, you can always record the output to a sound file if desired.) This, of course, brings us back to issues of design, and exactly what you choose to do will depend on your own needs and inclinations. 3.3.4 Making “Empty” Gestures Let’s start by making a list where all our Buffers will be stored. This will come in handy later on, as it will allow us to call up any file we opened with our file browser during the course of our session. In the following example we open a dialogue box and can select any sound(s) on our hard disk: \RXZLOOEHDEOHWRDGGPXOWLSOHVRXQGILOHVMXVWVKLIWFOLFNZKHQ VHOHFWLQJ YDUILOHVRXQG3DWK aEXIIHUV /LVW>@ 'LDORJJHW3DWKV ^DUJSDWKV SDWKVGR ^_VRXQG3DWK_ SRVWWKHSDWKWRYHULI\WKDWLWLVWKHRQH\RXH[SHFW VRXQG3DWKSRVWOQ DGGVWKHUHFHQWO\VHOHFWHG%XIIHUWR\RXUOLVW aEXIIHUVDGG %XIIHUUHDG VVRXQG3DWK  ` ` 

You can check to see how many Buffers are in your list so far (watch the post window!), aEXIIHUVVL]H

and you can see where each sound is inside your list. For example, here is the very first sound stored in our Buffer list: aEXIIHUV>@

Now that we have our sound in a Buffer, let’s try some basic manipulations. First, let’s just listen to the sound to verify that it is there: aEXIIHUV>@SOD\

Now, let’s make a simple 6\QWK'HI so we can create Synths which play our Buffer (for example, in any 5RXWLQH, 7DVN, or other 6WUHDP) later on. For the purposes of this demonstration we will use a very simple percussive envelope, making sure we have GRQH$FWLRQ in order to free the synth after the envelope terminates:

100

Scott Wilson and Julio d’Escriván

EXIIHUSOD\HUZLWKGRQHDFWLRQDQGFRQWURORIHQYHORSHDQGSDQQLQJ 6\QWK'HI ?VDPSOH3OD\HU^DUJRXW EXI  UDWH DW UHO SRV S6SHHG OHY  YDUVDPSOHSDQ7DPSDX[ VDPSOH 3OD\%XIDU EXIUDWH %XI5DWH6FDOHNU EXI   SDQ7 )6LQ2VFNU S6SHHG  DPS (QY*HQDU (QYSHUF DWUHOOHY GRQH$FWLRQ  2XWDU RXW3DQDU VDPSOHSDQ7DPS  ` DGG

As mentioned in chapter 1, we use the DGG method here rather than one of the more low-level SynthDef methods such as VHQG. In addition to sending the def to the server, DGG also stores it within the global 6\QWK'HVF/LE in the client app, so that its arguments can be looked up later by the Patterns and Streams system (see chapter 6). We’ll need this below. Let’s test the SynthDef: 6\QWK ?VDPSOH3OD\HU>?RXW?EXIQXPaEXIIHUV>@?UHO@ 

As you can hear, it plays 0.25 second of the selected sound. Of course, if you have made more than 1 Buffer list, you can play sounds from any list, and also play randomly from that list. For example, from the list we defined earlier we could do this: 6\QWK ?VDPSOH3OD\HU>?RXW?EXIQXPaEXIIHUVFKRRVH?UHO@ 

Let’s define a Routine that allows us to create a stuttering /rushing gesture in a glitch style. We’ll use a new Pattern here, 3JHRP, which specifies a geometric series.3 Note that Patterns can be nested. Figure 3.13 shows a Pseq whose list consists of two Pgeoms. Remember that you can use a 7DVN or 5RXWLQH to sequence several such gestures within your piece. You can, of course, modify the Routine to create other accel/decel Patterns by substituting different Patterns. You can also add variability by making some of them perform choices when they generate their values (e.g., using 3UDQG or 3[UDQG). You can use this, for example, to choose which speaker a sound comes from without repeating speakers: 3[UDQG >@LQI

The advantage of having assigned your gestures to environment variables (using the tilde shortcut) is that now you are able to experiment in real time with the ordering, simultaneity, and internal behavior of your gestures. Let’s take a quick look at 1 more important Pattern: 3ELQG. It creates a Stream of Events, which are like a kind of dictionary of named properties and associated values. If you send the message SOD\ to a Pbind, it will play the Stream of Events, in

101

3

Composition with SuperCollider

 DURXWLQHIRUFUHDWLQJDULWDUGDQGRVWXWWHUZLWKSDQQLQJ\RXPXVWKDYH UXQWKHFRGHLQILJVRWKDWWKLVURXWLQHPD\ILQGVRPHVRXQGVDOUHDG\ORDGHG LQWREXIIHUV\RXFDQFKDQJHWKHLQGH[RIaEXIIHUHG&XHVWRWHVWWKHURXWLQHRQ GLIIHUHQWVRXQGV 

aVWXW 5RXWLQH ^YDUGXUSRV aVWXW3DWW 3VHT >3JHRP  3Q  3JHRP  @  aVWU aVWXW3DWWDV6WUHDP GR^ GXU aVWUQH[W GXUSRVWOQ VRZHFDQFKHFNYDOXHVRQWKHSRVWZLQGRZ aVDPSOH 6\QWK VDPSOH3OD\HU>?RXW?EXIaEXIIHUHG&XHV>@?DW ?UHO?S6SHHG@  GXUZDLW ` `  QRZSOD\LW aVWXWSOD\ UHVHWEHIRUH\RXSOD\DJDLQ aVWXWUHVHW

Figure 3.13 Making a stuttering gesture using a geometric Pattern.

a fashion similar to the Clock examples above. Here’s a simple example which makes sound using what’s called the “default” SynthDef: UDQGRPO\VHOHFWHGIUHTXHQF\GXUDWLRQVHFRQG 3ELQG ?IUHT3UDQG >@ ?GXU SOD\

It’s also possible to substitute Event Streams as they play. When you call SOD\ on a 3DWWHUQ, it returns an (YHQW6WUHDP3OD\HU, which actually creates the individual Events from the Stream defined by the Pattern. EventStreamPlayer allows its Stream to be substituted while it is playing. aJHVW 3ELQG ?LQVWUXPHQW?VDPSOH3OD\HU?GXU?UHO  aSOD\HU aJHVWSOD\PDNHLWSOD\ aSOD\HUVWUHDP 3ELQG ?LQVWUXPHQW?VDPSOH3OD\HU?GXU?UDWH 3[UDQG >@LQI ?UHO DV6WUHDPVXEVWLWXWHWKH VWUHDP aSOD\HUVWRS

102

Scott Wilson and Julio d’Escriván

If you have evaluated the expressions above, you will notice that you don’t hear the simple default SynthDef, but rather the one we made earlier. Since we added it above, the Pbind is able to look it up in the global library and get the information it needs about the def. Now, the Pbind plays repeatedly at intervals specified by the \dur argument, but it will stop playing as soon as it receives nil for this or any other argument. So we can take advantage of this to make Streams that are not repetitive and thus make single gestures (of course, we can also choose to work in a looping/ layering fashion, but more of that later). Here is a Pbind making use of our accelerando Pattern to create a rushing sound: aJHVW 3ELQG ?LQVWUXPHQW?VDPSOH3OD\HU?GXU3JHRP   ?UHO  aJHVWSOD\

When the Stream created from the Pgeom ended, it returned nil and the EventStreamPlayer stopped playing. If you call play on it again, you will notice that it makes the same rushing sound without the need to reset it, as we had to do with the Routine, since it will return a new EventStreamPlayer each time. More complex gestures can be made, of course, by nesting patterns: 3ELQG ?LQVWUXPHQW?VDPSOH3OD\HU?GXU3VHT >3JHRP   3JHRP  @ ?UHO?S6SHHG SOD\ 3ELQG ?LQVWUXPHQW?VDPSOH3OD\HU?GXU3VHT >3JHRP   3JHRP  @ ?UDWH3[UDQG >@LQI ?UHO ?S6SHHG SOD\

Similar things can be done with the 3GHI class from the -,7 library (see chapter 7). Let’s designate another environment variable to hold a sequence of values that we can plug in at will and change on the fly. This Pattern holds values that would work well for \dur: aUK\WKP 3VHT >QLO@ WKHQLOLVVRLWZLOO VWRS

We can then plug it into a 3GHI, which we’ll call ?D: aJHVW 3GHI ?D3ELQG ?LQVWUXPHQW?VDPSOH3OD\HU?GXUaUK\WKP?UHO ?S6SHHG   aJHVWSOD\

If we define another sequence of values we want to try, aUK\WKP 3VHT > QLO@ 

103

3

Composition with SuperCollider

and then reevaluate the 3GHI, aJHVW 3GHI ?D3ELQG ?LQVWUXPHQW?VDPSOH3OD\HU?GXUaUK\WKP?UHO ?S6SHHG  

we can hear that the new aUK\WKP has taken the place of the previous one. Notice that it played immediately, without the need for executing aJHVWSOD\. This is one of the advantages of working with the 3GHI class: once the 6WUHDP is running, anything that is “poured” into it will come out. In the following example, we assign a Pattern to the rate values and obtain an interesting variation: aJHVW 3GHI ?D3ELQG ?LQVWUXPHQW?VDPSOH3OD\HU?DWW?UHO ?OHY^UUDQG  `?GXU?UDWH3VHT >3EURZQ   @ 

Experiments like these can be conducted by creating Patterns for any of the arguments that our 6\QWK'HI will take. If we have “added” more than 1 6\QWK'HI, we can even modulate the ?LQVWUXPHQW by getting it to choose among several different options. Once we have a set of gestures we like, we can trigger them in a certain order using a 5RXWLQH, or we can record them separately and load them as audio files to our audio editor. The latter approach is useful if we want to use a cue player for the final structuring of a piece. 3.4

Conclusions What next? The best way to compose with SuperCollider is to set yourself a project with a deadline! In this way you will come to grips with specific things you need to know, and you will learn it much better than just by reviewing everything it can do. SuperCollider offers a variety of approaches to electronic music composition. It can be used for sound creation thanks to its rich offering of UGens (see chapter 2), as well as for assembling your piece in flexible ways. We have shown that the assembly of sounds itself can become a form of synthesis, illustrated by our use of Patterns and Streams. Another approach is to review some of the classic techniques used in electroacoustic composition and try to re-create them yourself using SuperCollider. Below we refer you to some interesting texts that may enhance your creative investigations.

Further Reading Budón, O. 2000. “Composing with Objects, Networks, and Time Scales: An Interview with Horacio Vaggione.” Computer Music Journal, 24(3): 9–22. Collins, N. 2010. Introduction to Computer Music. Chichester: Wiley.

104

Scott Wilson and Julio d’Escriván

Dodge, C., and T. A. Jerse. 1997. Computer Music: Synthesis, Composition, and Performance, 2nd ed. New York: Schirmer. Holtzman, S. R. 1981. “Using Generative Grammars for Music Composition.” Computer Music Journal, 5(1): 51–64. Loy, G. 1989. “Composing with Computers: A Survey of Some Compositional Formalisms and Music Programming Languages.” In M. V. Mathews and J. R. Pierce, eds., Current Directions in Computer Music Research, pp. 291–396. Cambridge, MA: MIT Press. Loy, G., and Abbott, C. 1985. “Programming Languages for Computer Music Synthesis, Performance, and Composition.” ACM Computing Surveys (CSUR), 17(2): 235–265. Mathews, M. V. 1963. “The Digital Computer as a Musical Instrument.” Science, 142(3592): 553–557. Miranda, E. R. 2001. Composing Music with Computers. London: Focal Press. Roads, C. 2001. Microsound. Cambridge, MA: MIT Press. Roads, C. 1996. The Computer Music Tutorial. Cambridge, MA: MIT Press. Wishart, T. 1994. Audible Design: A Plain and Easy Introduction to Practical Sound Composition. York, UK: Orpheus the Pantomime.

Notes 1. You may have noticed that the terms “message” and “method” used somewhat interchangeably. In polymorphism the distinction becomes clear: different objects may respond to the same message with different methods. In other words, the message is the command, and the method is what the object does in response. 2. Scott Wilson’s De-Interleaver application for OSX and Jeremy Friesner’s cross-platform command line tools audio_combine and audio_split allow for convenient interleaving and deinterleaving of audio files. 3. A geometric series is a series with a constant ratio between successive terms.

25

Writing Unit Generator Plug-ins Dan Stowell

Writing a unit generator (UGen) for SuperCollider 3 can be extremely useful, allowing the addition of new audio generation and processing capabilities to the synthesis server. The bulk of the work is C++ programming, but the API (Application Programming Interface) is essentially quite simple — so even if you have relatively little experience with C/C++, you can start to create UGens based on existing examples. You’re probably already familiar with UGens from other chapters. Before creating new UGens of your own, let’s first consider what a UGen really is, from the plug-in programmer’s point of view. 25.1

What Is a UGen, Really? A UGen is a component for the synthesis server, defined in a plug-in, which can receive a number of floating-point data inputs (audio- or control-rate signals or constant values) and produce a number of floating-point data outputs, as well as “side effects” such as writing to the post window, accessing a buffer, or sending a message over a network. The server can incorporate the UGen into a synthesis graph, passing data from 1 UGen to another. When using SC language, we need to have available a representation of each UGen which provides information about its inputs and outputs (the number, type, etc.). These representations allow us to define synthesis graphs in SC language (SynthDefs). Therefore, each UGen also comes with an SC class; these classes are always derived from a base class, appropriately called 8*HQ. So to create a new UGen you need to create both the plug-in for the server and the class file for the language client.

25.2

An Aside: Pseudo UGens Before we create a “real” UGen, we’ll look at something simpler. A pseudo UGen is an SC class that “behaves like” a UGen from the user’s point of view but doesn’t

692

Dan Stowell

involve any new plug-in code. Instead, it just encapsulates some useful arrangement of existing units. Let’s create an example, a simple reverb effect: 5HYHUE^  DU^_LQ_ YDURXW LQ RXW $OOSDVV1DU RXWUDQG  ARXW ` `

This isn’t a very impressive reverb yet, but we’ll improve it later. As you can see, this is a class like any other, with a single class method. The DU method name is not special — in fact, you could use any method name (including

QHZ). We are free to use the full power of SC language, including constructs such as UDQG, to choose a random delay time for our effect. The only real requirement for a pseudo UGen is that the method returns something that can be embedded in a synth graph. In our simple example, what is returned is an $OOSDVV1 applied to the input. Copy the above code into a new file and save it as, for instance, Reverb1.sc in your SCClassLibrary or Extensions folder; then recompile. You’ll now be able to use 5HYHUEDU within your SynthDefs, just as if it were a “real” UGen. Let’s test this: VERRW [ ^ YDUIUHTVRQRXW &KLUSVDWDUELWUDU\PRPHQWV IUHT (QY*HQDU (QYSHUF  'XVWDU   VRQ 6LQ2VFDU IUHT  :HDSSO\UHYHUEWRWKHOHIWDQGULJKWFKDQQHOVVHSDUDWHO\ RXW ^5HYHUEDU VRQFXWRII `GXS `SOD\ V  [IUHH

You may wish to save this usage example as a rudimentary Help file, Reverb1.html. To make the reverb sound more like a reverb, we modify it to perform 6 similar all-pass delays in a row, and we also add some LPF units in the chain to create a nice frequency roll-off. We also add parameters: 5HYHUE^  DU^_LQZHW FXWRII _ YDURXW LQ GR^RXW /3)DU $OOSDVV1DU RXWUDQG FXWRII `

693

25

Writing Unit Generator Plug-ins

A RXW ZHW  LQ  ²ZHW  ` `

This is on the way toward becoming a useful reverb unit without having created a real plug-in at all. This approach has definite limitations. It is of course confined to processes that can be expressed as a combination of existing units — it can’t create new types of processing or new types of server behavior. It may also be less efficient than an equivalent UGen, because it creates a small subgraph of units that pass data to each other and must maintain their own internal states separately. Now let’s consider what is involved in creating a “real” UGen. 25.3

Steps Involved in Creating a UGen 1. First, consider exactly what functionality you want to encapsulate into a single unit. An entire 808-drum machine, or just the cymbal sound? Smaller components are typically better, because they can be combined in many ways within a SynthDef. Efficiency should also be a consideration. 2. Second, write the Help file. Really — it’s a good idea to do this before you start coding, even if you don’t plan to release the UGen publicly. As well as being a good place to keep the example code which you can use while developing and testing the UGen, it forces you to think clearly about the inputs and outputs and how the UGen will be used in practice, thus weeding out any conceptual errors. A Help file is also a good reminder of what the UGen does — don’t underestimate the difficulties of returning to your own code, months or years later, and trying to decipher your original intentions! The Help file will be an HTML file with the same name as the UGen. There is a “Documentation Style Guide” in the SC Help system which includes tips and recommendations for writing Help documentation. But, of course, during development the Help file doesn’t need to be particularly beautiful. 3. Third, write the class file. You don’t need to do this before starting on the C++ code, but it’s a relatively simple step. Existing class files (e.g., for SinOsc, LPF, Pitch, Dwhite) can be helpful as templates. More on this shortly. 4. Fourth, write the plug-in code. The programming interface is straightforward, and again existing plug-in code can be a helpful reference: all UGens are written as plug-ins — including the “core” UGens — so there are lots of code examples available. We now consider writing the class file and writing the plug-in code.

694

25.4

Dan Stowell

Writing the Class File A class file for a UGen is much like any other SC class, with the following conditions: It must be a subclass of 8*HQ. This is so that methods defined in the 8*HQ class can be used when the language builds the SynthDef (synth graph definition). The name of the class must match the name used in the plug-in code — the class name is used to tell the server which UGen to instantiate. It must implement the appropriate class methods for the rates at which it can run (e.g., DU, NU, and/or LU). These method names are referenced for rate checking during the SynthDef building process. The class methods must call the PXOWL1HZ method (defined in the main 8*HQ class), which processes the arguments and adds the UGen correctly to the SynthDef that is being built. The class file does not have any direct connection with the C++ plug-in code — after all, it’s the server that uses the plug-in code, while the class file is for the language client. Let’s look at a well-known example: 6LQ2VF8*HQ^  DU^ DUJIUHT SKDVH PXO DGG  AWKLVPXOWL1HZ DXGLR IUHTSKDVH PDGG PXODGG `  NU^ DUJIUHT SKDVH PXO DGG  AWKLVPXOWL1HZ FRQWURO IUHTSKDVH PDGG PXODGG ` `

As you can see, 6LQ2VF is a subclass of 8*HQ and implements 2 class methods. Both of these methods call PXOWL1HZ and return the result, which is 1 or more instances of the UGen we are interested in. The methods also call PDGG, which we’ll discuss shortly. The first argument to PXOWL1HZ is a symbol to indicate the rate at which the particular UGen instance will be operating: this could be “DXGLR,” “FRQWURO,” “VFDODU,” or “GHPDQG.” The remaining arguments are those that will actually be passed to the C++ plug-in — here IUHT and SKDVH. If any of these arguments are arrays, PXOWL1HZ performs multichannel expansion, creating a separate unit to handle each channel. Indeed, this is why the method is called PXOWL1HZ. Note that the PXO and DGG arguments are not being passed in to PXOWL1HZ. This means that the actual plug-in code for SinOsc will never be able to access them. In-

695

25

Writing Unit Generator Plug-ins

stead, this UGen makes use of the PDGG method, which is essentially a convenience for multiplication and addition of the unit’s output. As well as avoiding the programmer’s having to implement the multiplication and addition part of the process, the PDGG method performs some general optimizations (e.g., in the very common degenerate case of multiplying by 1 and adding 0; no processing is really required, so the UGen is simply returned unaltered). It is the convention to add PXO and DGG arguments to UGens as the final 2 arguments, as is done here; these 2 arguments are often very useful and are supported by many UGens. (Due to their commonness, they are often undocumented in Help files.) Let’s start to draft the class file for a UGen we can implement. We’ll create a basic “flanger” which takes some input and then adds an effect controlled by rate and depth parameters: )ODQJHU8*HQ^  DU^ DUJLQUDWH GHSWK PXO DGG  AWKLVPXOWL1HZ DXGLR LQUDWHGHSWK PDGG PXODGG `  NU^ DUJLQUDWH GHSWK PXO DGG  AWKLVPXOWL1HZ FRQWURO LQUDWHGHSWK PDGG PXODGG ` `

Save this as Flanger.sc in your extensions directory. If you recompile, you’ll find that this is sufficient to allow you to use )ODQJHUDU or )ODQJHUNU in SynthDefs, which the SuperCollider language will happily compile — but of course those SynthDefs won’t run yet, because we haven’t created anything to tell the server how to produce the Flanger effect. 25.4.1

Checking the Rates of Your Inputs

Because SuperCollider supports different signal rates, it is useful to add a bit of “sanity checking” to your UGen class to ensure that the user doesn’t try to connect things in a way that doesn’t make sense: for example, plugging an audio-rate value into a scalar-rate input. The 8*HQ class provides a FKHFN,QSXWV method which you can override to perform any appropriate checks. When the SynthDef graph is built, each UGen’s FKHFN,QSXWV method will be called. The default method defined in 8*HQ simply passes through to FKHFN9DOLG,QSXWV, which checks that each of the inputs is really something that can be plugged into a synth graph (and not some purely client-side object such as, say, an 6&:LQGRZ or a 7DVN).

696

Dan Stowell

The %XI:U UGen is an example which implements its own rate checking. Let’s look at what the class does: FKHFN,QSXWV^ LI UDWH  DXGLR DQG^LQSXWVDW  UDWH  DXGLR `^  A SKDVHLQSXWLVQRWDXGLRUDWHLQSXWVDW  LQSXWVDW   UDWH  `  AWKLVFKHFN9DOLG,QSXWV `

If %XI:U is used to write audio-rate data to a buffer, then the input specifying the phase (i.e., the position at which data is written) must also be at audio rate — there’s no natural way to map control-rate index data to a buffer which is taking audio-rate data. Therefore the class overrides the FKHFN,QSXWV method to test explicitly for this. The UDWH variable is the rate of the unit under consideration (a symbol, just like the first argument to PXOWL1HZ). The LQSXWV variable is an array of the unit’s inputs, each of which will be a UGen and thus will also have a UDWH variable. So the method compares the present unit’s rate against its first input’s rate. It simply returns a string if there’s a problem (returning anything other than nil is a sign of an error found while checking input). If there’s not a problem, then it passes through to the default FKHFN9DOLG,QSXWV method — if you implement your own method checking, don’t forget to pass through to this check. Many UGens produce output at the same rate as their first input — for example, filters such as /3) or +3). If you look at their class definition (or their superclass, in the case of /3) and +3) — an abstract class called )LOWHU), you’ll see that they call a convenience method for this common case called FKHFN6DPH5DWH$V)LUVW,QSXW. Observe the result of these checks: VERRW [ ^/3)DU :KLWH1RLVHNU `SOD\ V (UURU [ ^/3)DU :KLWH1RLVHDU `SOD\ V 2. [IUHH [ ^/3)NU :KLWH1RLVHDU `SOD\ V (UURU [ ^/3)NU :KLWH1RLVHNU `SOD\ V 2. [IUHH

What happens if you don’t add rate checking to your UGens? Often it makes little difference, but ignoring rate checking can sometimes lead to unusual errors that are hard to trace. For example, a UGen that expects control-rate input is relatively safe, because it expects less input data than an audio-rate UGen — so if given audio-rate data, it simply ignores most of it. But in the reverse case, a UGen that expects audiorate data but is given only control-rate data may read garbage input from memory that it shouldn’t be reading.

697

25

Writing Unit Generator Plug-ins

Returning to the )ODQJHU example created earlier, you may wish to add rate checking to that class. In fact, since the Flanger is a kind of filter, you might think it sensible to use the FKHFN6DPH5DWH$V)LUVW,QSXW approach, either directly or by modifying the class so that it subclasses )LOWHU rather than 8*HQ. 25.5

Writing the C++ Code 25.5.1 Build Environments: Xcode, scons . . . UGen plug-ins are built just like any other C++ project. To make things easier for yourself as a developer, you can use and adapt 1 of the project files which are distributed along with SuperCollider’s source code: On Mac, the Xcode project file Plugins.xcodeproj is used to build the core set of SuperCollider plug-ins. It’s relatively painless to add a new “target” to this project in order to build your own plug-ins — this is the approach used in the SuperCollider Help document “Writing Unit Generators,” which has more details about the Xcode specifics. On Linux, the scons project file SConstruct is used to build SuperCollider as a whole. You can edit this file using a text editor to add your plug-in’s build instructions. Alternatively, the “sc3-plug-ins” SourceForge project provides an SConstruct file purely for building UGens — you may find it easier to start from that as a template. On Windows, Visual Studio project files are provided to compile plug-ins, including a UGEN_TEMPLATE_VCPROJ.vcprojtemplate file which you can use as a basis. You can, of course, use other build environments if you prefer. 25.5.2 When Your Code Will Be Called The server (scsynth) will call your plug-in code at 4 distinct points: When scsynth boots, it calls the plug-in’s ORDG function, which primarily declares which UGens the plug-in can provide. When a UGen is instantiated (i.e., when a synth starts playing), scsynth calls the UGen’s constructor function to perform the setting up of the UGen. To produce sound, scsynth calls each UGen’s calculation function in turn, once for every control period. This is typically the function which does most of the interesting work in the UGen. Since it is called only once during a control period, this function must produce either a single control-rate value or a whole block’s worth of audiorate values during 1 call. (Note: Demand UGens don’t quite fit this description and will be covered later.)

698

Dan Stowell

When a synth is ended, some UGens may need to perform some tidying up, such as freeing memory. If so, these UGens provide a destructor function which is called at this point. 25.5.3

The C++ Code for a Basic UGen

The code in figure 25.1 shows the key elements we need to include in our Flanger plug-in code. Here is what this code does: First, the LQFOXGH command calls the main header file for SuperCollider’s plug-in interface, SC_PlugIn.h. This is sufficient to include enough SuperCollider infrastructure for most types of UGen. (For phase vocoder UGens, more may be needed, as described later.) The static ,QWHUIDFH7DEOH pointer is a reference to a table of SuperCollider functions such as the ones used to register a new UGen. We define a data structure (a “struct”) which will hold any data we need to store during the operation of the UGen. This struct, which needs to be remembered or passed from 1 audio block to the next, must be stored here. Note that the struct inherits from the base struct 8QLW — this is necessary so that scsynth can correctly write information into the struct, such as the rate at which the unit is running. We declare our UGen’s functions, using the H[WHUQ& specifier so that the scsynth executable is able to reference the functions using C linkage. In a given plug-in we are allowed to define 1 or more UGens. Each of these will have 1 constructor (“Ctor”) function, 1 or more calculation (“next”) functions, and optionally 1 destructor (“Dtor”) function. Our constructor function, )ODQJHUB&WRU , takes a pointer to a Flanger struct and must prepare the UGen for execution. It must do the following 3 things: 1. Initialize the Flanger struct’s member variables appropriately. In this case we initialize the GHOD\VL]H member to a value representing a 20-millisecond maximum delay, making use of the 6$03/(5$7( macro which the SuperCollider API provides to specify the sample rate for the UGen. For some of the other struct members, we wish to calculate the values based on an input to the UGen. We can do this using the ,1 macro, which grabs a single control-rate value from the specified input. Here, we use ,1  — remembering that numbering starts at 0, this corresponds to the second input, defined in the Flanger class file as “rate.” These macros (and others) will be discussed later. 2. Tell scsynth what the calculation function will be for this instance of the UGen. The 6(7&$/& macro stores a reference to the function in our unit’s struct. In our example there’s only 1 choice, so we simply call 6(7&$/& )ODQJHUBQH[W . It’s possible

699

25

Writing Unit Generator Plug-ins

LQFOXGH6&B3OXJ,QK VWDWLF,QWHUIDFH7DEOH IW WKHVWUXFWZLOOKROGGDWDZKLFKZHZDQWWRSDVVIURPRQHIXQFWLRQWRDQRWKHU HJIURPWKHFRQVWUXFWRUWRWKHFDOFIXQF RUIURPRQHFDOORIWKHFDOFIXQFWRWKHQH[W VWUXFW)ODQJHUSXEOLF8QLW^  IORDWUDWHGHOD\VL]HIZGKRSUHDGSRV  LQWZULWHSRV ` IXQFWLRQGHFODUDWLRQVH[SRVHGWR& H[WHUQ&^  YRLGORDG ,QWHUIDFH7DEOH LQ7DEOH   YRLG)ODQJHUB&WRU )ODQJHU XQLW   YRLG)ODQJHUBQH[W )ODQJHU XQLWLQWLQ1XP6DPSOHV  `

YRLG)ODQJHUB&WRU )ODQJHU XQLW ^   +HUHZHPXVWLQLWLDOLVHVWDWHYDULDEOHVLQWKH)ODQJHUVWUXFW  XQLW!GHOD\VL]H 6$03/(5$7( I)L[HGPVPD[GHOD\  7\SLFDOO\ZLWKUHIHUHQFHWRFRQWUROUDWHVFDODUUDWHLQSXWV  IORDWUDWH ,1    5DWKHUWKDQXVLQJUDWHGLUHFWO\ZH UHJRLQJWRFDOFXODWHWKHVL]HRI  MXPSVZHPXVWPDNHHDFKWLPHWRVFDQWKURXJKWKHGHOD\OLQHDWUDWH  IORDWGHOWD  XQLW!GHOD\VL]H UDWH 6$03/(5$7(  XQLW!IZGKRS GHOWDI  XQLW!UDWH UDWH        `

,03257$177KLVWHOOVVFV\QWKWKHQDPHRIWKHFDOFXODWLRQIXQFWLRQ IRUWKLV8*HQ 6(7&$/& )ODQJHUBQH[W  6KRXOGDOVRFDOFVDPSOH VZRUWKRIRXWSXW² HQVXUHVHDFKXJHQ VSLSHVDUHSULPHG )ODQJHUBQH[W XQLW 

Figure 25.1 C++ code for a Flanger UGen. This code doesn’t add any effect to the sound yet, but contains the key elements required for all UGens.

700

Dan Stowell

YRLG)ODQJHUBQH[W )ODQJHU XQLWLQWLQ1XP6DPSOHV ^  

IORDW LQ ,1   IORDW RXW 287  



IORDWGHSWK ,1  

    

IORDWUDWH IORDWIZGKRS IORDWUHDGSRV LQWZULWHSRV LQWGHOD\VL]H



IORDWYDOGHOD\HG

 

IRU LQWL LLQ1XP6DPSOHVL ^  YDO LQ>L@

 

 

'RVRPHWKLQJWRWKHVLJQDOEHIRUHRXWSXWWLQJ  QRW\HWGRQH

 

 `

RXW>L@ YDO

  `

XQLW!ZULWHSRV ZULWHSRV XQLW!UHDGSRV UHDGSRV

XQLW!UDWH XQLW!IZGKRS XQLW!UHDGSRV XQLW!ZULWHSRV XQLW!GHOD\VL]H

YRLGORDG ,QWHUIDFH7DEOH LQ7DEOH ^ 

IW LQ7DEOH

 `

'HILQH6LPSOH8QLW )ODQJHU 

Figure 25.1 (continued)

701

25

Writing Unit Generator Plug-ins

to define multiple calculation functions and allow the constructor to decide which one to use. This is covered later. 3. Calculate one sample’s worth of output, typically by calling the unit’s calculation function and asking it to process 1 sample. The purpose of this is to “prime” the inputs and outputs of all the unit generators in the graph and to ensure that the constructors for UGens farther down the chain have their input values available so they can initialize correctly. Our calculation function, )ODQJHUBQH[W , should perform the main audio processing. In this example it doesn’t actually alter the sound — we’ll get to that shortly — but it illustrates some important features of calculation functions. It takes 2 arguments passed in by the server: a pointer to the struct and an integer specifying how many values are to be processed (this will be 1 for control-rate, more for audio-rate — typically 64). The last thing in our C++ file is the ORDG function, called when the scsynth executable boots up. We store the reference to the interface table which is passed in — note that although you don’t see any explicit references to IW elsewhere in the code, that’s because they are hidden behind macros which make use of it to call functions in the server. We must also declare to the server each of the UGens which our plug-in defines. This is done using a macro 'HILQH6LPSOH8QLW )ODQJHU , which tells the server to register a UGen with the name Flanger and with a constructor function named Flanger_Ctor. It also tells the server that no destructor function is needed. If we did require a destructor, we would instead use 'HILQH'WRU8QLW )ODQJHU , which tells the server that we’ve also supplied a destructor function named Flanger_Dtor. You must name your constructor/destructor functions in this way, since the naming convention is hardcoded into the macros. So what is happening inside our calculation function? Although in our example the input doesn’t actually get altered before being output, the basic pattern for a typical calculation function is given. We do the following: Create pointers to the input and output arrays which we will access: IORDW LQ  The macros ,1 and 287 return appropriate pointers for the desired inputs/outputs — in this case the first input and the first output. If the input is audio-rate, then LQ>@ will refer to the first incoming sample, LQ>@ to the next incoming sample, and so on. If the input is control-rate, then there is only 1 incoming value, LQ>@. We use the macro ,1 again to grab a single control-rate value, here the “depth” input. Note that ,1 is actually a shortcut to the first value in the location referenced by ,1 . ,1  is exactly the same as ,1  >@. ,1  IORDW RXW 287  ;

702

Dan Stowell

We copy some values from the UGen’s struct into local variables. This can improve the efficiency of the unit, since the C++ optimizer will typically cause the values to be loaded into registers. Next we loop over the number of input frames, each time taking an input value, processing it, and producing an output value. We could take values from multiple inputs, and even produce multiple outputs, but in this example we’re using only 1 full-rate input and producing a single output. Two important notes: If an input/output is control-rate and you mistakenly treat it as audio-rate, you will be reading/writing memory you should not be, and this can cause bizarre problems and crashes; essentially this is just the classic C/C++ “gotcha” of accidentally treating an array as being bigger than it really is. Note that in our example, we assume that the input and output are of the same size, although it’s possible that they aren’t — some UGens can take audio-rate input and produce control-rate output. This is why it is useful to make sure your SuperCollider class code includes the rate-checking code described earlier in this chapter. You can see why the FKHFN6DPH5DWH$V)LUVW,QSXW approach is useful in this case. The server uses a “buffer coloring” algorithm to minimize use of buffers and to optimize cache performance. This means that any of the output buffers may be the same as 1 of the input buffers. This allows for in-place operation, which is very efficient. You must be careful, however, not to write any output sample before you have read the corresponding input sample. If you break this rule, then the input may be overwritten with output, leading to undesired behavior. If you can’t write the UGen efficiently without breaking this rule, then you can instruct the server not to alias the buffers by using the 'HILQH6LPSOH&DQW$OLDV8QLW or 'HILQH'WRU&DQW$OLDV8QLW macros in the ORDG function, rather than the 'HILQH6LPSOH8QLW or 'HILQH'WRU8QLW macros. (The Help file on writing UGens provides an example in which this ordering is important.) Finally, having produced our output, we may have modified some of the variables we loaded from the struct; we need to store them back to the struct so the updated values are used next time. Here we store the UDWH value back to the struct — although we don’t modify it in this example, we will shortly change the code so that this may happen. The code in figure 25.1 should compile correctly into a plug-in. With the class file in place and the plug-in compiled, you can now use the UGen in a synth graph: VERRW [ ^ YDUVRQGO\RXW VRQ 6DZDU >@ PHDQ RXW )ODQJHUDU VRQ 

703

25

Writing Unit Generator Plug-ins

RXWGXS  `SOD\ V 

Remember that Flanger doesn’t currently add any effect to the sound. But we can at least check that it runs correctly (outputting its input unmodified and undistorted) before we start to make things interesting. 25.5.4

Summary: The Three Main Rates of Data Output

Our example has taken input in 3 different ways: Using ,1 in the constructor to take an input value and store it to the struct for later use. Since this reads a value only once, the input is being treated as a scalar-rate input. Using ,1 in the calculation function to take a single input value. This treats the input as control-rate. Using ,1 in the calculation function to get a pointer to the whole array of inputs. This treats the input as audio-rate. Typically the size of such an input array is accessed using the LQ1XP6DPSOHV argument, but note that if you create a control-rate UGen with audio-rate inputs, then LQ1XP6DPSOHV will be wrong (it will be 1), so you should instead use the macro )8//%8)/(1*7+ (see table 25.2). If the data that one of your UGen’s inputs is fed is actually audio-rate, there is no danger in treating it as control-rate or scalar-rate. The end result is to ignore the “extra” data provided to your UGen. Similarly, a control-rate input can safely be treated as scalar-rate. The result would be crude downsampling without low-pass filtering, which may be undesirable but will not crash the server. 25.5.5

Allocating Memory and Using a Destructor

Next we can develop our Flanger example so that it applies an effect to the sound. In order to create a flanging effect, we need a short delay line (around 20 milliseconds). We vary the amount of delay and mix the delayed sound with the input to produce the effect. To create a delay line, we need to allocate some memory and store a reference to that memory in the UGen’s data structure. And, of course, we need to free this memory when the UGen is freed. This requires a UGen with a destructor. Figure 25.2 shows the full code, with the destructor added, as well as the code to allocate, free, and use the memory. Note the change in the ORDG function — we use 'HILQH'WRU 8QLW rather than 'HILQH6LPSOH8QLW . (We’ve also added code to the calculation function which reads and writes to the delay line, creating the flanging effect.)

704

Dan Stowell

LQFOXGH6&B3OXJ,QK VWDWLF,QWHUIDFH7DEOH IW WKHVWUXFWZLOOKROGGDWDZKLFKZHZDQWWRSDVVIURPRQHIXQFWLRQWRDQRWKHU HJIURPWKHFRQVWUXFWRUWRWKHFDOFIXQF RUIURPRQHFDOORIWKHFDOFIXQFWRWKHQH[W VWUXFW)ODQJHUSXEOLF8QLW^  IORDWUDWHGHOD\VL]HIZGKRSUHDGSRV  LQWZULWHSRV   DSRLQWHUWRWKHPHPRU\ZH OOXVHIRURXULQWHUQDOGHOD\  IORDW GHOD\OLQH ` IXQFWLRQGHFODUDWLRQVH[SRVHGWR& H[WHUQ&^  YRLGORDG ,QWHUIDFH7DEOH LQ7DEOH   YRLG)ODQJHUB&WRU )ODQJHU XQLW   YRLG)ODQJHUBQH[W )ODQJHU XQLWLQWLQ1XP6DPSOHV   YRLG)ODQJHUB'WRU )ODQJHU XQLW  `

YRLG)ODQJHUB&WRU )ODQJHU XQLW ^           

+HUHZHPXVWLQLWLDOLVHVWDWHYDULDEOHVLQWKH)ODQJHUVWUXFW XQLW!GHOD\VL]H 6$03/(5$7( I)L[HGPVPD[GHOD\ 7\SLFDOO\ZLWKUHIHUHQFHWRFRQWUROUDWHVFDODUUDWHLQSXWV IORDWUDWH ,1   5DWKHUWKDQXVLQJUDWHGLUHFWO\ZH UHJRLQJWRFDOFXODWHWKHVL]HRI MXPSVZHPXVWPDNHHDFKWLPHWRVFDQWKURXJKWKHGHOD\OLQHDWUDWH IORDWGHOWD  XQLW!GHOD\VL]H UDWH 6$03/(5$7( XQLW!IZGKRS GHOWDI XQLW!UDWH UDWH XQLW!ZULWHSRV  XQLW!UHDGSRV 

 $OORFDWHWKHGHOD\OLQH  XQLW!GHOD\OLQH  IORDW 57$OORF XQLW!P:RUOGXQLW!GHOD\VL]H  VL]HRI IORDW   ,QLWLDOLVHLWWR]HURHV

Figure 25.2 Completed C++ code for the Flanger UGen.

705

25

Writing Unit Generator Plug-ins



PHPVHW XQLW!GHOD\OLQHXQLW!GHOD\VL]H VL]HRI IORDW 

       `

,03257$177KLVWHOOVVFV\QWKWKHQDPHRIWKHFDOFXODWLRQIXQFWLRQ IRUWKLV8*HQ 6(7&$/& )ODQJHUBQH[W  6KRXOGDOVRFDOFVDPSOH VZRUWKRIRXWSXW² HQVXUHVHDFKXJHQ VSLSHVDUHSULPHG )ODQJHUBQH[W XQLW 

YRLG)ODQJHUBQH[W )ODQJHU XQLWLQWLQ1XP6DPSOHV ^  

IORDW LQ ,1   IORDW RXW 287  



IORDWGHSWK ,1  

     

IORDWUDWH XQLW!UDWH IORDWIZGKRS XQLW!IZGKRS IORDWUHDGSRV XQLW!UHDGSRV IORDW GHOD\OLQH XQLW!GHOD\OLQH LQWZULWHSRV XQLW!ZULWHSRV LQWGHOD\VL]H XQLW!GHOD\VL]H



IORDWYDOGHOD\HGFXUUDWH



FXUUDWH ,1  

       

LI UDWH FXUUDWH ^  UDWHLQSXWQHHGVXSGDWLQJ  UDWH FXUUDWH  IZGKRS  GHOD\VL]H UDWH  6$03/(5$7( I `

   

   

IRU LQWL LLQ1XP6DPSOHVL ^  YDO LQ>L@ :ULWHWRWKHGHOD\OLQH GHOD\OLQH>ZULWHSRV@ YDO LI ZULWHSRV GHOD\VL]H  ZULWHSRV 

Figure 25.2 (continued)

706

Dan Stowell

        

        

5HDGIURPWKHGHOD\OLQH GHOD\HG GHOD\OLQH> LQW UHDGSRV@ UHDGSRV IZGKRS 8SGDWHSRVLWLRQ1%ZHPD\EHPRYLQJIRUZDUGVRUEDFNZDUGV  GHSHQGLQJRQLQSXW ZKLOH LQW UHDGSRV! GHOD\VL]H  UHDGSRV GHOD\VL]H ZKLOH LQW UHDGSRV  UHDGSRV GHOD\VL]H

  

  `

0L[GU\DQGZHWWRJHWKHUDQGRXWSXWWKHP RXW>L@ YDO GHOD\HG GHSWK 

    `

XQLW!UDWH UDWH XQLW!IZGKRS IZGKRS XQLW!ZULWHSRV ZULWHSRV XQLW!UHDGSRV UHDGSRV

YRLG)ODQJHUB'WRU )ODQJHU XQLW ^  57)UHH XQLW!P:RUOGXQLW!GHOD\OLQH  ` YRLGORDG ,QWHUIDFH7DEOH LQ7DEOH ^ 

IW LQ7DEOH

 `

'HILQH'WRU8QLW )ODQJHU 

Figure 25.2 (continued)

707

25

Writing Unit Generator Plug-ins

Table 25.1 Memory Allocation and Freeing Typical C Allocation/Freeing

In SuperCollider (using the real-time pool)

YRLG SWU PDOORF QXPE\WHV IUHH SWU

YRLG SWU 57$OORF XQLW!P:RUOGQXPE\WHV 57)UHH XQLW!P:RUOGSWU

SuperCollider UGens allocate memory differently from most programs. Ordinary memory allocation and freeing can be a relatively expensive operation, so SuperCollider provides a real-time pool of memory from which UGens can borrow chunks in an efficient manner. The functions to use in a plug-in are in the right-hand column of table 25.1, and the analogous functions (the ones to avoid) are shown in the left-hand column. 57$OORF and 57)UHH can be called anywhere in your constructor/calculation/ destructor functions. Often you will 57$OORF the memory during the constructor and 57)UHH it during the destructor, as is done in figure 25.2. Memory allocated in this way is taken from the (limited) real-time pool and is not accessible outside the UGen (e.g., to client-side processes). If you require large amounts of memory or wish to access the data from the client, you may prefer to use a buffer allocated and then passed in from outside — this is described later. 25.5.6

Providing More Than 1 Calculation Function

Your UGen’s choice of calculation function is specified within the constructor rather than being fixed. This gives an opportunity to provide different functions optimized for different situations (e.g., 1 for control-rate and 1 for audio-rate input) and to decide which to use. This code, used in the constructor, would choose between 2 calculation functions according to whether the first input was audio-rate or not: LI ,15$7(   FDOFB)XOO5DWH ^ 6(7&$/& )ODQJHUBQH[WBD  `HOVH^ 6(7&$/& )ODQJHUBQH[WBN  `

You would then provide both a )ODQJHUBQH[WBD and a )ODQJHUBQH[WBN function. Similarly, you could specify different calculation functions for audio-rate versus control-rate output (e.g., by testing whether %8)/(1*7+ is 1; see table 25.2), although this is often catered for automatically when your calculation function uses the LQ1XP6DPSOHV argument to control the number of loops performed, and so on.

708

Dan Stowell

Table 25.2 Useful Macros for UGen Writers Macro

Description

,1(index)

A float* pointer to input number index

287(index)

A float* pointer to output number index

,1(index)

A single (control-rate) value from input number index

287(index)

A single (control-rate) value at output number index

,15$7((index)

The rate of input index, an integer value corresponding to 1 of the following constants: FDOFB6FDODU5DWH (scalar-rate) FDOFB%XI5DWH (control-rate) FDOFB)XOO5DWH (audio-rate) FDOFB'HPDQG5DWH (demand-rate)

6(7&$/&(func)

Set the calculation function to func

6$03/(5$7(

The sample rate of the UGen as a double. Note: for control-rate UGens this is not the full audio rate but audio rate/blocksize)

6$03/('85

Reciprocal of 6$03/(5$7( (seconds per sample)

%8)/(1*7+

Equal to the block size if the unit is audio rate and to 1 if the unit is control rate

%8)5$7(

The control rate as a double

%8)'85

The reciprocal of %8)5$7(

*(7%8)

Treats the UGen’s first input as a reference to a buffer; looks this buffer up in the server, and provides variables for accessing it, including IORDW EXI'DWD, which points to the data; XLQW EXI)UDPHV for how many frames the buffer contains; XLQWEXI&KDQQHOV for the number of channels in the buffer

&OHDU8QLW2XWSXWV(unit,

A function which sets all the unit’s outputs to 0

inNumSamples) 3ULQW(fmt, ...)

Print text to the SuperCollider post window; arguments are just like those for the C function printf

'RQH$FWLRQ(doneAction, unit)

Perform a “doneAction,” as used in EnvGen, DetectSilence, and others

57$OORF(world, numBytes)

Allocate memory from the real-time pool— analogous to malloc(numBytes)

575HDOORF(world, pointer,

Reallocate memory in the real-time pool— analogous to realloc(pointer, numBytes)

numBytes)

709

25

Writing Unit Generator Plug-ins

Table 25.2 (continued) Macro

Description

57)UHH(world, pointer)

Free allocated memory back to the real-time pool— analogous to free(pointer)

6HQG7ULJJHU(node, triggerID, value)

Send a trigger from the node to clients, with integer ID, triggered, and float value value

)8//5$7(

The full audio sample rate of the server (irrespective of the rate of the UGen) as a double

)8//%8)/(1*7+

The integer number of samples in an audio-rate input (irrespective of the rate of the UGen)

The unit’s calculation function can also be changed during execution — the 6(7&$/& macro can safely be called from a calculation function, not just from the constructor. Whenever you call 6(7&$/& , this changes which function the server will call, from the next control period onward. The Help file on writing UGens shows more examples of 6(7&$/& in use. 25.5.7

Trigger Inputs

Many UGens make use of trigger inputs. The convention here is that if the input is nonpositive (i.e., 0 or negative), then crosses to any positive value, a trigger has occurred. If you wish to provide trigger inputs, use this same convention. The change from nonpositive to positive requires checking the trigger input’s value against its previous value. This means that our struct will need a member to store the previous value for checking. Assuming that our struct contains a float member SUHYWULJ, the following sketch outlines how we handle the incoming data in our calculation function: ҽRDWWULJ ,1  2UZKLFKHYHULQSXW\RXZLVK ҽRDWSUHYWULJ XQLW!SUHYWULJ LI SUHYWULJ  WULJ! ^ GRVRPHWKLQJ ` XQLW!SUHYWULJ WULJ6WRUHFXUUHQWYDOXH³³QH[WWLPHLW OOEHWKH SUHYLRXVYDOXH

The sketch is for a control-rate trigger input, but a similar approach is used for audio-rate triggering, too. For audio-rate triggering, you need to compare each value in the input block against the value immediately previous. Note that for the very first value in the block, you need to compare against the last value from the previous block (which you must have stored).

710

Dan Stowell

For complete code examples, look at the source of the 7ULJ UGen, found in TriggerUGens.cpp in the main SC distribution. 25.5.8 Accessing a Buffer When a buffer is allocated and then passed in to a UGen, the UGen receives the index number of that buffer as a float value. In order to get a pointer to the correct chunk of memory (as well as the size of that chunk), the UGen must look it up in the server’s list of buffers. In practice this is most easily achieved by using a macro called *(7B%8). You can call *(7B%8) near the top of your calculation function, and then the data are available via a float pointer EXI'DWD along with 2 integers defining the size of the buffer, EXI&KDQQHOV and EXI)UDPHV. Note that the macro assumes the buffer index is the first input to the UGen (this is the case for most buffer-using UGens). For examples which use this approach, look at the code for the 'LVN,Q or 'LVN2XW UGens, defined in DiskIO_UGens.cpp in the main SC distribution. Your UGen does not need to free the memory associated with a buffer once it ends. The memory is managed externally by the buffer allocation/freeing server commands. 25.5.9

Randomness

The API provides a convenient interface for accessing good-quality pseudo-random numbers. The randomness API is specified in SC_RGen.h and provides functions for random numbers from standard types of distribution: uniform, exponential, bilinear, and quasi-Gaussian (such as VXPUDQG, also available client-side). The server creates an instance of the random number generator for UGens to access. The following excerpt shows how to generate random numbers for use in your code: 5*HQ UJHQ  XQLW!P3DUHQW!P5*HQ ҽRDWUҽ UJHQIUDQG $UDQGRPҽRDWXQLIRUPO\GLVWULEXWHGWR  LQWUYDO UJHQLUDQG  $UDQGRPLQWHJHUXQLIRUPO\GLVWULEXWHG WRLQFOXVLYH ҽRDWUJDXV UJHQVXPUDQG  4XDVL*DXVVLDQOLPLWHGWRUDQJH“

25.5.10

When Your UGen Has No More to Do

Many UGens carry on indefinitely, but often a UGen reaches the end of its useful “life” (e.g., it finishes outputting an envelope or playing a buffer). There are 3 specific behaviors that might be appropriate if your UGen does reach a natural end:

711

25

Writing Unit Generator Plug-ins

1. Some UGens set a “done” flag to indicate that they’ve finished. Other UGens can monitor this and act in response to it (e.g., 'RQH, )UHH6HOI:KHQ'RQH). See the Help files for examples of these UGens. If you wish your UGen to indicate that it has finished, set the flag as follows: XQLW!P'RQH WUXH

This doesn’t affect how the server treats the UGen — the calculation function will still be called in future. 2. UGens such as (QY*HQ, /LQHQ, 'XW\, and /LQH provide a “doneAction” feature which can perform actions such as freeing the node once the UGen has reached the end of its functionality. You can implement this yourself simply by calling the 'RQH$FWLRQ macro, which performs the desired action. You would typically allow the user to specify the doneAction as an input to the unit. For example, if the doneAction is the sixth input to your UGen, you would call 'RQH$FWLRQ ,1  XQLW

Since this can perform behaviors such as freeing the node, many UGens stop calculating/outputting after they reach the point of calling this macro. See, for example, the source code for 'HWHFW6LOHQFH, which sets its calculation function to a no-op 'HWHFW6LOHQFHBGRQH function at the point where it calls 'RQH$FWLRQ. Not all GRQH$FWLRQV free the synth, though, so additional output is not always redundant. 3. If you wish to output zeroes from all outputs of your unit, you can simply call the &OHDU8QLW2XWSXWV function as follows: &OHDU8QLW2XWSXWV XQLWLQ1XP6DPSOHV 

Notice that this function has the same signature as a calculation function: as arguments it takes a pointer to the unit struct and an integer number of samples. You can take advantage of this similarity to provide an efficient way to stop producing output: 6(7&$/& &OHDU8QLW2XWSXWV 

Calling this would mean that your calculation function would not be called in future iterations. Instead, &OHDU8QLW2XWSXWV would be called. Therefore this provides an irreversible but efficient way for your UGen to produce silent output for the remainder of the synth’s execution. 25.5.11

Summary of Useful Macros

Table 25.2 summarized some of the most generally useful macros defined for use in your UGen code. Many of these are discussed in this chapter, but not all are covered explicitly. The macros are defined in SC_Unit.h and SC_InterfaceTable.h.

712

25.6

Dan Stowell

Specialized Types of UGen 25.6.1 Multiple-Output UGens In the C++ code, writing UGens which produce multiple outputs is very straightforward. The 287 macro gets a pointer to the desired-numbered output. Thus, for a 3-output UGen, assign each one (287  , 287  , 287  ) to a variable, then write output to these 3 pointers. In the SuperCollider class code, the default is to assume a single output, and we need to modify this behavior. Let’s look at the 3LWFK UGen to see how it’s done: 3LWFK0XOWL2XW8*HQ^  NU^DUJLQ LQLW)UHT PLQ)UHT PD[)UHT  H[HF)UHT PD[%LQV3HU2FWDYH PHGLDQ  DPS7KUHVKROG SHDN7KUHVKROG GRZQ6DPSOH  AWKLVPXOWL1HZ FRQWURO LQLQLW)UHTPLQ)UHTPD[)UHTH[HF)UHT  PD[%LQV3HU2FWDYHPHGLDQDPS7KUHVKROGSHDN7KUHVKROGGRZQ6DPSOH ` LQLW^DUJWKH,QSXWV LQSXWV WKH,QSXWV AWKLVLQLW2XWSXWV UDWH  ` `

There are 2 differences from an ordinary UGen. First, 3LWFK is a subclass of 0XOWL2XW8*HQ rather than of 8*HQ; 0XOWL2XW8*HQ takes care of some of the changes needed to work with a UGen with multiple outputs. Second, the LQLW function is overridden to say exactly how many outputs this UGen will provide (in this case, 2). For 3LWFK, the number of outputs is fixed, but in some cases it might depend on other factors. 3OD\%XI is a good example of this: its number of outputs depends on the number of channels in the EXIIHU(s) it is expecting to play, specified using the QXP&KDQQHOV argument. The init method for 3OD\%XI takes the QXP&KDQQHOV input (i.e., the first value from the list of inputs passed to init) and specifies that as the number of outputs. 25.6.2

Passing Arrays into UGens

25.6.2.1 The class file As described earlier, the PXOWL1HZ method automatically performs multichannel expansion if any of the inputs are arrays — yet in some cases we want a single unit to handle a whole array, rather than having 1 unit per array element. The %XI:U and 5HFRUG%XI UGens are good examples of UGens that do exactly this: each UGen can

713

25

Writing Unit Generator Plug-ins

take an array of inputs and write them to a multichannel buffer. Here’s how the class file handles this: 5HFRUG%XI8*HQ^ 

DU^DUJLQSXW$UUD\EXIQXP RIIVHW UHF/HYHO  SUH/HYHO UXQ ORRS WULJJHU   AWKLVPXOWL1HZ/LVW > DXGLR EXIQXPRIIVHWUHF/HYHOSUH/HYHO UXQORRSWULJJHU@LQSXW$UUD\DV$UUD\  ` `

Instead of calling the 8*HQ method PXOWL1HZ, we call PXOWL1HZ/LVW, which is the same except that all the arguments are a single array rather than a separated argument list. This means that the LQSXW$UUD\ argument (which could be either a single unit or an array), when concatenated onto the end of the argument list using the  array concatenation operator, in essence appears as a set of separate input arguments rather than a single array argument. Note that 5HFRUG%XI doesn’t know in advance what size the input array is going to be. Because of the array flattening that we perform, this means that the 5HFRUG%XI C++ plug-in receives a variable number of inputs each time it is instantiated. Our plug-in code will be able to detect how many inputs it receives in a given instance. Why do we put LQSXW$UUD\ at the end of the argument list? Why not at the beginning, in parallel with how a user invokes the 5HFRUG%XI UGen? The reason is to make things simpler for the C++ code, which will access the plug-in inputs according to their numerical position in the list. The UHF/HYHO input, for example, is always the third input, whereas if we inserted LQSXW$UUD\ into the list before it, its position would depend on the size of LQSXW$UUD\. The 3ROO UGen uses a very similar procedure, converting a string of text into an array of ASCII characters and appending them to the end of its argument list. However, the 3ROO class code must perform some other manipulations, so it is perhaps less clear as a code example than 5HFRUG%XI. But if you are developing a UGen that needs to pass text data to the plug-in, 3ROO shows how to do it using this array approach. 25.6.2.2 The C++ code Ordinarily we access input data using the ,1 or ,1 macro, specifying the number of the input we want to access. Arrays are passed into the UGen as a separate numeric input for each array element, so we access these elements in exactly the same way. But we need to know how many items to expect, since the array can be of variable size. The 8QLW struct can tell us how many inputs in total are being provided (the member XQLW!P1XP,QSXWV. Look again at the 5HFRUG%XI class code given above. There

714

Dan Stowell

are 7 “ordinary” inputs, plus the array appended to the end. Thus the number of channels in our input array is XQLW!P1XP,QSXWV² . We use this information to iterate over the correct number of inputs and process each element. 25.6.3

Demand-Rate UGens

25.6.3.1 The class file Writing the class file for a demand-rate UGen is straightforward. Look at the code for units such as 'VHULHV, 'JHRP, or 'ZKLWH as examples. They differ from other UGen class files in 2 ways: 1. The first argument to PXOWL1HZ (or PXOWL1HZ/LVW) is GHPDQG . 2. They implement a single class method, QHZ, rather than DU/ NU/ LU. This is because although some UGens may be able to run at multiple rates (e.g., audio rate or control rate), a demand-rate UGen can run at only 1 rate: the rate at which data are demanded of it. 25.6.3.2 The C++ code The C++ code for a demand-rate UGen works as normal, with the constructor specifying the calculation function. However, the calculation function behaves slightly differently. First, it is not called regularly (once per control period) but only when demanded, which during a particular control period could be more than once or not at all. This means that you can’t make assumptions about regular timing, such as the assumptions made in an oscillator which increments its phase by a set amount each time it is called. Second, rather than being invoked directly by the server, the calculation function calls are actually passed up the chain of demand-rate generators. Rather than using the ,1 or ,1 macros to access an input value (whose generation will have been coordinated by the server), we instead use the '(0$1',1387 macro, which requests a new value directly from the unit farther up the chain, “on demand.” Note: because of the method used to demand the data, demand-rate UGens are currently restricted to being single-output. 25.6.4

Phase Vocoder UGens

Phase vocoder UGens operate on frequency-domain data stored in a buffer (produced by the ))7 UGen). They don’t operate at a special “rate” of their own: in reality they are control-rate UGens. They produce and consume a control-rate signal which acts as a type of trigger: when an FFT frame is ready for processing, its value

715

25

Writing Unit Generator Plug-ins

is the appropriate buffer index; otherwise, its value is –1. This signal is often referred to as the “chain” in SC documentation. 25.6.4.1 The class file As with demand-rate UGens, phase vocoder UGens (PV UGens) can have only a single rate of operation: the rate at which FFT frames are arriving. Therefore, PV UGens implement only a single QHZ class method, and they specify their rate as “control” in the call to PXOWL1HZ. See the class files for 39B0DJ0XO and 39B%ULFN:DOO as examples of this. PV UGens process data stored in buffers, and the C++ API provides some useful macros to help with this. The macros assume that the first input to the UGen is the one carrying the FFT chain where data will be read and then written, so it is sensible to stick with this convention. 25.6.4.2 The C++ code PV UGens are structured just like any other UGen, except that to access the frequency-domain data held in the external buffer, there are certain macros and procedures to use. Any of the core UGens implemented in PV_UGens.cpp should serve as a good example to base your own UGens on. Your code should include the header file FFT_UGens.h, which defines some PV-specific structs and macros. Two important macros are 39B*(7B%8) and 39B*(7B%8), one of which you use at the beginning of your calculation function to obtain the FFT data from the buffer. These macros implement the special PV UGen behavior: if the FFT chain has “fired,” then they access the buffer(s) and continue with the rest of the calculation function; but if the FFT chain has not “fired” in the current control block, then they output a value of –1 and return (i.e., they do not allow the rest of the calculation function to proceed). This has the important consequence that although your calculation function code will look “as if” it is called once per control block, in fact your code will be executed only at the FFT frame rate. 39B*(7B%8) will take the FFT chain indicated by the first input to the UGen and create a pointer to these data called EXI. 39B*(7B%8) is for use in UGens which process 2 FFT chains and write the result back out to the first chain: it takes the FFT chain indicated by the first and second inputs to the UGen and creates pointers to the data called EXI and EXI. It should be clear that you use 39B*(7B%8) or 39B*(7B%8), but not both.

Having acquired a pointer to the data, you will of course wish to read/write that data. Before doing so, you must decide whether to process the complex-valued data as polar coordinates or Cartesian coordinates. The data in the buffer may be in

716

Dan Stowell

either format (depending on what has happened to it so far). To access the data as Cartesian values you use 6&&RPSOH[%XI S 7R&RPSOH[$S[ EXI 

and to access the data as polar values you use 6&3RODU%XI S 7R3RODU$S[ EXI 

These 2 data structures, and the 2 functions for obtaining them, are declared in FFT_UGens.h. The name S for the pointer is of course arbitrary, but it’s what we’ll use here. FFT data consist of a complex value for each frequency bin, with the number of bins related to the number of samples in the input. But in the SuperCollider context the input is real-valued data, which means that (a) the bins above the Nyquist frequency (which is half the sampling frequency) are a mirror image of the bins below, and can therefore be neglected; and (b) phase is irrelevant for the DC and Nyquist frequency bins, so these 2 bins can be represented by a single-magnitude value rather than a complex value. The end result of this is that we obtain a data structure containing a single DC value, a single Nyquist value, and a series of complex values for all the bins in between. The number of bins in between is given by the value QXPELQV, which is provided for us by 39B*(7B%8) or 39B*(7B%8). The data in a Cartesian-type struct (an 6&&RPSOH[%XI) are of the form S!GF S!ELQ>@UHDO S!ELQ>@LPDJ S!ELQ>@UHDO S!ELQ>@LPDJ  S!ELQ>QXPELQV²@UHDO S!ELQ>QXPELQV²@LPDJ S!Q\T

The data in a polar-type struct (an 6&3RODU%XI) is of the form S!GF S!ELQ>@PDJ S!ELQ>@SKDVH S!ELQ>@PDJ S!ELQ>@SKDVH  S!ELQ>QXPELQV²@PDJ S!ELQ>QXPELQV²@SKDVH S!Q\T

717

25

Writing Unit Generator Plug-ins

Note that the indexing is slightly strange: engineers commonly refer to the DC component as the “first” bin in the frequency-domain data. However in these structs, because the DC component is represented differently, ELQ>@ is actually the first nonDC bin — what would sometimes be referred to as the second bin. Similarly, keep in mind that QXPELQV represents the number of bins not including the DC or Nyquist bins. To perform a phase vocoder manipulation, simply read and write to the struct (which actually is directly in the external buffer). The buffer will then be passed down the chain to the next phase vocoder UGen. You don’t need to do anything extra to “output” the frequency-domain data. When compiling your PV UGen, you will need to compile/link against SCComplex .cpp from the main SuperCollider source, which provides the implementation of these frequency-domain data manipulations. 25.7

Practicalities 25.7.1

Debugging

Standard C++ debugging procedures can be used when developing UGens. The simplest method is to add a line into your code which prints out values of variables — you can use the standard C++ SULQWI method, which in a UGen will print text to the post window. For more power, you can launch the server process, then attach a debugger such as gdb (the GNU debugger) or Xcode’s debugger (which is actually gdb with a graphical interface) to perform tasks such as pausing the process and inspecting values of variables. On Mac, if you use the debugger to launch SuperCollider.app, remember that the local server runs in a process different from the application. You can either launch the application using the debugger and booting the internal server, or you can launch just the server (scsynth) using the debugger, which then runs as a local server. In the latter case you need to ensure your debugger launches scsynth with the correct arguments (e.g., X). When debugging a UGen that causes server crashes, you may wish to look at your system’s crash log for scsynth. The most common cause of crashes is introduced when using 57$OORF and 57)UHH — if you try to 57)UHH something that has not yet been 57$OORF’ed, or otherwise is not a pointer to the real-time memory pool, this can cause bad-access exceptions to appear in the crash log. If the crash log seems to reveal that your UGen is somehow causing crashes inside core UGens which normally behave perfectly, then check that your code does not write data outside of the expected limits: make sure you 57$OORF the right amount of space for what you’re

718

Dan Stowell

doing (for example, with arrays, check exactly which indices your code attempts to access). 25.7.2

Optimization

Optimizing code is a vast topic and often depends on the specifics of the code in question. However, we can suggest some optimization tips for writing SuperCollider UGens. The efficiency/speed of execution is usually the number-one priority, especially since a user may wish to employ many instances of the UGen simultaneously. The difference between a UGen that takes 2.5% and another that takes 1.5% CPU may seem small, but the first limits you to 40 simultaneous instances, while the second will allow up to 66; a 65% increase. Imagine doing your next live performance on a 4-year-old processor — that’s essentially the effect of the less efficient code. Avoid calls to “expensive” procedures whenever possible. For example, floatingpoint division is typically much more expensive than multiplication, so if your unit must divide values by some constant value which is stored in your struct, rewrite this so that the reciprocal of that value is stored in the struct and you can perform a multiplication rather than a division. If you want to find an integer power of 2, use bit shifting (  Q) rather than the expensive math function (SRZ  Q ). Other expensive floating-point operations are square-root finding and trigonometric operations (VLQ, FRV, WDQ, etc.). Precalculate and store such values wherever possible, rather than calculating them afresh every time the calculation function is called. As a typical example, often a filter UGen will take a user parameter (such as cutoff frequency) and use it to derive internal filter coefficients. If you store the previous value of the user parameter and use this to check whether it has changed at all — updating the coefficients only upon a change — you can improve efficiency, since often UGens are used with fixed or rarely changing parameters. One of the most important SuperCollider-specific choices is, for reading a certain input or even performing a given calculation, whether to do this at scalar/control/ audio rate. It can be helpful to allow any and all values to be updated at audio rate, but if you find that a certain update procedure is expensive and won’t usually be required to run at audio rate, it may be preferable to update only once during a calculation function. Creating multiple calculation functions, each appropriate to a certain context (e.g., to a certain combination of input rates, as demonstrated earlier), and choosing the most appropriate, can allow a lot of optimization. For example, a purely controlrate calculation can avoid the looping required for audio-rate calculation and typically produces a much simpler calculation as a result. There is a maintenance overhead in providing these alternatives, but the efficiency gains can be large. In this

719

25

Writing Unit Generator Plug-ins

tension between efficiency and code comprehensibility/reusability, you should remember the importance of adding comments to your code to clarify the flow and the design decisions you have made. In your calculation function, store values from your struct as well as input/output pointers/values as local variables, especially if referring to them multiple times. This avoids the overhead of indirection and can be optimized (by the compiler) to use registers better. Avoid 'HILQH6LPSOH&DQW$OLDV8QLW and 'HILQH'WRU&DQW$OLDV8QLW. As described earlier, 'HILQH6LPSOH&DQW$OLDV8QLW is available as an alternative to 'HILQH6LPSOH8QLW in cases where your UGen must write output before it has read from the inputs, but this can decrease cache performance. Avoid peaky CPU usage. A calculation function that does nothing for the first 99 times it’s called, then performs a mass of calculations on the 100th call, could cause audio dropouts if this spike is very large. To avoid this, “amortize” your unit’s effort by spreading the calculation out, if possible, by precalculating some values which are going to be used in that big 100th call. On Mac, Apple’s vDSP library can improve speed by vectorizing certain calculations. If you make use of this, or other platform-specific libraries, remember the considerations of platform independence. For example, use preprocessor instructions to choose between the Mac-specific code and ordinary C++ code: LI6&B'$5:,1 7KH0DFVSHFLҼFYHUVLRQRIWKHFRGH LQFOXGLQJHJY'63IXQFWLRQV HOVH 7KHJHQHULFYHUVLRQRIWKHFRGH HQGLI

is a preprocessor value set to 1 when compiling SuperCollider on Mac (this is set in the Xcode project settings). Branching like this introduces a maintenance overhead, because you need to make sure that you update both branches in parallel.

6&B'$5:,1

25.7.3

Distributing Your UGens

Sharing UGens with others contributes to the SuperCollider community and is a very cool thing to do. A SourceForge project, “sc3-plug-ins,” exists as a repository for downloadable UGen plug-ins produced by various people. You may wish to publish your work either there or separately. Remember that SuperCollider is licensed under the well-known GPL (GNU Public License) open-source license, including the plug-in API. So if you wish to distribute your plug-ins to the world, they must also be GPL-licensed. (Note: you retain

720

Dan Stowell

copyright in any code you have written. You do not have to sign away your copyright in order to GPL-license a piece of code.) Practically, this has a couple of implications: You should include a copyright notice, a copy of the GPL license text, and the source code with your distributed plug-in. • If your plug-in makes use of third-party libraries, those libraries must be available under a “GPL-compatible” copyright license. See the GNU GPL Web site for further discussion of what this means. •

25.8

Conclusion This chapter doesn’t offer an exhaustive list of all that’s possible, but it provides you with the core of what all UGen programmers need to know. If you want to delve deeper, you will find the online community to be a valuable resource for answers to questions not covered here; and the source code for existing UGens provides a wealth of useful code examples. The open-source nature of SuperCollider makes for a vibrant online developer community. Whether you are tweaking 1 of SuperCollider’s core UGens or developing something very specialized, you’ll find the exchange of ideas with SuperCollider developers can be rewarding for your own projects as well as for others and can feed into the ongoing development of SuperCollider as a uniquely powerful and flexible synthesis system.

26

Inside scsynth Ross Bencina

This chapter explores the implementation internals of scsynth, the server process of SuperCollider 3, which is written in C++. This chapter is intended to be useful to people who are interested in modifying or maintaining the scsynth source code and also to those who are interested in learning about the structure and implementation details of one of the great milestones in computer music software. By the time you’ve finished this chapter, you should have improved your understanding of how scsynth does what it does and also have gained some insight into why it is written the way it is. In this chapter sometimes we’ll simply refer to scsynth as “the server.” “The client” usually refers to sclang or any other program sending OSC commands to the server. Although the text focuses on the server’s real-time operating mode, the information presented here is equally relevant to understanding scsynth’s non-real-time mode. As always, the source code is the definitive reference and provides many interesting details which space limitations didn’t allow to be included here. Wherever possible, the data, structure, and function names used in this chapter match those in the scsynth source code. However, at the time of writing there was some inconsistency in class and structure naming. Sometimes you may find that the source file, the class name, or both may have an SC_ prefix. I have omitted such prefixes from class and function names for consistency. Also note that I have chosen to emphasize an object-oriented interpretation of scsynth using UML diagrams to illuminate the code structure, as I believe scsynth is fundamentally object-oriented, if not in an idiomatically C++ way. In many cases structs from the source code appear as classes in the diagrams. Where appropriate, I have taken the liberty to interpret inheritance where a base struct is included as the first member of a derived struct. However, I have resisted the urge to translate any other constructs (such as the psuedo member functions mentioned below). All other references to names appear here as they do in the source code. Now that formalities are completed, in the next section we set out on our journey through the scsynth implementation with a discussion of scsynth’s coding style. Following that, we consider the structure of the code which implements what I call the

722

Ross Bencina

scsynth domain model: Nodes, Groups, Graphs, GraphDefs, and their supporting infrastructure. We then go on to consider how the domain model implementation communicates with the outside world; we consider threading, interthread communications using queues, and how scsynth fulfills real-time performance constraints while executing all of the dynamic behavior offered by the domain model. The final section briefly highlights some of the fine-grained details which make scsynth one of the most efficient software synthesizers on the planet. scsynth is a fantastically elegant and interesting piece of software; I hope you get as much out of reading this chapter as I did in writing it! 26.1

Some Notes on scsynth Coding Style scsynth is coded in C++, but for the most part uses a “C++ as a better C” coding style. Most data structures are declared as plain old C structs, especially those which are accessible to unit plug-ins. Functions which in idiomatic C++ might be considered member functions are typically global functions in scsynth. These are declared with names of the form 6WUXFW7\SHB0HPEHU)XQFWLRQ1DPH 6WUXFW7\SH V>@ , where the first parameter is a pointer to the struct being operated on (the “this” pointer in a C++ class). Memory allocation is performed with custom allocators or with PDOORF , IUHH , and friends. Function pointers are often used instead of virtual functions. A number of cases of what can be considered inheritance are implemented by placing an instance of the base class (or struct) as the first member of the derived struct. There is very little explicit encapsulation of data using getter/setter methods. There are a number of pragmatic reasons to adopt this style of coding. Probably the most significant is the lack of an Application Binary Interface (ABI) for C++, which makes dynamically linking with plug-ins using C++ interfaces compilerversion-specific. The avoidance of C++ constructs also has the benefit of making all code operations visible, in turn making it easier to understand and predict the performance and real-time behavior of the code. The separation of data from operations and the explicit representation of operations as data-using function pointers promotes a style of programming in which types are composed by parameterizing structs by function pointers and auxilliary data. The use of structs instead of C++ classes makes it less complicated to place objects into raw memory. Reusing a small number of data structures for many purposes eases the burden on memory allocation by ensuring that dynamic objects belong to only a small number of size classes. Finally, being able to switch function pointers at runtime is a very powerful idiom which enables numerous optimizations, as will be seen later.

723

26.2

26

Inside scsynths

The scsynth Domain Model At the heart of scsynth is a powerful yet simple domain model which manages dynamic allocation and evaluation of unit generator graphs in real time. Graphs can be grouped into arbitrary trees whose execution and evaluation order can be dynamically modified (McCartney, 2000). In this section we explain the main behaviors and relationships between entities in the domain model. The model is presented without concern for how client communication is managed or how the system is executed within real-time constraints. These concerns are addressed in later sections. Figure 26.1 shows an implementation-level view of the significant domain entities in scsynth. Each class shown on the diagram is a C++ class or struct in the scsynth source code. SC users will recognize the concepts modeled by many of these classes. Interested readers are advised to consult the “ServerArchitecture” section of the Help files for further information about the roles of these classes and the exact operations which can be performed by them. :RUOG is the top-level class which (with the exception of a few global objects) aggregates and manages the run-time data in the server. It is created by :RUOGB1HZ when scsynth starts up. An instance of :RUOG2SWLRQV is passed to :RUOGB1HZ . It stores the configuration parameters, which are usually passed to scsynth on the command line. scsynth’s main task is to synthesize and process sound. It does this by evaluating a tree of dynamically allocated 1RGH instances (near middle-left of figure 26.1), each of which provides its own 1RGH&DOF)XQF function pointer, which is called by the server to evaluate the Node at the current time step. 1RGHP,' is an integer used by clients to identify specific Nodes in server commands (such as suspending or terminating the Node, or changing its location in the tree). There are 2 subtypes of 1RGH: *UDSK and *URXS. *UDSK is so named because it executes an optimized graph of UGens. It can be likened to a voice in a synthesizer or an “instrument” in a Music N-type audio synthesis language such as Csound. The *UDSK type implements the SuperCollider concept of a Synth. *URXS is simply a container for a linked list of 1RGH instances, and since *URXS is itself a type of 1RGH, arbitrary trees may be constructed containing any combination of *URXS and *UDSK instances; readers may recognize this as the Composite design pattern (Gamma et al., 1995). The standard 1RGH&DOF)XQF for a Group (*URXSB&DOF in 6&B*URXSFSS) simply iterates through the Group’s contained Nodes, calling each Node’s 1RGH&DOF)XQF in turn. Although most code deals with Nodes polymorphically, the 1RGHP,V*URXS field supports discriminating between Nodes of type *UDSK and of *URXS at runtime. Any node can be temporarily disabled using the QBUXQ server command, which switches NodeCalcFuncs. When a Node is switched off, a 1RGH&DOF)XQF which does

724

Ross Bencina

Figure 26.1 Class diagram of significant domain entities.

725

26

Inside scsynths

nothing is substituted for the usual one. Disabling a Group disables the whole tree under that Group. A *UDSK is an aggregate of interconnected 8QLW subclasses (also known as Unit Generators or UGens). 8QLW instances are responsible for performing primitive audio DSP operations such as mixing, filtering, and oscillator signal generation. Each *UDSK instance is carved out of a single memory block to minimize the number of expensive calls to the memory allocator. Units are efficiently allocated from the Graph’s memory block and evaluated by iterating through a linear array containing pointers to all of the Graph’s Units. Each 8QLW instance provides a 8QLW&DOF)XQF function pointer to compute samples, which affords the same kind of flexibility as 1RGH&DOF)XQF described above. For example, many Units implement a form of self-modifying code by switching their UnitCalcFuncs on the fly to execute different code paths, depending on their state. Graphs are instantiated using a *UDSK'HI (Graph Definition), which defines the structure of a class of Graphs. The *UDSK'HI type implements the SuperCollider concept of a SynthDef. A *UDSK'HI includes both data for passive representation (used on disk and as communicated from clients such as sclang), and optimized in-memory information used to efficiently instantiate and evaluate Graphs. *UDSK'HI instances store data such as memory allocation size for *UDSK instances, Unit initialization parameters, and information about the connections between Units. When a new GraphDef is loaded into the server, most of the work is done in *UDSK'HIB5HDG , which converts the stored representation to the run-time representation. Aside from allocating and initializing memory and wiring in pointers, one of the main tasks *UDSK'HIB5HDG performs is to determine which inter-Unit memory buffers will be used to pass data between Units during Graph evaluation. The stored GraphDef representation specifies an interconnected graph of named 8QLW instances with generalized information about input and output routing. This information is loaded into an in-memory array of 8QLW6SHF instances where each Unit name is resolved to a pointer to a 8QLW'HI (see below), and the Unit interconnection graph is represented by instances of ,QSXW6SHF and 2XWSXW6SHF. This interconnection graph is traversed by a graph-coloring algorithm to compute an allocation of inter-Unit memory buffers, ensuring that the minimum number of these buffers is used when evaluating the Graph. Note that the order of Unit evaluation defined by a GraphDef is not modified by scsynth. scsynth’s tree of Nodes is rooted at a Group referenced by :RUOGP7RS*URXS. :RUOG is responsible for managing the instantiation, manipulation, and evaluation of the tree of Nodes. :RUOG also manages much of the server’s global state, including the buses used to hold control and audio input and output signals (e.g., :RUOGP$XGLR%XV) and a table of 6QG%XI instances (aka Buffers) used, for example, to hold sound data loaded from disk. An instance of :RUOG is accessible to 8QLW plug-ins via 8QLWP:RUOG

726

Ross Bencina

and provides :RUOGIW, an instance of ,QWHUIDFH7DEOH, which is a table of function pointers which Units can invoke to perform operations on the World. An example of Units using World state is the ,Q and 2XW units which directly access :RUOGP$XGLR%XV to move audio data between Graphs and the global audio buses. 8QLW subclasses provide all of the signal-processing functionality of scsynth. They are defined in dynamically loaded executable “plug-ins.” When the server starts, it scans the nominated plug-in directories and loads each plug-in, calling its ORDG function; this registers all available Units in the plug-in with the World via the ,QWHUIDFH7DEOHI'HILQH8QLW function pointer. Each call to I'HILQH8QLW results in a new 8QLW'HI being created and registered with the global J8QLW'HI/LE hash table, although this process is usually simplified by calling the macros defined in 6&B,QWHUIDFH7DEOHK, such as 'HILQH6LPSOH8QLW and 'HILQH'WRU8QLW . Some server data (more of which we will see later) is kept away from Unit plug-ins in an instance of +LGGHQ:RUOG. Of significance here are +LGGHQ:RUOGP1RGH/LE, a hash table providing fast lookup of Nodes by integer ID; +LGGHQ:RUOGP*UDSK'HI/LE, a hash table of all loaded GraphDefs, which is used when a request to instantiate a new Graph is received; and +LGGHQ:RUOGP:LUH%XI6SDFH, which contains the memory used to pass data between Units during Graph evaluation. 26.3

Real-Time Implementation Structure We now turn our attention to the context in which the server is executed. This includes considerations of threading, memory allocation, and interthread communications. scsynth is a real-time system, and the implementation is significantly influenced by real-time requirements. We begin by considering what “real-time requirements” means in the context of scsynth and then explore how these requirements are met. 26.3.1

Real-Time Requirements

scsynth’s primary responsibility is to compute blocks of audio data in a timely manner in response to requests from the OS audio service. In general, the time taken to compute a block of audio must be less than the time it takes to play it. These blocks are relatively small (on the order of 2 milliseconds for current generation systems), and hence tolerances can be quite tight. Any delay in providing audio data to the OS will almost certainly result in an audible glitch. Of course, computing complex synthesized audio does not come for free and necessarily takes time. Nonetheless, it is important that the time taken to compute each block is bounded and as close to constant as possible, so that exceeding timing constraints occurs only due to the complexity or quantity of concurrently active Graphs, not to the execution of real-time unsafe operations. Such unsafe operations include

727

26

Inside scsynths

Algorithms with high or unpredictable computational complexity (for example, amortized time algorithms with poor worst-case performance) • Algorithms which intermittently perform large computations (for example, precomputing a lookup table or zeroing a large memory block at Unit startup) • Operations which block or otherwise cause a thread context switch. •

The third category includes not only explicit blocking operations, such as attempting to lock a mutex or wait on a file handle, but also operations which may block due to unknown implementation strategies, such as calling a system-level memory allocator or writing to a network socket. In general, any system call should be considered real-time unsafe, since there is no way to know whether it will acquire a lock or otherwise block the process. Put simply, no real-time unsafe operation may be performed in the execution context which computes audio data in real time (usually a thread managed by the OS audio service). Considering the above constraints alongside the dynamic behavior implied by the domain model described in the previous section and the fact that scsynth can read and write sound files on disk, allocate large blocks of memory, and communicate with clients via network sockets, you may wonder how scsynth can work at all in real time. Read on, and all will be revealed. 26.3.2

Real-Time Messaging and Threading Implementation

SuperCollider carefully avoids performing operations which may violate real-time constraints by using a combination of the following techniques: Communication to and from the real-time context is mediated by lock-free First In First Out (FIFO) queues containing executable messages • Use of a fixed-pool memory allocator which is accessed only from the real-time context • Non-real-time safe operations (when they must be performed at all) are deferred and executed asynchronously in a separate “non-real-time” thread • Algorithms which could introduce unpredictable or transient high computational load are generally avoided • Use of user-configurable nonresizable data structures. Exhaustion of such data structures typically results in scsynth operations failing. •

The first point is possibly the most important to grasp, since it defines the pervasive mechanism for synchronization and communication between non-real-time threads and the real-time context which computes audio samples. When a non-realtime thread needs to perform an operation in the real-time context, it enqueues a message which is later performed in the real-time context. Conversely, if code in the

728

Ross Bencina

real-time context needs to execute a real-time unsafe operation, it sends the message to a non-real-time thread for execution. We will revisit this topic on a number of occasions throughout the remainder of the chapter. Figure 26.2 shows another view of the scsynth implementation, this time focusing on the classes which support the real-time operation of the server. For clarity, only a few key classes from the domain model have been retained (shaded gray). Note that $XGLR'ULYHU is a base class: in the implementation different subclasses of $XGLR'ULYHU are used depending on the target OS (CoreAudio for Mac OS X, PortAudio for Windows, etc.). Figure 26.3 illustrates the run-time thread structure and the dynamic communication pathways between threads via lock-free FIFO message queues. The diagram can be interpreted as follows: thick rectangles indicate execution contexts, which are either threads or callbacks from the operating system. Cylinders indicate FIFO message queue objects. The padlock indicates a lock (mutex), and the black circle indicates a condition variable. Full arrows indicate synchronous function calls (invocation of queue-member functions), and half arrows indicate the flow of asynchronous messages across queues. The FIFO message queue mechanism will be discussed in more detail later in the chapter, but for now, note that the :ULWH method enqueues a message, 3HUIRUP executes message-specific behavior for each pending message, and )UHH cleans up after messages which have been performed. The :ULWH , 3HUIRUP , and )UHH FIFO operations can be safely invoked by separate reader and writer threads without the use of locks. Referring to figures 26.2 and 26.3, the dynamic behavior of the server can be summarized as follows: 1. One or more threads listen to network sockets to receive incoming OSC messages which contain commands for the server to process. These listening threads dynamically allocate 26&B3DFNHWV and post them to “The Engine,” using the 3URFHVV26&3DFNHW function, which results in 3HUIRUPB7R(QJLQHB0VJ (a )LIR0VJ)XQF) being posted to the P2VF3DFNHWV7R(QJLQH queue. 26&B3DFNHW instances are later freed, using )UHH26&3DFNHW (a )LIR)UHH)XQF) by way of 0VJ)LIR)UHH , via a mechanism which is described in more detail later. 2. “The Synthesis Engine,” or “Engine” for short (also sometimes referred to here as “the real-time context”), is usually a callback function implemented by a concrete $XGLR'ULYHU which is periodically called by the OS audio service to process and generate audio. The main steps relevant here are that the Engine calls 3HUIRUP on the P2VF3DFNHWV7R(QJLQH and P7R(QJLQH queues, which execute the P3HUIRUP)XQF of any messages enqueued from other threads. Messages in P2VF3DFNHWV7R(QJLQH carry 26&B3DFNHW instances which are interpreted to manipulate the Node tree, instantiate

729

26

Inside scsynths

Figure 26.2 Real-time threading and messaging implementation structure.

730

Ross Bencina

Figure 26.3 Real-time thread and queue instances and asynchronous message channels.

731

26

Inside scsynths

new Graphs, and so on. Whenever the Engine wants to perform a non-real-time safe operation, it encodes the operation in a )LIR0HVVDJH instance and posts it to the nonreal-time thread for execution via the P)URP(QJLQH queue. Results of such operations (if any) will be returned via the P7R(QJLQH queue. After processing messages from P2VF3DFNHWV7R(QJLQH, P7R(QJLQH, and any previously scheduled OSC messages in P6FKHGXOHU, the Engine performs its audio duties by arranging for real-time audio data to be copied between OS buffers and P:RUOG!P$XGLR%XV and evaluating the 1RGH tree via P:RUOG!P7RS*URXS. When the Engine has completed filling the OS audio output buffers, it calls 6LJQDO on P$XGLR6\QF and returns to the OS. 3. Before the server starts servicing OS audio requests, it creates a thread for executing real-time unsafe operations (the non-real-time or NRT thread). This thread waits on P$XGLR6\QF until it is signaled by the Engine. When the non-real-time thread wakes up, it calls )UHH and 3HUIRUP on the P)URP(QJLQH queue to perform any non-real-time safe operations which the server has posted, then processes the P7ULJJHUV, P1RGH(QGV, and P'HOHWH*UDSK'HIV queues. These queues contain notifications of server events. Performing the enqueued notification messages results in OSC messages being sent to clients referenced by 5HSO\$GGUHVV. After calling 3HUIRUP on all queues, the non-real-time thread returns to waiting on P$XGLR6\QF until it is next wakened by the Engine. Note that P$XGLR6\QF is used to ensure that the NRT thread will always wake up and process Engine requests in a timely manner. However, it may never sleep, or it may not process the queues on every Engine cycle if it is occupied with time-consuming operations. This is acceptable since the Engine assumes non-real-time operations will take as long as necessary. The description above has painted the broad strokes of the server’s real-time behavior. Zooming in to a finer level of detail reveals many interesting mechanisms which are worth the effort to explore. A number of these are discussed in the sections which follow. 26.3.2.1 Real-time memory pool allocator Memory allocations performed in the real-time context, such as allocating memory for new *UDSK instances, are made using the $OORF3RRO class. $OORF3RRO is a reimplementation of Doug Lea’s fast general-purpose memory allocator algorithm (Lea, 2000). The implementation allocates memory to clients from a large, preallocated chunk of system memory. Because $OORF3RRO is invoked only by code running in the real-time context, it doesn’t need to use locks or other mechansims to protect its state from concurrent access and hence is real-time safe. This makes it possible for the server to perform many dynamic operations in the real-time thread without needing to defer to an NRT thread to allocate memory. That said, large allocations and other memory operations which are not time-critical are performed outside the real-time context. Memory allocated with an $OORF3RRO must of course also be freed

732

Ross Bencina

into the same $OORF3RRO, and in the same execution context, which requires some care to be taken. For example, )LIR0VJ instances posted by the Engine to the NRT thread with a payload allocated by $OORF3RRO must ensure that the payload is always freed into $OORF3RRO in the real-time execution context. This can be achieved using 0VJ)LIR)UHH , which is described in the next section. 25.3.2.2 FIFO queue message passing As already mentioned, scsynth uses FIFO queues for communicating between threads. The basic concept of a FIFO queue is that you push items on one end of the queue and pop them off the other end later, possibly in a different thread. A fixedsize queue can be implemented as a circular buffer (also known as a ring buffer) with a read pointer and a write pointer: new data are placed in the queue at the write pointer, which is then advanced; when the reader detects that the queue is not empty, data are read at the read pointer and the read pointer is advanced. If there’s guaranteed to be only 1 reading thread and 1 writing thread, and you’re careful about how the pointers are updated (and take care of atomicity and memory ordering issues) then it’s possible to implement a thread-safe FIFO queue without needing to use any locks. This lock-free property makes the FIFO queue ideal for implementing realtime interthread communications in scsynth. The queues which we are most concerned with here carry a payload of message objects between threads. This is an instance of the relatively well known Command design pattern (Gamma et al., 1995). The basic idea is to encode an operation to be performed as a class or struct, and then pass it off to some other part of the system for execution. In our case the Command is a struct containing data and a pair of function pointers, one for performing the operation and another for cleaning up. We will see later that scsynth also uses a variant of this scheme in which the Command is a C++ class with virtual functions for performing an operation in multiple stages. But for now, let’s consider the basic mechanism, which involves posting )LIR0VJ instances to a queue of type 0VJ)LIR. Figure 26.2 shows that P2VF3DFNHWV7R(QJLQH, P7R(QJLQH, and P)URP(QJLQH queues carry )LIR0VJ objects. The code below shows the )LIR0VJ)XQF type and the key fields of )LIR0VJ. W\SHGHIYRLG )LIR0VJ)XQF VWUXFW)LIR0VJ  VWUXFW)LIR0VJ^  )LIR0VJ)XQFP3HUIRUP)XQF )LIR0VJ)XQFP)UHH)XQF YRLG P'DWD  `

733

26

Inside scsynths

To enqueue a message, the sender initializes a )LIR0VJ instance and passes it to 0VJ)LIR:ULWH . Each FifoMsg contains the function pointer members P3HUIRUP )XQF and P)UHH)XQF. When the receiver calls 0VJ)LIR3HUIRUP , the P3HUIRUP)XQF of each enqueued message is called with a pointer to the message as a parameter. 0VJ)LIR also maintains an additional internal pointer which keeps track of which messages have been performed by the receiver. When 0VJ)LIR)UHH is called by the sending execution context, the P)UHH)XQF is invoked on each message whose P3HUIRUP)XQF has already completed. In a moment we will see how this mechanism is used to free SequencedCommand objects allocated in the real-time context. A separate 0VJ)LIR1R)UHH class is provided for those FIFOs which don’t require this freeing mechanism, such as P7ULJJHUV, P1RGH(QGV, and P'HOHWH*UDSK'HIV. These queues carry specialized notification messages. The functionality of these queues could have been implemented by dynamically allocating payload data and sending it using )LIR0VJ instances; however, since 0VJ)LIR and 0VJ)LIR1R)UHH are templates parameterized by message type, it was probably considered more efficient to create separate specialized queues using message types large enough to hold all of the necessary data rather than invoking the allocator for each request. The )LIR0VJ mechanism is used extensively in scsynth, not only for transporting OSC message packets to the real-time engine but also for arranging for the execution of real-time unsafe operations in the NRT thread. Many server operations are implemented by the FifoMsgFuncs defined in 6&B0LVF&PGVFSS. However, a number of operations need to perform a sequence of steps alternating between the real-time context and the NRT thread. For this, the basic )LIR0VJ mechanism is extended using the 6HTXHQFHG&RPPDQG class. 26.3.2.3 SequencedCommand Unlike )LIR0VJ, which just stores two C function pointers, 6HTXHQFHG&RPPDQG is a C++ abstract base class with virtual functions for executing up to 4 stages of a process. Stage 1 and 3 execute in the real-time context, while stages 2 and 4 execute in the NRT context. The 'HOHWH function is always called in the RT context, potentially providing a fifth stage of execution. SequencedCommands are used for operations which need to perform some of their processing in the NRT context. At the time of writing, all 6HTXHQFHG&RPPDQG subclasses were defined in 6&B6HTXHQFHG&RPPDQG FSS. They are mostly concerned with the manipulation of SndBufs and GraphDefs. (See table 26.1 for a list of SequencedCommands defined at the time of writing.) To provide a concrete example of the SequencedCommand mechanism, we turn to the Help file for Buffer (aka 6QG%XI), which reads: “Buffers are stored in a single global array indexed by integers beginning with zero. Buffers may be safely allocated, loaded and freed while synthesis is running, even while unit generators are using them.” Given that a SndBuf’s sample storage can be quite large, or contain

734

Ross Bencina

Table 26.1 Subclasses of 6HTXHQFHG&RPPDQG Defined in SC_SequencedCommand.cpp Buffer Commands

BufGenCmd, BufAllocCmd, BufFreeCmd, BufCloseCmd, BufZeroCmd, BufAllocReadCmd, BufReadCmd, SC_BufReadCommand, BufWriteCmd

GraphDef Commands

LoadSynthDefCmd, RecvSynthDefCmd, LoadSynthDefDirCmd

Miscellaneous

AudioQuitCmd, AudioStatusCmd, SyncCmd, NotifyCmd, SendFailureCmd, SendReplyCmd, AsyncPlugInCmd

sample data read from disk, it is clear that it needs to be allocated and initialized in the NRT thread. We now describe how the SequencedCommand mechanism is used to implement this behavior. To begin, it is important to note that the 6QG%XI class is a relatively lightweight data structure which mainly contains metadata such as the sample rate, channel count, and number of frames of the stored audio data. The actual sample data are stored in a dynamically allocated floating-point array pointed to by 6QG%XIGDWD. In the explanation which follows, we draw a distinction between instance data of 6QG%XI and the sample data array pointed to by 6QG%XIGDWD. In contrast to the client-oriented worldview presented in the Help file, :RUOG actually maintains 2 separate arrays of 6QG%XI instances: P6QG%XIV and P6QG%XIV1RQ 5HDO7LPH0LUURU. Each is always in a consistent state but is accessed or modified only in its own context: P6QG%XIV in the RT context via :RUOGB*HW%XI and P6QG%XIV1RQ 5HDO7LPH0LUURU in the NRT thread via :RUOGB*HW157%XI . On each iteration the engine performs messages in P7R(QJLQH and then evaluates the 1RGH tree to generate sound. Any changes to P6QG%XIV made when calling P7R(QJLQH!3HUIRUP are picked up by dependent Units when their UnitCalcFunc is called. The code may reallocate an existing SndBuf’s sample data array. It is important that the old sample data array is not freed until we can be certain no 8QLW is using it. This is achieved by deferring freeing the old sample data array until after the new one is installed into the RT context’s P6QG%XIV array. This process is summarized in figure 26.4. The details of the individual steps are described below. We now consider the steps performed at each stage of the execution of%XI$OORF 5HDG&PG, a subclass of 6HTXHQFHG&RPPDQG, beginning with the arrival of an 26&B3DFNHW in the real-time context. These stages are depicted in 4 sequence diagrams, figures 26.5 through 26.8. The exact function parameters have been simplified from those in the source code, and only the main code paths are indicated to aid understanding. The OSC message to request allocation of a Buffer filled with data from a sound file is as follows: EBDOORF5HDGEXIQXPSDWKVWDUW)UDPHQXP)UDPHV

735

26

Inside scsynths

Figure 26.4 Overview of multithreaded processing of the EBDOORF5HDG command.

Stage 1 (see figure 26.5): The real-time context processes an OSC packet containing the EBDOORF5HDG message. The OSC dispatch mechanism looks up the correct function pointer to invoke from J&PG/LEUDU\, in this case PHWKBEBDOORF5HDG . PHWKBEBDOORF5HDG calls &DOO6HTXHQFHG&RPPDQG to instantiate a new %XI$OORF 5HDG&PG instance (a subclass of 6HTXHQFHG&RPPDQG) which we will call FPG. &DOO 6HTXHQFHG&RPPDQG calls FPG!,QLW , which unpacks the parameters from the OSC packet and then calls FPG!&DOO1H[W6WDJH , which in turn invokes FPG!6WDJH , which in the case of %XI$OORF5HDG&PG does nothing. It then enqueues FPG to the NRT thread, using 6HQG0HVVDJH)URP(QJLQH with 'R6HTXHQFHG&RPPDQG as the )LIR0VJ )XQF. Stage 2 (see figure 26.6): Some time later, the P)URP(QJLQH FIFO is processed in the NRT thread. The )LIR0VJ containing our FPG is processed, which results in FPG!6WDJH being called via 'R6HTXHQFHG&RPPDQG and FPG!&DOO1H[W6WDJH . cPG!6WDJH does most of the work: first it calls :RUOGB*HW157%XI , which retrieves a pointer to the NRT copy of the 6QG%XI record for FPG!P%XI,QGH[. Then it opens the sound file and seeks to the appropriate position. Assuming no errors have occurred, the pointer to the old sample data array is saved in FPG!P)UHH'DWD so it can be freed later. Then DOORF%XI is called to update the 6QG%XI with the new file information and to allocate a new sample data array. The data are read from the file into the sample data array and the file is closed. A shallow copy of the NRT SndBuf is saved in FPG!P6QG%XI. Finally, FPG!&DOO1H[W6WDJH enqueues the FPG with the real-time context. Stage 3 (see figure 26.7): Similarly to stage 2, only this time in the real-time context, FPG!6WDJH is called via 'R6HTXHQFHG&RPPDQG and FPG!&DOO1H[W6WDJH . A pointer to the real-time copy of the 6QG%XI for index FPG!P%XI,QGH[ is retrieved

736

Ross Bencina

Figure 26.5 Stage 1 of processing the EBDOORF5HDGcommand in the real-time context.

Figure 26.6 Stage 2 of processing the EBDOORF5HDG command in the non-real-time (NRT) context.

737

26

Inside scsynths

Figure 26.7 Stage 3 of processing the EBDOORF5HDG command in the real-time context.

using :RUOGB*HW%XI FPG!P%XI,QGH[ , and the 6QG%XI instance data initialized in stage 2 is shallow copied into it from FPG!P6QG%XI. At this stage the sample data array which was allocated and loaded in stage 2 is now available to Units calling :RUOGB*HW%XI . FPG is then sent back to the non-real-time thread. Stage 4 (see figure 26.8): Once again, back in the non-real-time thread, FPG!6WDJH is invoked, which frees the old sample data array which was stored into FPG!P)UHH'DWD in stage 2. Then the 6HQG'RQH routine is invoked, which sends an OSC notification message back to the client who initiated the Buffer allocation. Finally, FPG is enqueued back to the real-time context with the )UHH6HTXHQFHG&RPPDQG )LIR0VJ)XQF, which will cause FPG to be freed, returning its memory to the real-time $OORF3RRO. 26.3.2.4 Processing and dispatching OSC messages The 3URFHVV26&3DFNHW function provides a mechanism for injecting OSC messages into the real-time context for execution. It makes use of P'ULYHU/RFN to ensure that only 1 thread is writing to the P2VF3DFNHWV7R(QJLQH queue at any time (this could occur, for example, when multiple socket listeners are active). To inject an OSC packet using 3URFHVV26&3DFNHW , the caller allocates a memory block using PDOORF , fills it with an OSC packet (for example, by reading from a network socket), and then calls 3URFHVV26&3DFNHW . 3URFHVV26&3DFNHW takes care of enqueuing the packet to the P2VF3DFNHWV7R(QJLQH queue and deleting packets, using IUHH , once they are no longer needed.

738

Ross Bencina

Figure 26.8 Stage 4 of processing the EBDOORF5HDG command in the non-real-time (NRT) context.

Once the real-time context processes OSC packets, they are usually freed using the 0VJ)LIR message-freeing mechanism; however, packets whose time-stamp values are in the future are stored in the P6FKHGXOHU 3ULRULW\4XHXH for later execution. Once a scheduled packet has been processed, it is sent to the NRT thread to be freed. scsynth dispatches OSC commands by looking up the 6&B&RPPDQG)XQF associated with a given OSC address pattern. At startup 6&B0LVF&PGVFSS wraps these functions in /LE&PG objects and stores them into both the J&PG/LE hash table and J&PG$UUD\ array. OSC commands sent to the server may be strings or special OSC messages with a 4-byte address pattern in which the low byte is an integer message index. Command strings are compatible with any OSC client, whereas the integer command indices are more efficient but don’t strictly conform to the OSC specification. When integer command indices are received, 3HUIRUP26&0HVVDJH looks up the appropriate 6&B&RPPDQG)XQF in the J&PG$UUD\ array; otherwise it consults the J&PG/LE hash table. The P7ULJJHUV , P1RGH(QGV , and P'HOHWH*UDSK'HIV FIFOs are used by the realtime context to enqueue notifications which are translated into OSC messages in the NRT thread and are sent to the appropriate reply address by invoking 5HSO\$GGUHVVP5HSO\)XQF. 26.3.2.5 Fixed-size data structures In real-time systems a common way to avoid the potential real-time unsafe operation of reallocating memory (which may include the cost of making the allocation and of

739

26

Inside scsynths

copying all of the data) is simply to allocate a “large enough” block of memory in the first place and have operations fail if no more space is available. This fixed-size allocation strategy is adopted in a number of places in scsynth, including the size of • • • •

FIFO queues which interconnect different threads P$OORF3RRO (the real-time context’s memory allocator) The P6FKHGXOHU priority queue for scheduling OSC packets into the future The P1RGH/LE hash table, which is used to map integer 1RGH IDs to 1RGH pointers.

In the case of P1RGH/LE the size of the table determines the maximum number of the server can accommodate and the speed of 1RGH lookup as P1RGH/LE becomes full. The sizes of many of these fixed-size data structures are configurable in :RUOG2SWLRQV (in general, by command line parameters), the idea being that the default values are usually sufficient, but if your usage of scsynth causes any of the default limits to be exceeded, you can relaunch the server with larger sizes as necessary. 1RGHV

26.4

Low-Level Mechanisms As may already be apparent, scsynth gains much of its power from efficient implementation mechanisms. Some of these fall into the category of low-bounded complexity methods which contribute to the real-time capabilities of the server, while others are more like clever optimizations which help the server to run faster. Of course the whole server is implemented efficiently, so looking at the source code will reveal many more optimizations than can be discussed here; however, a number of those which I have found interesting are briefly noted below. As always, consult the source code for more details. The Str4 string data type consists of a string of 32-bit integers, each containing 4 chars. Aside from being the same format that OSC uses, the implementation improves the efficiency of comparison and other string operations by being able to process 4 chars at once. • Hash tables in scsynth are implemented using open addressing with linear probing for collision resolution. Although these tables don’t guarantee constant time performance in the worst case, when combined with a good hashing function (Wang, 2007) they typically provide close to constant performance so long as they don’t get too full. • One optimization to hashing used in a number of places in the source code is that the hash value for each item (such as a 1RGH) is cached in the item. This improves performance when resolving collisions during item lookup. • The :RUOG uses a “touched” mechansim which Units and the AudioDriver can use to determine whether audio or control buses have been filled during a control cycle: •

740

Ross Bencina

maintains the P%XI&RXQWHU, which is incremented at each control cycle. When a Unit writes to a bus, it sets the corresponding touched field (for example, in the P$XGLR%XV7RXFKHG array for audio buses) to P%XI&RXQWHU. Readers can then check the touched field to determine whether the bus contains data from the current control cycle. If not, the data doesn’t need to be copied and zeros can be used instead. • Delay lines typically output zeros until the delay time reaches the first input sample. One way to handle this is to zero the internal delay storage when the delay is created or reset. The delay unit generators in scsynth (see 'HOD\8*HQVFSS) avoid this time-consuming (and hence real-time unsafe) operation by using a separate UnitCalcFunc during the startup phase. For example, %XI'HOD\1BQH[WB] outputs zeros for the first EXI6DPSOHV samples, at which point the UnitCalcFunc is switched to %XI'HOD\1BQH[W , which outputs the usual delayed samples. • For rate-polymorphic units, the dynamic nature of UnitCalcFuncs is used to select functions specialized to the rate type of the Unit’s parameters. For example, %LQDU\2S8JHQVFSS defines UnitCalcFuncs which implement all binary operations in separate versions for each rate type. For example, there are separate functions for adding an audio vector to a constant, DGGBDL , and adding 2 audio vectors, DGGBDD . When the binary-op 8QLW constructor %LQDU\2S8*HQB&WRU is called, it calls &KRRVH1RUPDO)XQF to select among the available UnitCalcFuncs based on the rate of its inputs. :RUOG

This concludes our little journey through the wonderful gem that is scsynth. I invite you to explore the source code yourself; it has much to offer, and it’s free! References Gamma, E., R. Helm, R. Johnson, and J. Vlissides. 1995. Design Patterns: Elements of Reusable Design. Reading, MA: Addison-Wesley. Lea, D. 2000. “A Memory Allocator,” (accessed January 9, 2008). McCartney, J. 2002. “Rethinking the Computer Music Language: SuperCollider.” Computer Music Journal, 26(4): 61–68. Wang, T. 1997. “Integer Hash Function,” (accessed January 9, 2008).

Subject Index

This index includes topics from the main body of the text. Ubiquitous topics have been limited to principal references. For messages and classes from the SC language, see the code index. For definitions of terms, see the syntax appendix. 12-Tone Matrix, 34–35 Abstraction, 210–211. See also chapter 7 Additive Synthesis, 3, 6, 34–37, 128 AIFF, 25, 195, 254, 483–485 Algorithm (algorithmic), 122, 420, 460, 560 composition, 599 inside a method, 161–162 as pattern, 607–608 for pitch extraction, 441–442 synthesis, 385, 390, 644, 653 Ambient Lights (project), 118 Ambisonics, 424–425 Analysis FFT, 431 real time, 440–446 signal, 61, 65 UGens, 122 Arduino, 120–124 Arguments, 6–10, 132–133, 148–149 Array, 11–14, 23, 28–36, 56–57 indexing, 238–240 literal, 742–744 nested, 89 ASCII, 115, 121, 128, 165, 362 Association, 163, 744 Audio rate, 17, 42, 56, 196 Balancing enclosures, 12 Beat Tracking. See Machine listening

Binary, 65, 133–134 numbers, 640, 642 operators, 12 Binaural, 420–422, 560, 582–586. See also chapter 19 Bipolar, 21, 42–46, 57, 65 Boolean, 31–33, 65 BPF (Band pass filter). See Filter Buffer, 24–29, 61, 76, 151, 184, 200, 205, 367–372, 480–484, 710, 725, 731–734, 737 Bus, 25–36, 43, 57–60, 80, 538, 550 Byte Code, 146–147, 676–679 C++, 55, 120, 128, 178, 240, 357, 483, 578, 659, 697–704, 712–723, 741–742 Carrier (phase modulation), 15–16, 20, 45 Cents, 508–509 Char, 287, 742 Class (classes), 56, 128–130, 168–172 as object models, 241–243 tree, 173 writing, 694–695 Clock (class), 83, 87, 101, 219, 228 AppClock, 83, 234, 246, 282, 626 SystemClock, 67, 83–84, 549, 626 TempoClock, 83–84, 197–202, 645 Cloud (CloudGenerator), 258, 261, 478– 480

746

Subject Index

Cocoa, 349, 356, 375 CocoaDocument (see Document) Coding conventions, 659–660 networked live, 230 scsynth style, 722 Collection, 12, 14, 28–29, 60, 87, 115, 118, 128, 132, 134, 152, 162–165 Comb (N, L, C), 61, 76, 80 Combinatorics, 230 Comments, 10–11, 52, 719 Compilation (compiler), 146, 659–660, 664–666 Composition. See chapter 3 DAW style, 53, 81, 93 object oriented (see chapter 18) Compression, 62 Conductor. See Patterns, conductor Constraints. See chapter 23 Control rate, 17, 26, 56, 79–80, 196 ControlSpec, 277, 279 Convolution, 417 CPU (usage), 72–76, 264, 401, 718–719 Crucial library. See Libraries, crucial Csound, 61, 723 Cue Players, 91 DAW (Digital audio workstation) Composition (see Composition, DAW) DC (offset), 716–717 Debugging, 48, 55, 62, 108, 325, 361, 717 Decorrelation, 428–436 Delay, 61–62, 76, 79–80, 415–417 Devices, external. See External devices Dialects, 635–637. See also chapter 23 Dialogue (windows), 99, 151, 302 Dictionary, 139, 141, 163, 165 Distortion, 62, 494 Document Emacs, 373 OS X GUI (Cocoa), 299–303 Dot (receiver dot message), 10–11, 114, 130 Drag and Drop, 288 Emacs (scel), 355–357, 366–374 Encapsulation, 557–564

Enclosures, 12 Envelope, 18, 25, 47, 99, 343, 345, 414 Environment, 55, 120–124, 166–167, 648 variables, 25, 41, 139 Evaluation (of code), 4, 7, 146 Event, 180–182. See also Patterns; chapter 6 note (keys), 184–189 as object models, 241–243 PatternProxy, 220–221 protoEvent, 193–197, 202–205, 603–605 streams, 220–222, 225–230 triggering, 55 Extensions. See Libraries External devices. See chapter 4 FFT, 440–442 Filter, 5, 17, 61, 68, 80 BPF, 122, 212 HPF, 80, 122 Klank, 36, 38, 50 Lag (Lag2), 62, 69, 185, 212 LeakDC, 402 LPF, 80, 397, 429, 692–693 Median, 122 Ringz, 231–232, 346, 353 RLPF, 3, 345 Flange, 695, 698, 703 Float (floating-point), 11, 239 Flow control, 160–162 FlowLayout, 285 Fourier, 357, 417 Frequency modulation (FM). See Modulation, frequency FreeVerb, 68–70, 417, 419 Function, 11–14, 60, 143–144, 147 FunctionDef, 671 iterating, 152 return, 130–131, 144 Garbage collection, 659–660, 684–685 Gate, 18, 28, 151 Gestures, 97 Granular synthesis, 64, 80, 197, 258, 432, 465–469. See also Microsound client-side, 432–433 server side, 480–483

747

Subject Index

sound files, 489–490 wave sets, 490–500 Grouping. See Precedence GUI (Graphical user interface). See also Platforms; chapters 9–12 cross-platform, 298–299 dynamically generated, 295–297 Emacs (see chapter 12) JSCUserView, 319–321 OS X, 274–276 SCUser, 291–294 static (singleton), 298 SwingOSC (see chapter 10) tuning, 529 windows, 349 GVerb, 68–70, 417, 419 Harmonic spectrum. See Spectrum, harmonic series HID (Human Interface Devices), 105–111 Linux, 365 HierSch, 644–647 History, 230–235, 243 HPF (High pass filter). See Filter Human Interface Devices. See HID IdentityDictionary, 163, 165–166, 183 If (statements). See Flow control Inharmonic spectrum. See Spectrum, inharmonic Inheritance, 129, 168–171 Instance methods, 130, 137, 139 Instance variables. See Variables, instance Interpolation, 56, 65, 68, 74, 76, 79–80 Interpreter, 180, 182, 205, 208–209, 240, 246, 307, 679–685 variables, 140–141 Introspection. See Linux, introspection iPhone, 633 Iteration, 28–31, 152–153 ixiQuarks, 614–619, 624–628 JACK. See Linux, JACK Japan. See chapter 22 Java, 128–309, 315–319, 326–329 JITLib (Just In Time), 102, 480, 603, 648

JSCUserView. See GUI Juggling, 395 Key Tracking. See Machine listening Keyboard and Mouse, 286–287 Keywords, 16, 132, 171, 741 Klank. See Filter Lag (Lag2), 62, 69, 185, 212 LazyEnvir, 211, 215, 646 LeakDC. See Filter Libraries C, 659 chucklib, 603–607 crucial, 303 dewdrop_lib, 589–611 extensions, 55, 62, 79, 303, 546, 572 Linux, 359–360 platform specific, 719–720 Windows (platform), 351 quarks, 615 Linear. See Interpolation Linux, 3–4, 11. See also chapter 12 ALSA, 363–365 introspection, 371–372 JACK, 362–363 Live performance. See chapter 20 ListPattern, 141 Literals, 129–130 Localization, 385 Logical expressions, 33. See also ==, !=, >, =,
View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF