ScalaColliderUGens
statement
Specification and base API of ScalaCollider UGens, as well as a core library of generated UGen classes.
This project is (C)opyright 2008–2022 by Hanns Holger Rutz. All rights reserved. All subprojects released under
the GNU LGPL v2.1+, except for the specification which is released under a BSD-style license. All code comes with
absolutely no warranties. To contact the author, send an e-mail to contact at sciss.de
.
sub projects
spec
contains an XML specification of the standard SuperCollider UGens, suitable for synthesising ScalaCollider UGen classes or other meta-data purposesapi
contains the base classes for ugens, graph elements, ugen and synth graphs.gen
generates the source codes of thecore
project from the descriptions provided by thespec
projectcore
contains ScalaCollider classes for the standard UGens
linking
All artifacts are published to Maven Central, and are available as follows:
"de.sciss" % "scalacolliderugens-spec" % v
"de.sciss" %% "scalacolliderugens-api" % v
"de.sciss" %% "scalacolliderugens-core" % v
"de.sciss" %% "scalacolliderugens-plugins" % v
The current stable version v
is "1.21.4"
.
The spec
contains the XML meta-data, api
contains basic types without specific UGens, core
contains the standard
UGens included with SuperCollider, and plugins
will include the third-party plugins managed by the
sc3-plugins project (still incomplete).
building
The project builds with sbt against Scala 2.13, 2.12, Dotty (JVM) and Scala 2.13 (JS). The last version to support Scala 2.11 was 1.19.5.
The synthetic UGen sources are inside directories core/gen
and plugins/gen
.
Since v1.20.1, when the XML specifications are changes, these must be recreated, by running sbt gen/ugen
!
The synthetic sources are currently checked into git, thus you do not necessarily have to regenerate these files.
To compile all sources, run sbt compile
.
contributing
Please see the file CONTRIBUTING.md
generating additional UGen class files
The UGen descriptions reside in XML files. The spec
subproject contains files for the standard UGens included with
a plain SuperCollider installation. You will need to create additional XML files if you wish to compile sources for
third party UGens.
To synthesize the source code for a given UGen description XML file, run as follows:
$ sbt
...
> project gen
> run -d path/to/scala/source/output path/to/descriptions.xml
The generated source files then need to be compiled against core
.
If you want to contribute UGen descriptions, there are two existing sbt subprojects to consider:
core
creates class files for the UGens included in the standard SuperCollider distribution. If you are not editing an existing XML file, you need to ensure a new XML file is included inUGenSpec.standardPlugins
.plugins
creates class files for the UGens included in the sc3-plugins umbrella project. Again, if you are not editing an existing XML file, you need to ensure a new XML file is included, this time in the listUGenSpec.thirdPartyPlugins
.- if you are planning to contribute the description of a plugin that is neither in the standard UGens nor in
the sc3-plugins project, please consult first where those classes should be published. Most likely, we
will add another sbt subproject similar to the
plugins
project. If you look at its code, you will see that you have to change the call torunUGenGenerator
so that instead ofargs = "--plugins" :: Nil
you provide an explicit list of paths to the XML files you wish to generate sources from.
Please have a good look at the current XML files and try to follow the documentation style used there. In particular
- try to be precise about the technical description of the behaviour of the UGen and its arguments. Try to think of the typical case where you would consult the API doc of the UGen. Which are the behavioural aspects you would want to look up, such as initialisation conditions, precise triggering behaviour, interaction of parameters, differences to related UGens.
- each argument should be described in terms of its behaviour, the physical units (if any), the allowed or expected value ranges, and corner cases.
- ideally, each UGen should be documented along with a concise example (in the XML file).
format of ugen XML descriptions
There is no DTD yet. But the structure of the XML file is as follows:
<ugens revision="<num>">
<ugen name="UGenName" [ ugenAttrs ]>
<rate name="RateName" [ implied="true" [ method="MethodName" ]]>
[ <arg ... /> ]
</rate>
<rate ... />
[ <output ... /> ]
<arg name="ArgumentName" [ argAttrs ]>
[ <doc>Argument description</doc> ]
</arg>
<arg ... />
[ <doc>
<text>UGen description</text>
[ <example name="description">
Example code demonstrating the UGen
</example>
<example ... /> ]
[ <see>ugen.RelatedUGenName</see>
<see ... /> ]
</doc> ]
</ugen>
[ <ugen ... /> ]
</ugens>
All UGens within one file are considered to be part of that particular .scx
plugin. Their synthesized classes will
also be grouped in a file by that name.
UGen Attributes
UGen Attributes (ugenAttr
) are boolean flags (all false by default) which can be set to characterize a UGen:
Attribute Name | Meaning when value is "true" |
Example |
---|---|---|
reads-bus |
UGen reads from a bus | In |
writes-bus |
UGen writes to a bus | Out |
reads-buf |
UGen reads audio buffer data | BufRd |
writes-buf |
UGen overwrites audio buffer data | BufWr |
reads-fft |
UGen reads from an FFT buffer | IFFT |
writes-fft |
UGen writes to an FFT buffer | FFT |
done-flag |
UGen sets a 'done flag' | Line |
side-effect |
UGen has another side effect, for example causing a 'done-action', sending OSC commands, or printing to the console | SendTrig |
random |
UGen depends on random seeding | WhiteNoise |
indiv |
Each UGen is otherwise individual, even with identical inputs | Demand UGens advance their inputs |
helper |
A helper element that is not a genuine UGen itself | Nyquist |
optimized |
A UGen that might be optimized at runtime to other UGens (allows to skip rate specification) | MulAdd |
sourcecode |
Manually written source code is provided | Nyquist |
fragment |
UGen that cannot be fully represented in the spec. For example, it has hidden or currently not represented argument types | LocalBuf |
elem |
Client facing class name differing from UGen | JPverbRaw |
Part of this information is used by ScalaCollider when building the UGen graph. For example, subtrees which do not
have any side effects are automatically removed. UGens which have side effects are those for which either of the
following flags is set: writes-bus
| writes-buf
| writes-fft
| side-effect
. Furthermore, multiple occurrences
of UGens which are functionally equivalent are collapsed. UGens are functionally not equivalent if either of the
following flags is set: any of the side effects | any of the resource readers | random
| indiv
. That is to
say, if there are two WhiteNoise
UGens, they are functionally distinct by definition and will thus not be
collapsed. The same is true for two Out
UGens, even if their inputs are the same, as they have accumulative side
effects on the bus to which they write. On the other hand, two SinOsc
UGens with the same frequency and rate
inputs are functionally equivalent and thus one can be replaced for the other.
More flags and meta data are planned in future version, e.g. oscillator signal ranges, filter coefficients.
UGen Rates
The possible rate names are "scalar"
, "control"
, "audio"
, and "demand"
. Each supported rate should have its
own element. There are three extra attributes, implied
, method
, and method-alias
.
implied
says that the UGen not only has exactly one supported rate (an exception is thrown if you have a UGen with
multiple rate elements and an implied
attribute), but that this a natural precondition for the type of UGen. That
way, the case class
for that UGen does not carry a rate
argument, but mixes in a trait which provides it. As a
consequence, there is no argument for the rate when using pattern matching against that UGen. For example, K2A
makes only sense at audio rate, A2K
makes only sense at control rate, FreeVerb
and Pitch
make only sense at
audio rate. Using this attribute, we have case class K2A(in: GE)
(with mixin AudioRated
) instead of the
redundant case class K2A(rate: Rate, in: GE)
.
Be very careful with this attribute, it should not be used if another rate could be added in a future
SuperCollider version, as this would break binary compatibility. This is why implied
has been removed from
DiskIn
, for example (there is no reason, why DiskIn
could not support control rate reading in the future).
The second attribute, method
, builds up implied
and requires that implied
has been specified. It states that
instead of the default method name in the companion object—ir
for scalar rate, kr
for control rate, ar
for audio rate, and dr
for demand rate—an alternative method name is used. The method name is typically
apply
, so that instead of FFT.kr(buf, sig)
you have to write FFT.apply(buf, sig)
or short FFT(buf, sig)
,
which is more convenient.
method-alias
adds an additional method for the rate. An example is IFFT
which specifies
<rate name="audio" method-alias="apply"/>
. This means the default method ar
is created, plus an apply
method
as an alias.
Argument Attributes
Attribute Name | Value | Example |
---|---|---|
default |
Default expression for the argument. This can be either a number literal or a special string such "nyquist" or "doNothing" (see below) |
440 , 1.0 , inf |
type |
Argument type when it is not ge (generic graph element). (See below) |
PanAz , Poll |
rate |
Constrains the supported rate for this argument. The only values presently recognized are "ugen" which means the argument is required to run at the same rate as the UGen, or a rate name such as "audio" which enforces that particular rate. Please see also the section below about rate specific argument settings. |
DiskOut (in ) |
The following table lists the allowed type
values, and corresponding ways of defining default values. If the default
value is unambiguous, the type is automatically inferred, e.g. using default="high"
implies a type="trig"
. If the
type and default value are incompatible, the parser will throw an exception.
Type name | Description | Example defaults |
---|---|---|
ge (default) |
Generic graph element | -1.0 , 440.0 |
ge-int |
Graph element used as integer | -1 , 18 |
ge-string |
String converted to variadic float constants | "poll" |
bus |
Bus index | no default allowed |
buf |
Buffer identifier | no default allowed |
fft |
FFT buffer phase chain signal | no default allowed |
trig |
Trigger signal (transition <= 0 to >0) | low , high |
switch |
Off/On signal (zero versus non-zero) | false , true |
gate |
Gating signal (open above zero) | closed , open |
mul |
Synthetic multiplier input | 1.0 |
action |
Done action | freeSelf , doNothing |
done-flag |
UGen which sets a done flag | no default allowed |
int |
Static integer (no graph element) | -1 , 18 |
A special default value "nyquist"
can be used which is understood as SampleRate.ir/2
. Note that expressions such
as "60.midicps"
have been currently disallowed for simplicity and language neutrality.
The following three argument attributes have boolean values, and are "false"
by default:
Attribute Name | Meaning when value is "true" |
Example |
---|---|---|
ugen-in |
Forces an Int type argument to be used as actual UGen input and not just auxiliary type. |
MFCC |
variadic |
Indicates an argument which expands over multiple UGen inputs. | RecordBuf (in ), Dseq (seq ) |
prepend-size |
Must be combined with variadic : the variadic arg's size is prepended as additional UGen input. |
PackFFT |
Arguments should be chosen careful not to conflict with methods available on GEOps
. This is the reason, why various
arguments which are named rate
in SCLang have been renamed for example to speed
, freq
etc. It is recommended to
take a look at the naming of the arguments in the default plugins (rather than relying on the naming in SCLang which
is often unreflected and irregular) and try to reuse them whenever possible, and to be as consistent as possible with
abbreviations. Care is also needed with the default values. There are some default values in SCLang which are
insensible, while other useful defaults are missing. The aim is not to provide default values for every possible
argument, but to require to fill in arguments for which defaults do not make sense.
Argument Positions
For some UGens, the actual positions of the arguments as they are coded in the underlying Plugin are either unintuitive (e.g. with respect to argument priority), inconvenient (e.g. with respect to default values), or irregular (e.g. different from an otherwise very similar other UGen). In those cases you are permitted to change the argument order as it is presented to the ScalaCollider user. To make sure the arguments are properly wired in the resulting SynthDef, explicit argument positions must be given.
If a UGen's arguments do not have pos
attributes, they are considered in the order in which they appear in the
XML file. Otherwise, the order of appearance in the XML file corresponds with the order in the underlying Plugin,
whereas the values of the pos
attributes specify the positions as presented to the user (counting from zero).
Please read the previous sentence very carefully, as a common mistake is to falsely believe the correspondence
to be the other way around.
As an example, consider XOut
which has the unintuitive argument order of bus, followed by cross-fade level,
followed by input signal. Compare this to Out
which has the two arguments of bus, followed by input signal.
We decided to make the XOut
arguments appear to the user in the order of bus, then input signal (just like
Out
), then followed by the distinguishing parameter of the cross-fade level. Thus, we assign pos="1"
to the
in
argument and pos="2"
to the xfade
argument, so they switch their positions. To minimize mistakes,
ScalaCollider-UGens requires that we also add pos="0"
to the bus
argument, even if that does not affect its
final position. The whole UGen specification thus becomes:
<ugen name="XOut" writes-bus="true">
<no-outputs/>
<rate name="audio">
<arg name="in" rate="ugen"/>
</rate>
<rate name="control"/>
<arg name="bus" pos="0"/>
<arg name="xfade" pos="2"/>
<arg name="in" variadic="true" pos="1"/>
<doc warn-pos="true"/>
</ugen>
Note how the attribute warn-pos="true"
was added to the doc
element. This makes Scaladoc add an extra note
to alert the user of the change in argument order. This is particularly important, as it may create confusion when
coming from SCLang. It is recommended to apply argument reordering only after careful consideration, and to abstain
from them when in doubt.
Rate Specific Argument Settings
Sometimes it is necessary to change the default value or the description of an argument with respect to the rate at
which the UGen runs. And sometimes the rate constraint for an argument only applies to particular rates at which the
UGen runs. In this case, it is permitted to embed an auxiliary arg
element inside the rate
element. This
auxiliary arg
element must have a corresponding element in the outer scope (inside the ugen
element). Their
correspondence is established by using the same name
attribute, and the auxiliary element may provide an
additional default
or rate
attribute and may contain an additional doc
element.
As an example for different default values, here is the full text of LeakDC
:
<ugen name="LeakDC">
<rate name="control">
<arg name="coeff" default="0.9"/> <!-- provide a default value for the `kr` method -->
</rate>
<rate name="audio">
<arg name="coeff" default="0.995"/> <!-- provide a default value for the `ar` method -->
</rate>
<arg name="in" rate="ugen"/>
<arg name="coeff"/> <!-- the outer argument must still be provided -->
</ugen>
An example of restricting the argument's rate only in certain cases is Out
:
<ugen name="Out" writes-bus="true">
<no-outputs/>
<rate name="audio">
<arg name="in" rate="ugen"/>
</rate>
<rate name="control"/>
<rate name="scalar"/>
<arg name="bus"/>
<arg name="in" variadic="true"/>
</ugen>
Here, the "outer" definition of argument in
says that the argument is a multichannel argument, but it does not
enforce a particular rate. Only for the case that Out
is run at audio rate, the auxiliary entry for in
enforces
that in
in this case must run at the same rate as the UGen (thus audio rate, too).
Outputs
By default, the UGen is considered to have one monophonic output. All other UGens must explicitly contain either a
<no-outputs/>
element, or one or more <output ... />
elements. An output element may have a name
and type
attribute, and one element may have a variadic="<id>"
attribute, where <id>
is the name of the input argument
determining the number of channels. A <doc>
element may be nested inside a <output>
node. Examples:
Example | UGen | Note |
---|---|---|
<no-outputs/> |
Out |
|
<output name="left"/><output name="right"/> |
Pan2 |
|
<output variadic="in"/> |
Demand |
in is a GE type input |
<output variadic="numChannels"/> |
DiskIn |
numChannels is an Int input |
<output name="chain" type="fft"/> |
PV_MagShift |
Descriptions
The description text for arguments is the text inside the argument's <doc></doc>
element. The description text for
the UGen is inside the <text></text>
element inside the <doc></doc>
element. In each case, standard
Scaladoc formatting is allowed. Cross-links are provided through any
number of <see></see>
elements.
Please follow carefully the style of the descriptions used for the standard UGens. They adhere mostly to Javadoc style practice, and not so much to the more colloquial style of SCLang docs. The purpose here is not to include lengthy examples, but to be technically precise in the meanings of the argument values and the exact functioning of the UGen, if possible covering corner cases, providing details about underlying formulas, phase behavior of oscillators, typical ranges and scale.
Whenever the argument order has been significantly changed from the SCLang counterpart, the UGen's doc
element
should contain the attribute warn-pos="true"
which will create a special highlight in the Scala docs to alert the
reader of this change.
Adjunct types
In order to register all necessary deserialization factories, UGens may define adjunct types. An adjunct is
defined by a reader class that implements ProductReader
, and a number of product prefixes that are returned
from the reader. For instance, the BinaryOpUGen
and UnaryOpUGen
each define their own Op
types which have
to be serialized and deserialized. In these cases, there is one reader and one returned type:
<adjunct reader="UnaryOpUGen.Op" self="true"/>
The reader object is UnaryOpUGen.Op
and the prefixes supported solely consists of itself (it will be
automatically translated to UnaryOpUGen$Op
). Any additional types deserialized can be added within the
adjunct
element as prefix
element with name
attributes:
<adjunct reader="Env.Curve">
<prefix name="Env$Curve$Apply" />
<prefix name="Env$Curve$Const" />
</adjunct>