ruck 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +21 -0
- data/README.markdown +146 -0
- data/Rakefile +11 -0
- data/VERSION +1 -1
- data/examples/ex01.rb +27 -0
- data/examples/ex02.rb +31 -0
- data/examples/ex03.rb +75 -0
- data/examples/ex04.rb +33 -0
- data/examples/ex05.rb +33 -0
- data/examples/ex06.rb +110 -0
- data/examples/space/media/Beep.wav +0 -0
- data/examples/space/media/Space.png +0 -0
- data/examples/space/media/Star.png +0 -0
- data/examples/space/media/Starfighter.bmp +0 -0
- data/examples/space/space.rb +307 -0
- data/lib/ruck.rb +4 -4
- data/lib/ruck/clock.rb +137 -0
- data/lib/ruck/event_clock.rb +39 -0
- data/lib/ruck/shred.rb +135 -0
- data/lib/ruck/shreduler.rb +149 -0
- data/ruck.gemspec +45 -8
- data/spec/clock_spec.rb +218 -0
- data/spec/shred_spec.rb +116 -0
- data/spec/shreduler_spec.rb +200 -0
- metadata +58 -12
- data/README +0 -48
- data/lib/ruck/shreduling.rb +0 -135
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2010 Tom Lieber
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# ruck: a port of ChucK's strong timing to Ruby!
|
2
|
+
|
3
|
+
ruck lets you create virtual timelines on which you can
|
4
|
+
precisely time the execution of events.
|
5
|
+
|
6
|
+
This is accomplished using Shred, Clock, and Shreduler:
|
7
|
+
|
8
|
+
- Shred: a resumable Proc
|
9
|
+
- Clock: manages occurrences on a timeline
|
10
|
+
- Shreduler: executes shreds on time
|
11
|
+
|
12
|
+
Here's an example of how to use Shred:
|
13
|
+
|
14
|
+
shred = Ruck::Shred.new do
|
15
|
+
puts "A"
|
16
|
+
Ruck::Shred.current.pause
|
17
|
+
puts "B"
|
18
|
+
Ruck::Shred.current.pause
|
19
|
+
puts "C"
|
20
|
+
end
|
21
|
+
|
22
|
+
shred.call
|
23
|
+
shred.call
|
24
|
+
shred.call
|
25
|
+
|
26
|
+
# prints:
|
27
|
+
# A
|
28
|
+
# B
|
29
|
+
# C
|
30
|
+
|
31
|
+
Here's how Clock works:
|
32
|
+
|
33
|
+
clock = Ruck::Clock.new
|
34
|
+
|
35
|
+
clock.schedule("C", 3)
|
36
|
+
clock.schedule("B", 2)
|
37
|
+
clock.schedule("A", 1)
|
38
|
+
|
39
|
+
3.times do
|
40
|
+
letter, time = clock.unschedule_next
|
41
|
+
puts "#{letter} @ #{time}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# prints:
|
45
|
+
# A @ 1.0
|
46
|
+
# B @ 2.0
|
47
|
+
# C @ 3.0
|
48
|
+
|
49
|
+
Here's how these two are combined with Shreduler:
|
50
|
+
|
51
|
+
@shreduler = Ruck::Shreduler.new
|
52
|
+
|
53
|
+
@shreduler.shredule(Ruck::Shred.new do
|
54
|
+
%w{ A B C D E }.each do |letter|
|
55
|
+
puts "#{letter}"
|
56
|
+
@shreduler.shredule(Ruck::Shred.current, @shreduler.now + 1)
|
57
|
+
Ruck::Shred.current.pause
|
58
|
+
end
|
59
|
+
end)
|
60
|
+
|
61
|
+
@shreduler.shredule(Ruck::Shred.new do
|
62
|
+
%w{ 1 2 3 4 5 }.each do |number|
|
63
|
+
puts "#{number}"
|
64
|
+
@shreduler.shredule(Ruck::Shred.current, @shreduler.now + 1)
|
65
|
+
Ruck::Shred.current.pause
|
66
|
+
end
|
67
|
+
end)
|
68
|
+
|
69
|
+
@shreduler.run
|
70
|
+
|
71
|
+
# prints
|
72
|
+
# A
|
73
|
+
# 1
|
74
|
+
# B
|
75
|
+
# 2
|
76
|
+
# C
|
77
|
+
# 3
|
78
|
+
# D
|
79
|
+
# 4
|
80
|
+
# E
|
81
|
+
# 5
|
82
|
+
|
83
|
+
Though this is somewhat inconvenient to use, so when you're
|
84
|
+
using just one global Shreduler, you can call
|
85
|
+
Shreduler#make_convenient, which adds useful methods to Object
|
86
|
+
and Shred so that you can write the above example more
|
87
|
+
concisely:
|
88
|
+
|
89
|
+
@shreduler = Ruck::Shreduler.new
|
90
|
+
@shreduler.make_convenient
|
91
|
+
|
92
|
+
spork do
|
93
|
+
%w{ A B C D E }.each do |letter|
|
94
|
+
puts "#{letter}"
|
95
|
+
Ruck::Shred.yield(1)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
spork do
|
100
|
+
%w{ 1 2 3 4 5 }.each do |number|
|
101
|
+
puts "#{number}"
|
102
|
+
Ruck::Shred.yield(1)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
@shreduler.run
|
107
|
+
|
108
|
+
## Shredulers and time
|
109
|
+
|
110
|
+
ruck doesn't specify any behavior for when time passes,
|
111
|
+
so by default all shreds are executed as fast as possible
|
112
|
+
as they're drained from the queue. In other words, there's
|
113
|
+
no mapping from virtual time to anything else, so Shreduler
|
114
|
+
only really cares about order.
|
115
|
+
|
116
|
+
You change this by sub-classing Shreduler and overriding its
|
117
|
+
methods. For example, an easy modification is to map the
|
118
|
+
time units to seconds:
|
119
|
+
|
120
|
+
class RealTimeShreduler < Ruck::Shreduler
|
121
|
+
def fast_forward(dt)
|
122
|
+
super
|
123
|
+
sleep(dt)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
## Useful Shredulers
|
128
|
+
|
129
|
+
These gems provide shredulers with other interesting mappings,
|
130
|
+
as well as defining convenient DSLs to make shreduling less
|
131
|
+
verbose:
|
132
|
+
|
133
|
+
<dl>
|
134
|
+
<dt>ruck-realtime</dt>
|
135
|
+
<dd>the above example</dd>
|
136
|
+
|
137
|
+
<dt>ruck-midi</dt>
|
138
|
+
<dd>maps to quarter notes in a MIDI file, quarter
|
139
|
+
notes in real-time, or both simultaneously (playing back,
|
140
|
+
then saving to disk)</dd>
|
141
|
+
|
142
|
+
<dt>ruck-ugen</dt>
|
143
|
+
<dd>maps to samples in an audio stream, providing a
|
144
|
+
simple unit generator framework for reading and writing WAV
|
145
|
+
files with effects</dd>
|
146
|
+
</dl>
|
data/Rakefile
CHANGED
@@ -15,11 +15,22 @@ begin
|
|
15
15
|
to its virtual clock. Schedulers can map virtual time to samples
|
16
16
|
in a WAV file, real time, time in a MIDI file, or anything else
|
17
17
|
by overriding "sim_to" in the Shreduler class.
|
18
|
+
|
19
|
+
A small library of useful unit generators and plenty of examples
|
20
|
+
are provided. See the README or the web page for details.
|
18
21
|
EOF
|
19
22
|
gem.has_rdoc = false
|
23
|
+
gem.add_dependency "PriorityQueue", ">= 0"
|
20
24
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
21
25
|
end
|
22
26
|
Jeweler::GemcutterTasks.new
|
23
27
|
rescue LoadError
|
24
28
|
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
25
29
|
end
|
30
|
+
|
31
|
+
if require "spec/rake/spectask"
|
32
|
+
desc "Run all specs"
|
33
|
+
Spec::Rake::SpecTask.new("spec") do |t|
|
34
|
+
t.spec_files = FileList["spec/**/*.rb"]
|
35
|
+
end
|
36
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/examples/ex01.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
require "ruck"
|
3
|
+
|
4
|
+
include Ruck
|
5
|
+
|
6
|
+
shred1 = Shred.new do |shred|
|
7
|
+
puts "A"
|
8
|
+
Shred.current.pause
|
9
|
+
puts "B"
|
10
|
+
Shred.current.pause
|
11
|
+
puts "C"
|
12
|
+
end
|
13
|
+
|
14
|
+
shred2 = Shred.new do |shred|
|
15
|
+
puts "1"
|
16
|
+
Shred.current.pause
|
17
|
+
puts "2"
|
18
|
+
Shred.current.pause
|
19
|
+
puts "3"
|
20
|
+
end
|
21
|
+
|
22
|
+
shred1.call
|
23
|
+
shred2.call
|
24
|
+
shred1.call
|
25
|
+
shred2.call
|
26
|
+
shred1.call
|
27
|
+
shred2.call
|
data/examples/ex02.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
require "ruck"
|
3
|
+
|
4
|
+
include Ruck
|
5
|
+
|
6
|
+
class RealTimeShreduler < Shreduler
|
7
|
+
def fast_forward(dt)
|
8
|
+
super
|
9
|
+
sleep(dt)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
@shreduler = RealTimeShreduler.new
|
14
|
+
|
15
|
+
@shreduler.shredule(Shred.new do
|
16
|
+
%w{ A B C D E }.each do |letter|
|
17
|
+
puts "#{letter}"
|
18
|
+
@shreduler.shredule(Shred.current, @shreduler.now + 1)
|
19
|
+
Shred.current.pause
|
20
|
+
end
|
21
|
+
end)
|
22
|
+
|
23
|
+
@shreduler.shredule(Shred.new do
|
24
|
+
%w{ 1 2 3 4 5 }.each do |number|
|
25
|
+
puts "#{number}"
|
26
|
+
@shreduler.shredule(Shred.current, @shreduler.now + 1)
|
27
|
+
Shred.current.pause
|
28
|
+
end
|
29
|
+
end)
|
30
|
+
|
31
|
+
@shreduler.run
|
data/examples/ex03.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
require "rubygems"
|
3
|
+
require "midiator"
|
4
|
+
require "ruck"
|
5
|
+
|
6
|
+
include Ruck
|
7
|
+
|
8
|
+
def setup_midi
|
9
|
+
midi = MIDIator::Interface.new
|
10
|
+
# midi.autodetect_driver
|
11
|
+
midi.use :dls_synth # OS X built-in synth
|
12
|
+
|
13
|
+
midi.control_change 32, 10, 1 # TR-808 is Program 26 in LSB bank 1
|
14
|
+
midi.program_change 10, 26
|
15
|
+
|
16
|
+
include MIDIator::Drums
|
17
|
+
|
18
|
+
midi
|
19
|
+
end
|
20
|
+
|
21
|
+
class RealTimeShreduler < Shreduler
|
22
|
+
def fast_forward(dt)
|
23
|
+
super
|
24
|
+
sleep(dt)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
@midi = setup_midi
|
31
|
+
@shreduler = RealTimeShreduler.new
|
32
|
+
|
33
|
+
@warped_clock = @shreduler.clock.add_child_clock(Clock.new)
|
34
|
+
|
35
|
+
|
36
|
+
# warped bass drum player
|
37
|
+
@shreduler.shredule(Shred.new do
|
38
|
+
loop do
|
39
|
+
@midi.note_on(BassDrum1, 10, 127)
|
40
|
+
@midi.note_on(BassDrum1, 10, 127)
|
41
|
+
|
42
|
+
@shreduler.shredule(Shred.current, @warped_clock.now + 1, @warped_clock)
|
43
|
+
Shred.current.pause
|
44
|
+
end
|
45
|
+
end, nil, @warped_clock)
|
46
|
+
|
47
|
+
|
48
|
+
# normal crash cymbal player
|
49
|
+
@shreduler.shredule(Shred.new do
|
50
|
+
loop do
|
51
|
+
@midi.note_on(CrashCymbal1, 10, 127)
|
52
|
+
@midi.note_on(CrashCymbal1, 10, 127)
|
53
|
+
|
54
|
+
@shreduler.shredule(Shred.current, @shreduler.now + 1)
|
55
|
+
Shred.current.pause
|
56
|
+
end
|
57
|
+
end)
|
58
|
+
|
59
|
+
|
60
|
+
# time warper
|
61
|
+
@shreduler.shredule(Shred.new do |shred|
|
62
|
+
p = 0.0
|
63
|
+
|
64
|
+
loop do
|
65
|
+
bpm = Math.sin(p += 0.1) * 270.0 + 300.0
|
66
|
+
@warped_clock.relative_rate = bpm / 60.0
|
67
|
+
puts "#{bpm} bpm"
|
68
|
+
|
69
|
+
@shreduler.shredule(Shred.current, @shreduler.now + 0.1)
|
70
|
+
Shred.current.pause
|
71
|
+
end
|
72
|
+
end)
|
73
|
+
|
74
|
+
|
75
|
+
@shreduler.run
|
data/examples/ex04.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
# this example demonstrates Shreduler#make_convenient,
|
3
|
+
# which adds Object#spork and Shred#yield
|
4
|
+
|
5
|
+
require "ruck"
|
6
|
+
|
7
|
+
include Ruck
|
8
|
+
|
9
|
+
class RealTimeShreduler < Shreduler
|
10
|
+
def fast_forward(dt)
|
11
|
+
super
|
12
|
+
sleep(dt)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
@shreduler = RealTimeShreduler.new
|
17
|
+
@shreduler.make_convenient
|
18
|
+
|
19
|
+
spork do
|
20
|
+
%w{ A B C D E }.each do |letter|
|
21
|
+
puts "#{letter}"
|
22
|
+
Shred.yield(1)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
spork do
|
27
|
+
%w{ 1 2 3 4 5 }.each do |number|
|
28
|
+
puts "#{number}"
|
29
|
+
Shred.yield(1)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
@shreduler.run
|
data/examples/ex05.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
require "ruck"
|
3
|
+
|
4
|
+
include Ruck
|
5
|
+
|
6
|
+
class RealTimeShreduler < Shreduler
|
7
|
+
def fast_forward(dt)
|
8
|
+
super
|
9
|
+
sleep(dt)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
@shreduler = RealTimeShreduler.new
|
14
|
+
@shreduler.make_convenient
|
15
|
+
|
16
|
+
spork_loop(:gogogo) do
|
17
|
+
puts "YEE HAW!"
|
18
|
+
puts
|
19
|
+
end
|
20
|
+
|
21
|
+
spork do
|
22
|
+
loop do
|
23
|
+
Shred.yield(1)
|
24
|
+
puts "not yet"
|
25
|
+
Shred.yield(1)
|
26
|
+
puts "still not yet"
|
27
|
+
Shred.yield(1)
|
28
|
+
puts "okay now!"
|
29
|
+
raise_event(:gogogo)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
@shreduler.run
|
data/examples/ex06.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
|
2
|
+
require "rubygems"
|
3
|
+
require "midiator"
|
4
|
+
require "ruck"
|
5
|
+
|
6
|
+
include Ruck
|
7
|
+
|
8
|
+
def setup_midi
|
9
|
+
midi = MIDIator::Interface.new
|
10
|
+
# midi.autodetect_driver
|
11
|
+
midi.use :dls_synth # OS X built-in synth
|
12
|
+
|
13
|
+
include MIDIator::Drums
|
14
|
+
|
15
|
+
midi
|
16
|
+
end
|
17
|
+
|
18
|
+
class RealTimeShreduler < Ruck::Shreduler
|
19
|
+
def run
|
20
|
+
@start_time = Time.now
|
21
|
+
@simulated_now = 0
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def fast_forward(dt)
|
26
|
+
super
|
27
|
+
|
28
|
+
actual_now = Time.now - @start_time
|
29
|
+
@simulated_now += dt
|
30
|
+
if @simulated_now > actual_now
|
31
|
+
sleep(@simulated_now - actual_now)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
@midi = setup_midi
|
38
|
+
@shreduler = RealTimeShreduler.new
|
39
|
+
@shreduler.make_convenient
|
40
|
+
|
41
|
+
@scale = [0, 2, 4, 5, 7, 9, 11]
|
42
|
+
@base = 30
|
43
|
+
def to_scale(note)
|
44
|
+
@base + @scale[note % @scale.length] + 12 * (note / @scale.length).to_i
|
45
|
+
end
|
46
|
+
|
47
|
+
spork_loop(0.2) do
|
48
|
+
@midi.play([to_scale((rand * 21).to_i), to_scale((rand * 21).to_i)])
|
49
|
+
end
|
50
|
+
|
51
|
+
spork_loop do
|
52
|
+
puts "major"
|
53
|
+
# @base = 30
|
54
|
+
@scale = [0, 2, 4, 5, 7, 9, 11]
|
55
|
+
Shred.yield 5
|
56
|
+
|
57
|
+
puts "minor"
|
58
|
+
# @base = 32
|
59
|
+
@scale = [0, 2, 3, 5, 7, 8, 11]
|
60
|
+
Shred.yield 5
|
61
|
+
end
|
62
|
+
|
63
|
+
@shreduler.run
|
64
|
+
|
65
|
+
exit
|
66
|
+
|
67
|
+
|
68
|
+
@warped_clock = @shreduler.clock.add_child_clock(Clock.new)
|
69
|
+
|
70
|
+
|
71
|
+
# warped bass drum player
|
72
|
+
@shreduler.shredule(Shred.new do
|
73
|
+
loop do
|
74
|
+
@midi.note_on(BassDrum1, 10, 127)
|
75
|
+
@midi.note_on(BassDrum1, 10, 127)
|
76
|
+
|
77
|
+
@shreduler.shredule(Shred.current, @warped_clock.now + 1, @warped_clock)
|
78
|
+
Shred.current.pause
|
79
|
+
end
|
80
|
+
end, nil, @warped_clock)
|
81
|
+
|
82
|
+
|
83
|
+
# normal crash cymbal player
|
84
|
+
@shreduler.shredule(Shred.new do
|
85
|
+
loop do
|
86
|
+
@midi.note_on(CrashCymbal1, 10, 127)
|
87
|
+
@midi.note_on(CrashCymbal1, 10, 127)
|
88
|
+
|
89
|
+
@shreduler.shredule(Shred.current, @shreduler.now + 1)
|
90
|
+
Shred.current.pause
|
91
|
+
end
|
92
|
+
end)
|
93
|
+
|
94
|
+
|
95
|
+
# time warper
|
96
|
+
@shreduler.shredule(Shred.new do |shred|
|
97
|
+
p = 0.0
|
98
|
+
|
99
|
+
loop do
|
100
|
+
bpm = Math.sin(p += 0.1) * 270.0 + 300.0
|
101
|
+
@warped_clock.relative_rate = bpm / 60.0
|
102
|
+
puts "#{bpm} bpm"
|
103
|
+
|
104
|
+
@shreduler.shredule(Shred.current, @shreduler.now + 0.1)
|
105
|
+
Shred.current.pause
|
106
|
+
end
|
107
|
+
end)
|
108
|
+
|
109
|
+
|
110
|
+
@shreduler.run
|