ChucK is a music programming language. As music is all about time, it describes itself as a "Strongly-timed, Concurrent, and On-the-fly Music Programming Language".
Programs described in this episode will make a lot of sounds, but due to technical limitations you won't be able to hear them, unless you download them and run them locally. You can install ChucK with brew install chuck
or equivalent, so it shouldn't be too hard.
This is not my first encounter with ChucK, once upon a time I set it up to use DDR dance mat as a musical instrument, but that was a while back, so I don't remember all the details.
Hello, World!
ChucK won't support #!
, we need to run scripts with chuck hello.ck
. Here's a very simple one:
SinOsc hello => dac;
420.0 => hello.freq;
1::second => now;
As you can see there's a lot of =>
s. To quote the docs "The ChucK operator (=>) is a massively overloaded operator that, depending on the types involved, performs various actions", and that pretty much sums it up.
You can already see it perform a lot of different roles:
- we create sine wave generator
hello
, then use the chuck operator to connects it todac
(audio output) - we chuck number
420.0
tohello.freq
to change the sine wave frequency to420.0
Hz - we chuck
1::second
tonow
making the program run for 1 second with current settings
Loops
As you can't really hear the sounds, I'll try to print what's going on to the terminal:
SinOsc sine_wave => dac;
for (0 => int i; i<=12*5; i++) {
Math.pow(2.0, i/12.0) * 55.0 => float freq;
freq => sine_wave.freq;
<<<"Playing frequency", freq>>>;
0.1::second => now;
}
Here's what happens, 0.1 seconds per iteration:
Playing frequency 55.000000
Playing frequency 58.270470
Playing frequency 61.735413
Playing frequency 65.406391
Playing frequency 69.295658
Playing frequency 73.416192
Playing frequency 77.781746
Playing frequency 82.406889
Playing frequency 87.307058
Playing frequency 92.498606
Playing frequency 97.998859
Playing frequency 103.826174
Playing frequency 110.000000
Playing frequency 116.540940
Playing frequency 123.470825
Playing frequency 130.812783
Playing frequency 138.591315
Playing frequency 146.832384
Playing frequency 155.563492
Playing frequency 164.813778
Playing frequency 174.614116
Playing frequency 184.997211
Playing frequency 195.997718
Playing frequency 207.652349
Playing frequency 220.000000
Playing frequency 233.081881
Playing frequency 246.941651
Playing frequency 261.625565
Playing frequency 277.182631
Playing frequency 293.664768
Playing frequency 311.126984
Playing frequency 329.627557
Playing frequency 349.228231
Playing frequency 369.994423
Playing frequency 391.995436
Playing frequency 415.304698
Playing frequency 440.000000
Playing frequency 466.163762
Playing frequency 493.883301
Playing frequency 523.251131
Playing frequency 554.365262
Playing frequency 587.329536
Playing frequency 622.253967
Playing frequency 659.255114
Playing frequency 698.456463
Playing frequency 739.988845
Playing frequency 783.990872
Playing frequency 830.609395
Playing frequency 880.000000
Playing frequency 932.327523
Playing frequency 987.766603
Playing frequency 1046.502261
Playing frequency 1108.730524
Playing frequency 1174.659072
Playing frequency 1244.507935
Playing frequency 1318.510228
Playing frequency 1396.912926
Playing frequency 1479.977691
Playing frequency 1567.981744
Playing frequency 1661.218790
Playing frequency 1760.000000
You can see it already does multiple things:
- chucking something into an
int
orfloat
variable assigns to it -
<<< ... >>>
operator prints stuff - ChucK has
while
loops and C-stylefor
loops, except it doesn't have=
assignment, you need to use a=>
chuck operator, an it goes the other way - In Western music, notes are constant multiple of each other, that multiple being 2^(1/12), thus the
Math.pow(2.0, i/12.0) * base_freq
formula
Instruments
ChucK of course comes with a lot of predefined instruments, based on either physical models, or mathematical formula. Let's try a sitar as it has fairly simple controls:
Sitar sitar => dac;
while(true) {
Math.random2(60, 80) => float note;
Std.mtof(note) => sitar.freq;
Math.random2f(0.5, 0.1) => sitar.noteOn;
<<<"Playing note", note>>>;
0.2::second => now;
}
$ chuck instrument.ck
Playing note 64.000000
Playing note 74.000000
Playing note 71.000000
Playing note 75.000000
Playing note 65.000000
Playing note 67.000000
...
Step by step:
- we create a
Sitar
and connect it with the outputdac
. We won't really be doing that, but we can also connect instruments to various filters, mixers, analyzers, dynamically disconnect them, and so on. -
Math.random2
generates random integer from a given range -
Math.random2f
generates random float from a given range -
Std.mtof
does the note to frequency calculations (the 2^(1/12) one), with note 69 being the 440 Hz. They were so close to greatness, and they blew it. -
sitar.noteOn
plucks a Sitar string with given strength - more complicated instruments have a lot of such parameters
Fibonacci
We could do it the usual way, but let's do a more convoluted way, with shreds (ChucK threads) and events:
class FibEvent extends Event {
int argument;
int result;
}
fun void reporter(string name, FibEvent e) {
while(true) {
// wait for event
e => now;
// report what we got the data
<<<name, "reporting that fib of", e.argument, "is", e.result>>>;
}
}
// the event
FibEvent e;
// spawn two listeners
spork ~ reporter("a", e);
spork ~ reporter("b", e);
1 => int a;
1 => int b;
1 => int i;
while(true) {
0.1::second => now;
i => e.argument;
a => e.result;
e.signal(); // send it to one of the reporters
a + b => int c;
b => a;
c => b;
i + 1 => i;
}
It reports the usual Fibonacci sequence:
$ chuck fib.ck
a reporting that fib of 1 is 1
b reporting that fib of 2 is 1
a reporting that fib of 3 is 2
b reporting that fib of 4 is 3
a reporting that fib of 5 is 5
b reporting that fib of 6 is 8
a reporting that fib of 7 is 13
b reporting that fib of 8 is 21
a reporting that fib of 9 is 34
b reporting that fib of 10 is 55
a reporting that fib of 11 is 89
b reporting that fib of 12 is 144
a reporting that fib of 13 is 233
b reporting that fib of 14 is 377
a reporting that fib of 15 is 610
b reporting that fib of 16 is 987
a reporting that fib of 17 is 1597
b reporting that fib of 18 is 2584
a reporting that fib of 19 is 4181
b reporting that fib of 20 is 6765
...
Step by step:
- we can create custom event
FibEvent
inheriting from baseEvent
, with some extra fields - we spawn a few shreds with
spork ~ reporter("a", e)
- to be honest I don't think renaming everything randomly like ChucK does is a particularly good practice - inside a
reporter
thread,e => now;
waits to be signaled by evente
- it's another overload of=>
- in the main event we have infinite loop calculating fibonacci numbers
- the interesting part is mainly
e.signal()
which signals one of the listeners that event is ready; there's alsoe.broadcast()
which would signal all listeners
Samples
ChucK obviously can load sample files like .wav
. Let's go through the whole list of samples included in the brew package:
[
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/special/geetar.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter8/audio/kick_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter8/audio/hihat_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/snare_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/cowbell_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/kick_04.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/clap_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/hihat_02.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/snare_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/snare_03.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/snare_02.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/cowbell_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/click_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/click_02.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/stereo_fx_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/kick_04.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/stereo_fx_03.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/kick_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/clap_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/hihat_04.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/hihat_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/hihat_02.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter5/audio/snare_03.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter5/audio/stereo_fx_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter5/audio/kick_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/snare_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/snare_03.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/snare_02.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/cowbell_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/click_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/click_02.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/stereo_fx_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/stereo_fx_03.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/kick_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/clap_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/hihat_04.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter4/audio/hihat_01.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/hihat.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/hihat-open.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/kick.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/snare.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/snare-chili.wav",
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/snare-hop.wav"
] @=> string files[];
SndBuf buf => dac;
for(0 => int i; i<files.size(); i++) {
// read a .wav
files[i] => buf.read;
// start at the beginning
0 => buf.pos;
<<<i, files[i]>>>;
// wait however long buf is, to let it finish playing
buf.length() => now;
}
To assign non-primitive types we need to do @=>
instead of =>
.
The output is a lot of sounds and their files:
$ chuck samples.ck
0 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/special/geetar.wav
1 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter8/audio/kick_01.wav
2 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter8/audio/hihat_01.wav
3 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/snare_01.wav
4 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/cowbell_01.wav
5 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/kick_04.wav
6 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/clap_01.wav
7 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/chapter9/DrumMachine/audio/hihat_02.wav
8 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/snare_01.wav
9 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/snare_03.wav
10 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/snare_02.wav
11 /usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/cowbell_01.wav
...
FizzBuzz
The classic FizzBuzz:
for (1 => int i; i<=100; i++) {
if (i % 15 == 0) {
chout <= "FizzBuzz" <= IO.newline();
} else if (i % 5 == 0) {
chout <= "Buzz" <= IO.newline();
} else if (i % 3 == 0) {
chout <= "Fizz" <= IO.newline();
} else {
chout <= i <= IO.newline();
}
}
We had to replace <<< >>>
with C++ style output. <<< >>>
outputs to stderr only, and does some weird formatting, so we can't really use it here.
ChucK renamed stdout
and stderr
to chout
and cherr
because it just loves doing such silly renames.
With these changes, we get exactly the FizzBuzz output.
Audio FizzBuzz
Here's ChucK playing FizzBuzz in audio form. Fizz and Buzz correspond to audio samples, numbers to notes on a sitar. It all sounds just as awful as you probably imagine.
Sitar sitar => dac;
SndBuf fizz => dac;
SndBuf buzz => dac;
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/book/digital-artists/audio/hihat_04.wav" => fizz.read;
"/usr/local/Cellar/chuck/1.4.1.0/share/chuck/examples/data/snare-chili.wav" => buzz.read;
fun void playFizz() {
0 => fizz.pos;
fizz.length() => now;
}
fun void playBuzz() {
0 => buzz.pos;
buzz.length() => now;
}
for (1 => int i; i<=100; i++) {
if (i % 15 == 0) {
playFizz();
playBuzz();
} else if (i % 5 == 0) {
playBuzz();
} else if (i % 3 == 0) {
playFizz();
} else {
Std.mtof(20+i) => sitar.freq;
Math.random2f(0.5, 0.1) => sitar.noteOn;
0.1::second => now;
}
}
Should you use ChucK?
ChucK's niche is so far from anything I usually do, that I can't really tell you if it's any good.
It definitely lacks functionality for any non-music-related tasks, like files, strings, etc.
From what I can tell, it's not popular among its intended audience, so I guess the answer is a no. It still might be of some interest as an esoteric language, as it's just so weird.
Code
All code examples for the series will be in this repository.
Top comments (3)
I think ChucK making note 69 equal to 420Hz would be needlessly weird, as it’d make ChucK notes about a semitone flat compared to MIDI notes (69 is A4, which is commonly tuned to 440Hz).
I'm aware of that, it was mostly just a bad joke on my part.
But from what I can tell, historically A tended to be lower. Wikipedia claims Baroque pitch was usually around A-415, so 420 could be a compromise.
There's a lot of numerological flat-earth conspiracy woo about A432. I bet A420 would blow their minds.