deftones 0.1.0 → 1.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -6
- data/README.md +5 -0
- data/Rakefile +50 -1
- data/lib/deftones/analysis/meter.rb +22 -2
- data/lib/deftones/component/channel.rb +1 -1
- data/lib/deftones/component/compressor.rb +127 -22
- data/lib/deftones/component/filter.rb +29 -19
- data/lib/deftones/component/merge.rb +14 -0
- data/lib/deftones/component/multiband_compressor.rb +1 -1
- data/lib/deftones/component/one_pole_filter.rb +10 -3
- data/lib/deftones/component/panner.rb +25 -2
- data/lib/deftones/component/panner3d.rb +0 -10
- data/lib/deftones/component/split.rb +14 -0
- data/lib/deftones/context.rb +90 -9
- data/lib/deftones/core/audio_block.rb +64 -5
- data/lib/deftones/core/audio_node.rb +98 -8
- data/lib/deftones/core/gain.rb +0 -8
- data/lib/deftones/core/instrument.rb +52 -10
- data/lib/deftones/core/param.rb +51 -1
- data/lib/deftones/core/signal.rb +79 -28
- data/lib/deftones/core/source.rb +71 -11
- data/lib/deftones/destination.rb +41 -17
- data/lib/deftones/draw.rb +6 -10
- data/lib/deftones/dsp/biquad.rb +9 -4
- data/lib/deftones/dsp/delay_line.rb +2 -2
- data/lib/deftones/dsp/helpers.rb +7 -0
- data/lib/deftones/effect/bit_crusher.rb +10 -2
- data/lib/deftones/effect/chebyshev.rb +7 -3
- data/lib/deftones/effect/distortion.rb +5 -3
- data/lib/deftones/effect/feedback_delay.rb +2 -1
- data/lib/deftones/effect/oversampling.rb +43 -0
- data/lib/deftones/effect/phaser.rb +2 -1
- data/lib/deftones/effect/pitch_shift.rb +1 -2
- data/lib/deftones/effect/reverb.rb +73 -5
- data/lib/deftones/event/callback_behavior.rb +7 -3
- data/lib/deftones/event/loop.rb +7 -2
- data/lib/deftones/event/part.rb +18 -3
- data/lib/deftones/event/pattern.rb +51 -6
- data/lib/deftones/event/sequence.rb +19 -5
- data/lib/deftones/event/tone_event.rb +7 -2
- data/lib/deftones/event/transport.rb +243 -21
- data/lib/deftones/instrument/poly_synth.rb +81 -15
- data/lib/deftones/instrument/sampler.rb +53 -10
- data/lib/deftones/io/buffer.rb +376 -55
- data/lib/deftones/io/buffers.rb +28 -4
- data/lib/deftones/io/recorder.rb +2 -1
- data/lib/deftones/music/frequency.rb +13 -8
- data/lib/deftones/music/midi.rb +132 -9
- data/lib/deftones/music/note.rb +13 -3
- data/lib/deftones/music/time.rb +42 -4
- data/lib/deftones/offline_context.rb +194 -17
- data/lib/deftones/portaudio_support.rb +68 -9
- data/lib/deftones/source/fat_oscillator.rb +28 -9
- data/lib/deftones/source/grain_player.rb +49 -2
- data/lib/deftones/source/noise.rb +42 -10
- data/lib/deftones/source/omni_oscillator.rb +1 -2
- data/lib/deftones/source/oscillator.rb +83 -19
- data/lib/deftones/source/player.rb +24 -6
- data/lib/deftones/source/players.rb +39 -6
- data/lib/deftones/source/tone_buffer_source.rb +12 -6
- data/lib/deftones/source/tone_oscillator_node.rb +4 -3
- data/lib/deftones/source/user_media.rb +83 -10
- data/lib/deftones/version.rb +1 -1
- data/lib/deftones.rb +108 -31
- metadata +3 -44
|
@@ -12,7 +12,7 @@ module Deftones
|
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def initialize(bpm: 120.0, time_signature: [4, 4], ppq: 192)
|
|
15
|
+
def initialize(bpm: 120.0, time_signature: [4, 4], ppq: 192, clock: nil)
|
|
16
16
|
@bpm = Core::Signal.new(
|
|
17
17
|
value: bpm,
|
|
18
18
|
units: :number,
|
|
@@ -27,9 +27,13 @@ module Deftones
|
|
|
27
27
|
@swing = 0.0
|
|
28
28
|
@swing_subdivision = "8n"
|
|
29
29
|
@timeline = {}
|
|
30
|
+
@state_timeline = []
|
|
31
|
+
@clock = clock
|
|
30
32
|
@next_id = 0
|
|
33
|
+
@next_state_id = 0
|
|
31
34
|
@started_at = 0.0
|
|
32
35
|
@position_seconds = 0.0
|
|
36
|
+
@last_state_update_time = 0.0
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
def bpm
|
|
@@ -40,6 +44,15 @@ module Deftones
|
|
|
40
44
|
@bpm.value = value
|
|
41
45
|
end
|
|
42
46
|
|
|
47
|
+
def bpm_signal
|
|
48
|
+
@bpm
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def set_bpm_at_time(value, time)
|
|
52
|
+
@bpm.set_value_at_time(value, resolve_time(time))
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
43
56
|
def swing_subdivision
|
|
44
57
|
@swing_subdivision
|
|
45
58
|
end
|
|
@@ -53,23 +66,48 @@ module Deftones
|
|
|
53
66
|
end
|
|
54
67
|
|
|
55
68
|
def start(time = nil)
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
resolved_time = resolve_time(time)
|
|
70
|
+
if future_time?(resolved_time)
|
|
71
|
+
add_state_event(:started, resolved_time)
|
|
72
|
+
else
|
|
73
|
+
apply_start(resolved_time)
|
|
74
|
+
end
|
|
58
75
|
self
|
|
59
76
|
end
|
|
60
77
|
|
|
61
78
|
def stop(time = nil)
|
|
62
|
-
|
|
63
|
-
|
|
79
|
+
resolved_time = time.nil? ? clock_time : resolve_time(time)
|
|
80
|
+
if future_time?(resolved_time)
|
|
81
|
+
add_state_event(:stopped, resolved_time)
|
|
82
|
+
else
|
|
83
|
+
apply_stop(resolved_time)
|
|
84
|
+
end
|
|
64
85
|
self
|
|
65
86
|
end
|
|
66
87
|
|
|
67
88
|
def pause(time = nil)
|
|
68
|
-
|
|
69
|
-
|
|
89
|
+
resolved_time = time.nil? ? clock_time : resolve_time(time)
|
|
90
|
+
if future_time?(resolved_time)
|
|
91
|
+
add_state_event(:paused, resolved_time)
|
|
92
|
+
else
|
|
93
|
+
apply_pause(resolved_time)
|
|
94
|
+
end
|
|
70
95
|
self
|
|
71
96
|
end
|
|
72
97
|
|
|
98
|
+
def state_at(time)
|
|
99
|
+
resolved_time = resolve_time(time)
|
|
100
|
+
effective_state = state_before_timeline
|
|
101
|
+
|
|
102
|
+
@state_timeline.sort_by { |event| [event[:time], event[:id]] }.each do |event|
|
|
103
|
+
break if event[:time] > resolved_time
|
|
104
|
+
|
|
105
|
+
effective_state = event[:state]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
effective_state
|
|
109
|
+
end
|
|
110
|
+
|
|
73
111
|
def position
|
|
74
112
|
seconds_to_position(@position_seconds)
|
|
75
113
|
end
|
|
@@ -92,12 +130,19 @@ module Deftones
|
|
|
92
130
|
end
|
|
93
131
|
|
|
94
132
|
def seconds
|
|
133
|
+
apply_due_state_events(clock_time)
|
|
95
134
|
return @position_seconds unless @state == :started
|
|
96
135
|
|
|
97
|
-
[
|
|
136
|
+
[clock_time - @started_at, 0.0].max
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def bpm_at(time)
|
|
140
|
+
@bpm.get_value_at_time(resolve_time(time))
|
|
98
141
|
end
|
|
99
142
|
|
|
100
143
|
def schedule(time, &block)
|
|
144
|
+
raise ArgumentError, "Transport callback is required" unless block
|
|
145
|
+
|
|
101
146
|
add_event(kind: :once, time: resolve_time(time), callback: block)
|
|
102
147
|
end
|
|
103
148
|
|
|
@@ -106,9 +151,14 @@ module Deftones
|
|
|
106
151
|
end
|
|
107
152
|
|
|
108
153
|
def schedule_repeat(interval, start_time: 0, duration: nil, &block)
|
|
154
|
+
raise ArgumentError, "Transport callback is required" unless block
|
|
155
|
+
|
|
156
|
+
resolved_interval = resolve_time(interval)
|
|
157
|
+
raise ArgumentError, "repeat interval must be positive" unless resolved_interval.positive?
|
|
158
|
+
|
|
109
159
|
add_event(
|
|
110
160
|
kind: :repeat,
|
|
111
|
-
interval:
|
|
161
|
+
interval: resolved_interval,
|
|
112
162
|
start_time: resolve_time(start_time),
|
|
113
163
|
duration: duration.nil? ? nil : resolve_time(duration),
|
|
114
164
|
callback: block
|
|
@@ -189,7 +239,23 @@ module Deftones
|
|
|
189
239
|
|
|
190
240
|
def prepare_render(duration)
|
|
191
241
|
render_duration = resolve_time(duration)
|
|
192
|
-
|
|
242
|
+
cursor = 0.0
|
|
243
|
+
|
|
244
|
+
while cursor < render_duration
|
|
245
|
+
window_end = [cursor + 1.0, render_duration].min
|
|
246
|
+
prepare_render_window(cursor, window_end)
|
|
247
|
+
cursor = window_end
|
|
248
|
+
end
|
|
249
|
+
self
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def prepare_render_window(start_time, end_time)
|
|
253
|
+
window_start = resolve_time(start_time)
|
|
254
|
+
window_end = resolve_time(end_time)
|
|
255
|
+
return self if window_end < window_start
|
|
256
|
+
|
|
257
|
+
apply_due_state_events(window_end)
|
|
258
|
+
due_events(window_start, window_end).each do |event|
|
|
193
259
|
event[:callback].call(event[:time])
|
|
194
260
|
end
|
|
195
261
|
self
|
|
@@ -204,29 +270,67 @@ module Deftones
|
|
|
204
270
|
event_id
|
|
205
271
|
end
|
|
206
272
|
|
|
207
|
-
def
|
|
273
|
+
def add_state_event(state, time)
|
|
274
|
+
event_id = @next_state_id
|
|
275
|
+
@state_timeline << { id: event_id, state: state, time: time }
|
|
276
|
+
@next_state_id += 1
|
|
277
|
+
event_id
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def due_events(window_start, window_end)
|
|
281
|
+
return looped_due_events(window_start, window_end) if loop_active?
|
|
282
|
+
|
|
208
283
|
events = @timeline.flat_map do |_id, event|
|
|
209
|
-
event[:kind] == :repeat ? materialize_repeat_event(event,
|
|
284
|
+
event[:kind] == :repeat ? materialize_repeat_event(event, window_start, window_end) : materialize_one_shot(event, window_start, window_end)
|
|
210
285
|
end
|
|
211
286
|
events.sort_by { |event| event[:time] }
|
|
212
287
|
end
|
|
213
288
|
|
|
214
|
-
def
|
|
215
|
-
|
|
289
|
+
def looped_due_events(window_start, window_end)
|
|
290
|
+
loop_start_seconds = resolve_time(@loop_start)
|
|
291
|
+
loop_end_seconds = resolve_time(@loop_end)
|
|
292
|
+
span = loop_end_seconds - loop_start_seconds
|
|
293
|
+
return [] unless span.positive?
|
|
294
|
+
|
|
295
|
+
first_iteration = [(window_start - loop_end_seconds) / span, 0.0].max.floor
|
|
296
|
+
last_iteration = [(window_end - loop_start_seconds) / span, 0.0].max.ceil
|
|
297
|
+
(first_iteration..last_iteration).flat_map do |iteration|
|
|
298
|
+
offset = iteration * span
|
|
299
|
+
due_events_without_loop(loop_start_seconds, loop_end_seconds).filter_map do |event|
|
|
300
|
+
global_time = event[:time] + offset
|
|
301
|
+
next unless time_in_window?(global_time, window_start, window_end)
|
|
302
|
+
|
|
303
|
+
event.merge(time: global_time)
|
|
304
|
+
end
|
|
305
|
+
end.sort_by { |event| event[:time] }
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def due_events_without_loop(window_start, window_end)
|
|
309
|
+
@timeline.flat_map do |_id, event|
|
|
310
|
+
if event[:kind] == :repeat
|
|
311
|
+
materialize_repeat_event(event, window_start, window_end)
|
|
312
|
+
else
|
|
313
|
+
materialize_one_shot(event, window_start, window_end)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def materialize_one_shot(event, window_start, window_end)
|
|
319
|
+
return [] unless time_in_window?(event[:time], window_start, window_end)
|
|
216
320
|
|
|
217
321
|
[{ time: apply_swing(event[:time], event[:time]), callback: event[:callback] }]
|
|
218
322
|
end
|
|
219
323
|
|
|
220
|
-
def materialize_repeat_event(event,
|
|
324
|
+
def materialize_repeat_event(event, window_start, window_end)
|
|
221
325
|
interval = [event[:interval], 1.0e-6].max
|
|
222
|
-
limit = event[:duration] ? [event[:start_time] + event[:duration],
|
|
326
|
+
limit = event[:duration] ? [event[:start_time] + event[:duration], window_end].min : window_end
|
|
223
327
|
events = []
|
|
224
|
-
occurrence =
|
|
225
|
-
current_time = event[:start_time]
|
|
328
|
+
occurrence = first_repeat_occurrence(event[:start_time], interval, window_start)
|
|
329
|
+
current_time = event[:start_time] + (occurrence * interval)
|
|
226
330
|
|
|
227
331
|
while current_time <= limit
|
|
228
332
|
actual_time = apply_swing(current_time, interval, occurrence)
|
|
229
|
-
events << { time: actual_time, callback: event[:callback] }
|
|
333
|
+
events << { time: actual_time, callback: event[:callback] } if time_in_window?(actual_time, window_start, window_end)
|
|
230
334
|
current_time += interval
|
|
231
335
|
occurrence += 1
|
|
232
336
|
end
|
|
@@ -234,6 +338,69 @@ module Deftones
|
|
|
234
338
|
events
|
|
235
339
|
end
|
|
236
340
|
|
|
341
|
+
def first_repeat_occurrence(start_time, interval, window_start)
|
|
342
|
+
return 0 if window_start <= start_time
|
|
343
|
+
|
|
344
|
+
((window_start - start_time) / interval).floor + 1
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def loop_active?
|
|
348
|
+
@loop && (resolve_time(@loop_end) - resolve_time(@loop_start)).positive?
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def future_time?(time)
|
|
352
|
+
return false unless @clock
|
|
353
|
+
|
|
354
|
+
time > clock_time
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def apply_due_state_events(up_to_time)
|
|
358
|
+
due, pending = @state_timeline.partition { |event| event[:time] <= up_to_time }
|
|
359
|
+
@state_timeline = pending
|
|
360
|
+
due.sort_by { |event| [event[:time], event[:id]] }.each do |event|
|
|
361
|
+
case event[:state]
|
|
362
|
+
when :started then apply_start(event[:time])
|
|
363
|
+
when :stopped then apply_stop(event[:time])
|
|
364
|
+
when :paused then apply_pause(event[:time])
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def apply_start(time)
|
|
370
|
+
@state = :started
|
|
371
|
+
@started_at = time - @position_seconds
|
|
372
|
+
@last_state_update_time = time
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def apply_stop(time)
|
|
376
|
+
@position_seconds = state_position_at(time)
|
|
377
|
+
@state = :stopped
|
|
378
|
+
@last_state_update_time = time
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def apply_pause(time)
|
|
382
|
+
@position_seconds = state_position_at(time)
|
|
383
|
+
@state = :paused
|
|
384
|
+
@last_state_update_time = time
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def state_position_at(time)
|
|
388
|
+
return @position_seconds unless @state == :started
|
|
389
|
+
|
|
390
|
+
[time - @started_at, 0.0].max
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def state_before_timeline
|
|
394
|
+
@state
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def time_in_window?(time, window_start, window_end)
|
|
398
|
+
return false if time > window_end
|
|
399
|
+
return time >= window_start if window_start.zero?
|
|
400
|
+
|
|
401
|
+
time > window_start
|
|
402
|
+
end
|
|
403
|
+
|
|
237
404
|
def apply_swing(time, interval, occurrence = 0)
|
|
238
405
|
return time if @swing.zero?
|
|
239
406
|
return time if resolve_time(@swing_subdivision) != interval
|
|
@@ -248,6 +415,12 @@ module Deftones
|
|
|
248
415
|
Deftones::Music::Time.parse(value, bpm: bpm, time_signature: time_signature, ppq: @ppq)
|
|
249
416
|
end
|
|
250
417
|
|
|
418
|
+
def clock_time
|
|
419
|
+
return @clock.current_time if @clock&.respond_to?(:current_time)
|
|
420
|
+
|
|
421
|
+
Deftones.now
|
|
422
|
+
end
|
|
423
|
+
|
|
251
424
|
def seconds_to_position(seconds)
|
|
252
425
|
beats_per_measure = Array(@time_signature).first || 4
|
|
253
426
|
beat_duration = 60.0 / bpm
|
|
@@ -259,18 +432,67 @@ module Deftones
|
|
|
259
432
|
end
|
|
260
433
|
|
|
261
434
|
def seconds_to_ticks(seconds)
|
|
262
|
-
((seconds.to_f
|
|
435
|
+
(beats_between(0.0, seconds.to_f) * @ppq).round
|
|
263
436
|
end
|
|
264
437
|
|
|
265
438
|
def ticks_to_seconds(ticks)
|
|
266
|
-
|
|
439
|
+
target_beats = ticks.to_f / @ppq
|
|
440
|
+
return target_beats * (60.0 / bpm) unless tempo_automated?
|
|
441
|
+
|
|
442
|
+
upper = [target_beats * (60.0 / [bpm, 1.0].max), 1.0e-6].max
|
|
443
|
+
upper *= 2.0 while beats_between(0.0, upper) < target_beats
|
|
444
|
+
lower = 0.0
|
|
445
|
+
40.times do
|
|
446
|
+
midpoint = (lower + upper) * 0.5
|
|
447
|
+
if beats_between(0.0, midpoint) < target_beats
|
|
448
|
+
lower = midpoint
|
|
449
|
+
else
|
|
450
|
+
upper = midpoint
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
upper
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def beats_between(start_time, end_time)
|
|
457
|
+
from = start_time.to_f
|
|
458
|
+
to = end_time.to_f
|
|
459
|
+
return 0.0 if to <= from
|
|
460
|
+
|
|
461
|
+
integration_points(from, to).each_cons(2).sum do |left, right|
|
|
462
|
+
midpoint = (left + right) * 0.5
|
|
463
|
+
((right - left) * bpm_at(midpoint)) / 60.0
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def tempo_automated?
|
|
468
|
+
bpm_events.any?
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def integration_points(start_time, end_time)
|
|
472
|
+
points = [start_time, end_time]
|
|
473
|
+
bpm_events.each do |event|
|
|
474
|
+
points << event[:time] if event[:time] && event[:time].between?(start_time, end_time)
|
|
475
|
+
points << event[:start_time] if event[:start_time] && event[:start_time].between?(start_time, end_time)
|
|
476
|
+
points << event[:end_time] if event[:end_time] && event[:end_time].between?(start_time, end_time)
|
|
477
|
+
end
|
|
478
|
+
points.uniq.sort
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def bpm_events
|
|
482
|
+
@bpm.instance_variable_get(:@events) || []
|
|
267
483
|
end
|
|
268
484
|
|
|
269
485
|
public :seconds_to_position, :seconds_to_ticks, :ticks_to_seconds
|
|
486
|
+
public :beats_between
|
|
270
487
|
alias scheduleOnce schedule_once
|
|
271
488
|
alias scheduleRepeat schedule_repeat
|
|
489
|
+
alias prepareRenderWindow prepare_render_window
|
|
272
490
|
alias setLoopPoints set_loop_points
|
|
273
491
|
alias nextSubdivision next_subdivision
|
|
492
|
+
alias bpmAt bpm_at
|
|
493
|
+
alias bpmSignal bpm_signal
|
|
494
|
+
alias setBpmAtTime set_bpm_at_time
|
|
495
|
+
alias stateAt state_at
|
|
274
496
|
end
|
|
275
497
|
end
|
|
276
498
|
end
|
|
@@ -3,13 +3,19 @@
|
|
|
3
3
|
module Deftones
|
|
4
4
|
module Instrument
|
|
5
5
|
class PolySynth < Core::Instrument
|
|
6
|
-
|
|
6
|
+
VOICE_STEALING_POLICIES = %i[oldest newest quietest released_first].freeze
|
|
7
|
+
RETRIGGER_POLICIES = %i[restart ignore].freeze
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
attr_reader :voice_pool, :voice_stealing, :retrigger
|
|
10
|
+
|
|
11
|
+
def initialize(voice_class = Synth, voices: 8, voice_stealing: :oldest, retrigger: :restart,
|
|
12
|
+
context: Deftones.context, **voice_options)
|
|
9
13
|
super(context: context)
|
|
10
14
|
@voice_class = voice_class
|
|
11
15
|
@voice_pool = Array.new(voices) { @voice_class.new(context: context, **voice_options) }
|
|
12
16
|
@voice_pool.each { |voice| voice >> @output }
|
|
17
|
+
@voice_stealing = normalize_voice_stealing(voice_stealing)
|
|
18
|
+
@retrigger = normalize_retrigger(retrigger)
|
|
13
19
|
@active_voices = {}
|
|
14
20
|
end
|
|
15
21
|
|
|
@@ -29,20 +35,35 @@ module Deftones
|
|
|
29
35
|
end
|
|
30
36
|
|
|
31
37
|
def trigger_attack(note, time = nil, velocity = 1.0)
|
|
32
|
-
|
|
38
|
+
scheduled_time = resolve_time(time)
|
|
39
|
+
return self if @active_voices.key?(note) && @retrigger == :ignore
|
|
40
|
+
|
|
41
|
+
voice = allocate_voice(note, scheduled_time)
|
|
33
42
|
@active_voices.delete(note)
|
|
34
|
-
@active_voices[note] =
|
|
35
|
-
|
|
43
|
+
@active_voices[note] = {
|
|
44
|
+
voice: voice,
|
|
45
|
+
attacked_at: scheduled_time,
|
|
46
|
+
released_at: nil,
|
|
47
|
+
velocity: velocity.to_f
|
|
48
|
+
}
|
|
49
|
+
voice.trigger_attack(note, scheduled_time, velocity)
|
|
36
50
|
self
|
|
37
51
|
end
|
|
38
52
|
|
|
39
53
|
def trigger_release(note, time = nil)
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
entry = @active_voices[note]
|
|
55
|
+
return self unless entry
|
|
56
|
+
|
|
57
|
+
scheduled_time = resolve_time(time)
|
|
58
|
+
entry[:released_at] = scheduled_time
|
|
59
|
+
entry[:voice].trigger_release(scheduled_time)
|
|
42
60
|
self
|
|
43
61
|
end
|
|
44
62
|
|
|
45
|
-
def set(**params)
|
|
63
|
+
def set(strict: false, **params)
|
|
64
|
+
unknown = params.keys.reject { |key| @voice_pool.all? { |voice| voice.respond_to?(:"#{key}=") } }
|
|
65
|
+
raise ArgumentError, "Unknown parameter(s): #{unknown.join(', ')}" if strict && unknown.any?
|
|
66
|
+
|
|
46
67
|
@voice_pool.each do |voice|
|
|
47
68
|
params.each do |key, value|
|
|
48
69
|
writer = :"#{key}="
|
|
@@ -54,7 +75,7 @@ module Deftones
|
|
|
54
75
|
|
|
55
76
|
def release_all(time = nil)
|
|
56
77
|
scheduled_time = resolve_time(time)
|
|
57
|
-
@active_voices.each_value { |
|
|
78
|
+
@active_voices.each_value { |entry| entry[:voice].trigger_release(scheduled_time) }
|
|
58
79
|
@active_voices.clear
|
|
59
80
|
self
|
|
60
81
|
end
|
|
@@ -64,7 +85,7 @@ module Deftones
|
|
|
64
85
|
end
|
|
65
86
|
|
|
66
87
|
def loaded?
|
|
67
|
-
|
|
88
|
+
!disposed?
|
|
68
89
|
end
|
|
69
90
|
|
|
70
91
|
alias loaded loaded?
|
|
@@ -74,16 +95,61 @@ module Deftones
|
|
|
74
95
|
@voice_pool.any?(&:active?)
|
|
75
96
|
end
|
|
76
97
|
|
|
98
|
+
def active_notes
|
|
99
|
+
@active_voices.keys
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def active_voice_count
|
|
103
|
+
@active_voices.length
|
|
104
|
+
end
|
|
105
|
+
|
|
77
106
|
private
|
|
78
107
|
|
|
79
|
-
def allocate_voice(note)
|
|
80
|
-
|
|
108
|
+
def allocate_voice(note, time)
|
|
109
|
+
cleanup_inactive_voices!
|
|
110
|
+
return @active_voices[note][:voice] if @active_voices.key?(note)
|
|
81
111
|
|
|
82
|
-
|
|
112
|
+
active_voice_ids = @active_voices.values.map { |entry| entry[:voice].object_id }
|
|
113
|
+
available_voice = @voice_pool.find { |voice| !active_voice_ids.include?(voice.object_id) }
|
|
83
114
|
return available_voice if available_voice
|
|
84
115
|
|
|
85
|
-
|
|
86
|
-
|
|
116
|
+
stolen_note, stolen_entry = steal_voice_entry(time)
|
|
117
|
+
@active_voices.delete(stolen_note)
|
|
118
|
+
stolen_entry[:voice]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def steal_voice_entry(time)
|
|
122
|
+
case @voice_stealing
|
|
123
|
+
when :newest
|
|
124
|
+
@active_voices.max_by { |_, entry| entry[:attacked_at] }
|
|
125
|
+
when :quietest
|
|
126
|
+
@active_voices.min_by { |_, entry| entry[:velocity] }
|
|
127
|
+
when :released_first
|
|
128
|
+
released = @active_voices.select { |_, entry| entry[:released_at] && entry[:released_at] <= time }
|
|
129
|
+
return released.min_by { |_, entry| entry[:released_at] } if released.any?
|
|
130
|
+
|
|
131
|
+
@active_voices.min_by { |_, entry| entry[:attacked_at] }
|
|
132
|
+
else
|
|
133
|
+
@active_voices.min_by { |_, entry| entry[:attacked_at] }
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def cleanup_inactive_voices!
|
|
138
|
+
@active_voices.delete_if { |_, entry| entry[:released_at] && !entry[:voice].active? }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def normalize_voice_stealing(policy)
|
|
142
|
+
normalized = policy.to_sym
|
|
143
|
+
return normalized if VOICE_STEALING_POLICIES.include?(normalized)
|
|
144
|
+
|
|
145
|
+
raise ArgumentError, "Unsupported voice stealing policy: #{policy}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def normalize_retrigger(policy)
|
|
149
|
+
normalized = policy.to_sym
|
|
150
|
+
return normalized if RETRIGGER_POLICIES.include?(normalized)
|
|
151
|
+
|
|
152
|
+
raise ArgumentError, "Unsupported retrigger policy: #{policy}"
|
|
87
153
|
end
|
|
88
154
|
|
|
89
155
|
def resolve_time(time)
|
|
@@ -3,13 +3,19 @@
|
|
|
3
3
|
module Deftones
|
|
4
4
|
module Instrument
|
|
5
5
|
class Sampler < Core::Instrument
|
|
6
|
-
attr_reader :samples, :voices
|
|
6
|
+
attr_reader :choke_group, :release, :samples, :voices
|
|
7
|
+
attr_reader :one_shot
|
|
7
8
|
|
|
8
|
-
def initialize(samples:, max_voices: 8,
|
|
9
|
+
def initialize(samples:, max_voices: 8, release: 0.0, one_shot: false, choke_group: nil,
|
|
10
|
+
context: Deftones.context)
|
|
9
11
|
super(context: context)
|
|
10
12
|
@samples = samples.transform_keys(&:to_s)
|
|
11
13
|
@max_voices = max_voices
|
|
14
|
+
@release = release.to_f
|
|
15
|
+
@one_shot = !!one_shot
|
|
16
|
+
@choke_group = choke_group
|
|
12
17
|
@voices = []
|
|
18
|
+
rebuild_root_note_cache
|
|
13
19
|
end
|
|
14
20
|
|
|
15
21
|
def play(notes, duration: "8n", at: nil, velocity: 1.0)
|
|
@@ -22,17 +28,21 @@ module Deftones
|
|
|
22
28
|
|
|
23
29
|
def trigger_attack(note, time = nil, velocity = 1.0)
|
|
24
30
|
buffer_note, buffer = closest_sample(note)
|
|
31
|
+
scheduled_time = resolve_time(time)
|
|
32
|
+
choke_matching_voices(scheduled_time)
|
|
25
33
|
playback_rate = Deftones::Music::Note.to_frequency(note) / Deftones::Music::Note.to_frequency(buffer_note)
|
|
26
|
-
player = Source::Player.new(buffer: buffer, playback_rate: playback_rate, context: context)
|
|
34
|
+
player = Source::Player.new(buffer: buffer, playback_rate: playback_rate, fade_out: @release, context: context)
|
|
27
35
|
gain = Core::Gain.new(gain: velocity, context: context)
|
|
28
36
|
player >> gain >> @output
|
|
29
|
-
player.start(
|
|
30
|
-
@voices << { note: note, player: player }
|
|
31
|
-
|
|
37
|
+
player.start(scheduled_time)
|
|
38
|
+
@voices << { note: note, player: player, choke_group: @choke_group }
|
|
39
|
+
steal_oldest_voice(scheduled_time) if @voices.length > @max_voices
|
|
32
40
|
self
|
|
33
41
|
end
|
|
34
42
|
|
|
35
43
|
def trigger_release(note, time = nil)
|
|
44
|
+
return self if @one_shot
|
|
45
|
+
|
|
36
46
|
voice = @voices.find { |entry| entry[:note] == note }
|
|
37
47
|
voice&.fetch(:player)&.stop(resolve_time(time))
|
|
38
48
|
self
|
|
@@ -47,6 +57,7 @@ module Deftones
|
|
|
47
57
|
|
|
48
58
|
def add(note, buffer)
|
|
49
59
|
@samples[note.to_s] = buffer.is_a?(Deftones::IO::Buffer) ? buffer : Deftones::IO::Buffer.load(buffer)
|
|
60
|
+
rebuild_root_note_cache
|
|
50
61
|
self
|
|
51
62
|
end
|
|
52
63
|
|
|
@@ -58,18 +69,20 @@ module Deftones
|
|
|
58
69
|
@samples.key?(note.to_s)
|
|
59
70
|
end
|
|
60
71
|
|
|
61
|
-
def release_all(time = nil)
|
|
72
|
+
def release_all(time = nil, force: false)
|
|
62
73
|
scheduled_time = resolve_time(time)
|
|
74
|
+
return self if @one_shot && !force
|
|
75
|
+
|
|
63
76
|
@voices.each { |voice| voice[:player].stop(scheduled_time) }
|
|
64
77
|
self
|
|
65
78
|
end
|
|
66
79
|
|
|
67
80
|
def loaded?
|
|
68
|
-
|
|
81
|
+
!disposed?
|
|
69
82
|
end
|
|
70
83
|
|
|
71
84
|
def dispose
|
|
72
|
-
release_all(context.current_time)
|
|
85
|
+
release_all(context.current_time, force: true)
|
|
73
86
|
@voices.clear
|
|
74
87
|
super
|
|
75
88
|
end
|
|
@@ -77,13 +90,43 @@ module Deftones
|
|
|
77
90
|
alias loaded loaded?
|
|
78
91
|
alias triggerAttackRelease trigger_attack_release
|
|
79
92
|
alias releaseAll release_all
|
|
93
|
+
alias oneShot one_shot
|
|
80
94
|
|
|
81
95
|
private
|
|
82
96
|
|
|
83
97
|
def closest_sample(note)
|
|
98
|
+
raise ArgumentError, "Sampler requires at least one sample" if @samples.empty?
|
|
99
|
+
|
|
84
100
|
target_midi = Deftones::Music::Note.to_midi(note)
|
|
85
101
|
@samples.min_by do |sample_note, _|
|
|
86
|
-
(
|
|
102
|
+
(@root_note_cache.fetch(sample_note) - target_midi).abs
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def steal_oldest_voice(time)
|
|
107
|
+
stolen = @voices.shift
|
|
108
|
+
return unless stolen
|
|
109
|
+
|
|
110
|
+
player = stolen[:player]
|
|
111
|
+
player.stop(time)
|
|
112
|
+
player.dispose
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def choke_matching_voices(time)
|
|
116
|
+
return unless @choke_group
|
|
117
|
+
|
|
118
|
+
@voices.delete_if do |voice|
|
|
119
|
+
next false unless voice[:choke_group] == @choke_group
|
|
120
|
+
|
|
121
|
+
voice[:player].stop(time)
|
|
122
|
+
voice[:player].dispose
|
|
123
|
+
true
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def rebuild_root_note_cache
|
|
128
|
+
@root_note_cache = @samples.each_key.to_h do |sample_note|
|
|
129
|
+
[sample_note, Deftones::Music::Note.to_midi(sample_note)]
|
|
87
130
|
end
|
|
88
131
|
end
|
|
89
132
|
|