project name: | phonotype |
project url: | https://github.com/sixolet/phonotype |
author: | sixolet |
description: | livecoding environment |
discussion url: | https://llllllll.co/t/49564 |
documentation url: | https://norns.community/authors/sixolet/phonotype |
tags: | generative keyboard midi |
It is like Teletype but for sound.
Useful for:
To use PHONOTYPE:
Like Teletype, PHONOTYPE is a prefix programming language. For example:
+ 1 * 2 2.5
Is a statement that yields the sum of 1 and the product of 2 and 2.5: 6.
Every operation in PHONOTYPE is declarative. Instead of executing, like a line
of Teletype code, it tells the audio engine (SuperCollider) what to connect to
what. You can think of this much like patching a modular synth. The last line of
your main (M
) script is output to the output jack.
The IT
op is special: it means “the result of the above line”.
Here is a program that outputs a FM drone:
SIN 440
SIN + 220 * 220 IT
F1..F8 on the keyboard navigates to script 1 through 8.
F9 on the keyboard navigates to the M
(main) script. This is the script that
outputs sound.
ESC nagivates to the scene description.
Arrow keys choose a line to edit; enter key actually changes the line.
Ctrl- or Alt- while pressing up or down will swap lines.
Shift while pressing up or down will navigate the history of lines you have entered.
Shift-enter inserts a new “passthrough” line above the current one.
E2 adjusts the fade time of the current line.
E3 adjusts when the line will actually be crossfaded, in beat multiples. For example, choose 4 to fade the line on a barline in 4/4 time.
Try SIN 440
in M.
Then on the next line put * IT PERC QN .5
(QN
= quarter notes. .5
is our env
decay)
Now add + IT * .5 DEL.F IT * .75 M 3
. DEL.F
is a delay w/ feedback. We delay
by a dotted eigth with a 3s decay.
Then go back to the first line. Hit ctrl-enter to get a new line above. Make it
say S+H SCL SIN .1 0 7 QN
and change SIN 440
to say SIN N.MIN IT
.
While in the scene description, Ctrl-Enter or Alt-Enter saves your scene, with the filename based on the first line of your scene description.
To load a scene, change the scene
parameter (on the params page) to a file of
your choice.
To load a fresh scene, press Ctrl-Shift-Escape.
Each is listed like so:
NAME <arg1> <arg2> <arg3>
- description
Longer explanation, if it seems necessary
Unlike Teletype, PHONOTYPE operates on floating point numbers, not integers.
+ <arg1> <arg2>
- Add
Mix/add two signals
* <arg1> <arg2>
- Multiply
Multiply/VCA/Ring modulate two signals.
- <arg1> <arg2>
- Subtract
/ <dividend> <divisor>
- Divide
% <modulend> <modulo>
- Mod
< <left> <right>
- Less than
> <left> <right>
- Greater than
1 if the comparison holds, 0 otherwise.
SIN <freq>
- Sine wave
PSIN <freq> <phase>
- Phase-modulatable sine wave
TRI <freq>
- Triangle wave
SAW <freq>
- Saw wave
VSAW <freq> <width>
- Variable-width saw
SQUARE <freq>
- Square wave
PULSE <freq> <width>
- Variable-width pulse
RSTEP <freq>
- Random steps
RRAMP <freq>
- Random ramps
Steps with linear slew
RSMOOTH <freq>
- Smooth random
Aka RSM
Uses cubic interpolation.
WHITE
- White noise
BROWN
- Brown noise
PINK
- Pink noise
Noise is good dontcha know.
There are 16 stereo buffers, numbered 0 through 15. They’re accessible from the “buffers” submenu in params - by default they are blank, but you can choose a file to initialize a buffer to (on the fly, even, if you wish) in the params menu.
There are two main buffer ops: PLAY
and LOOP
, and they differ only in
whether they loop automatically to the cue point or beginning at the end of the
buffer. Each of these has combinatorially many suffixes. I will list the
suffixes for PLAY
; all the same ones exist for LOOP
. In “loop” mode, the
trigger acts as a reset to the beginning or the cue point.
PLAY <bufnum> <trig>
- Play the buffer every time the trigger fires.
PLAY.B <bufnum> <trig> <beats>
- Play the buffer, beat-synced.
Assuming the buffer has the given number of beats, adjusts speed to match the current tempo of PHONOTYPE.
PLAY.T <bufnum> <trig> <bpm>
- Play the buffer, tempo-synced.
Assuming the buffer has the given BPM, adjusts speed to match the current tempo of PHONOTYPE.
PLAY.R <bufnum> <trig> <rate>
- Play the buffer, variable rate.
Play the buffer at the given rate multiple.
PLAY.O <bufnum> <trig> <octave>
- Play the buffer, variable octave
Play the buffer at the given octave multiple: 0 is normal speed, -1 is half speed, 2 is 4x speed, etc.
PLAY.BC <bufnum> <trig> <beats> <cue>
- Play the buffer, beat-synced, with cue.
Assuming the buffer has the given number of beats, adjusts speed to match the current tempo of PHONOTYPE. When a trigger is recieved, starts playing from the cue. The cue is a number of beats into the buffer.
PLAY.TC <bufnum> <trig> <bpm> <cue>
- Play the buffer, tempo-synced, with cue.
Assuming the buffer has the given BPM, adjusts speed to match the current tempo of PHONOTYPE. When a trigger is recieved, starts playing from the cue. The cue is a number of beats into the buffer.
PLAY.RC <bufnum> <trig> <rate> <cue>
- Play the buffer, variable rate, with cue.
Play the buffer at the given rate multiple. When a trigger is recieved, starts playing from the cue. The cue is a number of seconds into the buffer.
PLAY.OC <bufnum> <trig> <octave> <cue>
- Play the buffer, variable octave, with cue
Play the buffer at the given octave multiple: 0 is normal speed, -1 is half speed, 2 is 4x speed, etc. When a trigger is recieved, starts playing from the cue. The cue is a number of seconds into the buffer.
The same smoothness variants exist for all rate and cue variants. I told you this stuff was combinatorial.
PLAY.BS <bufnum> <trig> <beats>
- Play the buffer, beat-synced, smoothly.
Introduces a short crossfade when trigger is recieved to avoid clicks.
PLAY.BX <bufnum> <trig> <beats> <fade>
- Play the buffer, beat-synced, adjustable crossfade.
Introduces a user-specified crossfade when trigger is recieved to avoid clicks or transition slowly. The fade time parameter always comes after the cue parameter if present.
SR
- The sample rate.
A constant.
RD <bufnum> <phasor>
- Read the buffer with the phasor.
The phasor should be in samples, thus the SR
constant to multiply by if you like.
WR <bufnum> <phasor> <signal>
- Write to the buffer with the phasor.
Again, phasor in samples. YMMV with a phasor with a slope faster than 1.
PHASOR <trig> <start> <end>
- Make a phasor
Produces a sample-scale ramp from the start (in seconds) to the end (in seconds), looping. The trigger resets it to the start.
PHASOR <trig> <start> <end>
- Make a phasor specifying args in beats
Produces a sample-scale ramp from the start (in beats) to the end (in beats), looping. The trigger resets it to the start.
LEN <bufnum>
- Length of buffer
Outputs the length of a buffer in seconds. May change if the buffer is loaded to a different sample from the params menu.
L.MIX <low> <high>: <statement>
L.M <low> <high>: <statement>
Evaluates the statement to the right of the colon with I
bound to each integer
between
For example, this is a LFO-modulated drone of the first six partials of a low A:
L.MIX 1 6: * UNI RRAMP I / SIN * 110 I I
MIDI.1 <channel>: <statement>
- Not quite monophonic MIDI
MIDI.2 <channel>: <statement>
- 2 note polyphony
MIDI.4 <channel>: <statement>
- 4 note polyphony
MIDI.6 <channel>: <statement>
- 6 note polyphony
MIDI.8 <channel>: <statement>
- 8 note polyphony
Within <statement>
, you can use F
, G
, and V
to refer to the frequency,
gate, and velocity of the particular voice, and you can find the voice number (0
through number of voices) as I
, in case you want to make each voice distinct
in your poly synth.
PHONOTYPE will actually initialize one additional voice to allow one note to decay as it is being replaced. We use a voice-stealing allocator, and the voice we recycle is whichever is nearest in frequency to the newly allocated note.
For supercollider nerds: I wrote some code to pause the group when a voice isn’t in use. I hope it works to decrease the baseline CPU of this thing and early experimental evidence is that it does somewhat on my laptop, though of course if you’re playing every voice it’ll use all that CPU. The cost is that detecting the silence to pause the voice itself isn’t free.
LPF <signal> <freq>
- Low pass filter
HPF <signal> <freq>
- High pass filter
BPF <signal> <freq>
- Band pass filter
Basic.
RLPF <signal> <freq> <reciprocal-q>
- Resonant low pass filter
RHPF <signal> <freq> <reciprocal-q>
- Resonant high pass filter
Slightly less basic.
RING <signal> <freq> <ringtime>
- Ringing resonant bandpass filter
Combine these to get some basic physical modelling.
MOOG <signal> <freq> <resonance>
- Moog emulation filter
LPG <signal> <gate>
- 2 - Low-pass gate
Including a little bit of emulated “slowness” so you can get little “pok” sounds by pinging it.
DJF <signal> <slider>
- DJ filter
LAG <signal> <time>
- Lag/slew the same up and down
SLEW <signal> <attack> <release>
- Lag/slew time different up and down
DEL <signal> <time>
- Delay
Uses linear interpolation if it senses delay time will vary.
DEL.F <signal> <time> <decaytime>
- Delay with feedback
Also known as “comb filter”.
DEL.A <signal> <time> <decaytime>
- All-pass filter
LR <left> <right>
- Stereo
Makes the arguments mono, then uses them as the left and right channels of the new signal.
PAN <signal> <side>
- Pan
MONO <signal>
- Mono
ROT <signal> <rotation>
- Stereo rotate
I think you can get some mid/side translations with -.5 and .5
There are a pile of rhythm ops, all 0-argument, that yield different places in the measure. So far we’re only supporting 4/4 time, but within that you can use:
SN
- Trigger on every sixteenth noteEN
- Trigger on every eigth noteQN
- Trigger on every quarter noteHN
- Trigger on every half noteWN
- Trigger on every whole noteSNT
, ENT
, QNT
, and so on - sixteenth note triplets, eighth note triplets, etc.BT1
- Trigger on every beat-1 of a measure. BT2
triggers on every beat-2, etc.BT1.E
- Trigger on the second sixteenth note of beat-1 of every measure. Also
BT2.E
, BT1.&
, BT4.A
, and every combination like that.EVERY <beats> <offset>
- Do every N beats
Aka EV
EVERY
ops share the same clock, and the
same clock as the beat operations above. That means that
EV 16 0
will happen on the downbeat every 4 measurs of 4/4.ER <fill> <length> <offset> <noteduration>
- 4 - Euclidean rhythms
All arguments except duration round down to the nearest integer
SN.ER <fill> <length> <offset>
- Sixteenth-note euclidean rhythms
EN.ER <fill> <length> <offset>
- Eigth-note euclidean rhythms
QN.ER <fill> <length> <offset>
- Quarter-note euclidean rhythms
PROB <chance> <triggers>
- Trigger chance
Note that, like most ops, when given a stereo signal this operates on each
channel in stereo. If you are passing in a bus and don’t want weird hard-panned
random notes, apply MONO
to the input. Me, I kind of like the wierd
hard-panned random notes.
DUR <trig> <duration>
- 2 - Trigger to gate converter
Upon recieving a trigger, remain high for the duration.
CDIV <trig> <ratio>
- Clock divider
PERC <trig> <time>
- Percussive envelope
AR <trig> <time> <attack>
- Attack-release envelope.
Exponential-ish shape.
AR.L <trig> <time> <attack>
- Linear attack-release envelope
AR.C <trig> <time> <attack> <curve>
- Variable-curvature attack-release envelope
AR
, 0 is like AR.L
, and positive is funny.ADSR <trig> <attacktime> <decaytime> <sustainlevel> <releasetime>
- Attack-decay-sustain-release envelope
All pitch ops are relative to the root: a note of 0 is always the root.
ROOT
- Root, in CPS.
Default 440, set in parameters.
N <halfstep>
- Half-steps to Hz.
Unquantized. N 0 is the root. Note it’s also useful for filter cutoffs.
N.QT <halfstep>
- Half-steps to Hz, quantized.
N.MAJ <degree>
- Scale degrees to CPS, major
N.MIN <degree>
- Scale degrees to CPS, minor
N.HM <degree>
- Scale degrees to CPS, harmonic minor
N.MAJP <degree>
- Scale degrees to CPS, major pentatonic
N.MINP <degree>
- Scale degrees to CPS, minor pentatonic
N.DOR <degree>
- Scale degrees to CPS, dorian
A
, B
, C
, D
, J
, `AB
The result of a bus send operation is the signal being sent. This allows you to send to a bus but still use the expression in a different way.
.F
to the op to have PHONOTYPE assume it’s in the “sound frequency”
range (20 to 20000), .U
to assume it’s unipolar (0 to 1), and .B
to assume
it’s in the “bipolar” oscillator range (-1 to 1). This doesn’t clip the bus,
it just adjusts the implicit assumptions we make about its range, for example
to determine whether to use a control-rate or audio-rate generator, or for the
automatic scaling operations downstream.A
- Audio Bus
B
- Audio Bus
C
- Audio Bus
D
- Audio Bus
Global audio rate busses. Without an =
, reads from the bus.
A= <signal>
- Audio Bus send
B= <signal>
- Audio Bus send
C= <signal>
- Audio Bus send
D= <signal>
- Audio Bus send
Global audio rate bus sends. Mixes on the bus.
W
- Control Bus
X
- Control Bus
Y
- Control Bus
Z
- Control Bus
Global control rate busses. Without an =
, reads from the bus.
W= <signal>
- Control Bus send
X= <signal>
- Control Bus send
Y= <signal>
- Control Bus send
Z= <signal>
- Control Bus send
Global control rate bus sends. Mixes on the bus.
AB <n>
- Audio bus array
CB <n>
- Control bus array
Read from the global bus arrays, 16 each of audio and control indexed busses to play with.
For example, AB 2
is a read from audio bus 2.
AB= <n> <signal>
- Audio bus array send
CB= <n> <signal>
- Control bus array send
For example, AB= 2 SIN 440
sends a 440-hz size to audio bus 2.
PARAM <n>
- Norns parameters
Aka PRM
aka just P
PHONOTYPE is equipped with a bank of 16 general-purpose Norns parameters, mapped to
PARAM 0
through PARAM 15
. Each ranges from 0 to 1. 16n or similar recommended.
IT
- The previous line
This is the coolest shit. IT
allows you to smoothly patch in new bits in the
middle of your chain. IT
is the only bus operation that is flexible about its
rate - it will be whatever rate the previous line was.
IN
- The input
Whatever you’ve told the Norns mixer to pass to the engine from outside.
F
- Frequency from monophonic midi in
G
- Gate from monophonic midi in
V
- Velocity from monophonic midi in
SEL2 <selector> <a> <b>
- Select by value from two inputs
SEL3
, SEL4
, SEL5
- Select from various numbers of inputs, similarly
SEQ2 <adv> <reset> <a> <b>
- Sequential switch/Sequencer
SEQ3
, SEQ4
, SEQ5
- Similar for various numbers of inputs
S+H <signal> <trig>
- Sample-and-hold
XF <a> <b> <fade>
- Crossfade
Equal-power.
You call a script by prepending its number with a $
(and no space). You can
only call scripts from higher-numbered scripts; you can call any script from
M
. If you want to pass arguments, put a .
, and the the number of arguments
you want to pass. For example:
$1.3 IT SIN 1 4
Calls script 1 with 3 arguments, namely IT
, SIN 1
, and 4
.
Within script 1, those are available as I1
, I2
, and I3
. The first input is
also available on IT
of the first line in the script.
PI
- Pi.
Utility constant.
SILENCE
- Audio-rate zero
Literal 0
is considered “control-rate” by PHONOTYPE and cannot be output;
SILENCE
is an audio-rate version you can have as your output.
SCL <signal> <min> <max>
- Scale
Do our best to scale the input to the given range. Note that some estimates of mins and maxes of signals are approximate, so this won’t guarantee all signals in range.
SCL.X <signal> <min> <max>
- Scale exponential
Same as SCL
but the output range is exponential.
SCL.F <signal>
- Scale to frequency range
Scales the input from its natural input range to the exponential range 20 to 20000 - the range of human hearing in hz.
SCL.V <signal>
- Scale to volume range
Scales the input to a 0 to 1 range, but using a curve (similar to the one in the perc envelope) that better accounts for human volume perception being logarhythmic than a linear scale would.
UNI <signal>
- Unipolar
Scale 0 to 1.
CLIP <signal> <min> <max>
- Clip
WRAP <signal> <min> <max>
- Wrap
Aka WRP
POS <signal>
- Half-wave rectify
Returns only the positive part of the signal.
FOLD <signal> <foldamount>
- Wavefolder
The “amount to fold” signal is chosen so an envelope is a productive thing to put into it, and 0 is “no folding for -1 to 1 range signals”. Amplitude decreases as folding increases so as to maintain a roughly consistent overall volume.
SINFOLD <signal> <foldamount>
- Sinusoidal wavefolder
The “amount to fold” signal is chosen so an envelope is a productive thing to put into it, and 0 is “gentle folding for -1 to 1 range signals”. Amplitude decreases as folding increases so as to maintain a roughly consistent overall volume.
CRUSH <signal> <crush>
- Bitcrusher
The “amount to crush” signal is chosen so that an attenuated envelope is a productive thing to put into it, and 0 is “sample rate like 44k” and 1 is “sample rate like 440”.
TANH <signal>
- Hyperbolic tangent
Oh man the hyperbolic tangent is the most amazing thing you can do to any audio signal, and it will instantly make whatever you put through it sound gooder.