/usr/share/SuperCollider/HelpSource/Guides/WritingUGens.schelp is in supercollider-common 1:3.6.3~repack-5.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | title:: Writing Unit Generators
summary:: Get started with writing unit generators
categories:: Internals
section:: How Unit Generator plug-ins work.
The server loads unit generator plug-ins when it starts up. Unit Generator plug-ins are dynamically loaded libraries
written in C++. Each library may contain one or multiple unit generator definitions. A plug-in can also define things
other than unit generators such as buffer fill ("/b_gen") commands. Plug-ins are loaded during the startup of the
synthesis server. Therefore the server will have to be restarted after (re-)compiling the plugin.
section:: The Entry Point
When the library is loaded the server calls a function in the library, which is defined by the code::PluginLoad()::
macro. This entry point has two responsibilities:
list::
## It needs to store the passed in pointer to the InterfaceTable in a global variable.
## It registers the unit generators.
::
Unit Generators are defined by calling a function in the InterfaceTable and passing it the name of the unit generator,
the size of its C data struct, and pointers to functions for constructing and destructing it. There are 4 macros, which
can be used to simplify the process.
definitionList::
## DefineSimpleUnit || Define a `simple' unit generator
## DefineDtorUnit || Define a unit generator with a descructor
## DefineSimpleCantAliasUnit || Define a `simple' unit generator, whose input and output buffers cannot alias
## DefineDtorCantAliasUnit || Define a unit generator with a destructor, whose input and output buffers cannot alias
::
These macros depend on a specific naming convention:
list::
## The unit generator struct is named like the plug-in.
## The unit generator constructor is named code::PluginName_Ctor::
## The unit generator destructor is named code::PluginName_Dtor::
::
section:: A Simple Unit Generator Plugin
Unit generator plugins require two parts: A C++ part, which implements the server-side code that is loaded as a
dynamically loaded library, and an SCLang class, that is required to build the link::Classes/SynthDef::. The following
example implements a simple Sawtooth oscillator
subsection:: C++-side Definition of Unit Generators
The following code shows the C++ source of a simple unit generator.
code::
#include "SC_PlugIn.h"
// InterfaceTable contains pointers to functions in the host (server).
static InterfaceTable *ft;
// declare struct to hold unit generator state
struct MySaw : public Unit
{
double mPhase; // phase of the oscillator, from -1 to 1.
float mFreqMul; // a constant for multiplying frequency
};
// declare unit generator functions
static void MySaw_next_a(MySaw *unit, int inNumSamples);
static void MySaw_next_k(MySaw *unit, int inNumSamples);
static void MySaw_Ctor(MySaw* unit);
//////////////////////////////////////////////////////////////////
// Ctor is called to initialize the unit generator.
// It only executes once.
// A Ctor usually does 3 things.
// 1. set the calculation function.
// 2. initialize the unit generator state variables.
// 3. calculate one sample of output.
void MySaw_Ctor(MySaw* unit)
{
// 1. set the calculation function.
if (INRATE(0) == calc_FullRate) {
// if the frequency argument is audio rate
SETCALC(MySaw_next_a);
} else {
// if the frequency argument is control rate (or a scalar).
SETCALC(MySaw_next_k);
}
// 2. initialize the unit generator state variables.
// initialize a constant for multiplying the frequency
unit->mFreqMul = 2.0 * SAMPLEDUR;
// get initial phase of oscillator
unit->mPhase = IN0(1);
// 3. calculate one sample of output.
MySaw_next_k(unit, 1);
}
//////////////////////////////////////////////////////////////////
// The calculation function executes once per control period
// which is typically 64 samples.
// calculation function for an audio rate frequency argument
void MySaw_next_a(MySaw *unit, int inNumSamples)
{
// get the pointer to the output buffer
float *out = OUT(0);
// get the pointer to the input buffer
float *freq = IN(0);
// get phase and freqmul constant from struct and store it in a
// local variable.
// The optimizer will cause them to be loaded it into a register.
float freqmul = unit->mFreqMul;
double phase = unit->mPhase;
// perform a loop for the number of samples in the control period.
// If this unit is audio rate then inNumSamples will be 64 or whatever
// the block size is. If this unit is control rate then inNumSamples will
// be 1.
for (int i=0; i < inNumSamples; ++i)
{
// out must be written last for in place operation
float z = phase;
phase += freq[i] * freqmul;
// these if statements wrap the phase a +1 or -1.
if (phase >= 1.f) phase -= 2.f;
else if (phase <= -1.f) phase += 2.f;
// write the output
out[i] = z;
}
// store the phase back to the struct
unit->mPhase = phase;
}
//////////////////////////////////////////////////////////////////
// calculation function for a control rate frequency argument
void MySaw_next_k(MySaw *unit, int inNumSamples)
{
// get the pointer to the output buffer
float *out = OUT(0);
// freq is control rate, so calculate it once.
float freq = IN0(0) * unit->mFreqMul;
// get phase from struct and store it in a local variable.
// The optimizer will cause it to be loaded it into a register.
double phase = unit->mPhase;
// since the frequency is not changing then we can simplify the loops
// by separating the cases of positive or negative frequencies.
// This will make them run faster because there is less code inside the loop.
if (freq >= 0.f) {
// positive frequencies
for (int i=0; i < inNumSamples; ++i)
{
out[i] = phase;
phase += freq;
if (phase >= 1.f) phase -= 2.f;
}
} else {
// negative frequencies
for (int i=0; i < inNumSamples; ++i)
{
out[i] = phase;
phase += freq;
if (phase <= -1.f) phase += 2.f;
}
}
// store the phase back to the struct
unit->mPhase = phase;
}
// the entry point is called by the host when the plug-in is loaded
PluginLoad(MySaw)
{
// InterfaceTable *inTable implicitly given as argument to the load function
ft = inTable; // store pointer to InterfaceTable
DefineSimpleUnit(MySaw);
}
::
subsection:: SCLang-side Definition of Unit Generators
SuperCollider requires an SCLang class in order to build SynthDefs.
The arguments to the MySaw UGen are code::freq:: and code::iphase::. The code::multiNew:: method handles multi channel
expansion. The code::madd:: method provides support for the mul and add arguments. It will create a MulAdd UGen if
necessary. You could write the class without mul and add arguments, but providing them makes it more convenient for the
user. See link::Guides/WritingClasses:: for details on writing sclang classes.
code::
// without mul and add.
MySaw : UGen {
*ar { arg freq = 440.0, iphase = 0.0;
^this.multiNew('audio', freq, iphase)
}
*kr { arg freq = 440.0, iphase = 0.0;
^this.multiNew('control', freq, iphase)
}
}
::
subsection:: Building Unit Generator Plugins
The most portable way to build plugins is using cmake footnote::http://www.cmake.org::, a cross-platform build
system. In order build the example with cmake, the following code should go into a code::CMakeLists.txt:: file.
code::
cmake_minimum_required (VERSION 2.8)
project (MySaw)
include_directories(${SC_PATH}/include/plugin_interface)
include_directories(${SC_PATH}/include/common)
include_directories(${SC_PATH}/external_libraries/libsndfile/)
set(CMAKE_SHARED_MODULE_PREFIX "")
if(APPLE OR WIN32)
set(CMAKE_SHARED_MODULE_SUFFIX ".scx")
endif()
add_library(MySaw MODULE MySaw.cpp)
::
section:: Coding Guidelines
Unit generator plugins are called from the real-time context, which means that special care needs to be taken in order
to avoid audio dropouts.
definitionList::
## Memory Allocation || Do not allocate memory from the OS via code::malloc:: / code::free:: or code::new::/ code::delete::.
Instead you should use the real-time memory allocator via code::RTAlloc:: / code::RTFree::.
## STL Containers || It is generally not recommended to use STL containers, since they internally allocate memory. The only
way the STL containers can be used is by providing an Allocator, which maps to the allocating functions of
the server.
## Blocking API Calls || Unit generators should not call any code, which could block the execution of the current thread. In
particular, system calls should be avoided. If synchronization with other threads is required, this has to be
done in a lock-free manner.
::
section:: Thread Safety
There are two different implementations of the SuperCollider server. strong::scsynth:: is the traditional server and
strong::supernova:: is a new implementation with support for multi-processor audio synthesis. Since the plugins in
strong::supernova:: can be called at the same time from multiple threads, write access to global data structures needs
to be synchronized.
definitionList::
## Shared Global Data Structures || Unit generators should not share data structures, which are written to. While it it safe to use
global data structures for read-only purposes (e.g. different unit generators could use the same constant wavetable),
the data structures that are modified by the unit generators should not be shared among different instances.
## Resource Locking || SuperCollider's buffers and busses are global data structures, and access needs to be synchronized.
This is done internally by using reader-writer spinlocks. This is done by using the code::ACQUIRE_::, code::RELEASE_::, and
code::LOCK_:: macros, which are defined in SC_Unit.h.
::
subsection:: Deadlock Prevention
In order to prevent deadlocks, a simple deadlock prevention scheme is implemented, based on the following constraints.
list::
## Lock resources only when required: few unit generators actually require the access to more than one resource at the same time.
The main exception of this rule are the FFT Chain ugens, which access multiple buffers at the same time. There is no known unit
generator, which accesses both buffers and busses at the same time.
## Acquire reader locks if possible. Since multiple ugens can acquire a reader lock to the same resource at the same time, their
use reduces contention.
## Resources have to be acquired in a well-defined order: busses should be acquired before buffers and resources with a high index
should be acquired before resources with a low index.
::
|