NAV
SuperCollider Shell Emacs-Lisp

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:

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:

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

Work done August 2017

Future plans

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

bookmarks, bookmark+

Obtaining emacs

http://www.emacsformacosx.com

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:

  1. 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.
  2. 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):

  1. +-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.
  2. *-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
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:

  1. +> : 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.
  2. <+ : 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).
  3. *> : 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 with play 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.
  4. <* 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.

Additionally, the following introspection methods are provided:

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.

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:

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:

  1. Set process to nil if it is not identical with the synth that just ended
  2. Remove the SynthDef from the server if:
  3. It is not identical to the source stored in source.
  4. 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)

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.

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

Note: Possibly the notification should be emitted from the \synthName under which the synth is stored. The messages may be:

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:
  1. \name pplay: (event ...) Play event as EventPattern, using default play key settings, i.e. playing notes unless something else is specified.
  2. (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, to currentEnvironment. These changes are broadcast to the system using the changed 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:
  1. \name pplay: (event ...) Play event as EventPattern, using default play key settings, i.e. playing notes unless something else is specified.
  2. (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, to currentEnvironment. These changes are broadcast to the system using the changed 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:

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

Instead of adding more arguments, move to the next step: Define a class and provide the options as defaults.

  1. Control a routine from a GUI.
  2. Let the GUI elements set environment variables of an environment.
  3. Let the routine run within this environment to have access to its variables.
  4. Save the current settings on file.
  5. Reload settings from file.
  6. Do similar things as with 1, but with a synth instead of a routine.
  7. 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:
  1. 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.
  2. The UGen in question must use a control in its gate argument. The UGen restarts whenever the value of the gate control changes from 0 to 1. Furthermore, if the Envelope contains a release part, then the release part will be triggered when the gate 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:

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:

  1. As variable ~server
  2. In Library using Registry, using [environment \server] as key
  3. In prototype event for Nevent. (Needs to be programmed yet).
  4. 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