10msBoogie

Joined: Oct 20, 2008 Posts: 2 Location: Austin, TX
|
Posted: Mon Oct 20, 2008 8:37 pm Post subject:
Controlling Time, itself? |
 |
|
Hi folks, my name is Brent. After lurking around here almost a year I've finally registered. First off, a big thanks to all posters for sharing code -- it's been very helpful to see how other hackers have done things.
I've seen a few ways to synchronize separate shreds in Chuck, and I really didn't want to put clock-based tempos and synchronization bits in every module I used, a central control would be better. I've created a TimeMachine class that performs primary timing, and provides ticks at various intervals.
The TimeMachine knows the tempo and the number of beats in a measure, and once started it counts measures, beats and subbeats. It presently notifies on the downbeat, upbeat, eighth triplets and sixteenths. At each point, the BeatEvent is broadcast() to all shreds, which then react however they will.
Shreds wait for BeatEvent to trigger, then examine the current subbeat to determine what to do. A shred with a highhat sample may only react to upbeats, for example.
Anyway, here is the code and an example usage. FWIW, this code is in the public domain.
Main script to launch chuck with. Load shared classes, and the song/soundscape.
| Code: |
"beatevent.ck" => Machine.add;
"timemachine.ck" => Machine.add;
"brianeno-reject-number-743.ck" => Machine.add;
|
BeatEvent is small. It's just a custom Event-container thing.
| Code: |
// Values are zero if that kind of a subbeat is invalid.
// On the down beat, all will be set to one.
// On the up beat, eighth==2 and sixteenth==3, for example.
public class BeatEvent extends Event {
0 => int whole; // 1
0 => int eighth; // 1-2
0 => int triplet; // 1-3
0 => int sixteenth; // 1-4
}
|
TimeMachine is a little larger:
| Code: |
public class TimeMachine {
static BeatEvent @ beatEvent;
1::second => static dur beatDur;
fun static float setTempo(float bpm)
{
<<< "Setting tempo to ", bpm, " bpm" >>>;
minute/bpm => beatDur;
}
4 => static int barLength; // beats per measure
fun static void setBarLength(int beats_per_bar)
{
beats_per_bar => barLength;
}
0 => static int barCount; // which bar # is current?
0 => static int beatCount;
0 => static int timerShredID;
1 => static int timerShouldRun;
fun static void timerShred()
{
0 => barCount;
while (timerShouldRun)
{
++barCount;
0 => beatCount;
while (beatCount < barLength)
{
++beatCount;
// subdivide the beat, notify listeners via our event
1 => beatEvent.whole;
1 => beatEvent.eighth;
1 => beatEvent.triplet;
1 => beatEvent.sixteenth;
beatEvent.broadcast();
0.25::beatDur => now;
// second sixteenth
0 => beatEvent.whole;
0 => beatEvent.eighth;
0 => beatEvent.triplet;
2 => beatEvent.sixteenth;
beatEvent.broadcast();
0.08333::beatDur => now;
// second triplet
2 => beatEvent.triplet;
0 => beatEvent.sixteenth;
beatEvent.broadcast();
0.16666::beatDur => now;
// third sixteenth
2 => beatEvent.eighth;
0 => beatEvent.triplet;
3 => beatEvent.sixteenth;
beatEvent.broadcast();
0.16666::beatDur => now;
// third triplet
0 => beatEvent.eighth;
3 => beatEvent.triplet;
0 => beatEvent.sixteenth;
beatEvent.broadcast();
0.08333::beatDur => now;
// 4th 16th
0 => beatEvent.triplet;
4 => beatEvent.sixteenth;
beatEvent.broadcast();
0.25::beatDur => now;
}
}
}
fun static void start()
{
if (timerShredID == 0) {
// spork it, save ID
1 => timerShouldRun;
spork ~timerShred() @=> Shred @s;
s.id() => timerShredID;
<<< "TimeMachine.start: shred ID ", timerShredID >>>;
}
}
fun static void stop()
{
if (timerShredID != 0) {
0 => timerShouldRun;
0 => timerShredID;
}
}
}
new BeatEvent @=> TimeMachine.beatEvent; // workaround for static type implementation
4 => TimeMachine.barLength;
0 => TimeMachine.barCount;
0 => TimeMachine.timerShredID;
1 => TimeMachine.timerShouldRun;
|
brianeno-reject-number-743.ck is the "song" source. It loads up any other custom instruments, and each shred that represents a voice, or part. This is also the only place the tempo and time signature are set (not that you couldn't change it elsewhere). Two voices are loaded here.
| Code: |
TimeMachine.setTempo(96);
TimeMachine.setBarLength(4);
"kickdrum.ck" => Machine.add;
"bumbumbum.ck" => Machine.add;
TimeMachine.start();
week => now;
|
kickdrum.ck uses the kick.wav sample included with Chuck, and blaps it out periodically.
| Code: |
SndBuf buf => dac;
"kick.wav" => buf.read;
.7 => buf.gain;
while (1)
{
TimeMachine.beatEvent => now;
TimeMachine.beatEvent.eighth => int eighth;
TimeMachine.beatCount => int beat;
if ( (beat == 1 && eighth) // on both eighths
|| (beat == 2 && eighth == 2)
|| (beat == 3 && eighth == 1)
|| (beat == 4 && eighth == 2))
{
0 => buf.pos;
}
}
|
bumbumbum.ck is a triplet-based baseline. It shows how to use bar counts to create a pattern (a simple 8-bar pattern in this case).
| Code: |
SinOsc osc => ADSR adsr => dac;
adsr.set(20::ms, 100::ms, 1.0, 300::ms);
[0, 0, 8, 12] @=> int scale[];
while (1)
{
TimeMachine.beatEvent => now;
TimeMachine.beatEvent.triplet => int triplet;
TimeMachine.beatCount => int beat;
TimeMachine.barCount => int bar;
if ((bar-1)%8 < 4) {
16 => scale[3];
} else {
12 => scale[3];
}
if ((beat == 1 && triplet==3) ||
(beat == 2 && triplet==2) ||
(beat == 3 && triplet==1)
)
{
40 + scale[beat] => Std.mtof => osc.freq;
10 => adsr.keyOn;
} else {
1 => adsr.keyOff;
}
}
|
So far it's been working well, and it's been fun. Feel free to use it, modify it, or whatever. Again, it's in the public domain.
Cheers. |
|
rogan

Joined: Dec 16, 2007 Posts: 83 Location: Urbana, IL
Audio files: 5
|
Posted: Mon Oct 20, 2008 9:00 pm Post subject:
|
 |
|
Hey Brent. Thanks for the post -- extra points for the title.
The code looks good and I like the idea. I've actually been thinking about writing something like this myself. I'll have to take a look at it later when I have time -- I'm currently pulling what may be an all-nighter trying to meet a deadline. Boo. |
|
Antimon

Joined: Jan 18, 2005 Posts: 4145 Location: Sweden
Audio files: 373
G2 patch files: 100
|
Posted: Wed Oct 22, 2008 1:25 pm Post subject:
|
 |
|
Neat piece of code. I often run into these kinds of synchronization problems, and usually solve it in different ways each time. This seems like a logical way to organise timing. Now, all we need is for ChucK to be able to send MIDI clock, then this could be a perfect hub in the sound making network.
/Stefan _________________ Antimon's Window
@soundcloud @Flattr home - you can't explain music |
|
10msBoogie

Joined: Oct 20, 2008 Posts: 2 Location: Austin, TX
|
Posted: Wed Oct 22, 2008 6:07 pm Post subject:
|
 |
|
Thanks, guys.
One shortcoming of this code is turning off a note. A shred could just wait for the appropriate time to call noteOff() or keyOff(), but the logic gets more complicated.
I don't know anything about MIDI. Does the protocol have a clock as part of it, or can a MIDI device be a clock? |
|