electro-music.org   Dedicated to experimental electro-acoustic
and electronic music
 
 Front Page  |  Radio  |  Forum  |  Media  |  Playlists  |  Wiki
Forum with support of Syndicator RSS
 FAQFAQ   CalendarCalendar   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   LinksLinks
 RegisterRegister   ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in  Chat RoomChat Room 
 Forum index » DIY Hardware and Software » ChucK programming language
Controlling Time, itself?
Post new topic   Reply to topic Moderators: Kassen
Page 1 of 1 [4 Posts]
View unread posts
View new posts in the last week
Mark the topic unread :: View previous topic :: View next topic
Author Message
10msBoogie



Joined: Oct 20, 2008
Posts: 2
Location: Austin, TX

PostPosted: Mon Oct 20, 2008 8:37 pm    Post subject: Controlling Time, itself? Reply with quote  Mark this post and the followings unread

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.
Back to top
View user's profile Send private message
rogan



Joined: Dec 16, 2007
Posts: 83
Location: Urbana, IL
Audio files: 5

PostPosted: Mon Oct 20, 2008 9:00 pm    Post subject: Reply with quote  Mark this post and the followings unread

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.
Back to top
View user's profile Send private message
Antimon



Joined: Jan 18, 2005
Posts: 4145
Location: Sweden
Audio files: 373
G2 patch files: 100

PostPosted: Wed Oct 22, 2008 1:25 pm    Post subject: Reply with quote  Mark this post and the followings unread

Neat piece of code. Smile 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
Back to top
View user's profile Send private message Visit poster's website
10msBoogie



Joined: Oct 20, 2008
Posts: 2
Location: Austin, TX

PostPosted: Wed Oct 22, 2008 6:07 pm    Post subject: Reply with quote  Mark this post and the followings unread

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?
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic Moderators: Kassen
Page 1 of 1 [4 Posts]
View unread posts
View new posts in the last week
Mark the topic unread :: View previous topic :: View next topic
 Forum index » DIY Hardware and Software » ChucK programming language
Jump to:  

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum
Log in
Username:     Password:      Log me on automatically each visit    


Forum with support of Syndicator RSS
Powered by phpBB © 2001, 2005 phpBB Group
Copyright © 2003 through 2026 by electro-music.org