ruck 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ pkg
data/README ADDED
@@ -0,0 +1,85 @@
1
+ A port of ChucK's strong timing to Ruby!
2
+
3
+ Use Ruck's "shreds" to coordinate interleaved execution
4
+ of threads (continuations) with precise virtual timing.
5
+ Actual run-time is abstracted and shreds declare how much
6
+ virtual time they took to execute. Scheduling of shreds
7
+ is done by a ...
8
+
9
+ SHREDULER
10
+
11
+ UGenShreduler:
12
+
13
+ This shreduler calculates an audio unit generator graph's
14
+ output in virtual time. The graph can be modified by
15
+ shreds all the while.
16
+
17
+ The graph is written in Ruby for flexibility, so it's
18
+ too slow (on my computer) for real time, so there is no
19
+ real-time playback. You can use WavOut, though.
20
+ (See ex01.rb)
21
+
22
+ Check out the library of built-in unit generators in ugen/
23
+ and make your own.
24
+
25
+ Unit Generator Usage
26
+ ====================
27
+
28
+ Play a sine wave (ex02.rb):
29
+
30
+ s = SinOsc.new(:freq => 440)
31
+ w = WavOut.new(:filename => "ex02.wav")
32
+ s >> w >> blackhole
33
+ play 3.seconds
34
+
35
+ Attach lambdas to unit generator attributes (ex03.rb):
36
+
37
+ wav = WavOut.new(:filename => "ex03.wav")
38
+ sin2 = SinOsc.new(:freq => 3)
39
+ sin = SinOsc.new(:freq => L{ sin2.last * 220 + 660 },
40
+ :gain => L{ 0.5 + sin2.last * 0.5 })
41
+ [sin >> wav, sin2] >> blackhole
42
+ play 3.seconds
43
+
44
+
45
+ RealTimeShreduler
46
+
47
+ This shreduler attempts to keep virtual time in line with
48
+ real time.
49
+ (See ex10.rb)
50
+
51
+ MIDIShreduler
52
+
53
+ This shreduler uses MIDIator and midilib to support live
54
+ MIDI playback and saving MIDI to disk. An example runner
55
+ is provided in midi_runner.rb which you invoke like this:
56
+
57
+ $ ruby midilib_runner.rb MIDI_FILENAME NUM_TRACKS LIVE SCRIPT_FILENAME [...]
58
+
59
+ where LIVE is "no" to only save the MIDI output, or "yes"
60
+ to also play in real-time.
61
+
62
+ USAGE
63
+ =====
64
+
65
+ ugen_runner.rb is an (example) program that lets you run scripts
66
+ as you do with ChucK, with a shreduler that pulls samples from
67
+ the unit generator graph (through the blackhole):
68
+
69
+ $ ruby ugen_runner.rb examples/ex01.rb
70
+
71
+ It uses a UGenShreduler with a hard-coded sample rate. You can use it
72
+ as an example for how to embed your own shreduler. It boils down
73
+ to this:
74
+
75
+ require "ruck"
76
+
77
+ @shreduler = Ruck::Shreduler.new # replace with your subclass
78
+
79
+ # repeat this part as necessary to seed @shreduler with shreds
80
+ @shreduler.spork("main") do
81
+ ...
82
+ end
83
+
84
+ # this returns when no shreds remain
85
+ @shreduler.run
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+
2
+ begin
3
+ require "jeweler"
4
+ Jeweler::Tasks.new do |gemspec|
5
+ gemspec.name = "ruck"
6
+ gemspec.email = "tom@alltom.com"
7
+ gemspec.homepage = "http://github.com/alltom/ruck"
8
+ gemspec.authors = ["Tom Lieber"]
9
+ gemspec.summary = "strong timing for Ruby: cooperative threads on a virtual clock"
10
+ gemspec.description = <<-EOF
11
+ Ruck uses continuations and a simple scheduler to ensure "shreds"
12
+ (threads in Ruck) are woken at precisely the right time according
13
+ to its virtual clock. Schedulers can map virtual time to samples
14
+ in a WAV file, real time, time in a MIDI file, or anything else
15
+ by overriding "sim_to" in the Shreduler class.
16
+
17
+ A small library of useful unit generators and plenty of examples
18
+ are provided. See the README or the web page for details.
19
+ EOF
20
+ gemspec.has_rdoc = false
21
+ end
22
+ Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jewler not available. Install it with: sudo gem install jeweler"
25
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/ruck_glapp ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ # requires glapp gem
5
+ # sudo gem install alltom-glapp
6
+ # see http://alltom.com/pages/glapp for more details
7
+
8
+ require "ruck"
9
+
10
+ require "rubygems"
11
+ require "glapp"
12
+
13
+ class GLAppShreduler < Ruck::Shreduler
14
+ def initialize
15
+ @shreds_waiting_for_next_frame = []
16
+ super
17
+ end
18
+
19
+ def actual_now
20
+ Time.now - @start_time
21
+ end
22
+
23
+ def enqueue_for_next_frame(shred)
24
+ @shreds_waiting_for_next_frame << shred
25
+ @shreds.delete(shred)
26
+ end
27
+
28
+ def next_frame_has_arrived
29
+ @shreds += @shreds_waiting_for_next_frame
30
+
31
+ @shreds_waiting_for_next_frame.each do |shred|
32
+ shred.now = now
33
+ end
34
+
35
+ @shreds_waiting_for_next_frame = []
36
+ end
37
+
38
+ def catch_up
39
+ @start_time ||= Time.now
40
+
41
+ while shreds.length > 0 && next_shred.now < actual_now
42
+ run_one
43
+ end
44
+ end
45
+ end
46
+
47
+ module ShredLocal
48
+ def wait_for_frame
49
+ SHREDULER.enqueue_for_next_frame(SHREDULER.current_shred)
50
+ SHREDULER.current_shred.yield(0)
51
+ end
52
+
53
+ def wait(seconds)
54
+ SHREDULER.current_shred.yield(seconds)
55
+ end
56
+ end
57
+
58
+ class MySketch
59
+ def setup
60
+ ARGV.each do |filename|
61
+ SHREDULER.spork(filename) do
62
+ require filename
63
+ end
64
+ end
65
+ end
66
+
67
+ def draw
68
+ SHREDULER.catch_up
69
+ SHREDULER.next_frame_has_arrived
70
+ end
71
+ end
72
+
73
+ include GLApp
74
+ include ShredLocal
75
+ SHREDULER = GLAppShreduler.new
76
+
77
+ MySketch.new.show 800, 600, "My Sketch"
data/bin/ruck_midi ADDED
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require "ruck"
5
+
6
+ require "rubygems"
7
+ require "midilib"
8
+ require "midiator"
9
+
10
+ # configuration
11
+ if ARGV.length < 4
12
+ puts "ruby midilib_runner.rb MIDI_FILENAME NUM_TRACKS LIVE SCRIPT_FILENAME [...]"
13
+ exit 1
14
+ end
15
+
16
+ MIDI_FILENAME = ARGV[0]
17
+ NUM_TRACKS = ARGV[1].to_i
18
+ ALSO_LIVE = ["yes", "true", "1", "yeah"].include? ARGV[2].downcase
19
+ FILENAMES = ARGV[3..-1]
20
+
21
+ class MIDIShreduler < Ruck::Shreduler
22
+ def run
23
+ @start_time = Time.now
24
+ super
25
+ end
26
+
27
+ def sim_to(new_now)
28
+ d = new_now - @now
29
+ TRACK_DELTAS.each_with_index do |delta, i|
30
+ TRACK_DELTAS[i] = delta + d
31
+ end
32
+
33
+ # sync with wall clock
34
+ if ALSO_LIVE
35
+ actual_now = Time.now
36
+ simulated_now = @start_time + (new_now.to_f / SEQUENCE.ppqn / SEQUENCE.bpm * 60.0)
37
+ if simulated_now > actual_now
38
+ sleep(simulated_now - actual_now)
39
+ end
40
+ end
41
+
42
+ @now = new_now
43
+ end
44
+ end
45
+
46
+ # state
47
+ SHREDULER = MIDIShreduler.new
48
+ SEQUENCE = MIDI::Sequence.new
49
+ TRACKS = (1..NUM_TRACKS).map { MIDI::Track.new(SEQUENCE) }
50
+ TRACK_DELTAS = TRACKS.map { 0 }
51
+ if ALSO_LIVE
52
+ MIDI_PLAYER = MIDIator::Interface.new
53
+ end
54
+
55
+ # midi initialization stuff
56
+ TRACKS.each do |track|
57
+ SEQUENCE.tracks << track
58
+ #track.events << MIDI::Tempo.new(MIDI::Tempo.bpm_to_mpq(120))
59
+ end
60
+ if ALSO_LIVE
61
+ MIDI_PLAYER.use :dls_synth
62
+ MIDI_PLAYER.instruct_user!
63
+ end
64
+
65
+ # set up some useful time helpers
66
+ module MIDITime
67
+ def pulse
68
+ self
69
+ end
70
+ alias_method :pulses, :pulse
71
+
72
+ def quarter_note
73
+ self * SEQUENCE.ppqn
74
+ end
75
+ alias_method :quarter_notes, :quarter_note
76
+ alias_method :beat, :quarter_note
77
+ alias_method :beats, :quarter_note
78
+ end
79
+
80
+ class Fixnum
81
+ include MIDITime
82
+ end
83
+
84
+ class Float
85
+ include MIDITime
86
+ end
87
+
88
+ # stuff accessible in a shred
89
+ module ShredLocal
90
+
91
+ def now
92
+ SHREDULER.now
93
+ end
94
+
95
+ def spork(name = "unnamed", &shred)
96
+ SHREDULER.spork(name, &shred)
97
+ end
98
+
99
+ def wait(pulses)
100
+ SHREDULER.current_shred.yield(pulses)
101
+ end
102
+
103
+ def finish
104
+ shred = SHREDULER.current_shred
105
+ SHREDULER.remove_shred shred
106
+ shred.finish
107
+ end
108
+
109
+ def note_on(note, velocity = 127, channel = 0, track = 0)
110
+ TRACKS[track].events << MIDI::NoteOnEvent.new(channel, note, velocity, TRACK_DELTAS[track].to_i)
111
+ TRACK_DELTAS[track] = 0
112
+ if ALSO_LIVE
113
+ MIDI_PLAYER.driver.note_on(note, channel, velocity)
114
+ end
115
+ end
116
+
117
+ def note_off(note, channel = 0, track = 0)
118
+ TRACKS[track].events << MIDI::NoteOffEvent.new(channel, note, 0, TRACK_DELTAS[track].to_i)
119
+ TRACK_DELTAS[track] = 0
120
+ if ALSO_LIVE
121
+ MIDI_PLAYER.driver.note_on(note, channel, 0)
122
+ end
123
+ end
124
+
125
+ end
126
+
127
+ FILENAMES.each do |filename|
128
+ unless File.readable?(filename)
129
+ LOG.fatal "Cannot read file #{filename}"
130
+ exit
131
+ end
132
+ end
133
+
134
+ FILENAMES.each do |filename|
135
+ SHREDULER.spork(filename) do
136
+ include ShredLocal
137
+ require filename
138
+ end
139
+ end
140
+
141
+ SHREDULER.run
142
+
143
+ File.open(MIDI_FILENAME, "wb") { |file| SEQUENCE.write(file) }
data/bin/ruck_ugen ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require "ruck"
5
+
6
+ # stuff accessible in a shred
7
+ module ShredLocal
8
+
9
+ def blackhole
10
+ BLACKHOLE
11
+ end
12
+
13
+ def now
14
+ SHREDULER.now
15
+ end
16
+
17
+ def spork(name = "unnamed", &shred)
18
+ SHREDULER.spork(name, &shred)
19
+ end
20
+
21
+ def play(samples)
22
+ SHREDULER.current_shred.yield(samples)
23
+ end
24
+
25
+ def finish
26
+ shred = SHREDULER.current_shred
27
+ SHREDULER.remove_shred shred
28
+ shred.finish
29
+ end
30
+
31
+ end
32
+
33
+
34
+ SAMPLE_RATE = 22050
35
+ SHREDULER = Ruck::UGenShreduler.new
36
+ BLACKHOLE = Ruck::InChannel.new
37
+
38
+ filenames = ARGV
39
+ filenames.each do |filename|
40
+ unless File.readable?(filename)
41
+ LOG.fatal "Cannot read file #{filename}"
42
+ exit
43
+ end
44
+ end
45
+
46
+ filenames.each do |filename|
47
+ SHREDULER.spork(filename) do
48
+ include ShredLocal
49
+ include Ruck::Generators
50
+ require filename
51
+ end
52
+ end
53
+ SHREDULER.run
@@ -0,0 +1,13 @@
1
+
2
+ clear
3
+
4
+ loop do
5
+ wait 2
6
+
7
+ glTranslate(0, 0, -5)
8
+ glutSolidCube(2)
9
+
10
+ wait 0.1
11
+
12
+ clear
13
+ end
@@ -0,0 +1,24 @@
1
+
2
+ def maybe
3
+ rand >= 0.5
4
+ end
5
+
6
+ TRACKS[0].events << MIDI::Controller.new(10, 32, 1) # channel, controller, value
7
+ TRACKS[0].events << MIDI::ProgramChange.new(10, 26) # channel, program
8
+ MIDI_PLAYER.control_change 32, 10, 1 # number, channel, value
9
+ MIDI_PLAYER.program_change 10, 26 # channel, program
10
+
11
+ def play(note, dur = 1.quarter_note)
12
+ return if maybe
13
+ note_on note, 100, 10
14
+ wait dur
15
+ note_off note, 10
16
+ end
17
+
18
+ 10.times do
19
+ spork { play(rand(30) + 40, rand * 3.quarter_notes + 3.quarter_notes) }
20
+ wait 0.5.quarter_note
21
+ end
22
+
23
+ wait 2.quarter_notes; note_off 0 # end padding
24
+
@@ -0,0 +1,24 @@
1
+ def beep(wav, chan)
2
+ (s = SawOsc.new(:freq => 440, :gain => 0.25)) >> wav.in(chan)
3
+ 10.times do
4
+ play 0.1.seconds
5
+ s.freq *= 1.2
6
+ end
7
+ s << wav
8
+ end
9
+
10
+ wav = WavOut.new(:filename => "ex01.wav", :num_channels => 2)
11
+ SinOsc.new(:freq => 440, :gain => 0.25) >> wav
12
+ SinOsc.new(:freq => 880, :gain => 0.25) >> wav
13
+
14
+ wav >> blackhole
15
+
16
+ chan = 0
17
+
18
+ 10.times do
19
+ play 0.7.seconds
20
+ chan = (chan + 1) % 2
21
+ spork("beep") { beep(wav, chan) }
22
+ end
23
+
24
+ play 2.seconds