Escolar Documentos
Profissional Documentos
Cultura Documentos
edited
by
Scott
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
Beginners Tutorial 3
David Cottle
The Unit Generator 55
Joshua Parmenter
Composition with SuperCollider 81
Scott Wilson and Julio dEscrivn
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
271
9
10
11
12
Practical Applications
13
14
15
16
17
18
21
22
23
355
573
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
659
741
Foreword
James McCartney
Why use a computer programming language for composing music? Specically, 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 innitely long pieces, innite variations of
structure or surface detail, algorithmic mass production of synthesis voices, sonication 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 difcult 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, xing bugs, and, nally, 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.
James McCartney
xi
Foreword
and telescope observation planning. On the oors below my ofce 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 didnt 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 t 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 difcult one to make. But nancially it was looking like I would need a real job soon, anyway. I was also worried
that the period of self-employment on my rsum 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 trafc. 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. Were 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-specic 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 rst 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 youre programming
until its too late and youre 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, its 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. Well 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 arent designed to be read in any particular
xiv
order, so proceed with those of particular interest and relevance to you and your
pursuits. Naturally we have referred to other chapters for clarication 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 whod 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 sonication, 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 specically
in chapter 23, Dialects, Constraints, and Systems-Within-Systems). This sort of
intellectual pursuit has been an important part of SuperColliders development; SC
is a language that self-consciously aims for good design, and to allow and encourage
elegance, and even beauty, in the users code. Although this might seem a little abstract at rst, 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 le 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, theyll 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 arent 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 youre new to SuperCollider, dont
worry about this at all; chapter 1 will start you on the righteous path, and youll
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 (<http://supercolliderbook
.net>), 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 nd 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 correctthe ongoing development and improvement of environments such as SuperCollider are a big part of what makes them so exciting
weve 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 youre 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
youre not getting any sound, remember to check if youve left the monitors off or
xvi
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 couldnt 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.
Were sure youll have fun as you explore this compendium, and were also sure
youll be inspired to some fantastic art and science as you go. Enjoy exploring the
SuperCollider world rst charted by James McCartney but since expanded immeasurably by everyone who partakes in this innitely exible open source project.
Primary Web Resources
Main community home page: <http://supercollider.sourceforge.net>
Application download and project site: <http://sourceforge.net/projects/
supercollider>
James McCartneys home page: <http://www.audiosynth.com>
The venerable swiki site: <http://swiki.hfbk-hamburg.de:8888/MusicTechnology/6>
Acknowledgments
We owe a debt of gratitude to the chapter contributors and to the wider SuperCollider
community, who have supported this project. SCs 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 were 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, weve been working on this project for 2 years
to bring the nal 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 weve sent each other.
For many months, in the heat of the project, David modied 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.1
Introduction
The actual process of composing, and deciding how to go about it, can be one of
the most difcult things about using SuperCollider. People often nd 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 doesnt present the user with a single preferred way of working. This
can be confusing, but its an inevitable side effect of the exibility 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 ones 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 cant hope to be anywhere near exhaustive, as
the topic of the chapter is huge and in some senses encompasses all aspects of SC.
Thus well 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
Since environment variables do not have the limited scope of normal variables,
well use them in this chapter for creating simple examples. Keep in mind, however,
that in the nal version of a piece there may be good reasons for structuring your
code differently.
3.2
83
The rst 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
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 well see some GUI code in a few of the examples in this chapter, we
wont 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 arent
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 weve 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 theres only one each of SystemClock and AppClock. By varying
84
a TempoClocks tempo (in beats per second), you can change the speed. Heres 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 le for
more details.)
Now lets 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 youd 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 well explore below.
Now heres 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&ORFNVFKHGU
85
)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.2 is a similar example with a simple GUI control. This time well use a
7DVN, which you may remember from chapter 1. A Task works almost the same way
86
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 :LQGRZQHZ7DVN([DPSOH5HFWIURQW
ZYLHZGHFRUDWRU )ORZ/D\RXWZYLHZERXQGV
%XWWRQQHZZ5HFWVWDWHVB>>3OD\5HVXPH&RORUEODFN
&RORUFOHDU@@
DFWLRQB^WUHVXPH`
%XWWRQQHZZ5HFWVWDWHVB>>3DXVH&RORUEODFN&RORUFOHDU@@
DFWLRQB^WSDXVH`
%XWWRQQHZZ5HFWVWDWHVB>>)LQLVK&RORUEODFN&RORUFOHDU@@
DFWLRQB^
WVWRS
[UHOHDVH
ZFORVH
`
Figure 3.2
Using Task so you can pause the sequence.
87
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 xed scheduling and waiting for
a trigger to continue. The trigger neednt 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 xed 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
88
U 5RXWLQH^
F 7HPSR&ORFNQHZPDNHD7HPSR&ORFN
VWDUWD
ZREEO\
ORRS
W 7DVN^
ORRS^
[UHOHDVH
[ 6\QWK?GHIDXOW>IUHTPLGLFSVDPS@
ZDLW
[UHOHDVH
[ 6\QWK?GHIDXOW>IUHTPLGLFSVDPS@
UUDQGZDLWUDQGRPZDLWIURPWRVHFRQGV
`
`FXVHWKH7HPSR&ORFNWRSOD\WKLV7DVN
WVWDUW
QLO\LHOG
QRZDGGVRPHQRWHV
\ 6\QWK?GHIDXOW>IUHTPLGLFSVDPS@
QLO\LHOG
\UHOHDVH
\ 6\QWK?GHIDXOW>IUHTPLGLFSVDPS@
FWHPSR GRXEOHWLPH
QLO\LHOG
WVWRS\UHOHDVH[UHOHDVHVWRSWKH7DVNDQG6\QWKV
`
UQH[WVWDUWORRS
UQH[WILUVWQRWH
UQH[WVHFRQGQRWHORRSJRHV
GRXEOHWLPH
UQH[WVWRSORRSDQGIDGH
Figure 3.3
Nesting Tasks inside Routines.
89
UDQGRPQRWHVIURPO\GLDQEVFDOH
S 3[UDQG>@LQIDV6WUHDP
RUGHUHGVHTXHQFHRIGXUDWLRQV
T 3VHT>@LQIDV6WUHDP
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, its even possible to do this while the 7DVN is playing. (See gure 3.5.) Taking advantage of
polymorphism in ways like this can provide great exibility, 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 youll 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 didnt 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 le, well just refer you there if youd like to explore further. In any
case, if you nd over time that you prefer to work in messaging style rather than
object style, you may nd 6FRUH useful. Figure 3.6 provides a short example. Score
also provides some handy functionality for non-real-time synthesis (see chapter 18).
90
S DFRQVWDQWQRWH
T 3VHT>@LQIDV6WUHDPRUGHUHGVHTXHQFHRIGXUDWLRQV
W 7DVN^
ORRS^
[UHOHDVH
[ 6\QWK?GHIDXOW>IUHTSYDOXHPLGLFSV@
TYDOXHZDLW
`
`
WVWDUW
QRZFKDQJHS
S 3VHT>@LQIDV6WUHDPWRD3DWWHUQGRUHPL
S ^UUDQG`WRD)XQFWLRQUDQGRPQRWHVIURPD
FKURPDWLFRFWDYH
WVWRS[UHOHDVH
Figure 3.5
Thanks to polymorphism, we can substitute objects that understand the same message.
6\QWK'HI6FRUH6LQH^DUJIUHT
2XWDU
6LQ2VFDUIUHT
/LQHNUGRQH$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
KHUH
VDV\QWKGHIWKDWDOORZVXVWRSOD\IURPDEXIIHUZLWKDIDGHRXW
6\QWK'HISOD\EXI^DUJRXW EXIJDWH
2XWDURXW
3OD\%XIDUEXI%XI5DWH6FDOHNUEXIORRS
/LQHQNUJDWHGRQH$FWLRQUHOHDVHV\QWKZKHQIDGHGRQH
`DGG
ORDGDOOWKHSDWKVLQWKHVRXQGVIROGHULQWREXIIHUV
aVRPH6RXQGV VRXQGV
SDWK0DWFKFROOHFW^_SDWK_%XIIHUUHDGVSDWK`
QRZKHUH
VWKHVFRUHVRWRVSHDN
H[HFXWHWKHVHRQHOLQHDWDWLPH
aQRZ3OD\LQJ 6\QWKSOD\EXI>EXIaVRPH6RXQGV>@@
aQRZ3OD\LQJUHOHDVHaQRZ3OD\LQJ 6\QWKSOD\EXI>EXIaVRPH6RXQGV>@@
aQRZ3OD\LQJUHOHDVHaQRZ3OD\LQJ 6\QWKSOD\EXI>EXIaVRPH6RXQGV>@@
aQRZ3OD\LQJUHOHDVH
IUHHWKHEXIIHUPHPRU\
aVRPH6RXQGV%XIIHUHGGRBIUHH
Figure 3.7
Executing one line at a time.
Buffers play method is really just a convenience method, though, and well probably want to do something fancier, such as fade in or out. Figure 3.7 presents an
92
6\QWK'HISOD\EXI^DUJRXW EXIJDWH
2XWDURXW
3OD\%XIDUEXI%XI5DWH6FDOHNUEXIORRS
/LQHQNUJDWHGRQH$FWLRQ
ZLWK
GRQH$FWLRQ
ZHUHOHDVHV\QWKZKHQIDGHLVGRQH
`DGG
aVRPH6RXQGV VRXQGV
SDWK0DWFKFROOHFW^_SDWK_%XIIHUUHDGVSDWK`
Q DFRXQWHU
KHUH
VRXU*8,FRGH
Z :LQGRZQHZ6LPSOH&XH3OD\HU5HFWIURQW
ZYLHZGHFRUDWRU )ORZ/D\RXWZYLHZERXQGV
WKLVZLOOSOD\HDFKFXHLQWXUQ
%XWWRQQHZZ5HFWVWDWHVB>>3OD\&XH&RORUEODFN
&RORUFOHDU@@DFWLRQB^
LIQaVRPH6RXQGVVL]H^
LIQ ^aQRZ3OD\LQJUHOHDVH`
aQRZ3OD\LQJ 6\QWKSOD\EXI>EXIaVRPH6RXQGV>Q@@Q Q
`
`
WKLVVHWVWKHFRXQWHUWRWKHILUVWFXH
%XWWRQQHZZ5HFWVWDWHVB>>6WRS5HVHW&RORUEODFN
&RORUFOHDU@@DFWLRQB^Q aQRZ3OD\LQJUHOHDVH`
IUHHWKHEXIIHUVZKHQWKHZLQGRZLVFORVHG
ZRQ&ORVH ^aVRPH6RXQGVGRBIUHH`
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 gure 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 les 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
third-party extension classes that do things such as automating the required housekeeping (e.g., Fredrik Olofssons 5HG'LVN,Q6DPSOHU).
The previous examples deal with mono les. For multichannel les (stereo being
the most common case) it is simplest to deal with interleaved les.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
Recording
At some point youre probably going to want to record SCs 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 le format.
To illustrate this, lets make a sound by creating an effect that responds in an idiosyncratic way to the amplitude of an input le and then record the result. You may
not nd 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 gure 3.10.)
After executing this, you should have a sound le in SCs recordings folder (see the
doc for platform-specic 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 gure 3.11).
The above example uses the default recording options. Using the methods
SUHSDUH)RU5HFRUGSDWK, 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
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_
%XIIHUUHDGVSDWK``QRZDOORXUFXHILOHVDUHVLWWLQJLQWKHLUEXIIHUV
aEXIIHUHG&XHV>@KHUHLVFXH
VHHLWLQWKHSRVWZLQGRZ
3RVWaEXIIHUHG&XHV>@
SOD\WKHPDOOLQD*URXSXVLQJRXUSUHYLRXVV\QWKGHI
ZHXVHELQGKHUHWRHQVXUHWKH\VWDUWVLPXOWDQHRXVO\
VELQG^
aQRZ3OD\LQJ *URXSQHZVDJURXSWRSXWDOOWKHFKDQQHOV\QWKVLQ
aEXIIHUHG&XHV>@GR^_FXH_6\QWKSOD\EXI>EXIFXH@aQRZ3OD\LQJ`
`
IDGHWKHPRXWWRJHWKHUE\VHQGLQJDUHOHDVHPHVVDJHWRWKHJURXS
aQRZ3OD\LQJUHOHDVH
Figure 3.9
Gathering up les for multichannel cues.
95
VERRWPDNHVXUHWKHVHUYHULVUXQQLQJ
ILUVWHYDOXDWHWKLVVHFWLRQ
E %XIIHUUHDGVVRXQGVDZONZDYDVRXUFH
VSUHSDUH)RU5HFRUGSUHSDUHWKHVHUYHUWRUHFRUG\RXPXVWGRWKLVILUVW
VLPXOWDQHRXVO\VWDUWWKHSURFHVVLQJDQGUHFRUGLQJ
VELQG^
KHUH
VRXUIXQN\HIIHFW
[ ^YDUFROXPELDDPS
FROXPELD 3OD\%XIDUEORRS
DPS $PSOLWXGHDUFROXPELD
VWLFN\
DPSIROORZHU
2XWDU5HVRQ]DUFROXPELDDPSILOWHUIUHTIROORZVDPS
`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
record as 32-bit oating-point values. This has the advantage of tremendous dynamic range, which means you dont have to worry about clipping and can normalize later, but its not compatible with all audio software.
VUHF6DPSOH)RUPDWBLQW
More elaborate recording can be realized, of course, by using the 'LVN2XW UGen.
Servers 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
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[ILOOQ^_L_5HVRQ]DU'XVWDUI
L`
QUHFLSURFDOVFDOHWRHQVXUHQRFOLSSLQJ
`SOD\
This version has an equivalent result, but weve 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. Weve
97
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 exible than if you think in terms of
exact implementations. Now it happens that the example above is hardly shorter
than the rst, but look what we can do with it:
I
Q
^
0L[ILOOQ^_L_5HVRQ]DU'XVWDUI
L`
QUHFLSURFDOVFDOHWRHQVXUHQRFOLSSLQJ
`SOD\
By changing I and Q were 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 exible, its shorter;
and because of that, its much easier to understand. Its 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 gure 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 computers 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
I
Q QXPEHURIUHVRQDWRUV
W $UUD\ILOOQ^_L_
^
5HVRQ]DU'XVWDUI
L
QUHFLSURFDOVFDOHWRHQVXUHQRFOLSSLQJ
`SOD\
`
QRZPDNHD*8,
DVFUROOLQJZLQGRZVRZHGRQ
WUXQRXWRIVSDFH
Z :LQGRZQHZ%XWWRQV5HFWVFUROOWUXH
ZYLHZGHFRUDWRU )ORZ/D\RXWQHZZYLHZERXQGVDXWROD\RXWWKHZLGJHWV
QGR^_L_
%XWWRQQHZZ5HFWVWDWHVB>
>)UHTI
L2Q&RORUEODFN&RORUZKLWH@
>)UHTI
L2II&RORUZKLWH&RORUEODFN@
@
DFWLRQB^DUJEXWW
W>L@UXQEXWWYDOXH
`
`
ZIURQW
Figure 3.12
A variable number of resonators with an automatically created GUI.
99
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 le 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
Lets 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 le we opened with our le 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%XIIHUUHDGVVRXQG3DWK`
`
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
rst sound stored in our Buffer list:
aEXIIHUV>@
Now that we have our sound in a Buffer, lets try some basic manipulations. First,
lets just listen to the sound to verify that it is there:
aEXIIHUV>@SOD\
Now, lets 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
EXIIHUSOD\HUZLWKGRQHDFWLRQDQGFRQWURORIHQYHORSHDQGSDQQLQJ
6\QWK'HI?VDPSOH3OD\HU^DUJRXW EXI
UDWH DW UHO SRV S6SHHG OHY
YDUVDPSOHSDQ7DPSDX[
VDPSOH 3OD\%XIDUEXIUDWH
%XI5DWH6FDOHNUEXI
SDQ7 )6LQ2VFNUS6SHHG
DPS (QY*HQDU(QYSHUFDWUHOOHYGRQH$FWLRQ
2XWDURXW3DQDUVDPSOHSDQ7DPS
`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).
Well need this below. Lets 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 dened earlier we could do this:
6\QWK?VDPSOH3OD\HU>?RXW?EXIQXPaEXIIHUVFKRRVH?UHO@
101
DURXWLQHIRUFUHDWLQJDULWDUGDQGRVWXWWHUZLWKSDQQLQJ\RXPXVWKDYH
UXQWKHFRGHLQILJVRWKDWWKLVURXWLQHPD\ILQGVRPHVRXQGVDOUHDG\ORDGHG
LQWREXIIHUV\RXFDQFKDQJHWKHLQGH[RIaEXIIHUHG&XHVWRWHVWWKHURXWLQHRQ
GLIIHUHQWVRXQGV
Figure 3.13
Making a stuttering gesture using a geometric Pattern.
a fashion similar to the Clock examples above. Heres a simple example which makes
sound using whats called the default SynthDef:
UDQGRPO\VHOHFWHGIUHTXHQF\GXUDWLRQVHFRQG
3ELQG?IUHT3UDQG>@?GXUSOD\
Its 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 dened 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?UHODV6WUHDPVXEVWLWXWHWKH
VWUHDP
aSOD\HUVWRS
102
If you have evaluated the expressions above, you will notice that you dont 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 specied 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?S6SHHGSOD\
3ELQG?LQVWUXPHQW?VDPSOH3OD\HU?GXU3VHT>3JHRP
3JHRP@?UDWH3[UDQG>@LQI?UHO
?S6SHHGSOD\
Similar things can be done with the 3GHI class from the -,7 library (see chapter 7).
Lets designate another environment variable to hold a sequence of values that we
can plug in at will and change on the y. This Pattern holds values that would work
well for \dur:
aUK\WKP 3VHT>QLO@WKHQLOLVVRLWZLOO
VWRS
103
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 les to our
audio editor. The latter approach is useful if we want to use a cue player for the nal
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 specic 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 exible 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
Budn, O. 2000. Composing with Objects, Networks, and Time Scales: An Interview with
Horacio Vaggione. Computer Music Journal, 24(3): 922.
Collins, N. 2010. Introduction to Computer Music. Chichester: Wiley.
104
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): 5164.
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. 291396. 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): 235265.
Mathews, M. V. 1963. The Digital Computer as a Musical Instrument. Science, 142(3592):
553557.
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 Wilsons De-Interleaver application for OSX and Jeremy Friesners cross-platform
command line tools audio_combine and audio_split allow for convenient interleaving and
deinterleaving of audio les.
3. A geometric series is a series with a constant ratio between successive terms.
25
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.
Youre probably already familiar with UGens from other chapters. Before creating
new UGens of your own, lets rst consider what a UGen really is, from the plug-in
programmers point of view.
25.1
25.2
692
Dan Stowell
involve any new plug-in code. Instead, it just encapsulates some useful arrangement
of existing units. Lets create an example, a simple reverb effect:
5HYHUE^
DU^_LQ_
YDURXW LQ
RXW $OOSDVV1DURXWUDQG
ARXW
`
`
This isnt a very impressive reverb yet, but well 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 le and save it as, for instance, Reverb1.sc in your
SCClassLibrary or Extensions folder; then recompile. Youll now be able to use
5HYHUEDU within your SynthDefs, just as if it were a real UGen. Lets test this:
VERRW
[ ^
YDUIUHTVRQRXW
&KLUSVDWDUELWUDU\PRPHQWV
IUHT (QY*HQDU(QYSHUF'XVWDU
VRQ 6LQ2VFDUIUHT
:HDSSO\UHYHUEWRWKHOHIWDQGULJKWFKDQQHOVVHSDUDWHO\
RXW ^5HYHUEDUVRQFXWRII`GXS
`SOD\V
[IUHH
You may wish to save this usage example as a rudimentary Help le, 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$OOSDVV1DURXWUDQGFXWRII`
693
25
This is on the way toward becoming a useful reverb unit without having created a
real plug-in at all.
This approach has denite limitations. It is of course conned to processes that
can be expressed as a combination of existing units it cant create new types of
processing or new types of server behavior. It may also be less efcient 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 lets consider what is involved in creating a real UGen.
25.3
694
25.4
Dan Stowell
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 well discuss
shortly.
The rst 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
stead, this UGen makes use of the PDGG method, which is essentially a convenience
for multiplication and addition of the units output. As well as avoiding the programmers 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 nal 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 les.)
Lets start to draft the class le for a UGen we can implement. Well create a basic
anger which takes some input and then adds an effect controlled by rate and
depth parameters:
)ODQJHU8*HQ^
DU^
DUJLQUDWH GHSWK PXO DGG
AWKLVPXOWL1HZ
DXGLR
LQUDWHGHSWKPDGGPXODGG
`
NU^
DUJLQUDWH GHSWK PXO DGG
AWKLVPXOWL1HZ
FRQWURO
LQUDWHGHSWKPDGGPXODGG
`
`
Save this as Flanger.sc in your extensions directory. If you recompile, youll nd that
this is sufcient to allow you to use )ODQJHUDU or )ODQJHUNU in SynthDefs, which
the SuperCollider language will happily compile but of course those SynthDefs
wont run yet, because we havent created anything to tell the server how to produce
the Flanger effect.
25.4.1
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 doesnt try to connect things
in a way that doesnt 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 UGens FKHFN,QSXWV
method will be called. The default method dened 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. Lets look
at what the class does:
FKHFN,QSXWV^
LIUDWH
DXGLR
DQG^LQSXWVDWUDWH
DXGLR
`^
ASKDVHLQSXWLVQRWDXGLRUDWHLQSXWVDWLQSXWVDW
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 theres
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
rst argument to PXOWL1HZ). The LQSXWV variable is an array of the units inputs, each
of which will be a UGen and thus will also have a UDWH variable. So the method compares the present units rate against its rst inputs rate. It simply returns a string
if theres a problem (returning anything other than nil is a sign of an error found
while checking input). If theres not a problem, then it passes through to the default
FKHFN9DOLG,QSXWV method if you implement your own method checking, dont
forget to pass through to this check.
Many UGens produce output at the same rate as their rst input for example,
lters such as /3) or +3). If you look at their class denition (or their superclass, in
the case of /3) and +3) an abstract class called )LOWHU), youll 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\V2.
[IUHH
[ ^/3)NU:KLWH1RLVHDU`SOD\V(UURU
[ ^/3)NU:KLWH1RLVHNU`SOD\V2.
[IUHH
What happens if you dont 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 shouldnt be reading.
697
25
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 lter, 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
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 code in gure 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 le for SuperColliders plug-in
interface, SC_PlugIn.h. This is sufcient 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 dene 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 UGens functions, using the H[WHUQ& specier so that the scsynth
executable is able to reference the functions using C linkage. In a given plug-in we
are allowed to dene 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 structs 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 specied input. Here, we use
,1 remembering that numbering starts at 0, this corresponds to the second
input, dened in the Flanger class le 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 units struct. In our example theres only 1 choice, so we simply call 6(7&$/&)ODQJHUBQH[W. Its possible
699
25
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
`
,03257$177KLVWHOOVVFV\QWKWKHQDPHRIWKHFDOFXODWLRQIXQFWLRQ
IRUWKLV8*HQ
6(7&$/&)ODQJHUBQH[W
6KRXOGDOVRFDOFVDPSOH
VZRUWKRIRXWSXW
HQVXUHVHDFKXJHQ
VSLSHVDUHSULPHG
)ODQJHUBQH[WXQLW
Figure 25.1
C++ code for a Flanger UGen. This code doesnt add any effect to the sound yet, but contains
the key elements required for all UGens.
700
Dan Stowell
IORDWGHSWK ,1
IORDWUDWH
IORDWIZGKRS
IORDWUHDGSRV
LQWZULWHSRV
LQWGHOD\VL]H
IORDWYDOGHOD\HG
'RVRPHWKLQJWRWKHVLJQDOEHIRUHRXWSXWWLQJ
QRW\HWGRQH
`
RXW>L@ YDO
`
XQLW!ZULWHSRV ZULWHSRV
XQLW!UHDGSRV UHDGSRV
XQLW!UDWH
XQLW!IZGKRS
XQLW!UHDGSRV
XQLW!ZULWHSRV
XQLW!GHOD\VL]H
IW LQ7DEOH
`
'HILQH6LPSOH8QLW)ODQJHU
Figure 25.1
(continued)
701
25
to dene multiple calculation functions and allow the constructor to decide which
one to use. This is covered later.
3. Calculate one samples worth of output, typically by calling the units 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 doesnt actually alter the sound well 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++ le 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 dont see any explicit references to IW elsewhere in the code, thats 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 denes. 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
weve 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 doesnt 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 rst input and the rst output. If
the input is audio-rate, then LQ>@ will refer to the rst 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 rst value in the location referenced by ,1. ,1 is exactly the same as ,1>@.
,1IORDW
RXW 287;
702
Dan Stowell
We copy some values from the UGens struct into local variables. This can improve
the efciency 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 were 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 its possible that
they arent 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 efcient. 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
cant write the UGen efciently 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 le on writing UGens
provides an example in which this ordering is important.)
Finally, having produced our output, we may have modied 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
dont modify it in this example, we will shortly change the code so that this may
happen.
The code in gure 25.1 should compile correctly into a plug-in. With the class le in
place and the plug-in compiled, you can now use the UGen in a synth graph:
VERRW
[ ^
YDUVRQGO\RXW
VRQ 6DZDU>@PHDQ
RXW )ODQJHUDUVRQ
703
25
RXWGXS
`SOD\V
Remember that Flanger doesnt currently add any effect to the sound. But we can at
least check that it runs correctly (outputting its input unmodied and undistorted)
before we start to make things interesting.
25.5.4
Next we can develop our Flanger example so that it applies an effect to the sound.
In order to create a anging 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 UGens 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. (Weve also added code to the calculation
function which reads and writes to the delay line, creating the anging 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
`
+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
UDWH6$03/(5$7(
XQLW!IZGKRS GHOWDI
XQLW!UDWH UDWH
XQLW!ZULWHSRV
XQLW!UHDGSRV
$OORFDWHWKHGHOD\OLQH
XQLW!GHOD\OLQH IORDW
57$OORFXQLW!P:RUOGXQLW!GHOD\VL]H
VL]HRIIORDW
,QLWLDOLVHLWWR]HURHV
Figure 25.2
Completed C++ code for the Flanger UGen.
705
25
`
,03257$177KLVWHOOVVFV\QWKWKHQDPHRIWKHFDOFXODWLRQIXQFWLRQ
IRUWKLV8*HQ
6(7&$/&)ODQJHUBQH[W
6KRXOGDOVRFDOFVDPSOH
VZRUWKRIRXWSXW
HQVXUHVHDFKXJHQ
VSLSHVDUHSULPHG
)ODQJHUBQH[WXQLW
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
LIUDWH FXUUDWH^
UDWHLQSXWQHHGVXSGDWLQJ
UDWH FXUUDWH
IZGKRS GHOD\VL]H
UDWH
6$03/(5$7(I
`
Figure 25.2
(continued)
706
Dan Stowell
5HDGIURPWKHGHOD\OLQH
GHOD\HG GHOD\OLQH>LQWUHDGSRV@
UHDGSRV IZGKRS
8SGDWHSRVLWLRQ1%ZHPD\EHPRYLQJIRUZDUGVRUEDFNZDUGV
GHSHQGLQJRQLQSXW
ZKLOHLQWUHDGSRV! GHOD\VL]H
UHDGSRV GHOD\VL]H
ZKLOHLQWUHDGSRV
UHDGSRV GHOD\VL]H
`
0L[GU\DQGZHWWRJHWKHUDQGRXWSXWWKHP
RXW>L@ YDOGHOD\HG
GHSWK
`
XQLW!UDWH UDWH
XQLW!IZGKRS IZGKRS
XQLW!ZULWHSRV ZULWHSRV
XQLW!UHDGSRV UHDGSRV
IW LQ7DEOH
`
'HILQH'WRU8QLW)ODQJHU
Figure 25.2
(continued)
707
25
Table 25.1
Memory Allocation and Freeing
Typical C Allocation/Freeing
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 efcient 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 gure 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
Your UGens choice of calculation function is specied within the constructor rather
than being xed. 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 rst input was audio-rate or not:
LI,15$7( FDOFB)XOO5DWH^
6(7&$/&)ODQJHUBQH[WBD
`HOVH^
6(7&$/&)ODQJHUBQH[WBN
`
708
Dan Stowell
Table 25.2
Useful Macros for UGen Writers
Macro
Description
,1(index)
287(index)
,1(index)
287(index)
,15$7((index)
6(7&$/&(func)
6$03/(5$7(
6$03/('85
%8)/(1*7+
%8)5$7(
%8)'85
*(7%8)
&OHDU8QLW2XWSXWV(unit,
inNumSamples)
3ULQW(fmt, ...)
'RQH$FWLRQ(doneAction, unit)
57$OORF(world, numBytes)
575HDOORF(world, pointer,
numBytes)
709
25
Table 25.2
(continued)
Macro
Description
57)UHH(world, pointer)
)8//5$7(
)8//%8)/(1*7+
The units 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 le 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 inputs 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 oat member
SUHYWULJ, the following sketch outlines how we handle the incoming data in our
calculation function:
RDWWULJ ,12UZKLFKHYHULQSXW\RXZLVK
RDWSUHYWULJ XQLW!SUHYWULJ
LISUHYWULJ WULJ!^
GRVRPHWKLQJ
`
XQLW!SUHYWULJ WULJ6WRUHFXUUHQWYDOXHQH[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 rst
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 oat 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
servers 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 oat pointer
EXI'DWD along with 2 integers dening the size of the buffer,
EXI&KDQQHOV and EXI)UDPHV. Note that the macro assumes the buffer index is the rst
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, dened 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
25.5.10
Many UGens carry on indenitely, but often a UGen reaches the end of its useful
life (e.g., it nishes outputting an envelope or playing a buffer). There are 3 specic behaviors that might be appropriate if your UGen does reach a natural end:
711
25
1. Some UGens set a done ag to indicate that theyve nished. Other UGens can
monitor this and act in response to it (e.g., 'RQH, )UHH6HOI:KHQ'RQH). See the Help
les for examples of these UGens. If you wish your UGen to indicate that it has nished, set the ag as follows:
XQLW!P'RQH WUXH
This doesnt 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,1XQLW
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:
&OHDU8QLW2XWSXWVXQLWLQ1XP6DPSOHV
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 efcient 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 efcient way for your UGen to produce silent output for the remainder of the synths execution.
25.5.11
Table 25.2 summarized some of the most generally useful macros dened for use in
your UGen code. Many of these are discussed in this chapter, but not all are covered
explicitly. The macros are dened in SC_Unit.h and SC_InterfaceTable.h.
712
25.6
Dan Stowell
713
25
take an array of inputs and write them to a multichannel buffer. Heres how the class
le 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 doesnt know in advance what size the input array is going to
be. Because of the array attening 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
Phase vocoder UGens operate on frequency-domain data stored in a buffer (produced by the ))7 UGen). They dont 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
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 le
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 les 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 rst 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
le FFT_UGens.h, which denes some PV-specic 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 red,
then they access the buffer(s) and continue with the rest of the calculation function;
but if the FFT chain has not red 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 rst 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 rst chain: it takes the FFT chain indicated by the rst 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
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 its what well
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
717
25
Note that the indexing is slightly strange: engineers commonly refer to the DC component as the rst bin in the frequency-domain data. However in these structs,
because the DC component is represented differently, ELQ>@ is actually the rst 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 dont 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 Xcodes 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
systems 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$OORFed, 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 youre
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 specics of the code in
question. However, we can suggest some optimization tips for writing SuperCollider
UGens. The efciency/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 rst 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 thats essentially the effect of the less efcient code.
Avoid calls to expensive procedures whenever possible. For example, oatingpoint 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 nd an integer power of 2, use
bit shifting ( Q) rather than the expensive math function (SRZ Q). Other
expensive oating-point operations are square-root nding 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 lter UGen will take a user parameter (such as cutoff
frequency) and use it to derive internal lter coefcients. If you store the previous
value of the user parameter and use this to check whether it has changed at all
updating the coefcients only upon a change you can improve efciency, since often UGens are used with xed or rarely changing parameters.
One of the most important SuperCollider-specic 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 nd that a certain update procedure is expensive and wont 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 efciency gains can be large. In this
719
25
tension between efciency and code comprehensibility/reusability, you should remember the importance of adding comments to your code to clarify the ow 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 rst 99
times its 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 units 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, Apples vDSP library can improve speed by vectorizing certain calculations.
If you make use of this, or other platform-specic libraries, remember the considerations of platform independence. For example, use preprocessor instructions to
choose between the Mac-specic code and ordinary C++ code:
LI6&B'$5:,1
7KH0DFVSHFLFYHUVLRQRIWKHFRGHLQFOXGLQJHJY'63IXQFWLRQV
HOVH
7KHJHQHULFYHUVLRQRIWKHFRGH
HQGLI
6&B'$5:,1
25.7.3
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 doesnt offer an exhaustive list of all thats possible, but it provides you
with the core of what all UGen programmers need to know. If you want to delve
deeper, you will nd 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 SuperColliders core UGens or developing something very specialized, youll nd 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 exible
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 youve
nished 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 well 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 servers real-time operating mode, the information presented here is equally relevant to understanding scsynths non-real-time
mode. As always, the source code is the denitive reference and provides many interesting details which space limitations didnt 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 nd that the
source le, the class name, or both may have an SC_ prex. I have omitted such
prexes 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
rst 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 scsynths 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 fullls real-time performance constraints
while executing all of the dynamic behavior offered by the domain model. The nal
section briey highlights some of the ne-grained details which make scsynth one of
the most efcient 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
723
26.2
26
Inside scsynths
724
Ross Bencina
Figure 26.1
Class diagram of signicant 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, ltering, 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 efciently allocated from the Graphs memory block and evaluated by iterating through a linear array containing pointers to all
of the Graphs Units. Each 8QLW instance provides a 8QLW&DOF)XQF function pointer
to compute samples, which affords the same kind of exibility as 1RGH&DOF)XQF described above. For example, many Units implement a form of self-modifying code by
switching their UnitCalcFuncs on the y to execute different code paths, depending
on their state.
Graphs are instantiated using a *UDSK'HI (Graph Denition), which denes 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 efciently 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 species 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 dened by
a GraphDef is not modied by scsynth.
scsynths 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 servers 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
Real-Time Requirements
scsynths 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
The third category includes not only explicit blocking operations, such as attempting to lock a mutex or wait on a le 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 les 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
The rst point is possibly the most important to grasp, since it denes 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 ow 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-specic 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 gures 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 lling 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 notications of server events. Performing the enqueued notication 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 servers real-time behavior. Zooming in to a ner 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 Leas 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 doesnt 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 xedsize 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 theres guaranteed to be only 1 reading thread and 1 writing thread, and youre careful about how
the pointers are updated (and take care of atomicity and memory ordering issues)
then its 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, lets 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 elds
of )LIR0VJ.
W\SHGHIYRLG
)LIR0VJ)XQFVWUXFW)LIR0VJ
VWUXFW)LIR0VJ^
)LIR0VJ)XQFP3HUIRUP)XQF
)LIR0VJ)XQFP)UHH)XQF
YRLG
P'DWD
`
733
26
Inside scsynths
734
Ross Bencina
Table 26.1
Subclasses of 6HTXHQFHG&RPPDQG Dened in SC_SequencedCommand.cpp
Buffer Commands
GraphDef Commands
Miscellaneous
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 oating-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 le, :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 modied 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 SndBufs 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 contexts P6QG%XIV array. This process is summarized in
gure 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, gures
26.5 through 26.8. The exact function parameters have been simplied 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 lled with data from a sound le
is as follows:
EBDOORF5HDGEXIQXPSDWKVWDUW)UDPHQXP)UDPHV
735
26
Inside scsynths
Figure 26.4
Overview of multithreaded processing of the EBDOORF5HDG command.
Stage 1 (see gure 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 gure 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: rst 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 le 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 le
information and to allocate a new sample data array. The data are read from the le
into the sample data array and the le 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 gure 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.
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 efcient but dont strictly conform to the OSC specication. 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 notications 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 rst place and have operations fail if no more space is available. This xed-size
allocation strategy is adopted in a number of places in scsynth, including the size of
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 xed-size data structures are congurable in
:RUOG2SWLRQV (in general, by command line parameters), the idea being that the default values are usually sufcient, 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 efcient 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 efciently, 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 briey 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 efciency 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 dont 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 dont 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 lled during a control cycle:
740
Ross Bencina
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 its 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, <http://g.oswego.edu/dl/html/malloc.html> (accessed
January 9, 2008).
McCartney, J. 2002. Rethinking the Computer Music Language: SuperCollider. Computer
Music Journal, 26(4): 6168.
Wang, T. 1997. Integer Hash Function, <http://www.concentric.net/~Ttwang/tech/inthash
.htm> (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 denitions of terms, see the syntax appendix.
12-Tone Matrix, 3435
Abstraction, 210211. See also chapter 7
Additive Synthesis, 3, 6, 3437, 128
AIFF, 25, 195, 254, 483485
Algorithm (algorithmic), 122, 420, 460, 560
composition, 599
inside a method, 161162
as pattern, 607608
for pitch extraction, 441442
synthesis, 385, 390, 644, 653
Ambient Lights (project), 118
Ambisonics, 424425
Analysis
FFT, 431
real time, 440446
signal, 61, 65
UGens, 122
Arduino, 120124
Arguments, 610, 132133, 148149
Array, 1114, 23, 2836, 5657
indexing, 238240
literal, 742744
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
746
Subject Index
Enclosures, 12
Envelope, 18, 25, 47, 99, 343, 345, 414
Environment, 55, 120124, 166167, 648
variables, 25, 41, 139
Evaluation (of code), 4, 7, 146
Event, 180182. See also Patterns; chapter 6
note (keys), 184189
as object models, 241243
PatternProxy, 220221
protoEvent, 193197, 202205, 603605
streams, 220222, 225230
triggering, 55
Extensions. See Libraries
External devices. See chapter 4
FFT, 440442
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, 692693
Median, 122
Ringz, 231232, 346, 353
RLPF, 3, 345
Flange, 695, 698, 703
Float (oating-point), 11, 239
Flow control, 160162
FlowLayout, 285
Fourier, 357, 417
Frequency modulation (FM). See
Modulation, frequency
FreeVerb, 6870, 417, 419
Function, 1114, 60, 143144, 147
FunctionDef, 671
iterating, 152
return, 130131, 144
Garbage collection, 659660, 684685
Gate, 18, 28, 151
Gestures, 97
Granular synthesis, 64, 80, 197, 258, 432,
465469. See also Microsound
client-side, 432433
server side, 480483
747
Subject Index
748
Subject Index
749
Subject Index
Sidebands, 20, 46
Slider, 274276, 280, 307308, 311314
Smalltalk, 207, 237240, 269, 375, 570
Sonication, 236, 252258. See also chapter
13
Spatialization. See chapter 14
3D audio, 420421
Spectrum, 34, 61, 68, 80, 403, 405
diffusion, 432435
harmonic series, 1516, 28, 3335, 41,
4547, 441, 528
inharmonic, 34
StartUp, 306307, 340341, 347352,
366
Streams, 199202. See also Patterns
String, 11, 14, 23, 51, 361
Subtractive Synthesis, 3, 5
Switch (statements). See Flow control
SwingOSC. See GUI
Symbol, 12, 129
streams, 222
Synth Denitions, 2124, 179180. See also
chapter 6
Synthesis, non-real-time. See chapter 18
Task, 2932, 3941, 83, 8590
TaskProxy, 215218, 262265
Tempo clocks. See Clock, TempoClocks
Transcription. See Machine listening
Tuning. See chapter 17
equal, 505509
just, 515516
odd Limit, 517
poly, 519521
ratios, 515
tonality Diamond, 518
unequal divisions, 515
UGen (Unit Generator), 10. See also chapter
2 and chapter 25
pseudo, 691
UI. See GUI
Unicode, 287
Unipolar, 4446, 65
Unit Generator. See UGen
UNIX, 115116, 330, 360361
USB, 106, 120121
750
Subject Index
Code Index
This index contains language elements of SuperCollider. While most terms are used throughout the text, this index is limited to initial references, typically from the tutorial chapters.
Note that this index is divided into two sections: messages and classes.
background, 107, 244247
bufnum, 26, 418
Messages
, 33, 92, 313
, 522, 709
PRGPRGXOXV,
3133
,
42, 63, 94
, 94, 135
, 33
!, 293, 564
, 3133
! , 33, 171, 174
abs, 66, 398
add, 22, 24, 39, 5859, 6364
addAll, 134, 150
adsr, 345
amp, 312, 486, 494
ampdb, 313, 455
and, 3233
ar, 416, 5859
asArray, 120, 204, 256, 529530
asAscii, 120
asCompileString, 302
asInteger, 455
asKeyValuePairs, 295
asr, 531532
asStream, 89, 101, 201, 220221, 488,
500503
asString, 12, 42, 119, 134, 142
at (accessing elements of a collection),
2930, 113, 132, 203
audio, 30, 73
752
Code Index
info, 108
init, 113, 364
insert, 35, 51
interpret, 134, 518, 652
ir, 69, 259, 388, 391
isClosed, 245, 251
isEmpty, 362
isFloat, 33
isInteger, 32, 33
isKindOf, 161, 295, 567
items, 268, 289, 290
embedInStream, 193194
env, 486, 551
envir, 261, 266
error, 257, 329
even, 33
explin, 277, 479, 640
exprange, 64, 216, 231232
753
Code Index
odd, 32, 33
onClose, 92, 145, 153
option, 346, 410, 415
or, 33
path, 26, 335
pause, 86, 95, 222
perc, 24, 39, 4850, 82
permute, 13
phase, 716
play, 1319
plot, 11, 13, 35, 351
poll, 42, 44, 63
pop, 210, 214215
pos, 321
post, 29, 32, 35, 3940
postln, 11
pow, 18, 132133
prClose, 333
put, 132, 203, 257, 261262
putAll, 119120, 219, 267
pyramid, 150
quant, 228, 260, 263
rand, 8, 1113
range, 28, 45, 64, 69, 70
ratio, 533
read, 25, 91, 99
reciprocal, 64, 78, 96, 145
record, 95
register, 302, 329
release, 6978, 8594
render, 183, 195
reset, 87, 101, 158159
reverse, 13
rotate, 13
round, 11, 66
rrand, 7176, 162
run, 69, 350
sampleRate, 335, 418, 433
sched, 67, 8384
schedAbs, 647
scope, 13, 58, 63
scramble, 13, 220221, 257
select, 295, 620
754
Code Index
Classes
Allpass (N, L, C), 29, 50, 416
AppClock, 83, 246, 334
Array, 150, 391
BeatTrack, 444, 450, 451
BiPanB2, 426
Blip, 14, 241
BPF, 212, 397
BrownNoise, 175, 216
BufAllpass (N, L, C), 416
BufChannels, 708, 710
BufComb (N, L, C), 415
BufDelay (N, L, C), 415
BufDur, 487, 490
Buffer, 95, 388, 507
BufFrames, 198, 418, 484
BufRateScale, 91, 198
BufRd, 56, 61, 496
BufSampleRate, 491, 547
Bus, 2728, 5961, 447
Char, 107, 119, 129, 663667
Clip, 324, 402, 637, 641
CmdPeriod, 67, 277, 368, 626
CocoaDocument, 299
Comb (N, L, C), 10, 7678
Compander, 63
Convolution, 419
Decay, 63, 7778, 212, 388
Delay (N, L, C), 63, 415416
DetectSilence, 39
Dialog, 99
Dictionary, 202, 744
Dust, 5, 19, 63, 70, 97
EnvGen, 24, 26, 64, 69, 133, 138
Environment, 139142, 166167, 239
Event, 179, 204, 239, 311312
EventStreamPlayer, 101, 197, 201, 371
ExpRand, 5, 8, 13, 68, 153
FFT, 431, 434
FlowLayout, 159, 266267, 350
Free, 19, 49, 58, 63
FSinOsc, 259
Function, 83, 87, 149, 155159, 171
GrayNoise, 133
GUI, 245, 247, 250, 266
Harmonics, 164
HPF, 122, 451, 696
IdentityDictionary, 743
Impulse, 6364, 7778, 231233, 350,
353
In, 2630, 73, 422, 591
IRand, 24, 6869
KeyCodeResponder, 287
KeyState, 106
KeyTrack, 444, 453
Klank, 36, 38, 50
Lag (Lag2), 69, 212
Latch, 49
LeakDC, 402
LFClipNoise, 2627, 212, 216
LFDNoise1, 140
LFDNoise3, 69
LFNoise (0, 1, 2), 56, 1314, 64, 66, 69,
70, 140
LFPulse, 28, 4345, 215
LFSaw, 15, 43, 346, 351
LFTri, 1720, 64, 145
Limiter, 6263
Line, 388
Linen, 1719, 91, 97, 180, 197
LinExp, 191, 198, 277
LinPan2, 413, 414
LinXFade2, 413
LPF, 397, 429, 451, 458
MIDIIn, 111, 364, 458
MIDIOut, 111, 364
Mix, 3538, 458, 616, 625
MoogFF, 61
MouseX (MouseY), 1516, 1921, 106,
122, 389
MultiSliderView, 447, 623, 625
755
Code Index
756
Code Index
Splay, 414
SplayAz, 414
StaticText, 176, 268, 617619
StereoConvolution2L, 417, 419, 422
SystemClock, 67, 8384, 549
TabletView, 109
Tdef, 215220
TempoClock, 8384, 88, 185
TExpRand, 591
TGrains, 25, 64, 480482
TIRand, 6869
TRand, 17, 1921
TToggle, 293