Awesome
INSTRUMENT
pre-alpha
afterthought
INSTRUMENT is a library for livecoding music (beats, basslines, harmony, looping, FX, signal routing, synthesis, etc.) and interfacing with musical instruments and controllers from inside the SuperCollider environment.
// Create a beat
// First, boot server:
s.options.memSize=2048*1024;
s.options.maxNodes=128*1024;
s.boot;
// Then, evaluate following block
(
i = INSTRUMENT();
d = i.synths.drums.electro;
i.drums=(
k: d.kick,
h: d.hihat,
s: d.snare,
);
i.drums.k.seq("1 1 1 1 ").speed(2);
i.drums.h.seq("1");
i.drums.s[0].seq(" 1 1 1 1 1").speed(2).do(3);
i.drums.s[1].seq(" 1x4 1 1x2 1").speed(2).do(1);
i.drums.clock=4;
i.tempo = 150;
)
// Wait a while and then run this block:
(
i.drums.clock=1.5;
i.drums.fx='reverb.large';
)
// Return to first beat, with new FX
(
i.drums.clock=4;
i.drums.fx="gateDistortion.hardcore";
)
// change group
i.drums=(
// k: d.kick,
h: 'snareNeuro',
s: d.snare,
);
// run this
(
i.drums.fx='reverb.infinite';
i.drums.fx.reverb.wet=1/2;
)
// now this
(
i.drums.fx.reverb.wet=1;
i.drums.stop;
)
Create a basic beat
(
i = INSTRUMENT();
i.drums = (
kick: 'kick',
hihat: 'hihat',
clap: 'clap'
);
i.drums.kick.seq("1");
i.drums.hihat.seq(" 1").speed(2);
i.drums.clap.seq(" 1");
i.drums.clock = 2;
)
IMPORTANT
INSTRUMENT is now publicly available as an pre-alpha release.
Please try this tool and get in touch.
Expect turbulence in this repository while this test phase evolves.
Motivation
This tool is aimed at the creation of musical compositions from scratch, 'on the fly'. INSTRUMENT focuses on musical language: rhythm, harmony, melody, audio processing.
INSTRUMENT also enables the connection of MIDI controllers with an easy interface that facilitates its mapping to different sound parameters.
The name is inspired by Fugazi.
INSTRUMENT is a voluntary independent open source initiative operating from the Mexican live coding underground
For questions, inquiries, help, or fun conversations, please contact me at furenku@gmail.com.
Disclaimers:
- Full Documentation + Tutorial coming soon.
- INSTRUMENT is in pre-alpha phase. The API is subject to change in the near future.
Installation:
- Figure out your Supercollider Extensions folder path. You can find this inside SuperCollider environment, by running the following command:
Platform.userExtensionDir;
-
To install INSTRUMENT, theres two possible ways:
- Using git, clone the repo inside your 'Extensions/' folder
- or download the .zip file and place it inside the 'Extensions/' folder. Make sure the folder's name is INSTRUMENT. (Downloading from this repo creates a folder named INSTRUMENT-main. Please rename it to INSTRUMENT)
-
Restart or Recompile SuperCollider.
-
Verify installation. Type:
i=INSTRUMENT();
to see if the Library was successfully added.
Getting Started
Start Server
(
// useful snippet for increasing default memory
s.options.memSize=2048*1024;
s.options.maxNodes=128*1024;
s.boot;
)
Using INSTRUMENT
Most things related with INSTRUMENT are accessed through a single intance.
You will normally begin by typing and evaluating the following line:
i = INSTRUMENT();
This line will be repeated throughout this document, so any individual fragment can be tried in isolation.
Synths
INSTRUMENT comes with some predefined SynthDefs that you can use.
See Synthesizers, at the end of this document, for more info about working with Synths, Effects, and the automatic synth loader.
If you want to add your own, see Loading more Synths
i=INSTRUMENT();
// list all preloaded synths:
i.synths.list;
i.synths.kick.simple;
i.kick=i.synths.kick.choose;
i.kick.seq("1 1").speed(4);
i=INSTRUMENT();
// creates a dictionary inside **i.synths**
// access by name
i.kick= "kick";
i.kick.seq("1").speed(2);
// use the dictionary
i.kick='kick';
// replace sounds
i.kick='hihat';
// access by index
i.kick=i.synths.drums.kick[0];
i.kick=i.synths.drums.kick[1];
i.kick=i.synths.drums.kick[2];
i.kick=i.synths.drums.kick[3];
// index wraps around total synths number inside folder
i.kick=i.synths.drums.kick[99999];
// access randomly
i.kick=i.synths.drums.kick.choose;
i.kick=i.synths.drums.kick.choose;
i.kick=i.synths.drums.snare.choose;
i.kick=i.synths.drums.snare.choose;
// long vs short synth routes are equivalent:
i.synths.percussion.drums.kick[3] == i.synths.kick[3]
// each synth gets added to root folder
i.synths.bass.simpleBass == i.synths.simpleBass;
Basic sequencing:
i=INSTRUMENT();
i.kick= i.synths.kick.deep;
i.kick.seq("1");
// equivalent syntax:
i.kick.seq = "1";
// trigger synths with different amp values:
i.kick.seq("1 0.3 0.75 2");
i.kick.seq([1, 0.3, 0.75, 2]);
i.kick.stop;
// Press Ctrl/Cmd + . to stop the server
.seq method allows to sequence any parameter. In previous example, the default parameter 'trigger' is being automatically passed.
There are shorthands 'seq' methods for some common parameters, some of them:
- trigger
- note
- chord
- vol
- pan
- fx
- fxSet
Lets pass a different parameter, note.
i=INSTRUMENT();
i.bass=i.synths.trance.choose;
i.bass.seq(\note, "0 2 3 5");
// shorthand:
i.bass.note("0 2 3 5");
i.bass.note = "0 2 3 5";
Sequencing notes
i=INSTRUMENT();
i.bass=i.synths.trance.choose;
i.bass.note("0 2 3 5");
// microtonality
i.bass.note("0 2.25 3.75 5");
// you can also use note names:
i.bass.note("C D Eb F D# D").speed(4);
// you can choose octaves
i.bass.note("C3 D4 Eb5 F6");
// a more complex bassline
i.bass.note = "C3 Db3 C4 C3 :1.5 Db3x2 :1/2 Bb2x2 ";
i.bass.clock=4;
i.bass.amp=2;
Note modifiers:
When sequencing notes, you can use amplitude and release time modifiers
Release time
i=INSTRUMENT();
i.bass=i.synths.trance[0];
i.bass.note = "0 2< 3> 5<< 7>> 8<<< 10>>> 12<<<<";
// equivalent:
i.bass.note = "0 2< 3> 5<2 7>2 8<3 10>3 12<4";
Apply release modifier to a subsequence:
i.bass.note = "0 (3 5)>>>> (7 8)<<<<";
// equivalent:
i.bass.note = "0 (3 5)>4. (7 8)<4";
Piano and forte (volume)
i=INSTRUMENT();
i.piano=i.synths.piano[0];
i.piano.note = "0 2p 3f 5pp 7ff 8ppp 10fff 12ffff";
Apply volume modifier to a subsequence:
i.bass.note = "0 (3 5)ppp (7 8)fff";
// equivalent:
i.bass.note = "0 (3 5)p3 (7 8)f3";
Changing octaves
(
i=INSTRUMENT();
i.bass=i.synths.trance.choose;
i.bass.note("0 2 3 7");
i.bass.note("C D Eb G");
i.bass.octave=4;
i.bass.octave=3;
i.bass.octave=5;
i.bass.octave=6;
)
Playing chords
i = INSTRUMENT();
i.bass=i.synths.trance.choose;
i.chords = i.synths.note.dist;
i.chords.octave=5;
// use comma to join notes in a chord (no spaces!):
i.chords.note("C,Eb,G,Bb F,A,C5,Eb D,F,A,C Bb,D5,F5,A ");
// alternate chords and single notes
i.chords.note("5 3 C,Eb,G,Bb 5 7 F,A,C5,Eb 2 5 D,F,A,C 7 10 Bb,D5,F5,A ").speed(4);
Use chord notation using 'C'
Class 'C' is an alias for 'I8TChord': C( interval, chordtype/chordarray, inversion, additional );
i = INSTRUMENT();
i.chords = i.synths.note.dist;
i.chords.note([
C(0,\m),
C(7,\M,1),
C(3,\Mmaj7),
C(1,\m,0,[16])
]).speed(1/2);
Sequencing different progressions:
(
i = INSTRUMENT();
i.chords = i.synths.note.dist;
i.chords[0].note([
C(0,\m),
C(1,\M,0,[16]),
]).speed(1/2).do(2);
i.chords[1].note([
C(0,\m),
C(3,\sus2),
C(2,\dim),
C(7,\M)
]).speed(1/2).do(3);
)
<a name="chord-types"></a>
Chord types:
(
i = INSTRUMENT();
i.chords = i.synths.pad.dist;
i.chords.note([
C(0,\M),
C(0,\m),
C(0,\M7),
C(0,\m7),
C(0,\dim),
C(0,\aug),
C(0,\Mmaj7),
C(0,\mmaj7),
C(0,\M9),
C(0,\M9m),
C(0,\m9),
C(0,\m9m),
C(0,\sus2),
C(0,\sus4),
// custom chord type
C(0,[6,10,15]),
]).speed(1/2);
)
Adding probability in sequences
Maybe
You can add some probability for a certain event using the '?' operator:
i = INSTRUMENT();
i.bass = i.synths.bass.dist;
// 50% probability is the default value
i.bass[0].note = "0 3? 5";
// 10% probability
i.bass[1].note = "12 15?0.1 17";
// 90% probability
i.bass[2].note = "14 18?0.9 20";
// 40% probability applied to each event in a group of events
i.bass.note = "14 (18 20)?0.4 8";
Or
You can have a value be chosen between two options with the '|' operator:
// 50% probability of choosing D or F
i.bass.note = "C D|F";
Set tempo
i=INSTRUMENT();
i.kick="kick";
i.kick.seq = "1 1";
i.kick.speed(8)
i.tempo=180;
i.tempo=140;
i.tempo=120;
i.tempo=220;
i.tempo=400;
Add silences using spaces
i=INSTRUMENT();
i.kick="kick";
i.kick.seq("1 0.5").speed(4);
Change instrument parameters:
i=INSTRUMENT();
i.kick="kick";
i.kick.seq("1 0.5").speed(4);
i.kick.amp=1/2;
i.kick.amp=3/4;
i.kick.amp=1;
i.kick.amp=2;
i.kick.amp=2;
i.kick.clock=2;
i.kick.clock=8;
Repeating events:
The 'x' operator inside string Patterns allow for repetition of last value
(
i=INSTRUMENT();
i.piano=i.synths.piano;
i.piano.amp=4;
i.piano.clock=2;
i.piano.note("0xx 2xxx 3xxxxx");
i.piano.note("0x2 2x3 3x5");
// get last pattern duration:
i.piano.duration;
i.kick='kick';
// repeat three times
i.kick.seq("1 0.5x3");
i.kick.duration;
// lazy equivalent
i.kick.seq("1 0.5xxx");
// clear patterns
i.kick.rm(\trigger);
)
(
i=INSTRUMENT();
i.kick='kick';
i.kick[0].seq("1");
i.kick[1].seq("1 1");
i.kick[2].seq("1x14 ").speed(16).x(1);
i.kick.seq("1x4 :0.5 1x8 :0.25 1x16 :0.125 1x32");
// add some silences
i.kick.seq("1x5 :0.25 1 1x3 1 1x3");
// check duration
i.kick.duration;
i.bass=i.synths.bass.trance[2];
i.bass.octave=3;
i.bass.clock=3;
i.bass.amp=2;
i.bass.note("C Dx2 Ebx3 Bbx2 Ax3 F ");
i.bass.duration;
)
Changing steps duration
The ':' operator inside string Patterns allows for setting of duration any values following it.
It requires a duration parameter, which must be a number (integer or fractionary).
// all subsequent events are affected
i=INSTRUMENT();
i.kick='kick';
// decimal representation
i.kick.seq("1 :0.25 1x3 ");
// fraction representation
i.kick.seq("1 :1/4 1x3 ");
i.kick.seq("1x2 :1/4 1x4 :1/8 1x8 :2 1");
Using fractions allow some interesting rhythms:
i=INSTRUMENT();
i.kick='kick';
i.hihat=i.synths.hihat.choose.postln;
i.kick.seq("1xx :3/8 1x3 1x2 :1/4 1x3 1x2 :1/2 1xx");
i.hihat.seq("1xx :1/3 1x3");
Subsequences
You can group patterns inside patterns using parenthesis. This is useful for playing parts of patterns with different durations and repetitions.
i=INSTRUMENT();
i.bass = i.synths.bass.trance[2];
i.bass.clock=4;
// repeat subsequence times
i.bass.note("Cx3 (Dx2 Gx2)x2 A");
i.bass.note("(Dx2 Gx2)x3 F A ");
// change subsequence duration
i.bass.note("(Dx2 Gx2):0.5 F A ");
// same result:
i.bass.note("(:0.5 Dx2 Gx2) F A ");
// same:
i.bass.note("(Dx2 Gx2):1/2 F A ");
i.bass.note("(Dx3 Gx2 B Cx7):1/3 F A");
// change both duration and repetition
i.bass.note("(Dx2 Gx2):1/2x4 F A ");
// multiple subselections
i.bass.note("(Dx2 Gx2):1/2x4 (F A)x3 ");
// between patterns
i.bass.note("C D E (Dx2 Ex2):1/2x3 F G C5 (B A)x2 C5 B D ");
Sequencing patterns
// Ctrl/Cmd + Period
i=INSTRUMENT();
i.kick=i.synths.kick[3];
i.kick.clock=2;
i.kick[0].seq("1 1");
i.kick[1].seq("1xx 1xxx ").speed(2);
i.kick[2].seq("1 1xx 1x2 1x4 ").speed(4);
i.kick[3].seq("1 1xx ").speed(8);
// You can also use '=' syntax (setter)
i.kick[0].seq = "1";
i.kick[1].seq = ":1/2 1xx 1xxx ";
i.kick[1].speed(2);
// or just pass speed as a speed modifier:
i.kick[2].seq = ":1/4 1 1x2 1x3 1x4 ";
Removing patterns:
i=INSTRUMENT();
i.kick=i.synths.kick[3];
i.kick.clock=2;
// first add some patterns:
i.kick[1].seq("1xx 1xxx ").speed(2);
i.kick[2].seq("1 1xx 1x2 1x4 ").speed(4);
// now remove them:
i.kick.rm(\trigger,2);
i.kick.rm(\trigger,1);
i.kick.rm(\trigger,0);
Control pattern speeds
i=INSTRUMENT();
i.kick=i.synths.kick[3];
// first add some patterns:
i.kick[0].seq("1").speed(1);
i.kick[1].seq("1").speed(2);
i.kick[2].seq("1").speed(4);
Controlling pattern repetitions
i=INSTRUMENT();
i.kick=i.synths.kick[3];
i.kick[0].seq("1").speed(1).repeat(4);
// different names for the same function:
i.kick[1].seq("1").speed(2).do(8);
i.kick[2].seq("1").speed(4).x(16);
Jump to position
i = INSTRUMENT();
(
i.hihat="hihatElectro";
i.hihat.seq("1xx :0.25 1xxx :0.5 1xxx :2 1").speed(2);
i.kick="kickElectro";
i.kick.seq("1xx :0.25 1xxx :0.5 1xxx :2 1").speed(1/3);
)
i.hihat.clock=2;
i.kick.clock=2;
// repeteadly evaluate this group:
(
i.hihat.go(0);
i.kick.go(0);
)
// again:
(
i.hihat.go(13);
i.kick.go(13);
)
Setting parameters
(
i = INSTRUMENT();
i.bass=i.synths.trance[0];
i.bass.note("0 2 3");
)
i.bass.rel=2;
i.bass.rel=0.2;
i.bass.rel=1/2;
i.bass.dist=2;
i.bass.dist=1;
i.bass.dist=4;
i.bass.dist=14;
i.bass.dist=1;
Sequencing parameters
i = INSTRUMENT();
i.bass=i.synths.trance[0];
i.bass.note("0 2 3");
i.bass.seq(\rel,[2,0.2,1]);
// rests in params:
i.bass.seq(\rel,[2,0.2,\r,\r,1]);
Sequencing synthdefs:
i=INSTRUMENT();
(
i.kick="kickDeep";
i.kick.seq("1");
i.kick.synthdef([\kickSyn1,\kickSyn2,\kickSyn3]);
i.bass=i.synths.trance.choose;
i.bass.note("0 2 3");
i.bass.synthdef([\bassTrance1,\bassTrance2,\bassTrance3]);
)
FX (Effects)
(
i=INSTRUMENT();
i.piano=i.synths.dist.note;
i.piano.note("0 2 3 5").random().mirror.speed(2);
i.piano.octave=4;
)
// using fx name
i.piano.fx="reverb";
// using synth dictionary
i.piano.fx=i.synths.fx.reverb;
// using synth variants
// by name
i.piano.fx=i.synths.fx.reverb.infinite;
// by index
i.piano.fx=i.synths.fx.reverb[2];
// variants
i.piano.fx.reverb="large";
// equivalent to:
i.piano.fx="reverb.large";
i.piano.fx.reverb="small";
i.piano.fx.reverb="infinite";
// modify parameters
i.piano.fx.reverb.wet=0;
i.piano.fx.reverb.wet=1;
i.piano.fx.reverb.wet=1/4;
i.piano.fx.reverb.wet=3/4;
i.piano.fx.reverb.room=7/8;
i.piano.fx.reverb.damp=7/8;
i.piano.fx.reverb.room=1/8;
i.piano.fx.reverb.damp=1/8;
i.piano.fx.reverb.room=1;
i.piano.fx.reverb.damp=1;
i.piano.fx=nil;
### FX Chains:
i.piano.fx = [ "reverb", "lpf" ];
i.piano.fx = [ "delay2", "distortion" ];
i.piano.fx.distortion.gain=30;
i.piano.fx.distortion.gain=3;
i.piano.fx.delay2.time=0.1;
i.piano.fx.delay2.time=0.5;
Sequencing fx
(
i = INSTRUMENT();
i.clap="clapElectro";
i.clap.seq(" 1 :0.25 1xx 1").speed(2);
i.clap.seq(\fx, [\reverb,\reverbLPF,\gateDistort]).speed(2);
)
i.piano=i.synths.piano[0];
i.piano.note("0 2 3 5").random().mirror.speed(2);
i.piano.seq(\fx,[
"reverb",
"gateDistortion",
"lpf"
]).speed(2);
// clear all FX:
i.piano.fx=nil;
InstrumentGroup
You can group Instruments for easier manipulation, while retaining access to individual instruments
i = INSTRUMENT();
i.drums=(
kick: 'kick',
hihat: i.synths.hihat.choose,
snare: i.synths.snare.choose,
);
i.drums.kick.seq("1");
i.drums.hihat.seq(" 1").speed(2);
i.drums.snare.seq(" 1").speed(1/2);
i.drums.clock=1/2;
i.drums.clock=2;
i.drums.clock=1;
i.drums.amp=1/2;
i.drums.amp=1;
i.drums.amp=2;
i.drums.stop;
i.drums.play;
// add fx to group
i.drums.fx = "reverb";
i.drums.fx = nil;
// redeclare group with less instruments
i.drums=(
hihat: i.synths.hihat.choose,
)
// restore
i.drums=(
kick: 'kick',
hihat: i.synths.hihat.choose,
snare: i.synths.snare.choose,
)
// melodic synths:
i.melodies=(
note1: i.synths.note.dist[0],
note2: i.synths.note.dist[1]
);
i.melodies.note1.note("0 7 8");
i.melodies.note2.note("12 15 13");
i.melodies.amp=3;
i.melodies.octave=5;
i.melodies.octave=6;
i.melodies.octave=3;
i.melodies.octave=4;
i.melodies.fx = "reverb.large";
Playing with groups (Example)
(
i=INSTRUMENT();
i.drums=(
k:i.synths.kick[3],
h:i.synths.hihat[2],
s:i.synths.snare[2],
);
i.drums.k.seq("1x3 1x3 1x3 :2 1").speed(4);
i.drums.h.seq("1x2 1 ").speed(8);
i.drums.s.seq(" 1x3 1x2 :2 1 :1/2 1x3 1x2 ").speed(2);
)
i.drums=(
k:i.synths.kick[4],
h:"kickDeep",
s:i.synths.snare[2],
);
i.drums=(
// k:'kick',
h:"hihatClosed",
s:i.synths.snare[5],
);
(
i.drums=(
k:'kick',
h:"kickDamp",
hh:i.synths.hihat[0],
s:i.synths.snare[2],
);
i.drums.k.seq("1").speed(2);
i.drums.hh.clock=2;
i.drums.hh.seq("1x7 1x6 ").speed(4);
)
i.drums.hh=i.synths.hihat[2];
i.drums.k=i.synths.hihat[0];
// wait a bit:
i.drums.k='kick';
(
i.drums.clock=1/8;
i.drums.fx="reverb.infinite";
)
(
i.drums.clock=2;
// i.drums.fx=i.synths.reverb;
i.drums.fx="reverb.small";
)
(
i.drums.k.seq("1").speed(2);
i.drums.fx=nil;
i.drums.clock=1;
i.drums.go(0);
)
i.drums.stop;
Group Channels
There is a Mixer channel automatically created for each group, and one for each of its children.
(
s.boot;
s.doWhenBooted({
Task.new({
i = INSTRUMENT();
i.drums=(
k: 'kickElectro',
h: 'hihat',
s: 'snareNeuro',
);
i.drums.k[0].seq = "1 ";
i.drums.k[1].seq = "1 1 ";
i.drums.h.seq = " 1";
i.drums.s.seq = " 1 ";
i.drums.play;
i.drums.fx=["reverb","lpf"];
i.drums.clock=4;
i.drums.fx.lpf.freq=15000;
4.wait;
i.drums.fx.lpf.lag=8;
i.drums.fx.lpf.freq=80;
12.wait;
i.drums.fx="hpf";
i.drums.fx.hpf.freq=40;
4.wait;
i.drums.fx.hpf.lag=8;
i.drums.fx.hpf.freq=15000;
8.wait;
i.drums.fx=["reverb.infinite"];
(3/8).wait;
i.stop;
}).play;
s.volume = (-12);
});
)
Clear and Restore
(
i = INSTRUMENT();
i.drums=( k: 'kickElectro', h: 'hihat', s: 'snareNeuro', );
i.drums.k[0].seq = "1 "; i.drums.k[1].seq = "1 1 ";
i.drums.h.seq = " 1"; i.drums.s.seq = " 1 ";
i.drums.clock=4;
)
i.clear;
i.restore;
Use i.clear to stop running instruments,, so you can easily create quick changes without pausing main thread. You can use i.restore to bring back cleared instruments and sequencer functions.
i=INSTRUMENT();
(
i.drums=(
k:i.synths.kick[3],
h:i.synths.hihat[2],
s:i.synths.snare[2],
);
i.drums.k.seq("1x3 1x3 1x3 :2 1").speed(4);
i.drums.h.seq("1x2 1 ").speed(8);
i.drums.s.seq(" 1x3 1x2 :2 1 :1/2 1x3 1x2 ").speed(2);
i.bass = i.synths.bass.trance[2];
i.bass.note("0 2 3");
i.bass.fx="reverb";
i.bass.octave=3;
i.bass.amp=1.5;
i.every(2,{ c=[2,4,8].choose; i.bass.clock=c; ["chose",c].postln; })
)
(
// Clear previous sounds
i.clear;
i.drums2=(
k:i.synths.kick[0],
s:i.synths.snare[4],
);
i.drums2.k.seq("1x2 1x4 1x3").speed(8);
i.drums2.s.seq(" 1x2 :2 1 :1/2 1x3 1x2 ").speed(4);
)
// restore first group
i.restore;
i.drums2.stop;
named clear and restore
You can store nodes and groups inside named spaces, and then restore them:
i=INSTRUMENT();
i.k=i.synths.kick.syn[2];
i.h=i.synths.hihat.dist;
i.k.seq("1").speed(2);
i.h.seq(" 1").speed(4);
i.clear("test1");
i.b=i.synths.bass.sintri;
i.b.note("0");
i.clear()
i.restore()
i.restore("test1")
i.kk=i.synths.kick.syn[2];
i.hh=i.synths.hihat.dist;
i.kk.seq("1 1 ").speed(4);
i.hh.seq(" 1 1").speed(8);
i.clear("test2")
i.restore("test1")
i.clear("test1")
i.restore("test2")
i.clear("test2")
i.restore()
i.clear()
Array manipulation
i = INSTRUMENT();
(
i.bass=i.synths.bass.trance;
i.hihat=i.synths.hihat.electro;
i.bass.clock=4;
i.bass.note("0 2 3");
)
i.bass.note("0 2 3").pyramid.mirror.speed(2);
// randomness
i.bass.note("0 2 3 5 7 10 12").random;
i.bass.note("0 2 3 5 7 10 12").random;
i.hihat.seq("1xxxxxxxxx").speed(8).maybe(0.5); // default value
i.hihat.seq("1xxxxxxxxx").speed(8).maybe(0.25);
i.hihat.seq("1xxxxxxxxx").speed(8).maybe(0.75);
i.hihat.seq("1xxxxxxxxx").speed(8).maybe(0); // never play
i.hihat.seq("1xxxxxxxxx").speed(8).maybe(1); // always play
i.hihat.seq("1xxxxxxxxx").speed(8).maybe(0.35);
A small composition:
// Ctrl/Cmd + .
i = INSTRUMENT();
(
i.voices = (
a: i.synths.pad.dist[0],
b: i.synths.pad.dist[1],
c: i.synths.note.dist[0],
d: i.synths.note.dist[1],
);
i.voices.a.note("0 2 0 2 3 0 2 3 7 0 2 3 7 14").speed(1/4);
i.voices.a.amp=1.5;
i.voices.a.set(\rel,8);
i.voices.b.note("0 2 0 2 3 0 2 3 7 0 2 3 7 14").speed(1/2).random;
i.voices.b.octave=6;
i.voices.b.amp=1/4;
i.voices.c.note("0 2 0 2 3 0 2 3 7 0 2 3 7 14").speed(2).random;
i.voices.c.octave=8;
i.voices.c.amp=1/4;
i.voices.d.note("0 2 0 2 3 0 2 3 7 0 2 3 7 14").speed(3).random;
i.voices.d.octave=7;
i.voices.d.amp=1/3;
)
Scheduling functions:
Scheduling a repeating function:
i = INSTRUMENT();
i.hihat = i.synths.hihat.choose;
i.hihat.seq("1xxxxxxxxx");
i.drums=(
kick: i.synths.kick.choose,
hihat: i.synths.hihat.choose,
snare: i.synths.snare.choose,
);
i.drums.kick.seq("1");
i.drums.hihat.seq(" 1").speed(2);
i.drums.snare.seq(" 1").speed(1/2);
i.drums.clock=2;
i.every(4,{
i.drums=(
kick: i.synths.kick.choose,
hihat: i.synths.hihat.choose,
snare: i.synths.snare.choose,
);
});
// remove repeating function:
i.every(4, nil);
i.hihat.seq("1xxxxxxxxx").speed(2);
Scheduling a single function:
i = INSTRUMENT();
i.hihat = i.synths.hihat.choose;
i.hihat.seq("1xxxxxxxxx").speed([4,8,16].choose).maybe(0.75);
i.when(8,{
i.hihat.fx="distortion";
});
i.when(16,{
i.hihat.fx="reverb.large";
i.hihat.clock=1/4;
});
Using ProxySpace
One of its main motivations is to easily integrate with the awesome JITLib, that allows musicians to incorporate mutating synthesis onto their live acts.
To sync tempo in ProxySpace with INSTRUMENT, run following code:
(
i = INSTRUMENT();
p = ProxySpace.push(s);
i.proxyspace = p;
)
After that, you can easily use tempo in the following way:
i.drums=(
kick: i.synths.kick.choose,
hihat: i.synths.hihat.choose,
snare: i.synths.snare.choose,
);
i.drums.kick.seq("1");
i.drums.hihat.seq(" 1").speed(2);
i.drums.snare.seq(" 1").speed(1/2);
~z.play;
~z = { Decay2.kr(Impulse.kr( ~tempo.kr * 4 )) * Mix.new(RLPF.ar(WhiteNoise.ar,SinOsc.kr(~tempo.kr*8,0,1000,4000),0.1)) / 2}
Tempo changes in INSTRUMENT now affect ProxySpace's tempo:
i.tempo = 120;
i.tempo = 200;
i.tempo = 90;
i = INSTRUMENT();
p=ProxySpace.push(s);
i.proxyspace = p;
~z.play;
~z.fadeTime=10;
~z={|freq=200| RLPF.ar(WhiteNoise.ar,freq,0.2) };
i.z=Proxy(~z);
i.z.seq(\freq,"(1000 600):1/4x3 700");
~z.fadeTime=3;
i.z.clock=1;
~z={|freq=200| (Resonz.ar(WhiteNoise.ar,Lag2.kr(freq,1/4),0.01)*30).tanh/2 };
i = INSTRUMENT();
p=ProxySpace.push(s);
i.proxyspace = p;
~z.play;
~z.fadeTime=1;
~z={|freq=200, t_trig=1| (LFPulse.ar(freq,SinOsc.kr(1/2).linlin(-1,1,0,1),SinOsc.kr(105).linlin(-1,1,0,1))*30).tanh/2*Decay2.kr(t_trig,2,0) };
i=INSTRUMENT();
i.z=Proxy(~z);
// automatic mapping of note to 'freq and t_trig':
i.z.note("0 2 3");
i.z.clock=4;
i.z.octave=5;
Playing chord progressions using a NodeProxy:
i = INSTRUMENT();
p=ProxySpace.push(s);
p.fadeTime = 10;
~sound.play;
~sound = {|notes=#[60,65,67,72],gain=1| (SinOsc.ar(notes.midicps)*gain).tanh / 6 ! 2 };
~sound = {|notes=#[60,65,67,72],gain=1| (Saw.ar(notes.midicps*[1/2, 2])*gain).tanh / 6 ! 2 };
i.sound=Proxy(~sound);
(
i.sound.chord([
C(0,\m),
C(7,\M,1),
C(3,\Mmaj7),
C(1,\m,0,[16])
]).speed(1/2);
)
(
i.sound.chord([
C(0,\m),
C(0,\m,1),
C(0,\m,2),
C(0,\m,2,[14]),
C(7,[6,10,16])
]).speed(1/4);
)
See Chord types
FX Sends
You can create shared channels for routing and sharing effects.
(
Task.new({
i=INSTRUMENT();
i.fx.ch1="reverb.infinite";
i.kick = 'kick';
i.hihat = 'hihat';
i.snare = 'snareNeuro';
i.kick.seq = ":1/4 1 2 4 ";
i.hihat.seq( ":1/4 5 2x3 1xx 3x3 :1/8 1x4 ").random;
i.snare.seq( " 5 2x3 1xx 3x3 :1/4 1x4 1X3 ").random;
4.wait;
i.hihat.send(i.fx.ch1);
4.wait;
i.fx.ch2=["reverb.infinite","delay2","lpf"];
i.snare.send(i.fx.ch2);
4.wait;
i.fx.ch1=nil;
i.fx.ch2=["reverb.small"];
4.wait;
i.fx.ch2=["reverb.infinite","gateDistort.hardcore"];
i.kick.send(i.fx.ch2);
6.wait;
i.fx.ch3=["shineDestroy","delay2"];
i.snare.send(i.fx.ch3);
i.hihat.stop;
4.wait;
i.fx.ch2=nil;
i.fx.ch1=["reverb.infinite"];
i.kick.send(i.fx.ch1);
4.wait;
i.snare.send(i.fx.ch1, false);
4.wait;
i.snare.stop;
8.wait;
i.kick.send(i.fx.ch1, false);
6.wait;
i.kick.stop;
}).play;
)
Loopers:
A multi-layer looper
- Time-synced to main sequencer.
- Allows recording of multiple layers.
- Sequenceable amp and rate manipulation.
- Works in the same way as any other INSTRUMENT
- Sequence any parameter
- Add FX
- etc...
i=INSTRUMENT();
// create looper connected to audio interface's first audio input:
i.loop1=Looper(0);
// record looper for the 1st channel:
i.loop1.rec;
i.loop1.start;
// replace
i.loop1.rec;
i.loop1.start;
i.loop1.rec(1);
i.loop1.start(1);
i.loop1.rec(2);
i.loop1.start(2);
i.loop1.rec(3);
i.loop1.start(3);
i.loop1.amp=0.5;
i.loop1.amp=1;
i.loop1.amp=0;
i.loop1.amp=0.3;
i.loop1.amp=1;
i.loop1.amp_(1);
// sequence amp
i.loop1.seq(\amp,"1 0.2 1 0.5 0.75").speed(2)
i.loop1.rate = 1/2;
i.loop1.rate = -2;
i.loop1.rate([1, 2, -1, \r, 3, \r , 1/2]).speed(1);
// remove rate sequencer:
i.loop1.rm(\rate,0);
i.loop1.rate(1/8);
i.loop1.rate(2.5);
// change rate separately for each of the layers:
i.loop1.rate(1,0);
i.loop1.rate(1.5,1);
i.loop1.rate(3,0);
i.loop1.rate(1/4,1);
i.loop1.rate(4,0);
i.loop1.rate(1/2,0);
i.loop1.rate(1/2,1);
i.loop1.rate(1/4,1);
i.loop1.rm(\amp)
i.loop1.fx="distortion"
i.loop1.fx=nil
i.loop1.amp=1;
i.loop1.rate(1);
// create another separate looper:
i.loop2=Looper(0);
i.loop2.rec;
i.loop2.start;
i.loop2.amp=0.5;
i.loop2.rate([1, 2, -1, \r, 3, \r , 1/2]).speed(2);
i.loop2.amp("1 0.3 1 0.5 0 0.1").speed(4)
i.loop2.amp(0.5);
i.loop2.rate(-1);
// stop loopers:
i.loop1.stop;
i.loop2.stop;
i.loop1.play;
i.loop1.stop;
MIDI Control:
//Docs coming soon...
Harmony
(work in progress)
/*
random harmony generator
following simple rules:
- no parallel fifths or octaves in first 2 voices
- when first two do move parallelly,
move third direction in opposite direction
*/
(
s.boot;
s.doWhenBooted({
i=INSTRUMENT();
// create 3 voices:
i.voices=(
v1:i.synths.note.dist[1],
v2:i.synths.note.dist[2],
v3:i.synths.note.dist[3],
);
h=I8THarmony();
v=h.generateVoicings();
h=v.collect(_.collect(Scale.minor.degrees.at(_)));
i.voices.v1.note(h[0]);
i.voices.v2.note(h[1]);
i.voices.v2.note(h[2]);
// set parameters:
i.voices.octave=5;
i.voices.set(\rel,0.3);
i.voices.v1.set(\rel,4);
i.voices.v3.set(\rel,1);
i.voices.v1.fx="reverb.small";
i.voices.v2.fx="reverb.large";
i.voices.v3.fx="reverb.medium";
s.volume=(-12);
})
)
(
i.every(16,{
var nextScale = Scale.choose;
h=I8THarmony();
v=h.generateVoicings();
h=v.collect(_.collect(nextScale.degrees.at(_)));
i.voices.v1.note(h[0]);
i.voices.v2.note(h[1]);
i.voices.v2.note(h[2]);
["nextScale", nextScale.name].postln;
});
""
)
<a name="synthesizers"></a>
Synthesizers
i.synths
Automatic Synth Loading
INSTRUMENT comes with a group of SynthDefs that are automatically loaded when a new instance of INSTRUMENT is created.
You can access them via i.synths
i=INSTRUMENT();
i.synths.list;
i.synths.percussion.list;
i.synths.percussion.drums.list;
// all synths accesible using their names:
// (Currently, synthnames are converted to lowercase).
i.synths.simple == i.synths.simple;
// references are created without redundant names:
i.synths.simple==i.synths.percussion.drums.kick.simple;
// Multiple hierarchies support.
i.synths.drums == i.synths.percussion.drums;
i.synths.electro == i.synths.percussion.drums.kits.electro;
// smart organization
i.synths.electro.kick===i.synths.kick.electro;
// numeric indexes are fixed by prefixing an 's'
i.synths.kick.syn.s1===i.synths.kicksyn1;
i.synths.kick.s808===i.synths.kick808;
// numerical indexing based on alphabetical order.
i.synths.kick[0].name
i.synths.kick[1].name
i.synths.kick[2].name
i.synths.kick[3].name
// (number wraps around item size)
(
Task.new({
30.do{|h|
[h,i.synths.kick[h].name].postln;
0.01.wait;
}
}).play;
)
// choose one Synthdef at random
'kick'.postln;
i.synths.bass.choose.postln;
// a specific SynthDef
i.synths.kick.simple
You can load your own SynthDef paths:
i.loadSynths("path/to/synths");
Using your own Synthdefs
You can use your own Synthdefs. You need to have an out parameter for signal routing.
Optionally, you can make them add themselves to the server by putting them inside INSTRUMENT's Sounds/Synthdef folder.
<a name="load-more-synths"></a>
Loading More Synths
They are automatically added on startup, but you can manually re-load them, or load any path that contains SynthDefs or folders containing them.
i=INSTRUMENT();
// load your own path with SynthDefs
i.loadSynths("path/to/your/synthdefs/*");
Synthdef(\yourSynthdef, {|out=0,amp=1,freq,gate=1|
//... your synthesis code
Out.ar( out, /*...*/ ));
}).store;
Convenient parameters
-
amp - for setting amplitude manually or via a sequence
-
gate useful for retriggering and monophonic synths
-
note
-
freq
NOTE: you can use note or freq interchangeably. When you sequence notes, both parameters are addressed. note must be converted to freq using the .midicps method.
You can check currently existing Synthdefs here
FX Synthdefs
Only requirements are using inBus, outBus, wet, and amp params.
See following example:
SynthDef(\reverb, {
arg
inBus=0,
outBus=0,
wet=0.5,
room=0.3,
damp=0.3
amp=1;
var sig;
var targetRoom, targetDamp, lag=0.3;
var dsp, mix;
sig = In.ar(inBus) * 1.25;
dsp = FreeVerb.ar( sig, 1, room, damp );
mix = (sig * (1-wet)) + (dsp * wet);
ReplaceOut.ar( outBus, Pan2.ar( mix * amp.linlin(0,1,0,1.5), 0 ) );
}).store;