ruck-ugen 0.2.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/.document +5 -0
- data/.gitignore +21 -0
- data/README +31 -0
- data/Rakefile +24 -0
- data/VERSION +1 -0
- data/bin/ruck_ugen +30 -0
- data/examples/ex01.rb +24 -0
- data/examples/ex02.rb +2 -0
- data/examples/ex03.rb +8 -0
- data/examples/ex04.rb +14 -0
- data/examples/ex05.rb +9 -0
- data/examples/ex06.rb +10 -0
- data/examples/ex07.rb +35 -0
- data/examples/ex08.rb +28 -0
- data/examples/ex09.rb +26 -0
- data/examples/ex10.rb +10 -0
- data/examples/ex11.rb +9 -0
- data/lib/ruck/misc/linkage.rb +52 -0
- data/lib/ruck/misc/riff.rb +101 -0
- data/lib/ruck/ugen/basic.rb +235 -0
- data/lib/ruck/ugen/oscillators.rb +114 -0
- data/lib/ruck/ugen/time_helpers.rb +31 -0
- data/lib/ruck/ugen/ugen.rb +229 -0
- data/lib/ruck/ugen/wav.rb +187 -0
- data/lib/ruck/ugen.rb +7 -0
- data/ruck-ugen.gemspec +85 -0
- data/test/bench.rb +57 -0
- metadata +110 -0
@@ -0,0 +1,235 @@
|
|
1
|
+
|
2
|
+
# basic unit generators
|
3
|
+
|
4
|
+
module Ruck
|
5
|
+
|
6
|
+
module UGen
|
7
|
+
|
8
|
+
module Generators
|
9
|
+
|
10
|
+
class Gain
|
11
|
+
include UGenBase
|
12
|
+
include Source
|
13
|
+
include Target
|
14
|
+
|
15
|
+
linkable_attr :gain
|
16
|
+
|
17
|
+
def initialize(attrs = {})
|
18
|
+
parse_attrs({ :gain => 1.0 }.merge(attrs))
|
19
|
+
@ins = []
|
20
|
+
@last = 0.0
|
21
|
+
end
|
22
|
+
|
23
|
+
def next(now)
|
24
|
+
return @last if @now == now
|
25
|
+
@now = now
|
26
|
+
@last = @ins.inject(0) { |samp, ugen| samp += ugen.next(now) } * gain
|
27
|
+
end
|
28
|
+
|
29
|
+
def attr_names
|
30
|
+
[:gain]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Step
|
35
|
+
include UGenBase
|
36
|
+
include Source
|
37
|
+
|
38
|
+
linkable_attr :value
|
39
|
+
|
40
|
+
def initialize(attrs = {})
|
41
|
+
parse_attrs({ :value => 0.0 }.merge(attrs))
|
42
|
+
@last = value
|
43
|
+
end
|
44
|
+
|
45
|
+
def next(now)
|
46
|
+
return @last if @now == now
|
47
|
+
@now = now
|
48
|
+
@last = value
|
49
|
+
end
|
50
|
+
|
51
|
+
def attr_names
|
52
|
+
[:value]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Delay
|
57
|
+
include UGenBase
|
58
|
+
include Target
|
59
|
+
include Source
|
60
|
+
|
61
|
+
linkable_attr :gain
|
62
|
+
|
63
|
+
def initialize(attrs = {})
|
64
|
+
require_attrs attrs, [:time]
|
65
|
+
samples = attrs.delete(:time)
|
66
|
+
parse_attrs attrs
|
67
|
+
@ins = []
|
68
|
+
@last = 0.0
|
69
|
+
@queue = [0.0] * samples
|
70
|
+
end
|
71
|
+
|
72
|
+
def next(now)
|
73
|
+
return @last if @now == now
|
74
|
+
@now = now
|
75
|
+
|
76
|
+
@queue << @ins.inject(0) { |samp, ugen| samp += ugen.next(now) } * gain
|
77
|
+
@last = @queue.shift
|
78
|
+
end
|
79
|
+
|
80
|
+
def attr_names
|
81
|
+
[:time]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class Noise
|
86
|
+
include UGenBase
|
87
|
+
include Source
|
88
|
+
|
89
|
+
linkable_attr :gain
|
90
|
+
|
91
|
+
def initialize(attrs = {})
|
92
|
+
parse_attrs({ :gain => 1.0 }.merge(attrs))
|
93
|
+
@last = 0.0
|
94
|
+
end
|
95
|
+
|
96
|
+
def next(now)
|
97
|
+
return @last if @now == now
|
98
|
+
@now = now
|
99
|
+
@last = rand * gain
|
100
|
+
end
|
101
|
+
|
102
|
+
def attr_names
|
103
|
+
[:gain]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Ramp
|
108
|
+
include UGenBase
|
109
|
+
include Source
|
110
|
+
|
111
|
+
linkable_attr :from
|
112
|
+
linkable_attr :to
|
113
|
+
linkable_attr :duration
|
114
|
+
linkable_attr :progress
|
115
|
+
linkable_attr :paused
|
116
|
+
|
117
|
+
def initialize(attrs = {})
|
118
|
+
parse_attrs({ :from => 0.0,
|
119
|
+
:to => 1.0,
|
120
|
+
:duration => 1.second }.merge(attrs))
|
121
|
+
@progress = 0.0
|
122
|
+
@paused = false
|
123
|
+
@last = 0.0
|
124
|
+
end
|
125
|
+
|
126
|
+
def next(now)
|
127
|
+
return @last if @now == now
|
128
|
+
@now = now
|
129
|
+
@last = progress * (to - from) + from
|
130
|
+
inc_progress
|
131
|
+
@last
|
132
|
+
end
|
133
|
+
|
134
|
+
def reverse
|
135
|
+
@from, @to = @to, @from
|
136
|
+
end
|
137
|
+
|
138
|
+
def reset
|
139
|
+
@progress = 0.0
|
140
|
+
end
|
141
|
+
|
142
|
+
def finished?
|
143
|
+
progress == 1.0
|
144
|
+
end
|
145
|
+
|
146
|
+
def attr_names
|
147
|
+
[:from, :to, :duration, :progress, :paused]
|
148
|
+
end
|
149
|
+
|
150
|
+
protected
|
151
|
+
|
152
|
+
def inc_progress
|
153
|
+
return if @paused
|
154
|
+
@progress += 1.0 / duration
|
155
|
+
@progress = 1.0 if @progress > 1.0
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
class ADSR
|
161
|
+
include UGenBase
|
162
|
+
include Target
|
163
|
+
include Source
|
164
|
+
|
165
|
+
attr_accessor :attack_time
|
166
|
+
attr_accessor :attack_gain
|
167
|
+
attr_accessor :decay_time
|
168
|
+
attr_accessor :sustain_gain
|
169
|
+
attr_accessor :release_time
|
170
|
+
|
171
|
+
def initialize(attrs = {})
|
172
|
+
parse_attrs({ :attack_time => 50.ms,
|
173
|
+
:attack_gain => 1.0,
|
174
|
+
:decay_time => 50.ms,
|
175
|
+
:sustain_gain => 0.5,
|
176
|
+
:release_time => 500.ms }.merge(attrs))
|
177
|
+
|
178
|
+
@ramp = Ramp.new
|
179
|
+
|
180
|
+
@ins = []
|
181
|
+
@last = 0.0
|
182
|
+
@gain = 0.0
|
183
|
+
@state = :idle
|
184
|
+
end
|
185
|
+
|
186
|
+
def next(now)
|
187
|
+
return @last if @now == now
|
188
|
+
@now = now
|
189
|
+
@gain = case @state
|
190
|
+
when :idle
|
191
|
+
0
|
192
|
+
when :attack
|
193
|
+
if @ramp.finished?
|
194
|
+
@ramp.reset
|
195
|
+
@ramp.from, @ramp.to = @ramp.last, @sustain_gain
|
196
|
+
@ramp.duration = @decay_time
|
197
|
+
@state = :decay
|
198
|
+
end
|
199
|
+
@ramp.next(now)
|
200
|
+
when :decay
|
201
|
+
@state = :sustain if @ramp.finished?
|
202
|
+
@ramp.next(now)
|
203
|
+
when :sustain
|
204
|
+
@sustain_gain
|
205
|
+
when :release
|
206
|
+
@state = :idle if @ramp.finished?
|
207
|
+
@ramp.next(now)
|
208
|
+
end
|
209
|
+
@last = @ins.inject(0) { |samp, ugen| samp += ugen.next(now) } * @gain
|
210
|
+
end
|
211
|
+
|
212
|
+
def on
|
213
|
+
@ramp.reset
|
214
|
+
@ramp.from, @ramp.to = @gain, @attack_gain
|
215
|
+
@ramp.duration = @attack_time
|
216
|
+
@state = :attack
|
217
|
+
end
|
218
|
+
|
219
|
+
def off
|
220
|
+
@ramp.reset
|
221
|
+
@ramp.from, @ramp.to = @gain, 0
|
222
|
+
@ramp.duration = @release_time
|
223
|
+
@state = :release
|
224
|
+
end
|
225
|
+
|
226
|
+
def attr_names
|
227
|
+
[:attack_time, :attack_gain, :decay_time, :sustain_gain, :release_time]
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
|
2
|
+
# a bunch of oscillating unit generators (SinOsc,
|
3
|
+
# SawOsc, TriOsc) and their base class, Oscillator
|
4
|
+
|
5
|
+
module Ruck
|
6
|
+
|
7
|
+
module UGen
|
8
|
+
|
9
|
+
module Oscillator
|
10
|
+
include UGenBase
|
11
|
+
TWO_PI = 2 * Math::PI
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.instance_eval do
|
15
|
+
linkable_attr :freq
|
16
|
+
linkable_attr :phase
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def phase_forward
|
21
|
+
@phase = (@phase + freq.to_f / SAMPLE_RATE.to_f) % 1.0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Generators
|
26
|
+
|
27
|
+
class SinOsc
|
28
|
+
include Source
|
29
|
+
include Oscillator
|
30
|
+
|
31
|
+
linkable_attr :freq
|
32
|
+
linkable_attr :gain
|
33
|
+
|
34
|
+
def initialize(attrs = {})
|
35
|
+
parse_attrs({ :freq => 440.0,
|
36
|
+
:gain => 1.0 }.merge(attrs))
|
37
|
+
@phase = 0.0
|
38
|
+
@last = 0.0
|
39
|
+
end
|
40
|
+
|
41
|
+
def next(now)
|
42
|
+
return @last if @now == now
|
43
|
+
@now = now
|
44
|
+
@last = gain * Math.sin(phase * TWO_PI)
|
45
|
+
phase_forward
|
46
|
+
@last
|
47
|
+
end
|
48
|
+
|
49
|
+
def attr_names
|
50
|
+
[:freq, :gain, :phase]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class SawOsc
|
55
|
+
include Source
|
56
|
+
include Oscillator
|
57
|
+
|
58
|
+
linkable_attr :gain
|
59
|
+
|
60
|
+
def initialize(attrs = {})
|
61
|
+
parse_attrs({ :freq => 440.0,
|
62
|
+
:gain => 1.0 }.merge(attrs))
|
63
|
+
@phase = 0.0
|
64
|
+
@last = 0.0
|
65
|
+
end
|
66
|
+
|
67
|
+
def next(now)
|
68
|
+
return @last if @now == now
|
69
|
+
@now = now
|
70
|
+
@last = ((phase * 2.0) - 1.0) * gain
|
71
|
+
phase_forward
|
72
|
+
@last
|
73
|
+
end
|
74
|
+
|
75
|
+
def attr_names
|
76
|
+
[:freq, :gain, :phase]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class TriOsc
|
81
|
+
include Source
|
82
|
+
include Oscillator
|
83
|
+
|
84
|
+
linkable_attr :gain
|
85
|
+
|
86
|
+
def initialize(attrs = {})
|
87
|
+
parse_attrs({ :freq => 440.0,
|
88
|
+
:gain => 1.0 }.merge(attrs))
|
89
|
+
@phase = 0.0
|
90
|
+
@last = 0.0
|
91
|
+
end
|
92
|
+
|
93
|
+
def next(now)
|
94
|
+
return @last if @now == now
|
95
|
+
@now = now
|
96
|
+
@last = if phase < 0.5
|
97
|
+
phase * 4.0 - 1.0
|
98
|
+
else
|
99
|
+
1.0 - ((phase - 0.5) * 4.0)
|
100
|
+
end * gain
|
101
|
+
phase_forward
|
102
|
+
@last
|
103
|
+
end
|
104
|
+
|
105
|
+
def attr_names
|
106
|
+
[:freq, :gain, :phase]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
# time helpers so you can write 4.seconds, 1.sample, etc
|
3
|
+
|
4
|
+
module RuckTime
|
5
|
+
def sample
|
6
|
+
self
|
7
|
+
end
|
8
|
+
alias_method :samples, :sample
|
9
|
+
|
10
|
+
def ms
|
11
|
+
self.to_f * SAMPLE_RATE / 1000.0
|
12
|
+
end
|
13
|
+
|
14
|
+
def second
|
15
|
+
self.to_f * SAMPLE_RATE
|
16
|
+
end
|
17
|
+
alias_method :seconds, :second
|
18
|
+
|
19
|
+
def minute
|
20
|
+
self.to_f * SAMPLE_RATE * 60.0
|
21
|
+
end
|
22
|
+
alias_method :minutes, :minute
|
23
|
+
end
|
24
|
+
|
25
|
+
class Fixnum
|
26
|
+
include RuckTime
|
27
|
+
end
|
28
|
+
|
29
|
+
class Float
|
30
|
+
include RuckTime
|
31
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
|
2
|
+
# UGenShreduler (for scheduling shreds against the virtual
|
3
|
+
# clock of an audio file), and a framework for writing unit
|
4
|
+
# generators
|
5
|
+
|
6
|
+
module Ruck
|
7
|
+
|
8
|
+
module UGen
|
9
|
+
|
10
|
+
class UGenShreduler < Ruck::Shreduler
|
11
|
+
def run
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def sim_to(new_now)
|
16
|
+
while @now < new_now.to_i
|
17
|
+
BLACKHOLE.next @now
|
18
|
+
@now += 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# stuff accessible in a shred
|
24
|
+
module ShredLocal
|
25
|
+
def blackhole
|
26
|
+
BLACKHOLE
|
27
|
+
end
|
28
|
+
|
29
|
+
def now
|
30
|
+
SHREDULER.now
|
31
|
+
end
|
32
|
+
|
33
|
+
def spork(name = "unnamed", &shred)
|
34
|
+
SHREDULER.spork(name, &shred)
|
35
|
+
end
|
36
|
+
|
37
|
+
def play(samples)
|
38
|
+
SHREDULER.current_shred.yield(samples)
|
39
|
+
end
|
40
|
+
|
41
|
+
def finish
|
42
|
+
shred = SHREDULER.current_shred
|
43
|
+
SHREDULER.remove_shred shred
|
44
|
+
shred.finish
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module UGenBase
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
"<#{self.class}" +
|
52
|
+
(name ? "(#{name})" : "") +
|
53
|
+
" #{attr_names.map { |a| "#{a}:#{send a}" }.join " "}>"
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_accessor :name
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
def require_attrs(attrs, names)
|
61
|
+
names.each do |name|
|
62
|
+
unless attrs.has_key? name
|
63
|
+
raise "#{self} requires attribute #{name}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_attrs(attrs)
|
69
|
+
attrs.each do |attr, value|
|
70
|
+
send("#{attr}=", value)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def pop_attrs(attrs, names)
|
75
|
+
names.map { |name| attrs.delete(name) }
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
module Target
|
81
|
+
def add_source(ugen)
|
82
|
+
if ugen.is_a? Array
|
83
|
+
ugen.each { |u| add_source u }
|
84
|
+
else
|
85
|
+
@ins << ugen
|
86
|
+
end
|
87
|
+
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
def remove_source(ugen)
|
92
|
+
if ugen.is_a? Array
|
93
|
+
ugen.each { |u| remove_source u }
|
94
|
+
else
|
95
|
+
@ins.delete(ugen)
|
96
|
+
end
|
97
|
+
|
98
|
+
self
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
module MultiChannelTarget
|
103
|
+
def add_source(ugen)
|
104
|
+
if ugen.is_a? Array
|
105
|
+
ugen.each { |u| add_source u }
|
106
|
+
return self
|
107
|
+
end
|
108
|
+
|
109
|
+
if ugen.out_channels.length == 1
|
110
|
+
@in_channels.each { |chan| chan.add_source ugen.out(0) }
|
111
|
+
else
|
112
|
+
1.upto([ugen.out_channels.length, @in_channels.length].min) do |i|
|
113
|
+
@in_channels[i-1].add_source ugen.out(i-1)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
def remove_source(ugen)
|
121
|
+
if ugen.is_a? Array
|
122
|
+
ugen.each { |u| remove_source u }
|
123
|
+
return
|
124
|
+
end
|
125
|
+
|
126
|
+
# remove all outputs of ugen from all inputs of self
|
127
|
+
@in_channels.each do |in_chan|
|
128
|
+
ugen.out_channels.each do |out_chan|
|
129
|
+
in_chan.remove_source out_chan
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
def in_channels
|
137
|
+
@in_channels
|
138
|
+
end
|
139
|
+
|
140
|
+
def in(chan)
|
141
|
+
@in_channels[chan]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
module Source
|
146
|
+
def >>(ugen)
|
147
|
+
ugen.add_source self
|
148
|
+
end
|
149
|
+
|
150
|
+
def <<(ugen)
|
151
|
+
ugen.remove_source self
|
152
|
+
end
|
153
|
+
|
154
|
+
def out_channels
|
155
|
+
[self]
|
156
|
+
end
|
157
|
+
|
158
|
+
def out(chan)
|
159
|
+
self if chan == 0
|
160
|
+
end
|
161
|
+
|
162
|
+
def next(now); @last; end
|
163
|
+
def last; @last; end
|
164
|
+
end
|
165
|
+
|
166
|
+
module MultiChannelSource
|
167
|
+
def >>(ugen)
|
168
|
+
ugen.add_source self
|
169
|
+
end
|
170
|
+
|
171
|
+
def <<(ugen)
|
172
|
+
ugen.remove_source self
|
173
|
+
end
|
174
|
+
|
175
|
+
def out_channels
|
176
|
+
@out_channels
|
177
|
+
end
|
178
|
+
|
179
|
+
def out(chan)
|
180
|
+
@out_channels[chan]
|
181
|
+
end
|
182
|
+
|
183
|
+
def next(now, chan = 0); @last[chan]; end
|
184
|
+
def last(chan = 0); @last[chan]; end
|
185
|
+
end
|
186
|
+
|
187
|
+
class InChannel
|
188
|
+
include UGenBase
|
189
|
+
include Target
|
190
|
+
|
191
|
+
def initialize(attrs = {})
|
192
|
+
parse_attrs attrs
|
193
|
+
@ins = []
|
194
|
+
@last = 0.0
|
195
|
+
end
|
196
|
+
|
197
|
+
def next(now)
|
198
|
+
return @last if @now == now
|
199
|
+
@now = now
|
200
|
+
@last = @ins.inject(0) { |samp, ugen| samp += ugen.next(now) }
|
201
|
+
end
|
202
|
+
|
203
|
+
def attr_names
|
204
|
+
[]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
class OutChannel
|
209
|
+
include Source
|
210
|
+
|
211
|
+
def initialize(parent, channel_number)
|
212
|
+
@parent = parent
|
213
|
+
@channel_number = channel_number
|
214
|
+
end
|
215
|
+
|
216
|
+
def next(now)
|
217
|
+
return @last if @now == now
|
218
|
+
@last = @parent.next(now, @channel_number)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
# Allow chucking all elements of an array like "[a, b, c] >> ugen"
|
227
|
+
class Array
|
228
|
+
include Ruck::UGen::Source
|
229
|
+
end
|