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.
@@ -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