ChucK is an audio programming language, the most productivity-enhancing DSL I've ever used, and I kind of use it for everything. It has two great things going for it:
- Connections between audio processing units can be reconfigured programmatically on the fly with a syntax so simple that usually, if you can think it, you can code it in a few minutes.
- You can reconfigure those audio processing units with any time resolution you desire: there are no fixed control rates.
Both are practical features, but the second is what captures my imagination. To the point that I haven't been able to think about anything but "strongly-timed" scheduling for a long time. The idea is simple and powerful, but underutilized as far as I've seen.
Time should be a first-class citizen in a programming language. ruck does this for Ruby.
require "ruck" @shreduler = Ruck::Shreduler.new @shreduler.make_convenient @shreduler.run
ruck works by introducing shredulers. Shredulers shredule sporked shreds on a virtual timeline. Unlike threads, shreds stay in sync without explicit synchronization.
spork do puts "ping" Shred.yield(2) end spork do Shred.yield(1) puts "pong" Shred.yield(1) end
Shreds are cooperatively multi-shredded to maintain the illusion that code execution is instantaneous. All code shreduled for a particular time is allowed to finish before later code is run.
spork do sleep 15 end spork do Shred.yield(1) puts "real time: +15 seconds" puts "virtual time: +1 time unit" end
How virtual time maps to real time is up to you. The default Ruck::Shreduler executes everything as quickly as it can, but is written to be easily sub-classed for more complex behavior. The most obvious sub-class makes one time unit equivalent to one second.
# imprecise, but effective real-time shreduling
class RealTimeShreduler < Ruck::Shreduler
def fast_forward(dt)
super
sleep(dt)
end
end
RealTimeShreduler.new.make_convenient
spork do
t = 0
loop do
puts "#{t}..."
Shred.yield(1)
t += 1
end
end
$shreduler.run
Or you can do like ChucK and map virtual time units to samples in an audio stream.
class AudioShreduler < Ruck::Shreduler
def fast_forward(dt)
super
@audio_now ||= 0
@synthesis.process(self.now.to_i - @audio_now)
@audio_now = now.to_i
end
end
spork do
osc = SinOscillator.new
output.connect(osc)
loop do
osc.freq = rand * 400 + 200
Shred.yield(44100)
end
end
Or you can map to MIDI ticks, frames of video, or bytes in a file. But real-time shredulers are the most generally useful. It's easiest to think about time as time. The problem with typical render loops is that they force you to think in frames and buffers instead of time. ruck can help you there.
class RenderLoopShreduler < Ruck::Shreduler
def catch_up
@start_time ||= Time.now
real_now = Time.now - @start_time
run_until(real_now)
end
end
class GameWindow < Gosu::Window
def initialize
super(640, 480, false, 16)
RenderLoopShreduler.new.make_convenient
@images = Gosu::Image::load_tiles(self, "animation.png", 25, 25, false)
@frame = 0
spork_loop(:draw) do
@images[@frame].draw(320, 240)
end
spork_loop(0.1) do
@frame = (@frame + 1) % @images.size
end
end
def draw
$shreduler.raise_all(:draw)
$shreduler.catch_up
end
end
I'm scratching the surface of ruck here, not to mention the potential of strong timing. There are plenty of examples in the ruck-realtime, ruck-midi, ruck-ugen, and ruck-glapp projects, not to mention ruck itself.
If you're interested, I recommend starting with ruck's README.