Skip to content
/ tiny-sc Public

Compact but powerful tools for SuperCollider, with Emacs Org-mode intergration

License

Notifications You must be signed in to change notification settings

iani/tiny-sc

Repository files navigation

Tiny-sc / Branch: retiny

Compact coding tools for SuperCollider, with Emacs Org-mode intergration

Canis plenus et libum totum

(Libum mandere et habere)

This is version 1.2alpha (retiny).

Installation

  • Download the library from http://github.com/iani/tiny-sc.
  • Place the tiny-sc folder in the SuperCollider Extensions folder (found by executing this in SuperCollider):
    Platform.userExtensionDir;
        
  • Recompile the library (Command-Shift-L or menu Lang Recompile in the IDE).

Introduction

Basic concepts

Tiny-sc stores objects that create synths and tasks in the global Library under symbols, so that they can be recalled easily by their names, in order to play, start, stop, modify, and combine them. There are two main types of objects: A SynthPlayer is an object that can execute a function or create a synth. A TaskPlayer is an object that plays tasks. One can play a SynthPlayer on its own, or one can attach a SynthPlayer to a TaskPlayer in order to play a sequence of events.

SynthPlayer and TaskPlayer each have their own type of operator:

+>
Do something with a SynthPlayer
*>
Do something with a TaskPlayer

Basic example: Set the source of a SynthPlayer, and then play it:

{ SinOsc.arps(Rand(400, 1000)).perc } +> \test0; // set the source
\test0.play; // play the source

Tiny-sc is written so that one can achieve an audible change in the sound with a single statement, as far as possible. The example above can be written in one statement like this:

{ SinOsc.arps(Rand(400, 1000)).perc } ++> \test0; // ++> = set the source and play

One can combine a sound-source and a task expression in one statement like this:

{ SinOsc.arps(Rand(400, 1000)) * 0.3 } +> \test0 *> 0.1; // play a sequence of events

The above creates a SynthPlayer test0 playing the function as source, and schedules it to play events with a TaskPlayer named test0. To stop the Task player, run:

\test0.stop;

There are two variants of the basic operators +> and *>:

++>
Send a new function or SynthDef name to a SynthPlayer, and play it immediately. NOTE: This also disconnects the SynthPlayer from any task that it may be attached to.
**>
Modify a TaskPlayer, but do not start it.

For example:

Send a new SynthDef function to SynthPlayer test0, and play it:

{ SinOsc.arp(Rand(1000, 2000)).perc } ++> \test0;

Send duration 2 to TaskPlayer test0 and start it:

2 *> \test0;

Send a func pattern as duration to TaskPlayer test0, but do not start it. Change will take effect at the next event played by the already playing TaskPlayer:

Pfunc({ 0.01 rrand: 0.5 }) **> \test0;

Stop:

\test0.stop;

Synth Processes and their parameters

Tiny-sc uses class SynthPlayer to define containers that can run synths. It also defines some shortcuts for commonly used UGen combinations and patterns and for control UGen arugments such as amp, pan and various envelopes. SynthPlayer instances are accessed by their name. Playing a SynthDef function or a SynthDef name will start a new Synth and fade out the previous one:
{ WhiteNoise.ar(0.1) } ++> \test; // ++> starts immediately

Play different synthdef, replace previous synth:

"default" ++> \test; // play with SynthDef "default"

Parameters of a SynthPlayer’s synth process can be set as numbers:

1000 +>.freq \test;

Or as patterns:

(50..40).midicps.pseq +>.freq \test;

Play again, using the next values from argument patterns:

\test.play;

Release with custom fade-out duration:

\test.release(3);

Starting and stopping, Tasks and SynthPlayers

Overview of methods (messages) sent to symbols to play with SynthPlayer and TaskPlayer instances:

To play SynthPlayers named by a symbol:
send that symbol messages play to start, release to stop.
To play tasks named by a symbol:
send that symbol start to start playing, stop to stop playing

In other words:

Examples:

"default" +> \c; // set source of SynthPlayer c to SynthDef "default", but do not play
\c.play;         // play SynthPlayer c

Release (stop) SynthPlayer:

\c.release;      // stop playing synthPlayer c
\c **> \c; // attach synthPlayer c to task c, without starting
\c.start;  // start task c explicitly. Default event duration is 1 second
\c.stop; // stop task c

TaskPlayers stop when their duration pattern ends:

{ SinOsc.arp(\freq.kr(400)).perc } +> \c;
[60, 65, 67, 72].midicps.pseq +>.freq \c; // set an endless frequency patern to c
0.1.pn(16) *> \c;  // Play the c synthPlayer with a task using a duration pattern of 16 events

Playing sequences of synth events

A SynthPlayer can play with a duration pattern, which runs in a TaskPlayer.

It which may be a single number:

{Klank.arps(`[[40,120,200]*Rand(1,9),nil,(3..1)],PinkNoise.ar(0.1))}+>\test1*>1;

or a pattern:

\test1 *> [Pn(0.05, 50) ! 2, Pn(0.2, 10), Pn(2, 2)].flat.prand;

Set the fadeTime to create texture of cross-fading tones:

\test1.fadeTime = 1;

Stop the pattern that drives the SynthPlayer:

\test1.stop;

Task Player filters

A SynthPlayer can be driven by a TaskPlayer to run patterns in a similar way as Pbind. The difference is that any key of a pattern as well as the duration pattern can be changed at any time. Furthermore, a SynthPlayer can switch TaskPlayers at any time, and can also be made to respond to a TaskFilter which only plays at beats that match a given condition. In this way, several SynthPlayer instances can be synchronized by following the same TaskPlayer or its attached filters.

Start the test2 SynthPlayer with a SynthDef function, and a pattern:

{ Blip.arps(\freq.kr(400), 3) } +> \test2 *> 0.1;
(60..90).midicps.prand +>.freq \test2

Add a second synthPlayer to the same pattern:

(Note: +> waits to be triggered, and **> does not restart the task)

{ SinOsc.arps(Rand(2000, 3000)).perc } +> \twock **> \test2;

Currently there is only one kind of filter, which is created by adding an adverb with its pattern to the *> operator. The adverb may consist of characters x, o and _. Their meanings are:

x
play new note for this beat.
o
stop note, remain silent.
_
hold previous note (do not release).

Important: The subfilter to a Task is registered under its own name, which is created automatically by prepending underscore =_= to the name of the parent task.

In the example below, the filter task of test2 has the name _test2.

Example:

\test2 **>.x___xoxox_x_xoxxxooo \test2; // **> : Do not start parent task

Remove synthPlayer named test from its task - but leave the other SynthPlayers still attached:

\test2.removeTask;

Stop the task, and all it’s dependent SynthPlayers:

\test2.stop;

Here is a more complicated example:

"default" +> \high; // set source of synthPlayer high
Pwhite(70, 90, inf).midicps +>.freq \high; // set frequency pattern for high
{ Blip.arps(Rand(50, 200), Rand(1, 10)).perc * 1.2 } +> \low; // set source of synthPlayer low
\high **> \low; // Make high follow task pattern of low
\low *> 0.1;   // Set and start task pattern of low to play beats at 0.1 seconds

Let high play a sub-pattern of the TaskPlayer low.

\high **>.x___x_xoxoxxxooo \low; // creates filter \_low and attaches synthPlayer \high to it

Change the duration pattern of master Task low:

\low **> Prand([Pn(0.08, 4), 0.3], inf);

Make both high and low play on the same filter of low Task;

\high **>.xooox___xxox_xxxx \low; // create filter \_low and attaches synthPlayer \low to it
\low **> \_low; // also make synthPlayer low follow filter \_low

Change base beat pattern and reset low to that root pattern:

\low **> 0.2;

Change base beat pattern again:

\low **> Prand([Pn(0.12, 4), 0.3, 0.6, Pn(0.06, 2)], inf);

Synch synthPlayer low with synthPlayer high again:

\low **> \_low; // also make synthPlayer low follow filter \_low

Stop the master pattern:

\low.stop;

Adding Task filters to Task filters

\name *>.pattern \othername Adds a pattern filter to othername, or substitutees the new pattern to an existing pattern filter =othername=. In order to add a new pattern filter under an existing pattern filter, use the operator *>> or **>>.

Examples:

Example 1

{ SinOsc.arps(2000).perc } +> \level1a *> 0.1;
{ SinOsc.arps(1800).perc } +> \level2a **>.xo \level1a;
{ SinOsc.arps(1500).perc } +> \level3a **>>.xo \_level1a;

Add level1a as pattern filter of the master task leval1a:

\level1a *>.xooxxoxxx \level1a;
\level1a.stop;

Example 2

[1800, 2000, 2400].pseq +>.freq \level1b;
{ SinOsc.arps(\freq.kr(400)).sine } +> \level1b *> [0.1, 0.2].pseq;
{ SinOsc.arps(1500).sine } +> \level2b **>.xoo \level1b;
{ SinOsc.arps(1200).sine } +> \level3b **>>.xoo \_level1b;
{ SinOsc.arps(900).sine } +> \level4b **>>.xoo \__level1b;
{ SinOsc.arps(600).sine } +> \level5b **>>.xoo \___level1b;

As explained above, the task-filters are stored under names generated automatically by prepending _ to the name of the parent task.

\level1b.stop;

Linking audio inputs and outputs between synths

MiniSteno

Note: MiniSteno is inspired by syntax of Steno by Julian Rohrhuber. (See https://github.com/telephon/Steno)

Syntax for creating link configurations from strings

With MiniSteno one can specify the interconnections of several synthPlayer instances to create a tree consisting of parallel and serial branches. The tree is written as a string. The names of the synthPlayers are separated by “.”. Parentheses () indicate serial interconnections. Brackets [] indicate parallel intereconnections. Thus:
"(source.effect)".miniSteno;

… creates a serial interconnection between source and effect. effect reads its input from the output of source.

"[c1.c2]".miniSteno;

… creates a group of parallel synthPlayers. c1 and c2 read from the same input and write to the same output bus.

Playing link configurations

Use the message addBranch to add link configurations to a global tree for playing. The default branch of the tree is root, and adding a branch to root replaces the previous root, thus changing the global configuration. Thus, to install a MiniSteno tree as the root tree that is currently played, send it the message addBranch.

Examples:

Add 2 synthPlayers a and b linked together in series:

"(a.b)".addBranch;
{ WhiteNoise.arp } ++> \a;
{ Ringz.arp(Inp.ar, LFNoise2.kr(1).range(30, 1000), 1) * 0.2 } ++> \b;

Remove the effect from the audible tree:

"a".addBranch

Bring back the effect and add a second effect to it, serially:

"(a.b.c)".addBranch;
{ Inp.ar.abs * Decay2.kr(Dust.kr(3), 0.1, 0.7) } ++> \c;

Linking effects in parallel:

First listen to a single effect in series

"(a.b)".addBranch;
{ Blip.arp(LFNoise2.kr(1).range(40, 400), 5) * 2 } ++> \a;
{ Ringz.ar(Inp.ar, LFNoise1.kr(25).range(30, 1000)) * 0.002 } ++> \b;

Add a second effect in series

"(a.b.c)".addBranch;
{ Inp.ar.abs * LFNoise0.kr(10) } ++> \c;

Now in parallel

"(a[b.c])".addBranch;
{ Inp.ar.abs.sqrt * LFPulse.kr(LFDNoise1.kr(0.3).range(0.6, 20), 0, 0.8, 0.03) } ++> \c;

Vary the effects:

{ Limiter.ar(Ringz.ar(Inp.ar, LFNoise1.kr(10).range(300, 4000)), 0.02) } ++> \b;
{ Inp.ar.sqrt.abs * SinOsc.ar(LFNoise0.kr(10).range(1000, 2000)) * 0.05 } ++> \c;

Add slow variations in amplitude to distinguish:

{ Limiter.ar(Ringz.ar(Inp.ar, LFNoise1.kr(10).range(300, 4000)), 0.02) * LFNoise1.kr(0.3) } ++> \b;
{ Inp.ar.sqrt.abs * SinOsc.ar(LFNoise0.kr(10).range(90, 200)) * 0.05 * LFNoise1.kr(0.3) } ++> \c;

Release with different durations:

\a.release(10);
\b.release(5);
\c.release(2);

Linking to multiple inputs of one SynthPlayer

Connect the reading SynthPlayer instance in multiple serial branches, and use “:” as separator between synthPlayer name and input name to indicate the name of the input.

Create source and effect synths, and listen to the 2 source synths separately:

{ SinOsc.arp (300 * LFPulse.kr(1).range (4, 5)) } ++> \sine;
{ SinOsc.arp (400) } ++> \pulse;
{ Inp.ar (\in1) * Inp.ar (\in2) * 5 } ++> \ringmod;

Link the output of the first source to in1 and the output of the second source to in2:

"(sine.ringmod:in1)(pulse.ringmod:in2)".addBranch;

Multiple voice example

Just a demo with 8 SynthPlayer instances playing on 8 different TaskPlayers.
(
{ | n |
    var name;
    name = format("multi%", n).asSymbol;
    n = n % 4;
    {
        Blip.arps(
            1 + n / 2 * Line.kr(Rand(20, 80).midicps, Rand(20, 80).midicps, Rand(0.1, 1)),
            Line.kr(Rand(5, 25), Rand(5, 25), 0.5)
        ).perform([\perc, \sine]@@n)
    } +> name;

    ([0.25.pn(14), 5 ! 3, 1, 2, 0.1 ! 10, 3] / (0.5 + (n / 4))).flat.prand *> name;
} ! 8;
)

Use task filters to change density of the texture:

\multi0 **>.xoooo \multi0; // use **> to prevent re-starting the master Task
\multi1 **>.xoooo \multi1;
\multi2 **>.xoooo \multi2;
\multi3 **>.x \multi3;
\multi4 **>.xoooo \multi4;
\multi5 **>.xoooo \multi5;
\multi6 **>.xoooo \multi6;
\multi7 **>.xoooo \multi7;

Variation 1:

\multi0 **>.xoooo \multi0;
\multi1 **>.o \multi1;
\multi2 **>.o \multi2;
\multi3 **>.x \multi3;
\multi4 **>.o \multi4;
\multi5 **>.o \multi5;
\multi6 **>.o \multi6;
\multi7 **>.xo \multi7;

Variation 2:

\multi7 **> \multi7;
\multi6 **>.xo \multi7;
\multi5 **>>.xo \_multi7;
\multi4 **>>.xo \__multi7;
\multi3 **>>.xo \___multi7;
\multi2 **>>.xo \____multi7;
\multi1 **>>.xo \_____multi7;
\multi0 **>>.xo \______multi7;

Variation 3:

\multi7 *> 0.1;

Variation 4:

([0.25.pn(14), 5 ! 3, 1, 0.4 ! 10, 0.1 ! 20, 3] / 4).flat.prand *> \multi7;
{ | n | Pfunc({ 0.01 exprand: 0.35 }) +>.amp format("multi%", n).asSymbol } ! 8;

Variation 5:

([0.25.pn(14), 5 ! 3, 1, 0.4 ! 10, 0.1 ! 20, 3] / 4).flat.prand *> \multi7;
\multi7 **> \multi7;
\multi6 **>.xo \multi7;
\multi5 **>>.xo \_multi7;
\multi4 **>>.xo \__multi7;
([0.25.pn(14), 5 ! 3, 1, 0.4 ! 10, 0.1 ! 20, 3]).flat.prand *> \multi3;
\multi3 **>.xoo \multi3;
\multi2 **>>.xoo \_multi3;
\multi1 **>>.xoo \__multi3;
\multi0 **>>.xoo \___multi3;

To end the sound, stop all tasks:

TaskPlayer.stopAll;

Now gradually build some background texture and rhythms with the same synth sources:

0.01 *> \multi0;
0.1 *> \multi1;
\multi2 **>>.xo \multi1;
0.5 +>.amp \multi2;
\multi3 **>>.xo \_multi1;

Speed Limits

Using the precise SynthDef loading method of SynthDefLoader as of v1.2.2alpha, playing patterns with up to 500 events per second is safe, with light-weight SynthDefs, when there is only one task-thread playing, and dependent on general CPU capacity and other load on the machine. See: StressTests.scd.

About

Compact but powerful tools for SuperCollider, with Emacs Org-mode intergration

Resources

License

Stars

Watchers

Forks

Packages

No packages published