Introduction
Anti-Mythology
As of August 2017, sc-hacks is being re-done from scratch. Both the library and its API will change in the near future.
The purpose of the sc-hacks library is to make experimentation and music making in SuperCollider easier, by offering simpler and shorter coding options for tasks that usually require complex and careful coding. Such are:
- Replace one synth with another seamlessly while playing.
- Freely change the contents of patterns while playing.
- Set the parameters of synths either by setting environment variables or through a GUI.
- Link outputs and inputs of synths and place them in the right order to filter the output of a synth with another synth.
- Manage sets of code files to be loaded at startup, and of audio files to be loaded when the server boots.
- Map the control signal of synth functions or envelopes to the parameters of a synth.
Caveat
sc-hacks is in permanent alpha. According to semantic versioning, "If you’re changing the API every day you should either still be in version 0.y.z or on a separate development branch working on the next major version.". This library will remain 0.n alpha for a long time, as changes in operator names and syntax are happening constantly.
Right now, this site is a design draft to guide the development of the next version of sc-hacks. The source currently available on github is still under development and many features are unavailable.
History
Initially, sc-hacks was motivated by the need to improve some aspects JITLib, such as:
- The use of signal-copying synths to audio busses for linking i/o between synths.
- The modification of patterns while they are being played with Pbind.
Fascinated by the => operator of ChucK, and by the idea of adverbs for binary operators in SuperCollider, I wanted to explore binary operators as a way to write commonly used expressions in a shorter way.
The first results of these attempts are the libraries tiny-sc and tiny-inc. These have many shortcomings, which the present library is trying to fix. At present, sc-hacks is being redesigned once more, in order to implement two of the most attractive features of JITLib: sequential playing synth-functions and patterns in one Ndef
and linking the output of an Ndef to the input of another Ndef.
Roadmap
https://github.com/iani/sc-hacks/blob/master/Notes/ROADMAP.org https://raw.githubusercontent.com/iani/sc-hacks/master/Notes/ROADMAP.org
For a breakdown of the most recent changes and progress report of the project, see the ROADMAP.org file in raw format here: (See right pane for raw link).
Under development September 2017
- Test Linking with
InFeedback
, using own shortcut classFin
.
Work done August 2017
- Dispatch class for use in Nevent
- Rework of Group allocation scheme
- New class: Player for playing Synths and Patterns in the same instance.
- Linking with InFeedback.
Future plans
- sound file display with sound file view.
- sound file processing from emacs-dired. Test NRT file processing too.
- SCMIR
- NRT SCMIR
- Concatenative Synthesis
By way of demo
INCOMPLETE
Synths
Start a synth
#+BEGIN_SRC javascript \default +> \example1; #+END_SRC
Control the parameters of the synth from the enviroment
Replace the synth with a different synth
#+BEGIN_SRC javascript { SinOsc.ar(\freq.kr(440, )[]) } #+END_SRC
Notice that the new synth reads the current values for each parameter from the environment.
Use a simple gui to control synth parameters
Start a second synth in a new environment
#+BEGIN_SRC javascript
#+END_SRC
Notice that the synth in the new environment
One can jump between
Design
The trouble with operators
Full syntax of a binary operator in SuperCollider. Up to 4 items are involved:
// 1. receiver 2. operator 3. adverb 4.argument
{ WhiteNoise.ar(0.1) } +>.newenvir \mysynth
sc-hacks uses binary operators to shorten and help classify the coding of common operations such as playing a synth or a pattern. Binary operators in SuperCollider are methods whose name is composed entirely from combinations of a selected number of symbols such as: +, *, / -, <, >, =, |, %, &, @. These operators are called binary operators, because in their syntax by convention they combine two object, a left argument (the receiver of the method) and a right argument (the argument). SuperCollider additionally implements an interesting but not so widely used extension of this syntax: It permits one to add a second argument by appending it directly after the operator using a dot = . = as separator. sc-hacks makes use of this feature.
While operators can shorten the amount of code needed - which is why they are used here - they also have the disadvantage of being not as easy to familiarize and understand as the usual names which describe the action of a method. It takes some extra effort to understand code using many unfamiliar operators. Therefore, in designing this library, the goal has been to avoid undue proliferation of operators. Currently, this library proposes 6 custom operators: +>
, <+
, *>
, *<
, <*
and @
. The action performed by each operator varies according to the Classes of the receiver and the argument. sc-hacks defines 16 different kinds of actions in all.
A word (or 2) about EMACS
Startup files and buffers management with dired, bookmarks, projectile
Obtaining emacs
Configuring emacs for SuperCollider
There is a very good guide here: http://www.rockhoppertech.com/blog/supercollider-with-emacs-on-osx/
I quote from the guide below:
BEGIN QUOTE
SuperCollider with Emacs on OSX
By GENE DE LISA | Published: MAY 16, 2013
SuperCollider (Github) has a decent IDE these days. But perhaps you’d like to use an actual editor like Emacs.
[...]
Getting the source
git clone https://github.com/supercollider/supercollider.git
You need to get the current source for scmode. There is no separate repo, so you need the whole SuperCollider repo. Once you clone the repo, the scmode code is under supercollider/editors/scel.
Adding EMACS functionality to SuperCollider
Platform.userAppSupportDir;
This step puts the Class code that is needed to access EMACS from SuperCollider inside the Users Extensions
folder so that SuperCollider can use it.
The contents of the sc directory needs to be copied to the Extensions directory of your application support directory. You can discover the location of your application support directory by evaluating in the SuperCollider interpreter the line posted in the SuperCollider
tab of the code column to the right of the present text text.
cd supercollider/editors/scel
sudo mkdir ~/Library/Application\ Support/SuperCollider/Extensions
sudo cp -r sc ~/Library/Application\ Support/SuperCollider/Extensions
git clone https://github.com/supercollider/supercollider.git
On OSX, the user application support directory is ~/Library/Application Support/SuperCollider, so copy scel to ~/Library/Application Support/SuperCollider/Extensions, creating the directory if it doesn’t already exist. Once you have cloned the repo and gone to scel
as indicated above, copy the scel folder to SuperCollider/Extensions. The code for this is found in the Shell
tab of the code column to the right of the present text text.
Adding SuperCollider functionality to EMACS
(add-to-list 'load-path "~/.emacs.d/vendor/scel/el")
(require 'sclang)
(add-to-list 'load-path "~/.emacs.d/vendor/scel/el")
(require 'sclang)
You need to add the contents of supercollider/editors/scel/el to your load path. You can just point to where you cloned the git repo, but I prefer to have my emacs lisp in a logical place. I copied the el directory to ~/.emacs.d/vendor/scel/el
directory. Add to your init file the code displayed here under the "emacs-lisp" tab.
(setenv "PATH" (concat (getenv "PATH")
":/Applications/SuperCollider:/Applications/SuperCollider/SuperCollider.app/Contents/Resources"))
(setq exec-path (append exec-path '("/Applications/SuperCollider" "/Applications/SuperCollider/SuperCollider.app/Contents/Resources" )))
You need to set your path within Emacs also. The sclang program is located in /Applications/SuperCollider/SuperCollider.app/Contents/Resources
,
so that needs to be in your path. The code for doing this is found under the emacs-lisp
tab in the right column of this web page.
(For hacking in iTerm, I’ve also added it to my PATH in my bash startup file).
Launch Emacs. When the dust settles, type M-x sclang-start
The interpreter will start in a buffer named SCLang:PostBuffer and you will be presented with a new buffer named SCLang:Workspace.
There will be a SCLang menu and you can execute commands from there. You’ll see the C-x C-f will evaluate the entire document. (Or type C-c C-p b to boot. Of course you can type C-h m to get the help for sclang mode).
[...]
Awesome, thank you! I wasn’t doing two things exactly right and this post was the missing link among all the various bits of emacs-supercollider info out there.
Two notes that may be helpful to others, or may simply be the result of mistakes on my part:
- In my scel/el directory, there was a file with a .in ending (specifically “sclang-vars.el.in”) that was causing a problem. It has something to do with cmake, I’ve inferred, but I simply just changed to the file ending to “.el” and I stopped getting the error.
- Apparently, there were/are two “Document.sc” files (one in Supercollider/extensions directory and one buried in the SCClassLibrary/Common/GUI directory); that caused an error. I simply removed the one from the Supercollider/extensions (they appeared to hold the same contents) and, voilá/voilà!
Thanks again for spelling things out nicely. Will try to check back and dig the other things on your blog!
END QUOTE
Operators
Operator Overview
Examples of 4 operator types in sc-hacks
// type 1: +>
{ WhiteNoise.ar(0.1) } +> \test; // 1. function +>player
(dur: 0.1, degree: Pwhite(1, 10)) +> \test; // 2. event +> player
\default +> \test; // SynthDef name +> player
// type 2: <+
\freq <+ { LFNoise0.kr(5).range(500, 1500) }; // 1. parameter <+ Function
\amp <+ Env.perc; // parameter <+ Envelope
// type 3: *>
\source *> \effect // source player *> effect player
(freq: Pbrown(50, 90, 5), dur: 0.1) *> \effect; // 2. event *> player's environment
// type 4: <*
\test <* \hihat; // 1. parameter <* buffer name
As basic operators, sc-hacks uses four combinations built from + and * with > and <. They are in two groups (=+=-Group and =*=-Group):
- +-Group :
+>
,<+
. +> plays the receiver (left argument) object in the player whose name is named by the left argument. <+ plays the left argument object in the parameter whose name is named by the receiver. - *-Group :
*>
,*<
,<*
. The operators of the*
group have parallel usage principles as that of the+
group, but they interpret the same kinds of receiver in different ways, to provide additional functionality.
Here is a tabular overview of operator actions associated to different types of receiver and argument:
no | receiver | operator | argument | action |
1 | Symbol | +> |
Symbol | Play Synthdef |
2 | Symbol | *> |
Symbol | Link Players |
3 | Symbol | *< |
Symbol | Link Players |
4 | Symbol | <+ |
Number | Set parameter |
5 | Symbol | <+ |
Function | Map parameter |
6 | Symbol | <+ |
Env | Map parameter |
7 | Symbol | <* |
Symbol | Set bufnum |
8 | Symbol | <* |
MIDI | Bind MIDIFunc |
9 | Symbol | <* |
OSC | Bind OSCFunc |
10 | Symbol | <* |
Widget | Bind GUI Widget |
11 | Function | +> |
Symbol | Play Synth |
12 | Function | *> |
Symbol | Play Routine |
13 | Event | +> |
Symbol | Play Pattern in Player |
14 | Event | *> |
Symbol | Play Pattern in Player's Environment |
15 | UGen | <+ |
Symbol | Read input from Player's Output |
16 | Symbol | @ |
Symbol | Make Player/Param/Chan proxy for bus linking |
1. Symbol +>
Symbol: Play Synthdef
\default +> \player1; // play SynthDef named default in player named player1
2. Symbol *>
/ *<
Symbol: Link Players
a = { Out.ar(\out.kr(0), WhiteNoise.ar(0.1) * (Dust.kr([10, 10]))) }.play;
b = { Ringz.ar(Fin(\in, 10), LFNoise1.kr([5, 6]).range(100, 1000), 0.25) }.play; // Fin = InFeedback.ar
//:
a.set(\out, 4);
b.set(\in, 4);
\source *> \effect // resolves to: \source@out *> (\effect@in)
// operations equivalent to the expression \source *> \effect:
~bus = Bus.audio(Server.default, 1);
~sourcee.set(\out, ~bus.index);
~effect.set(\in, ~bus.index);
The operators *>
and <*
create audio links between the players of 2 environments by setting the parameters of input and output controls to shared input and output busses. Following example illustrates what is meant by sharing busses. The output bus of synth a
is set to the same bus as the input of synth b
.
This statement \source *> \effect
can be understood as a shortcut for the following operations:
The names of the parameters \out
and \in
are provided by default. One can override these defaults through adverbs to the operator or by using the operator @
as shown in the next examples.
Since the SC compiler evaluates SC expressions from left to right, it allocates the bus for the left argument player first (receiver), and then sets the parameter in the environment of the right argument to that same bus. Therefore, the expression \source *> \effect
will first obtain the bus in the default parameter \in
the receiver environment \source
and then store this bus in the default parameter \out
in the (right) argument environment \effect
. This means that the input of the second (right) argument's environment will always be overwritten by the output bus of the first (left) argument.
\effect@in *> (\source@out) // overwrite default parameters for reverse order
// corresponding shortcut:
\effect *< \source // resolves to: \effect@in *> (\source@out)
One may reverse this by writing the arguments in reverse order, but one would have to overwrite the default arguments. The operator @
is used here As a shortcut for specifying the parameters.
Since the reverse argument order is useful in many situations, the operator *<
provides a shortcut for it.
\source *> \effect; // use default i/o parameters (\out, \in)
\effect *< \source; // use default i/o parameters in reverse order (\in, \out)
\source *>.input2 \effect; // specify custom input parameter
\source @.output2 *> \effect // specify custom output parameter
\source @.output2 *> (effect@\input2); // specify custom input and output parameter
Adverbs to @
and *>
can be used to specify parameters.
3. Symbol <+
Number: Set parameter
4. Symbol <+
Function: Map parameter
5. Symbol <+
Env: Map parameter
6. Symbol <+
Symbol: Set bufnum
7. Symbol <*
MIDI: Bind MIDIFunc
8. Symbol <*
OSC: Bind OSCFunc
9. Symbol <*
Widget: Bind GUI Widget
10. Function +>
Symbol: Play Synth
11. Function *>
Symbol: Play Routine
12. Event +>
Symbol: Play Pattern in Player
13. Event +>
Function: Play function as instrument in Pattern
13. Event *>
Symbol: Play Pattern in Player's Environment
14. UGen <+
Symbol: Read input from Player's Output
The code examples to the right illustrate the action of different types of objects sent to players, which are:
+>
: Play the receiver in the Player named by the argument. 3 types of receivers can be used: 1. Function: Play as synth function into the Player named by the argument. 2. Event: Play as pattern (via EventStreamPlayer) into the Player named by the argument. 3. Symbol: Play as synth using the Receiver Symbol as name of the Synth to be player.<+
: Use the argument as input to the environment variable named by the receiver (in the current environment). Several different types of argument are possible: Function, SimpleNumber (Integer, Float), MIDI and OSC-function templates (custom classes in this library), GUI type objects, Symbol (names of buffers or of players).*>
: The*>
operator functions like the+>
operator in that it plays the receiver in the player named by the argument, but it interprets the types of the receiver in a different way: Functions get played as routines in the environment of the player. Events get played as patterns, but using a custom type which instead of playing sounds withplay
event type, just set the environment variables of the environment of the player named by the receiver, with the values produced by each key in the event. A Symbol as receiver indicates to connect the output of the player named by the receiver to the input of the player named by the argument.<*
This operator is used to send the bufnum of buffers to parameters.
15. Symbol @
Symbol: Envir/Parameter/numChannels
Bus binding operator: Store bus in parameter of player. Adverb indicates number of channels.
// create 2-channel bus for parameter \in of envir \source:
\source @.2 \in;
// create 1-channel bus for parameter \in of environment \source1.
\source1 @ \in;
// Chain @ operator expressions to customize linked bus assignment:
\source @.4 \out2 *> (\effect @ \in3);
In addition to the 4 operators above, sc-hacks uses @
to configure busses bound to parameters of player. The @
operator returns a player-parameter-bus object that can be linked to another player-parameter-bus.
Playing Synths
Playing Patterns
Linking I/O
Using the Fin pseudo-UGen
{ Fin() }
//: Is equivalent to:
{ InFeedback.ar(\in.kr, 1) }
// The control name and number of channels can be given as arguments.
//: For example:
{ // use \input2 as control and input 4 channels of audio:
Fin(\input2, 4);
}
sc-hacks defines the pseuedo-UGen Fin
as a shortcut for =InFeedback=:
InFeedback.ar(<controlname>, <numChannels>)
.
InFeedback
acts as audio input UGen similar to In.ar
, but can also read output from Synths that are placed after the current synth in the order of nodes in the Server. It is necessary to use InFeedback
or Fin
if one wants to read audio from other synths independently of their order in the Server. (See http://doc.sccode.org/Guides/Order-of-execution.html on the order of execution of nodes in the Server.)
Use *> to route I/O through private busses
FEEDBACK (wrong)
{ WhiteNoise.ar(0.01) } +> \source;
{ Ringz.ar(Fin(), LFNoise0.kr(10).range(150, 1000), 0.1) } +> \effect;
No feedback (correct)
\source *> \effect;
{ WhiteNoise.ar(0.01) } +> \source;
{ Ringz.ar(Fin(), LFNoise0.kr(10).range(150, 1000), 0.1) } +> \effect;
Using the default bus values for function.play and Fin()
results in feedback because the synth of \effect
writes its output to bus 0 and also reads that output from bus 0.
In order to set the outputs and inputs of two synths to a separate bus, use the *>
operator to link their environments: \source *> \effect
.
Multiple sources with one effect
\source1 *> \effect1;
\source2 *< \effect1;
// OK: start order: effect, source1, source2
{ Ringz.ar(Fin(), LFNoise0.kr(1).range(150, 1000), 0.1) } +> \effect1;
{ Impulse.ar(4, 0, 0.1) } +> \source1;
{ WhiteNoise.ar(Decay2.kr(Impulse.kr(0.5, 0, 0.1), 0.3, 1, 0.1)) } +> \source2;
//: OK: start order: source 1, source2, effect
{ Impulse.ar(4, 0, 0.1) } +> \source1;
{ WhiteNoise.ar(Decay2.kr(Impulse.kr(0.5, 0, 0.1), 0.3, 1, 0.1)) } +> \source2;
{ Ringz.ar(Fin(), LFNoise0.kr(1).range(150, 1000), 0.1) } +> \effect1;
//: start order: source 1, effect, source2
// PROBLEM: source2 overwrites source1
{ Impulse.ar(4, 0, 0.1) } +> \source1;
{ Ringz.ar(Fin(), LFNoise0.kr(1).range(150, 1000), 0.1) } +> \effect1;
{ WhiteNoise.ar(Decay2.kr(Impulse.kr(0.5, 0, 0.1), 0.3, 1, 0.1)) } +> \source2;
To add a second source to the same effect, use the *<
operator to set the output bus to the input bus of an already existing effect.
If the effect synth is started between the first and the second source, then the first source is overwritten. Therefore it will be necessary to devise a mechanism for ordering synths automatically to prevent this from happening. Here is a study with named synthdefs to show the order of synths using Server.default.plotTree
Server.default.plotTree;
//:
SynthDef("impulse", { | out = 0 |
Out.ar(out, Impulse.ar(4, 0, 0.1))
}).add;
SynthDef("decay", { | out = 0 |
Out.ar(out,
WhiteNoise.ar(
Decay2.kr(Impulse.kr(1), 0.3, 0.4, 0.1)
)
)
}).add;
SynthDef("ring", {
Ringz.ar(Fin(), LFNoise0.kr(1).range(150, 500))
}).add;
//:
\source1 *> \effect1;
\source2 *< \effect1;
//:
\ring +> \effect1;
\impulse +> \source1;
Accessing named objects
(SynthPlayers, EventPatterns/EventStreamPlayers, Routines and Windows)
The following messages are used to access a SynthPlayer, EventStreamPlayer, Routine or Window stored under a name in an environment. If no enviroment is specified, then the currentEnvironment is used.
symbol.synth(optional: envir)
symbol.pattern(optional: envir)
symbol.routine(optional: envir)
symbol.window(optional: envir)
Additionally, the following introspection methods are provided:
Nevent:envirs
: Dictionary with all Nenvirs stored in Library under\environments
Registry;envirs
: Synonym ofNevent:envirs
\envirName.synths
: All synth players of Nenvir named\envirName
.\envirName.patterns
: All EventStreamPlayers of Nenvir named\envirName
.\envirName.routines
: All Routines of Nenvir named\envirName
.\envirName.windows
: All windows of Nenvir named\envirName
.
Method implementation code
The code implementing these methods for Symbol
in Nevent.sc
is:
// Return nil if no element found.
// Also, do not push the environment if created.
synth { | envir |
^Registry.at (envir.asEnvironment (false), \synths, this);
}
pattern { | envir |
^Registry.at (envir.asEnvironment (false), \patterns, this);
}
routine { | envir |
^Registry.at (envir.asEnvironment (false), \routines, this);
}
window { | envir, rect |
// always construct new window, and push the environment
^Registry (envir.asEnvironment, \windows, this, {
Window (this, rect)
.onClose_ ({ | me | me.objectClosed; });
})
}
Tests
#+BEGIN_SRC javascript //: explicitly push \envir as currentEnvironment (only for clarity). \envir.push; //: Initially, no synth is available: \envir.synths; //: Also no synth is available in //: Play a function in envir: { PinkNoise.ar(0.2) } +>.envir \test; #+END_SRC
Basic Classes
Notification
Notification: Register a function callback to a message and a pair of objects.
Notification
is an improved version of the NotificationCenter class. This class enables objects to register a function which is called when another object changes according to some aspect. The aspect is expressed by a symbol. To trigger a notification action, one sends the message changed
to an object together with a symbol expressing the aspect which changed. The function will only be evaluated for those objects which have registered to listen to the changing object according to the given aspect symbol. This allows detailed control of callback execution amongst objects of the system. For example, one can tell a gui object to change its color to green when a synth starts, and to change its color to red when that synth stops.
-
=Notification= adds the following enhancements:
- Ability to remove existing notification connections reliably.
- Several methods for easily adding or modifying connections between objects.
- Ability to remove all connections from an object with a single message,
objectClosed
, and also to execute additional actions when that object closes. Closing here means that the object is freed and will no longer be used by the system.
Examples
onObjectClosed
Registry
uses onObjectClosed
to remove objects by sending them that message:
*new { | ... pathAndFunc |
var path, makeFunc, instance;
makeFunc = pathAndFunc.last;
path = pathAndFunc[0..pathAndFunc.size-2];
instance = Library.global.atPath(path);
if (instance.isNil) {
instance = makeFunc.value;
Library.global.putAtPath(path, instance);
instance.onObjectClosed(this, {
this.remove(*path)
});
};
^instance;
}
addNotifierOneShot
This is in turn used by oscFunc
and rout
methods for removing OSCFunc
and Routine
objects in order to replace them with new instances.
The addNotifierOneShot
method executes the registered Function just once. This is useful when it is clear that only one call is required. For example, the onEnd
and onStart
methods for Node
make use of the addNotifierOneShot
method:
+ Node {
onStart { | listener, action |
NodeWatcher.register(this);
listener.addNotifierOneShot(this, \n_go, action);
}
onEnd { | listener, action |
NodeWatcher.register(this);
// this.isPlaying = true; // dangerous
listener.addNotifierOneShot(this, \n_end, action);
}
}
Similar extensions can be built for whatever object needs to monitor the closing or end of an object such as closing of a Window or stopping of a Pattern.
Nevent
Player
\test.player; // Obtain the player stored under name \test.
The Player
class plays Synths and Patterns in sc-hacks. Its features are:
- Players are accessible per name using the message player (
name.player
) - Players can play either synths or patterns
- A synth can be created in Players either from a Function or from the name of a SynthDef.
- A pattern can be created from an Event, whose values may contain patterns.
- A pattern playing be modified while playing in a player by adding key-value pairs from any other event.
- Creating a new synth in a Player releases any previously running synth or pattern, replacing it with the new synth.
- Sending an event to a player to be played will replace any previous synth with a pattern made from the event. If a pattern is already playing in that player, then the event's patterns are merged to those of the pattern playing.
- Changes between subsequent synths or patterns happen with a cross-fade envelope.
- A player always plays in an EnvironmentRedirect which informs it about changes done to its values. Setting environment variables (keys) in that environment will update the values of control parameters in the corresponding synth played by the player.
- One can play control-rate synths and map them to the inputs of a player synth via its environment.
Player
Player creates either a SynthPlayer or a PatternPlayer depending on the kind of argument that is to be played.
receiver | argument | method / action | result returned |
Nil | Function | makeSource | new SythPlayer |
Nil | Symbol | makeSource | new SynthPlayer |
Nil | Event | makeSource | new PatternPlayer |
SynthPlayer | Function | clearPreviousSynth | old SynthPlayer |
SynthPlayer | Symbol | clearPreviousSynth | old SynthPlayer |
SynthPlayer | Event | clearPreviousSynth | new PatternPlayer |
PatternPlayer | Function | stop pattern | new SynthPlayer |
PatternPlayer | Symbol | stop pattern | new SynthPlayer |
PatternPlayer | Event | merge into pattern | old PatternPlayer |
The decision table above shows the kinds of SourcePlayer that are generated in response to different types of receiver and argument pairs.
Auxiliary Classes
SourcePlayer
SynthPlayer
Release and SynthDef clean up on synth end
When the synth ends:
- Set process to nil if it is not identical with the synth that just ended
- Remove the SynthDef from the server if:
- It is not identical to the source stored in source.
- It is temporary.
The only fine point in this process is 2.2: Checking for temporary.
PatternPlayer
EventPattern: Modify patterns while playing
~pattern = EventPattern(()); // create an EventPattern from an empty Event
~player = ~pattern.play; // play the pattern and store streamplayer in player
// modify the stream while it is playing
~player.originalStream.event[\degree] = Pwhite(0, 7).asStream;
~player.originalStream.event[\dur] = 0.1;
EventPattern
is a subclass of Pattern
that can play Events
. It plays in a manner similar to Pbind
, with the difference that it uses an Event
instead of an Array
to store its key-value pairs of streams. To play, EventPattern
creates a new kind of stream called EventStream
. EventStream
stores the event, so that it is accessible from the EventStreamPlayer
that playes the EventPattern
stream. This means that one can modify the streams in the pattern while it is playing. For example:
~player = ().p;
~player addEvent: (dur: 0.25, degree: Pbrown(-10, 10, 3));
The methods p
and addEvent
work as shortcuts for playing events as EventPatterns and modifying the resulting EventStreamPlayer:
PersistentBus
PersistentBusProxy
reated by symbol @.adverb symbol
.
Helps player bus links with extra specs provided by @
, when using *>
, *<
operators.
Utility Classes
Functions and Synth Playing
Improving Function:eplay
Individual improvement steps for Function:eplay: Done and TODO. Thesse notes are from an early stage in development. Now the functionality of most done steps has been integrated in class SynthPlayer
.
#+HTML:
Done (11 Jun 2017 21:48)
- Store the resulting synth in envir under
[\synths, synthName]
(default forsynthName=: =synth
). - If previous synth exists under
[\synths, synthName]
path, then free/release it. - When a synth ends, remove it from its path in envir.
- Make sure that when a synth
previousSynth
is released by replacing it through a new synth created throughFunction:eplay
, thepreviousSynth
ending does not remove the new synth that has already been stored in the same path. - Notification actions for updates from enviroment variable changes are added when the Synth starts (not when the
Function:play
message is sent). This way, there are no more errors "Node not found" when changing the environment before the synth has started.
Done (19 Jun 2017 10:52):
Synth start-stop (release) and set control from Event:eplay (EventPatterns)
The following will add some complexity to the current scheme and may therefore be added later on only.
- Define
SynthPlayer
to add synth start-stop capabilities toFunction:eplay
- Add synths instance variable to Nevent for faster access to SynthPlayers
In addition to listening for changes from the currentEnvironment
, a second notification mechanism should be added to Function:eplay, to enable creating new synths, releasing or freeing this synth when playing EventPatterns with an EventStreamPlayer
. A simple way to do this is by a stream which generates the commands to be played. The stream is stored in a key with the same name as the synth to be controlled. Function:eplay or SynthPlayer:play adds the environment as notifier to the synth with the synth's name as message and a switch statement which choses the action to be performed depending on the value that was sent in addition to the key (message) by the changed message. A draft for this code has been added now to Function:eplay:
synth.onStart (this, {| myself |
// "Synth started, so I add notifiers for updates".postln;
argNames do: { | param |
synth.addNotifier (envir, param, { | val |
synth.set (param, val);
});
// Experimental:
synth.addNotifier (envir, name, { | command |
// command.postln;
switch (command,
\stop, {
synth.objectClosed;
synth.release (envir [\releaseTime] ? 0.02);
},
{ postf ("the command was: %\n", command)}
)
})
};
});
Still TODO (19 Jun 2017 10:52)
Synth start-stop notifications for GUI and other listeners
- When a new synth starts or when an old synth is removed, it should emit notifications so that GUIS that depend on it update their status. Such guis may be:
- Start-stop buttons
- Guis with widgets for controlling the synths parameters. When a new synth starts, then the old gui should be replaced with a new one with widgets corresponding to the new synth's control parameters.
Note: Possibly the notification should be emitted from the \synthName
under which the synth is stored. The messages may be:
\started
\stopped
\replaced
The above is subject to testing.
Patterns
PLAYING Events as Patterns
:PROPERTIES: :filename: playing-events-as-patterns :END:
-
/sc-hacks/ *sc-hacks* /sh-hacks/ provides two ways to play Events as patterns:
\name pplay: (event ...)
Play event as EventPattern, using defaultplay
key settings, i.e. playing notes unless something else is specified.(event ...) eplay: \name
Play event as EventPattern using a custom partial event type\envEvent
. This does not produce any sounds per default, but only writes the results of playing all streams in the event, including the default translations of\degree
,\ctranspose
and related keys, tocurrentEnvironment
. These changes are broadcast to the system using thechanged
mechanism and can be used to drive both GUI and synths.
#+HTML:
Both pplay
and eplay
use EventPattern
, EventStream
and EventStreamPlayer
. This means, it is easy to modify the event and thus change the behaviour of the player, while it is playing.
Symbol pplay
Here the pattern is modified with addKey
, addEvent
and setEvent
while playing:
#+BEGIN_SRC javascript \p.pplay; //: \p.pplay ((freq: Pbrown (10, 200, 10, inf) * 10)); //: \p.pplay ((degree: Pbrown (1, 20, 3, inf))); //: \p.addKey (\dur, 0.1); //: \p addEvent: (ctranspose: Pstutter (20, Pbrown (-6, 6, 1, inf)), legato: Pseq ([0.2, 0.1, 0.2, 4], inf)); //: \p setEvent: (freq: Pwhite (300, 3000, inf), dur: Pseq ([0.1, 0.3], inf), legato: 0.1); #+END_SRC
Event eplay
#+BEGIN_SRC javascript (freq: Pwhite (400, 20000, inf).sqrt, dur: 0.1).eplay; \window.vlayout (CSlider (\freq)).front; /: (freq: Pwhite (400, 2000, inf), dur: 0.1).eplay; //: \test splay: { SinOsc.ar (\freq.kr (400), 0, 0.1) }; //: (degree: Pbrown (0, 20, 2, inf), dur: 0.1).eplay; //: Play different functions in parallel, with crossfade: ~fadeTime = 2; { var players; players = Pseq ([ { LFPulse.ar (\freq.kr (400) [2, 3], 0, 0.5, 0.1) }, /* { Blip.ar (\freq.kr (400 * [1, 1.2]), LFNoise0.kr (5).range (5, 250), 0.3) }, ,*/ { LPF.ar ( Ringz.ar (PinkNoise.ar (0.1), \freq.kr (400) * [1, 1.5], 0.1), LFNoise2.kr (1).range (400, 1000) ) } ], inf).asStream; loop { \test splay: players.next; 2.5.wait; } }.fork;
//: NOTE: other keys are overwritten in the source event of the other #+END_SRC
Playing Events as Patterns
:PROPERTIES: :filename: events-as-patterns :END:
-
sc-hacks provides two ways to play Events as patterns:
\name pplay: (event ...)
Play event as EventPattern, using defaultplay
key settings, i.e. playing notes unless something else is specified.(event ...) eplay: \name
Play event as EventPattern using a custom partial event type\envEvent
. This does not produce any sounds per default, but only writes the results of playing all streams in the event, including the default translations of\degree
,\ctranspose
and related keys, tocurrentEnvironment
. These changes are broadcast to the system using thechanged
mechanism and can be used to drive both GUI and synths.
#+HTML:
Both pplay
and eplay
use EventPattern
, EventStream
and EventStreamPlayer
. This means, it is easy to modify the event and thus change the behaviour of the player, while it is playing.
Symbol pplay
Here the pattern is modified with addKey
, addEvent
and setEvent
while playing:
#+BEGIN_SRC javascript \p.pplay; //: \p.pplay ((freq: Pbrown (10, 200, 10, inf) * 10)); //: \p.pplay ((degree: Pbrown (1, 20, 3, inf))); //: \p.addKey (\dur, 0.1); //: \p addEvent: (ctranspose: Pstutter (20, Pbrown (-6, 6, 1, inf)), legato: Pseq ([0.2, 0.1, 0.2, 4], inf)); //: \p setEvent: (freq: Pwhite (300, 3000, inf), dur: Pseq ([0.1, 0.3], inf), legato: 0.1); #+END_SRC
Event eplay
#+BEGIN_SRC javascript (freq: Pwhite (400, 20000, inf).sqrt, dur: 0.1).eplay; \window.vlayout (CSlider (\freq)).front; /: (freq: Pwhite (400, 2000, inf), dur: 0.1).eplay; //: \test splay: { SinOsc.ar (\freq.kr (400), 0, 0.1) }; //: (degree: Pbrown (0, 20, 2, inf), dur: 0.1).eplay; //: Play different functions in parallel, with crossfade: ~fadeTime = 2; { var players; players = Pseq ([ { LFPulse.ar (\freq.kr (400) [2, 3], 0, 0.5, 0.1) }, /* { Blip.ar (\freq.kr (400 * [1, 1.2]), LFNoise0.kr (5).range (5, 250), 0.3) }, ,*/ { LPF.ar ( Ringz.ar (PinkNoise.ar (0.1), \freq.kr (400) * [1, 1.5], 0.1), LFNoise2.kr (1).range (400, 1000) ) } ], inf).asStream; loop { \test splay: players.next; 2.5.wait; } }.fork;
//: NOTE: other keys are overwritten in the source event of the other #+END_SRC
Modify Patterns while playing
:PROPERTIES: :filename: event-stream :END:
An EventStream makes it easy to modify patterns while playing them.
#+HTML:
How it works
An EventPattern
creates EventStreams
which are played by EventStreamPlayer
. EventStream
stores an event whose values are streams and uses this to produce a new event which is played by EventStreamPlayer. It is thus possible to modify the event stored in the EventStream while the EventStreamPlayer is playing it. This makes it easy to modify a playing pattern (which is difficult when using Pbind).
Example
#+BEGIN_EXAMPLE
() > \e;
0.1 +>.dur \e;
Pseq ((0..20), inf) +>.degree \e;
Prand ((0..20), inf) +>.degree \e;
(freq: Pfunc ({ 300 rrand: 1000 }), dur: Prand ([0.1, 0.2], inf)) +> \e;
(freq: Pfunc ({ 30.rrand(80).midicps }), dur: Pfunc ({ 0.1 exprand: 1 }), legato: Prand ([0.1, 2], inf)) +> \e;
(degree: Pseq ((0..20), inf), dur: 0.1) !>.degree \e;
#+END_EXAMPLE
Note:
+>
adds all key-value pairs of an event to the event being played.+!>
replaces the contents of the event being played by the contents of the new event.
Patterns updating current environment
:PROPERTIES: :filename: event-eplay :END:
anEnvironment.eplay(envir)
Play an event as EventPattern, updating the values in envir from each event. Use custom event type: envEvent. This only updates the values in the environment. The sound must be generated by Function:eplay
. See examples below.
#+HTML:
Play the receiver Event in the environment given in argument envir
using a custom event type to just set the values of corresponding variables at each event. For example, playing ()
set ~freq
to 60.midicps
every 1 second.
#+BEGIN_SRC javascript (dur: 0.1).eplay; #+END_SRC
#+BEGIN_SRC javascript ().eplay; #+END_SRC
#+BEGIN_SRC javascript () eplay: \newEnvir; #+END_SRC
Example 1: Single synth, pattern, gui update
Example 2: Envelope gate on-off
#+BEGIN_SRC javascript (degree: Pbrown (0, 30, 2, inf), dur: 0.1).eplay; { | freq = 400 | SinOsc.ar (freq, 0, 0.1 )}.eplay; \window.vlayout (CSlider (\freq)).front; #+END_SRC To test triggering of envelopes, instead of writing this:
#+BEGIN_SRC javascript { | freq = 400, gate = 0 | var env; env = Env.adsr (); SinOsc.ar (freq, 0, 0.1) ,* EnvGen.kr (env, gate: gate, doneAction: 0) }.eplay; //: { inf do: { | i | ~gate = i % 2; 0.1.rrand (0.5).wait } }.rout; #+END_SRC
one may write this:
#+BEGIN_SRC javascript { | freq = 400, gate = 0 | var env; env = Env.adsr (0.01, 0.01, 0.9, 0.3); SinOsc.ar (freq, 0, 0.1) ,* EnvGen.kr (env, gate: gate, doneAction: 0) }.eplay;
Example 3: Many patterns + many synths in same environment
(dur: Pwhite (0.01, 0.15, inf), gate: Pseq([0, 1], inf)).eplay; //: Note the overlap with the still playing note in the freq change: (dur: 0.15, gate: Pseq([0, 1], inf), degree: Pstutter (4, Pbrown (-5, 20, 5, inf))).eplay; #+END_SRC
#+BEGIN_SRC javascript { | freq = 400 | SinOsc.ar (freq * [1, 1.25], 0, 0.1) }.eplay; (dur: 0.1, degree: Pbrown (-1, 20, 2, inf)).eplay; //: Start the next synth later: { | freq = 400 | Ringz.ar (PinkNoise.ar, freq * 1.2) }.eplay (\synth2); //: Test fade out when releasing synth to replace with new one: ~fadeTime = 5; //: { | freq = 400, ringTime = 3 | Ringz.ar (PinkNoise.ar, freq * [2, 1.2], ringTime) }.eplay (\synth2); //: ~ringTime = 0.03; //: ~ringTime = 2; //: Start a new pattern in place of the old one: (dur: 0.1, degree: Pbrown (-1, 20, 2, inf), ringTime: Pwhite (0.01, 1.2, inf)).eplay; //: remove the ringTime from the next version of the pattern: (dur: 0.1, degree: Pbrown (-1, 20, 2, inf)).eplay; //: add a new pattern on the same environment, playing ringTime: (dur: 0.01, ringTime: Pbrown (0.001, 3, 0.1, inf)).eplay (\ringTime);`` //: add a new pattern on the same environment, playing ringTime: (dur: 0.1, ringTime: Pwhite (0.01, 4, inf)).eplay (\ringTime); //: #+END_SRC
GUI
Creating GUIs
This library provides utilities for creating GUI elements and connecting them to both patterns and synths. Here are some examples.
Windows
Sliders
Buttons
Building GUIs
This notebook shows how to build GUIs starting from scratch, with minimal code and in small steps.
#+HTML:
Just a window
#+BEGIN_SRC javascript //: 1 Just a window Window ("window name").front; #+END_SRC
Add a slider and resize.
Use VLayout for automatic resizing.
#+BEGIN_SRC javascript
Window ("window name") .view.layout_ ( VLayout ( Slider () ) ).front; //: Make the slider horizontal. Window ("window name") .view.layout_ ( VLayout ( Slider () .orientation_ (\horizontal) ) ).front; #+END_SRC
Add more sliders
Use a function to avoid repeating spec code! Make the orientation an argument to enable customization.
#+BEGIN_SRC javascript var makeSlider;
makeSlider = { | orientation = \horizontal | Slider () .orientation_ (orientation) };
Window ("window name") .view.layout_ ( VLayout ( *({makeSlider.([\horizontal, \vertical].choose)} ! 10) ) ).front;
#+END_SRC
Add more arguments for controlSpec, label, and action
#+BEGIN_SRC javascript
var makeSlider;
makeSlider = { | label = "slider", min = 0, max = 10, step = 0, default = 0.5, orientation = \horizontal, action | var controlSpec; controlSpec = [min, max, \lin, step, default].asSpec; // provide default action if needed: action ?? { action = { | value | controlSpec.map (value).postln } }; HLayout ( StaticText ().string_ (label), Slider () .action_ ({ | me | action.(me.value)}) .orientation_ (orientation) ) };
Window ("window name") .view.layout_ ( VLayout ( *({makeSlider.([\horizontal, \vertical].choose)} ! 10) ) ).front;
#+END_SRC
Add number box, after the slider.
#+BEGIN_SRC javascript
var makeSlider;
makeSlider = { | label = "slider", min = 0, max = 10, step = 0, default = 0.5, orientation = \horizontal, action | var controlSpec; controlSpec = [min, max, \lin, step, default].asSpec; // provide default action if needed: action ?? { action = { | value | controlSpec.map (value).postln } }; HLayout ( StaticText ().string_ (label), Slider () .action_ ({ | me | action.(me.value)}) .orientation_ (orientation), NumberBox ()) };
Window ("window name") .view.layout_ ( VLayout ( *({makeSlider.( max: 0.0.rrand (20.0), orientation: [\horizontal, \vertical].choose)} ! 10) ) ).front;
#+END_SRC
Add value update for number box and slider
#+BEGIN_SRC javascript
var makeSlider;
makeSlider = { | label = "slider", min = 0, max = 10, step = 0, default = 0.5, orientation = \horizontal, action | var controlSpec, mappedVal, slider, numberBox; controlSpec = [min, max, \lin, step, default].asSpec; // provide default action if needed: action ?? { action = { | value | value.postln } }; HLayout ( StaticText ().string_ (label), slider = Slider () .action_ ({ | me | mappedVal = controlSpec.map (me.value); numberBox.value = mappedVal; action.(mappedVal)}) .orientation_ (orientation), numberBox = NumberBox () .action_ ({ | me | mappedVal = me.value; slider.value = controlSpec.unmap (mappedVal); action.(mappedVal) }) ) };
Window ("window name") .view.layout_ ( VLayout ( *({ | i | makeSlider.(format ("slider %", i), max: 0.0.rrand (20.0), orientation: [\horizontal, \vertical].choose) } ! 10) ) ).front;
#+END_SRC
Group the controlSpec arguments to make them more compact
Also initialize the values of gui elements
#+BEGIN_SRC javascript var makeSlider;
makeSlider = { | label = "slider", controlSpec, orientation = \horizontal, action | var mappedVal, slider, numberBox;
// convert controlSpec arg into ControlSpec controlSpec = (controlSpec ? []).asSpec; // provide default action if needed: action ?? { action = { | value | value.postln } }; HLayout ( StaticText ().string_ (label), slider = Slider () .action_ ({ | me | mappedVal = controlSpec.map (me.value); numberBox.value = mappedVal; action.(mappedVal)}) .orientation_ (orientation) .value_ (controlSpec unmap: controlSpec.default), numberBox = NumberBox () .action_ ({ | me | mappedVal = me.value; slider.value = controlSpec.unmap (mappedVal); action.(mappedVal) }) .value_ (controlSpec.default) ) };
Window ("window name") .view.layout_ ( VLayout ( ([\freq, \amp] collect: { | name | makeSlider.(format (" %", name), controlSpec: name, orientation: \horizontal / [\horizontal, \vertical].choose */ ) }) ) ).front; #+END_SRC
- How to add all available options for Slider and NumberBox, eg. floating point digits etc?
Instead of adding more arguments, move to the next step: Define a class and provide the options as defaults.
- Next steps
- Control a routine from a GUI.
- Let the GUI elements set environment variables of an environment.
- Let the routine run within this environment to have access to its variables.
- Save the current settings on file.
- Reload settings from file.
- Do similar things as with 1, but with a synth instead of a routine.
- Do similar things as with 1, but with an EventPattern instead of a routine.
Playing buffers and grains
Triggering Methods
This section illustrates several different ways to trigger grains, envelopes or other gate-triggered UGens
Triggering from lang with set
Triggering internally UGens
Impulse, Dust etc.
Demand UGens
Triggering EnvGen and Line
Besides controlling the amplitude shape of a single sound-event or note in its entirety, envelopes can also be used to generate streams of sound events with the same synth. The same technique can also be applied to Line or Xline to arbitrarily construct shapes controlling any parameter on the fly. Here are some examples explaining how to do this using either just the default SCClassLibrary and the sc-hacks library.
-
Two items are necessary to re-trigger envelope or line-segment UGens in a Synth:
- The
doneAction
argument of the UGen in question must have a value of 0 in order to keep the Synth alive afer the UGen has finished playing. - The UGen in question must use a control in its
gate
argument. The UGen restarts whenever the value of thegate
control changes from 0 to 1. Furthermore, if the Envelope contains a release part, then the release part will be triggered when thegate
control changes from 1 to 0.
Caution: Use a name other than gate
as control name for triggering
*NOTE: The control used to trigger the EnvGen must have a name different than gate
, otherwise SC will be unable to release the synth because it creates a second gate
control by default when creating synthdefs from functions. For example:
#+BEGIN_SRC javascript : This is wrong: a = { | freq = 400, gate = 0 | var env; env = Env.adsr (); SinOsc.ar (freq, 0, 0.1) ,* EnvGen.kr (env, gate: gate, doneAction: 0) }.play; NodeWatcher.register (a); register a to update state when it ends : Test if a is playing a.isPlaying; first let it make a sound: a.set (\gate, 1); : then try to release it: a.release; //: The output amplitude is 0 but the synth is still playing: a.isPlaying; //: Try again, specifying a release time: a.release (0.1); //: To really stop the synth, you have to free it a.free; //: The synth has stopped playing. Synth count should be 0 in the server display a.isPlaying; returns false #+END_SRC
Function play and Synth set (SCClassLibrary)
Here is a simple example using only the standard SCClassLibrary:
#+BEGIN_SRC javascript : Start a triggerable synth and store it in variable a Note: The EnvGen will not be triggered because trig is 0. // Thus the synth will not produce an audible signal.
// use trig instead of gate as control name! a = { | freq = 400, trig = 0 | var env; env = Env.adsr (); SinOsc.ar (freq, 0, 0.1) ,* EnvGen.kr (env, gate: trig, doneAction: 0) }.play; : Trigger the envelope a.set (\trig, 1); //: Release the envelope a.set (\trig, 0); //: Re-trigger the nevelope a.set (\trig, 1); //: Release the envelope again a.set (\trig, 0); //: Run a routine to start and stop the synth several times { 10 do: { | i | a.set (\trig, (i + 1) % 2); i+1 : start with trigger on 0.01.exprand (0.5).wait; } }.fork; : Do the same as above, but also change the frequency { 40 do: { | i | only change frequency when starting, not when releasing: if (i + 1 % 2 > 0) { // i / 2: wholetone -> chromatic a.set (\freq, (i / 2 + 60).midicps, \trig, 1); }{ a.set (\trig, 0); }; 0.01.exprand (0.5).wait; }; }.fork; //:
#+END_SRC
Control through environment variables, using Function +>
In sc-hacks, the operator +>
plays a function and stores its synth in the global Library using a SynthPlayer
instance. Additionally, it makes the synth react whenever an environment variable whose name corresponds to a control parameter of the synth is set in the currentEnvironment
. This makes it easy to control the synth through the environment.
#+BEGIN_SRC javascript
{ | freq = 400, trig = 0 |
var env;
env = Env.adsr ();
SinOsc.ar (freq, 0, 0.1)
,* EnvGen.kr (env, gate: trig, doneAction: 0)
} > \test;
//:
~trig = 1;
//:
~trig = 0;
//:
{
var trig;
inf do: { | i |
trig = i 1 % 2;
if (trig > 0) { ~freq = 300 rrand: 3000 };
~trig = trig;
0.9.rand.wait;
};
}.rout;
// rout starts a routine like fork, and stores it in the library
// under a name (default: 'routine')
// Additionally, if a routine is running under the same name,
// it stops that routine before starting the new one to replace it.
/:
{
var trig;
26 do: { | i |
trig = i + 1 % 2;
if (trig > 0) { ~freq = (i 2 + 60).midicps };
~trig = trig;
0.24.wait;
};
}.rout;
//:
{
20 do: { | i |
~trig = i + 1 % 2;
~freq = 300 rrand: 3000;
0.9.rand.wait;
}
}.rout;
#+END_SRC
Playing Grains
Here are some examples for playing grains of sound from a buffer with
To be provided ...
Server objects
About Server Objects
Server objects managed by Nevent
These are:
- The Group which is used to create all Synths in the event.
- Busses connecting inputs or outputs of Synths in the event to those of other events.
- Buffers used for playing by Synths
Allocating Server Objects at Reboot
Server and Nevent
Each Nevent
Environment has only one Server instance. Synths and Patterns play in that instance.
Open issue: Where to store the server of an Environment. Possibilities:
- As variable
~server
- In Library using Registry, using
[environment \server]
as key - In prototype event for Nevent. (Needs to be programmed yet).
- Do not store, use
Server.default
Currently option 4 is used, to concentrate on finishing the other features of the library. Adding multiple servers will probably require a NeventGroup
class whose instances create and manage groups of Nevents playing on the same server. NeventGroup
will store the selected Server instance in the parent event of each Nevent
instance which it creates.
Target Group
Maintaining Target Group Order
Busses
Bus Allocation
Bus Index Parameter Updates
Buffers
Use examples
Pattern and Synth play scenarios
:PROPERTIES: :filename: pattern-synth-play :END:
Event:eplay
and SynthPlayer
can play with several instances in parallel on the same environment, and also provide several additional features. This section gives some examples that explore these features and show how they work and notes some questions regarding future features.
#+HTML:
Creating, starting and stopping patterns
Playing several patterns in the same environment
Playing several synths in the same environment
Playing with data
Playing With Data
:PROPERTIES: :filename: playing-with-data :END:
This section discusses problems and approaches for sonifying data using EventPlayer and SynthPlayer.
Data dimensions and play method
The data is a 2 dimensional array. It is played as a Pseq
along the first dimension. This results in a stream of rows. Each subsequent element returned by the stream is a row of data.
Different ways of playing rows
Playing each data row as one Synth
Playing each d
Making this site
This site was made with hugo using the docuapi theme.
You can clone the source for this site from: https://github.com/iani/sc-hacks-doc.
Contact: zannos [at] gmail.com