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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -6
  3. data/README.md +5 -0
  4. data/Rakefile +50 -1
  5. data/lib/deftones/analysis/meter.rb +22 -2
  6. data/lib/deftones/component/channel.rb +1 -1
  7. data/lib/deftones/component/compressor.rb +127 -22
  8. data/lib/deftones/component/filter.rb +29 -19
  9. data/lib/deftones/component/merge.rb +14 -0
  10. data/lib/deftones/component/multiband_compressor.rb +1 -1
  11. data/lib/deftones/component/one_pole_filter.rb +10 -3
  12. data/lib/deftones/component/panner.rb +25 -2
  13. data/lib/deftones/component/panner3d.rb +0 -10
  14. data/lib/deftones/component/split.rb +14 -0
  15. data/lib/deftones/context.rb +90 -9
  16. data/lib/deftones/core/audio_block.rb +64 -5
  17. data/lib/deftones/core/audio_node.rb +98 -8
  18. data/lib/deftones/core/gain.rb +0 -8
  19. data/lib/deftones/core/instrument.rb +52 -10
  20. data/lib/deftones/core/param.rb +51 -1
  21. data/lib/deftones/core/signal.rb +79 -28
  22. data/lib/deftones/core/source.rb +71 -11
  23. data/lib/deftones/destination.rb +41 -17
  24. data/lib/deftones/draw.rb +6 -10
  25. data/lib/deftones/dsp/biquad.rb +9 -4
  26. data/lib/deftones/dsp/delay_line.rb +2 -2
  27. data/lib/deftones/dsp/helpers.rb +7 -0
  28. data/lib/deftones/effect/bit_crusher.rb +10 -2
  29. data/lib/deftones/effect/chebyshev.rb +7 -3
  30. data/lib/deftones/effect/distortion.rb +5 -3
  31. data/lib/deftones/effect/feedback_delay.rb +2 -1
  32. data/lib/deftones/effect/oversampling.rb +43 -0
  33. data/lib/deftones/effect/phaser.rb +2 -1
  34. data/lib/deftones/effect/pitch_shift.rb +1 -2
  35. data/lib/deftones/effect/reverb.rb +73 -5
  36. data/lib/deftones/event/callback_behavior.rb +7 -3
  37. data/lib/deftones/event/loop.rb +7 -2
  38. data/lib/deftones/event/part.rb +18 -3
  39. data/lib/deftones/event/pattern.rb +51 -6
  40. data/lib/deftones/event/sequence.rb +19 -5
  41. data/lib/deftones/event/tone_event.rb +7 -2
  42. data/lib/deftones/event/transport.rb +243 -21
  43. data/lib/deftones/instrument/poly_synth.rb +81 -15
  44. data/lib/deftones/instrument/sampler.rb +53 -10
  45. data/lib/deftones/io/buffer.rb +376 -55
  46. data/lib/deftones/io/buffers.rb +28 -4
  47. data/lib/deftones/io/recorder.rb +2 -1
  48. data/lib/deftones/music/frequency.rb +13 -8
  49. data/lib/deftones/music/midi.rb +132 -9
  50. data/lib/deftones/music/note.rb +13 -3
  51. data/lib/deftones/music/time.rb +42 -4
  52. data/lib/deftones/offline_context.rb +194 -17
  53. data/lib/deftones/portaudio_support.rb +68 -9
  54. data/lib/deftones/source/fat_oscillator.rb +28 -9
  55. data/lib/deftones/source/grain_player.rb +49 -2
  56. data/lib/deftones/source/noise.rb +42 -10
  57. data/lib/deftones/source/omni_oscillator.rb +1 -2
  58. data/lib/deftones/source/oscillator.rb +83 -19
  59. data/lib/deftones/source/player.rb +24 -6
  60. data/lib/deftones/source/players.rb +39 -6
  61. data/lib/deftones/source/tone_buffer_source.rb +12 -6
  62. data/lib/deftones/source/tone_oscillator_node.rb +4 -3
  63. data/lib/deftones/source/user_media.rb +83 -10
  64. data/lib/deftones/version.rb +1 -1
  65. data/lib/deftones.rb +108 -31
  66. metadata +3 -44
@@ -6,13 +6,18 @@ module Deftones
6
6
  module PortAudioSupport
7
7
  class << self
8
8
  def available?
9
+ load_backend!
9
10
  !!defined?(PortAudio)
10
11
  end
11
12
 
12
13
  def acquire!
13
14
  raise Deftones::MissingRealtimeBackendError, "PortAudio backend is unavailable" unless available?
14
15
 
15
- PortAudio.init
16
+ mutex.synchronize do
17
+ PortAudio.init if ref_count.zero?
18
+ @ref_count = ref_count + 1
19
+ end
20
+ self
16
21
  rescue StandardError => error
17
22
  raise Deftones::MissingRealtimeBackendError, error.message
18
23
  end
@@ -20,17 +25,35 @@ module Deftones
20
25
  def release
21
26
  return unless available?
22
27
 
23
- PortAudio.terminate
28
+ mutex.synchronize do
29
+ return self if ref_count.zero?
30
+
31
+ @ref_count = ref_count - 1
32
+ PortAudio.terminate if ref_count.zero?
33
+ end
34
+ self
24
35
  rescue StandardError
25
36
  nil
26
37
  end
27
38
 
28
- def output_parameters(channels)
29
- build_stream_parameters(direction: :output, channels: channels)
39
+ def output_parameters(channels, device_id: nil, label: nil, sample_rate: nil)
40
+ build_stream_parameters(
41
+ direction: :output,
42
+ channels: channels,
43
+ device_id: device_id,
44
+ label: label,
45
+ sample_rate: sample_rate
46
+ )
30
47
  end
31
48
 
32
- def input_parameters(channels, device_id: nil, label: nil)
33
- build_stream_parameters(direction: :input, channels: channels, device_id: device_id, label: label)
49
+ def input_parameters(channels, device_id: nil, label: nil, sample_rate: nil)
50
+ build_stream_parameters(
51
+ direction: :input,
52
+ channels: channels,
53
+ device_id: device_id,
54
+ label: label,
55
+ sample_rate: sample_rate
56
+ )
34
57
  end
35
58
 
36
59
  def check_error!(result, fallback: nil)
@@ -41,11 +64,29 @@ module Deftones
41
64
 
42
65
  private
43
66
 
44
- def build_stream_parameters(direction:, channels:, device_id: nil, label: nil)
67
+ def load_backend!
68
+ return true if defined?(PortAudio)
69
+
70
+ require "portaudio"
71
+ true
72
+ rescue LoadError
73
+ false
74
+ end
75
+
76
+ def mutex
77
+ @mutex ||= Mutex.new
78
+ end
79
+
80
+ def ref_count
81
+ @ref_count ||= 0
82
+ end
83
+
84
+ def build_stream_parameters(direction:, channels:, device_id: nil, label: nil, sample_rate: nil)
45
85
  device =
46
86
  resolve_device(direction: direction, device_id: device_id, label: label)
47
87
 
48
88
  raise Deftones::MissingRealtimeBackendError, "No default #{direction} device available" unless device
89
+ detect_sample_rate_mismatch!(device, sample_rate) if sample_rate
49
90
 
50
91
  {
51
92
  device: device,
@@ -101,11 +142,29 @@ module Deftones
101
142
  candidates = []
102
143
  candidates << device.label if device.respond_to?(:label)
103
144
  candidates << device.name if device.respond_to?(:name)
104
- candidates.compact.any? { |candidate| candidate.to_s == label.to_s }
145
+ matcher = label.is_a?(Regexp) ? label : Regexp.new(Regexp.escape(label.to_s), Regexp::IGNORECASE)
146
+ candidates.compact.any? { |candidate| candidate.to_s.match?(matcher) }
105
147
  end
106
148
 
107
149
  def suggested_latency(device, direction)
108
- direction == :input ? device.default_low_input_latency : device.default_low_output_latency
150
+ method_name = direction == :input ? :default_low_input_latency : :default_low_output_latency
151
+ return device.public_send(method_name) if device.respond_to?(method_name)
152
+
153
+ 0.05
154
+ end
155
+
156
+ def detect_sample_rate_mismatch!(device, requested_sample_rate)
157
+ device_sample_rate =
158
+ if device.respond_to?(:default_sample_rate)
159
+ device.default_sample_rate
160
+ elsif device.respond_to?(:sample_rate)
161
+ device.sample_rate
162
+ end
163
+ return unless device_sample_rate
164
+ return if device_sample_rate.to_f == requested_sample_rate.to_f
165
+
166
+ raise Deftones::MissingRealtimeBackendError,
167
+ "PortAudio device sample rate #{device_sample_rate} does not match requested #{requested_sample_rate}"
109
168
  end
110
169
  end
111
170
  end
@@ -3,21 +3,32 @@
3
3
  module Deftones
4
4
  module Source
5
5
  class FatOscillator < Core::Source
6
- attr_reader :frequency
7
- attr_accessor :count, :spread, :type
6
+ attr_reader :count, :frequency, :spread, :type
8
7
 
9
8
  def initialize(type: :sawtooth, frequency: 440.0, count: 3, spread: 20.0, context: Deftones.context)
10
9
  super(context: context)
11
- @type = type.to_sym
12
10
  @frequency = Core::Signal.new(value: frequency, units: :frequency, context: context)
13
- @count = count.to_i
14
- @spread = spread.to_f
15
- @phases = Array.new(@count, 0.0)
11
+ @phases = []
12
+ self.type = type
13
+ self.count = count
14
+ self.spread = spread
15
+ end
16
+
17
+ def count=(value)
18
+ @count = [value.to_i, 1].max
19
+ @phases = Array.new(@count, 0.0) if @phases.length != @count
20
+ end
21
+
22
+ def spread=(value)
23
+ @spread = value.to_f
24
+ end
25
+
26
+ def type=(value)
27
+ @type = normalize_type(value)
16
28
  end
17
29
 
18
30
  def process(_input_buffer, num_frames, start_frame, _cache)
19
31
  frequencies = @frequency.process(num_frames, start_frame)
20
- generator = Oscillator::GENERATORS.fetch(@type) { Oscillator::GENERATORS[:sawtooth] }
21
32
 
22
33
  Array.new(num_frames) do |index|
23
34
  current_time = (start_frame + index).to_f / context.sample_rate
@@ -25,8 +36,9 @@ module Deftones
25
36
 
26
37
  detuned = detune_frequencies(frequencies[index])
27
38
  samples = @phases.each_with_index.map do |phase, voice_index|
28
- sample = generator.call(phase)
29
- @phases[voice_index] = (phase + (detuned[voice_index] / context.sample_rate)) % 1.0
39
+ phase_increment = detuned[voice_index] / context.sample_rate
40
+ sample = Oscillator.sample(@type, phase, phase_increment)
41
+ @phases[voice_index] = (phase + phase_increment) % 1.0
30
42
  sample
31
43
  end
32
44
  samples.sum / samples.length.to_f
@@ -44,6 +56,13 @@ module Deftones
44
56
  @phases = Array.new(@count, 0.0) if @phases.length != @count
45
57
  offsets
46
58
  end
59
+
60
+ def normalize_type(type)
61
+ normalized = type.to_sym
62
+ return normalized if Oscillator::TYPES.include?(normalized)
63
+
64
+ raise ArgumentError, "Unsupported oscillator type: #{type}"
65
+ end
47
66
  end
48
67
  end
49
68
  end
@@ -3,21 +3,33 @@
3
3
  module Deftones
4
4
  module Source
5
5
  class GrainPlayer < Player
6
+ WINDOWS = %i[linear hann hamming blackman].freeze
7
+
6
8
  attr_reader :detune
7
9
  attr_accessor :grain_size, :overlap, :jitter
10
+ attr_reader :window
8
11
 
9
- def initialize(grain_size: 0.05, overlap: 0.5, jitter: 0.002, detune: 0.0, **options)
12
+ def initialize(grain_size: 0.05, overlap: 0.5, jitter: 0.002, detune: 0.0,
13
+ window: :linear, jitter_seed: nil, rng: nil, **options)
10
14
  super(**options)
11
15
  @grain_size = grain_size.to_f
12
16
  @overlap = overlap.to_f
13
17
  @jitter = jitter.to_f
14
18
  @detune = Core::Signal.new(value: detune, units: :number, context: context)
19
+ @window = normalize_window(window)
20
+ @jitter_seed = jitter_seed
21
+ @rng = rng
22
+ @grain_random_cache = {}
15
23
  end
16
24
 
17
25
  def detune=(value)
18
26
  @detune.value = value
19
27
  end
20
28
 
29
+ def window=(value)
30
+ @window = normalize_window(value)
31
+ end
32
+
21
33
  def process(_input_buffer, num_frames, start_frame, _cache)
22
34
  rates = @playback_rate.process(num_frames, start_frame)
23
35
  detunes = @detune.process(num_frames, start_frame)
@@ -156,10 +168,35 @@ module Deftones
156
168
  end
157
169
 
158
170
  def grain_random(grain_index)
159
- Math.sin((grain_index + 1) * 12_989.0).abs % 1.0
171
+ return @grain_random_cache[grain_index] if @grain_random_cache.key?(grain_index)
172
+
173
+ @grain_random_cache[grain_index] =
174
+ if @rng
175
+ @rng.rand
176
+ elsif @jitter_seed
177
+ Random.new(@jitter_seed.to_i + grain_index).rand
178
+ else
179
+ Math.sin((grain_index + 1) * 12_989.0).abs % 1.0
180
+ end
160
181
  end
161
182
 
162
183
  def grain_window_gain(grain_elapsed, grain_duration, overlap_duration)
184
+ return overlapped_linear_gain(grain_elapsed, grain_duration, overlap_duration) if @window == :linear
185
+
186
+ progress = (grain_elapsed / grain_duration).clamp(0.0, 1.0)
187
+ case @window
188
+ when :hann
189
+ 0.5 - (0.5 * Math.cos(2.0 * Math::PI * progress))
190
+ when :hamming
191
+ 0.54 - (0.46 * Math.cos(2.0 * Math::PI * progress))
192
+ when :blackman
193
+ 0.42 - (0.5 * Math.cos(2.0 * Math::PI * progress)) + (0.08 * Math.cos(4.0 * Math::PI * progress))
194
+ else
195
+ overlapped_linear_gain(grain_elapsed, grain_duration, overlap_duration)
196
+ end
197
+ end
198
+
199
+ def overlapped_linear_gain(grain_elapsed, grain_duration, overlap_duration)
163
200
  return 1.0 if overlap_duration <= 0.0
164
201
 
165
202
  fade_in = (grain_elapsed / overlap_duration).clamp(0.0, 1.0)
@@ -167,6 +204,16 @@ module Deftones
167
204
  [fade_in, fade_out].min
168
205
  end
169
206
 
207
+ def normalize_window(window)
208
+ normalized = window.to_sym
209
+ return normalized if WINDOWS.include?(normalized)
210
+
211
+ raise ArgumentError, "Unsupported grain window: #{window}"
212
+ end
213
+
214
+ alias grainWindow window
215
+ alias grainWindow= window=
216
+
170
217
  def naturally_finished?(current_time, playback_rate, grain_duration)
171
218
  return false if @loop
172
219
  return false unless playback_rate.positive?
@@ -5,17 +5,18 @@ module Deftones
5
5
  class Noise < Core::Source
6
6
  TYPES = %i[white pink brown].freeze
7
7
 
8
- attr_accessor :type, :fade_in, :fade_out
9
- attr_reader :playback_rate
8
+ attr_accessor :fade_in, :fade_out
9
+ attr_reader :playback_rate, :type
10
10
 
11
- def initialize(type: :white, playback_rate: 1.0, fade_in: 0.0, fade_out: 0.0, context: Deftones.context)
11
+ def initialize(type: :white, playback_rate: 1.0, fade_in: 0.0, fade_out: 0.0, seed: nil, rng: nil,
12
+ context: Deftones.context)
12
13
  super(context: context)
13
- @type = normalize_type(type)
14
14
  @playback_rate = playback_rate.to_f
15
15
  @fade_in = fade_in.to_f
16
16
  @fade_out = fade_out.to_f
17
- @pink_state = 0.0
18
- @brown_state = 0.0
17
+ @rng = rng || (seed.nil? ? Random : Random.new(seed))
18
+ reset_colored_state
19
+ self.type = type
19
20
  @held_sample = next_noise_sample
20
21
  @playback_phase = 0.0
21
22
  end
@@ -24,6 +25,15 @@ module Deftones
24
25
  @playback_rate = value.to_f
25
26
  end
26
27
 
28
+ def type=(value)
29
+ normalized = normalize_type(value)
30
+ return @type = normalized if @type == normalized
31
+
32
+ @type = normalized
33
+ reset_colored_state
34
+ @held_sample = next_noise_sample if defined?(@held_sample)
35
+ end
36
+
27
37
  def process(_input_buffer, num_frames, start_frame, _cache)
28
38
  Array.new(num_frames) do |index|
29
39
  current_time = (start_frame + index).to_f / context.sample_rate
@@ -72,19 +82,41 @@ module Deftones
72
82
  end
73
83
 
74
84
  def next_noise_sample
75
- white = (rand * 2.0) - 1.0
85
+ white = (@rng.rand * 2.0) - 1.0
76
86
 
77
87
  case normalize_type(@type)
78
88
  when :white
79
89
  white
80
90
  when :pink
81
- @pink_state = (0.98 * @pink_state) + (0.02 * white)
82
- @pink_state * 3.5
91
+ pink_noise_sample(white)
83
92
  when :brown
84
- @brown_state = Deftones::DSP::Helpers.clamp(@brown_state + (white * 0.02), -1.0, 1.0)
93
+ brown_noise_sample(white)
85
94
  end
86
95
  end
87
96
 
97
+ def pink_noise_sample(white)
98
+ @pink_state[0] = (0.99886 * @pink_state[0]) + (white * 0.0555179)
99
+ @pink_state[1] = (0.99332 * @pink_state[1]) + (white * 0.0750759)
100
+ @pink_state[2] = (0.96900 * @pink_state[2]) + (white * 0.1538520)
101
+ @pink_state[3] = (0.86650 * @pink_state[3]) + (white * 0.3104856)
102
+ @pink_state[4] = (0.55000 * @pink_state[4]) + (white * 0.5329522)
103
+ @pink_state[5] = (-0.7616 * @pink_state[5]) - (white * 0.0168980)
104
+
105
+ sample = @pink_state[0..6].sum + (white * 0.5362)
106
+ @pink_state[6] = white * 0.115926
107
+ Deftones::DSP::Helpers.clamp(sample * 0.11, -1.0, 1.0)
108
+ end
109
+
110
+ def brown_noise_sample(white)
111
+ @brown_state = (@brown_state + (0.02 * white)) / 1.02
112
+ Deftones::DSP::Helpers.clamp(@brown_state * 3.5, -1.0, 1.0)
113
+ end
114
+
115
+ def reset_colored_state
116
+ @pink_state = Array.new(7, 0.0)
117
+ @brown_state = 0.0
118
+ end
119
+
88
120
  def normalize_type(type)
89
121
  normalized = type.to_sym
90
122
  return normalized if TYPES.include?(normalized)
@@ -29,8 +29,7 @@ module Deftones
29
29
  fat: FatOscillator
30
30
  }.freeze
31
31
 
32
- attr_reader :source
33
- attr_accessor :type
32
+ attr_reader :source, :type
34
33
 
35
34
  def initialize(type: :sine, context: Deftones.context, **options)
36
35
  super(context: context)
@@ -3,27 +3,82 @@
3
3
  module Deftones
4
4
  module Source
5
5
  class Oscillator < Core::Source
6
+ TYPES = %i[sine square sawtooth triangle].freeze
6
7
  GENERATORS = {
7
- sine: lambda { |phase|
8
- Math.sin(2.0 * Math::PI * phase)
9
- },
10
- square: lambda { |phase|
11
- phase < 0.5 ? 1.0 : -1.0
12
- },
13
- sawtooth: lambda { |phase|
14
- (2.0 * phase) - 1.0
15
- },
16
- triangle: lambda { |phase|
17
- (4.0 * (phase < 0.5 ? phase : 1.0 - phase)) - 1.0
18
- }
8
+ sine: ->(phase) { Math.sin(2.0 * Math::PI * phase) },
9
+ square: ->(phase) { phase < 0.5 ? 1.0 : -1.0 },
10
+ sawtooth: ->(phase) { (2.0 * phase) - 1.0 },
11
+ triangle: ->(phase) { (4.0 * (phase < 0.5 ? phase : 1.0 - phase)) - 1.0 }
19
12
  }.freeze
20
13
 
21
- attr_reader :frequency, :detune
22
- attr_accessor :type
14
+ class << self
15
+ def sample(type, phase, phase_increment = 0.0)
16
+ case type
17
+ when :sine
18
+ Math.sin(2.0 * Math::PI * phase)
19
+ when :square
20
+ bandlimited_square(phase, phase_increment)
21
+ when :sawtooth
22
+ bandlimited_sawtooth(phase, phase_increment)
23
+ when :triangle
24
+ bandlimited_triangle(phase, phase_increment)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def bandlimited_square(phase, phase_increment)
31
+ sample = phase < 0.5 ? 1.0 : -1.0
32
+ sample += poly_blep(phase, phase_increment)
33
+ sample -= poly_blep((phase + 0.5) % 1.0, phase_increment)
34
+ sample
35
+ end
36
+
37
+ def bandlimited_sawtooth(phase, phase_increment)
38
+ ((2.0 * phase) - 1.0) - poly_blep(phase, phase_increment)
39
+ end
40
+
41
+ def bandlimited_triangle(phase, phase_increment)
42
+ return naive_triangle(phase) unless phase_increment.positive?
43
+
44
+ max_harmonic = (0.5 / phase_increment.abs).floor
45
+ return naive_triangle(phase) if max_harmonic < 1
46
+
47
+ sum = 0.0
48
+ harmonic = 1
49
+ while harmonic <= max_harmonic
50
+ sum += Math.cos(2.0 * Math::PI * harmonic * phase) / (harmonic * harmonic)
51
+ harmonic += 2
52
+ end
53
+ -(8.0 / (Math::PI * Math::PI)) * sum
54
+ end
55
+
56
+ def naive_triangle(phase)
57
+ (4.0 * (phase < 0.5 ? phase : 1.0 - phase)) - 1.0
58
+ end
59
+
60
+ def poly_blep(phase, phase_increment)
61
+ increment = [phase_increment.abs, 1.0e-9].max
62
+ return poly_blep_start(phase / increment) if phase < increment
63
+ return poly_blep_end((phase - 1.0) / increment) if phase > 1.0 - increment
64
+
65
+ 0.0
66
+ end
67
+
68
+ def poly_blep_start(t)
69
+ (t + t) - (t * t) - 1.0
70
+ end
71
+
72
+ def poly_blep_end(t)
73
+ (t * t) + (t + t) + 1.0
74
+ end
75
+ end
76
+
77
+ attr_reader :detune, :frequency, :type
23
78
 
24
79
  def initialize(type: :sine, frequency: 440.0, detune: 0.0, phase: 0.0, context: Deftones.context)
25
80
  super(context: context)
26
- @type = normalize_type(type)
81
+ self.type = type
27
82
  @frequency = Core::Signal.new(value: frequency, units: :frequency, context: context)
28
83
  @detune = Core::Signal.new(value: detune, units: :number, context: context)
29
84
  self.phase = phase
@@ -41,8 +96,12 @@ module Deftones
41
96
  @phase = value.to_f % 1.0
42
97
  end
43
98
 
99
+ def type=(value)
100
+ @type = normalize_type(value)
101
+ end
102
+
44
103
  def process(_input_buffer, num_frames, start_frame, _cache)
45
- generator = GENERATORS.fetch(normalize_type(@type))
104
+ oscillator_type = normalize_type(@type)
46
105
  frequencies = @frequency.process(num_frames, start_frame)
47
106
  detunes = @detune.process(num_frames, start_frame)
48
107
 
@@ -50,22 +109,27 @@ module Deftones
50
109
  current_time = (start_frame + index).to_f / context.sample_rate
51
110
  next 0.0 unless active_at?(current_time)
52
111
 
53
- sample = generator.call(@phase)
54
112
  frequency = frequencies[index] * detune_ratio(detunes[index])
55
- @phase = (@phase + (frequency / context.sample_rate)) % 1.0
113
+ phase_increment = frequency / context.sample_rate
114
+ sample = sample_for(oscillator_type, @phase, phase_increment)
115
+ @phase = (@phase + phase_increment) % 1.0
56
116
  sample
57
117
  end
58
118
  end
59
119
 
60
120
  private
61
121
 
122
+ def sample_for(type, phase, phase_increment)
123
+ Oscillator.sample(type, phase, phase_increment)
124
+ end
125
+
62
126
  def detune_ratio(cents)
63
127
  2.0**(cents.to_f / 1200.0)
64
128
  end
65
129
 
66
130
  def normalize_type(type)
67
131
  normalized = type.to_sym
68
- return normalized if GENERATORS.key?(normalized)
132
+ return normalized if TYPES.include?(normalized)
69
133
 
70
134
  raise ArgumentError, "Unsupported oscillator type: #{type}"
71
135
  end
@@ -79,6 +79,7 @@ module Deftones
79
79
 
80
80
  def process(_input_buffer, num_frames, start_frame, _cache)
81
81
  rates = @playback_rate.process(num_frames, start_frame)
82
+ sample_positions = sample_positions_for(num_frames, start_frame, rates)
82
83
  return process_multichannel_buffer(num_frames, start_frame, rates) if multichannel_process?
83
84
 
84
85
  Array.new(num_frames) do |index|
@@ -86,7 +87,7 @@ module Deftones
86
87
  notify_stop(current_time) if @stop_time && current_time >= @stop_time
87
88
  next 0.0 unless active_at?(current_time)
88
89
 
89
- sample_position = sample_position_for(current_time, rates[index])
90
+ sample_position = sample_positions[index]
90
91
  if sample_position.negative?
91
92
  @stop_time ||= current_time
92
93
  notify_stop(current_time)
@@ -161,13 +162,14 @@ module Deftones
161
162
 
162
163
  def process_multichannel_buffer(num_frames, start_frame, rates)
163
164
  output = Array.new(@buffer.channels) { Array.new(num_frames, 0.0) }
165
+ sample_positions = sample_positions_for(num_frames, start_frame, rates)
164
166
 
165
167
  num_frames.times do |index|
166
168
  current_time = (start_frame + index).to_f / context.sample_rate
167
169
  notify_stop(current_time) if @stop_time && current_time >= @stop_time
168
170
  next unless active_at?(current_time)
169
171
 
170
- sample_position = sample_position_for(current_time, rates[index])
172
+ sample_position = sample_positions[index]
171
173
  if sample_position.negative?
172
174
  @stop_time ||= current_time
173
175
  notify_stop(current_time)
@@ -183,10 +185,26 @@ module Deftones
183
185
  Core::AudioBlock.from_channel_data(output)
184
186
  end
185
187
 
186
- def sample_position_for(current_time, rate)
187
- elapsed_frames = (current_time - @start_time) * @buffer.sample_rate * rate
188
- base_position = @seek_position + elapsed_frames
189
- resolve_buffer_position(base_position)
188
+ def sample_positions_for(num_frames, start_frame, rates)
189
+ return Array.new(num_frames, -1.0) if @start_time.infinite?
190
+
191
+ ratio = @buffer.sample_rate.to_f / context.sample_rate
192
+ base_position = integrated_position_at_frame(start_frame)
193
+
194
+ Array.new(num_frames) do |index|
195
+ position = resolve_buffer_position(base_position)
196
+ base_position += rates[index].to_f * ratio
197
+ position
198
+ end
199
+ end
200
+
201
+ def integrated_position_at_frame(frame_index)
202
+ start_frame = (@start_time * context.sample_rate).floor
203
+ frames_to_integrate = [frame_index - start_frame, 0].max
204
+ return @seek_position if frames_to_integrate.zero?
205
+
206
+ rates = @playback_rate.process(frames_to_integrate, start_frame)
207
+ @seek_position + (rates.sum * (@buffer.sample_rate.to_f / context.sample_rate))
190
208
  end
191
209
 
192
210
  def resolve_buffer_position(base_position)
@@ -7,7 +7,7 @@ module Deftones
7
7
 
8
8
  class VolumeProxy
9
9
  attr_reader :players
10
- attr_accessor :value
10
+ attr_reader :value
11
11
 
12
12
  def initialize(players, value: 0.0)
13
13
  @players = players
@@ -19,13 +19,29 @@ module Deftones
19
19
  players.send(:apply_controls!)
20
20
  end
21
21
 
22
- def ramp_to(target_value, _duration = nil)
23
- self.value = target_value
22
+ def ramp_to(target_value, duration = nil)
23
+ @value = target_value.to_f
24
+ players.each { |player| player.volume.ramp_to(@value, duration) }
24
25
  self
25
26
  end
26
27
 
27
- alias linear_ramp_to ramp_to
28
- alias exponential_ramp_to ramp_to
28
+ def set_value_at_time(target_value, time)
29
+ @value = target_value.to_f
30
+ players.each { |player| player.volume.set_value_at_time(@value, time) }
31
+ self
32
+ end
33
+
34
+ def linear_ramp_to(target_value, duration = nil)
35
+ ramp_to(target_value, duration)
36
+ end
37
+
38
+ def exponential_ramp_to(target_value, duration = nil)
39
+ ramp_to(target_value, duration)
40
+ end
41
+
42
+ alias setValueAtTime set_value_at_time
43
+ alias linearRampTo linear_ramp_to
44
+ alias exponentialRampTo exponential_ramp_to
29
45
  end
30
46
 
31
47
  attr_reader :volume
@@ -41,6 +57,8 @@ module Deftones
41
57
  end
42
58
 
43
59
  def add(name, buffer)
60
+ raise Deftones::Error, "cannot add player to disposed Players" if @disposed
61
+
44
62
  player = Player.new(buffer: buffer, context: @context)
45
63
  player.volume.value = @volume.value
46
64
  player.mute = @mute
@@ -93,11 +111,25 @@ module Deftones
93
111
  @volume.value = value
94
112
  end
95
113
 
96
- def stop_all(time = nil)
114
+ def stop_all(time = nil, dispose: false)
97
115
  @players.each_value { |player| player.stop(time) }
116
+ self.dispose if dispose
98
117
  self
99
118
  end
100
119
 
120
+ def stop_all_and_dispose(time = nil)
121
+ stop_all(time)
122
+ dispose
123
+ end
124
+
125
+ def stopped?(time = @context.current_time)
126
+ state(time: time).values.all? { |entry| entry == :stopped }
127
+ end
128
+
129
+ def disposed?
130
+ @disposed
131
+ end
132
+
101
133
  def state(name = nil, time: @context.current_time)
102
134
  return get(name)&.state(time) if name
103
135
 
@@ -118,6 +150,7 @@ module Deftones
118
150
  end
119
151
 
120
152
  alias stopAll stop_all
153
+ alias stopAllAndDispose stop_all_and_dispose
121
154
 
122
155
  private
123
156