deftones 0.1.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 (135) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +5 -0
  3. data/CHANGELOG.md +12 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +197 -0
  6. data/Rakefile +8 -0
  7. data/examples/poly_chord.rb +14 -0
  8. data/examples/render_sampler.rb +16 -0
  9. data/examples/render_sequence.rb +13 -0
  10. data/examples/render_synth.rb +18 -0
  11. data/lib/deftones/analysis/analyser.rb +162 -0
  12. data/lib/deftones/analysis/dc_meter.rb +56 -0
  13. data/lib/deftones/analysis/fft.rb +128 -0
  14. data/lib/deftones/analysis/meter.rb +83 -0
  15. data/lib/deftones/analysis/waveform.rb +93 -0
  16. data/lib/deftones/component/amplitude_envelope.rb +8 -0
  17. data/lib/deftones/component/biquad_filter.rb +7 -0
  18. data/lib/deftones/component/channel.rb +109 -0
  19. data/lib/deftones/component/compressor.rb +64 -0
  20. data/lib/deftones/component/convolver.rb +99 -0
  21. data/lib/deftones/component/cross_fade.rb +67 -0
  22. data/lib/deftones/component/envelope.rb +160 -0
  23. data/lib/deftones/component/eq3.rb +73 -0
  24. data/lib/deftones/component/feedback_comb_filter.rb +75 -0
  25. data/lib/deftones/component/filter.rb +75 -0
  26. data/lib/deftones/component/follower.rb +55 -0
  27. data/lib/deftones/component/frequency_envelope.rb +24 -0
  28. data/lib/deftones/component/gate.rb +46 -0
  29. data/lib/deftones/component/lfo.rb +88 -0
  30. data/lib/deftones/component/limiter.rb +11 -0
  31. data/lib/deftones/component/lowpass_comb_filter.rb +43 -0
  32. data/lib/deftones/component/merge.rb +45 -0
  33. data/lib/deftones/component/mid_side_compressor.rb +43 -0
  34. data/lib/deftones/component/mid_side_merge.rb +53 -0
  35. data/lib/deftones/component/mid_side_split.rb +54 -0
  36. data/lib/deftones/component/mono.rb +8 -0
  37. data/lib/deftones/component/multiband_compressor.rb +70 -0
  38. data/lib/deftones/component/multiband_split.rb +133 -0
  39. data/lib/deftones/component/one_pole_filter.rb +71 -0
  40. data/lib/deftones/component/pan_vol.rb +26 -0
  41. data/lib/deftones/component/panner.rb +75 -0
  42. data/lib/deftones/component/panner3d.rb +322 -0
  43. data/lib/deftones/component/solo.rb +56 -0
  44. data/lib/deftones/component/split.rb +47 -0
  45. data/lib/deftones/component/volume.rb +31 -0
  46. data/lib/deftones/context.rb +213 -0
  47. data/lib/deftones/core/audio_block.rb +82 -0
  48. data/lib/deftones/core/audio_node.rb +262 -0
  49. data/lib/deftones/core/clock.rb +91 -0
  50. data/lib/deftones/core/computed_signal.rb +69 -0
  51. data/lib/deftones/core/delay.rb +44 -0
  52. data/lib/deftones/core/effect.rb +66 -0
  53. data/lib/deftones/core/emitter.rb +51 -0
  54. data/lib/deftones/core/gain.rb +39 -0
  55. data/lib/deftones/core/instrument.rb +109 -0
  56. data/lib/deftones/core/param.rb +31 -0
  57. data/lib/deftones/core/signal.rb +452 -0
  58. data/lib/deftones/core/signal_operator_methods.rb +73 -0
  59. data/lib/deftones/core/signal_operators.rb +138 -0
  60. data/lib/deftones/core/signal_shapers.rb +83 -0
  61. data/lib/deftones/core/source.rb +213 -0
  62. data/lib/deftones/core/synced_signal.rb +88 -0
  63. data/lib/deftones/destination.rb +132 -0
  64. data/lib/deftones/draw.rb +100 -0
  65. data/lib/deftones/dsp/biquad.rb +129 -0
  66. data/lib/deftones/dsp/delay_line.rb +41 -0
  67. data/lib/deftones/dsp/helpers.rb +25 -0
  68. data/lib/deftones/effect/auto_filter.rb +92 -0
  69. data/lib/deftones/effect/auto_panner.rb +57 -0
  70. data/lib/deftones/effect/auto_wah.rb +98 -0
  71. data/lib/deftones/effect/bit_crusher.rb +38 -0
  72. data/lib/deftones/effect/chebyshev.rb +36 -0
  73. data/lib/deftones/effect/chorus.rb +73 -0
  74. data/lib/deftones/effect/distortion.rb +22 -0
  75. data/lib/deftones/effect/feedback_delay.rb +38 -0
  76. data/lib/deftones/effect/freeverb.rb +11 -0
  77. data/lib/deftones/effect/frequency_shifter.rb +89 -0
  78. data/lib/deftones/effect/jc_reverb.rb +11 -0
  79. data/lib/deftones/effect/modulation_control.rb +159 -0
  80. data/lib/deftones/effect/phaser.rb +72 -0
  81. data/lib/deftones/effect/ping_pong_delay.rb +40 -0
  82. data/lib/deftones/effect/pitch_shift.rb +156 -0
  83. data/lib/deftones/effect/reverb.rb +71 -0
  84. data/lib/deftones/effect/stereo_widener.rb +34 -0
  85. data/lib/deftones/effect/tremolo.rb +52 -0
  86. data/lib/deftones/effect/vibrato.rb +47 -0
  87. data/lib/deftones/event/callback_behavior.rb +61 -0
  88. data/lib/deftones/event/loop.rb +53 -0
  89. data/lib/deftones/event/part.rb +51 -0
  90. data/lib/deftones/event/pattern.rb +94 -0
  91. data/lib/deftones/event/sequence.rb +87 -0
  92. data/lib/deftones/event/tone_event.rb +77 -0
  93. data/lib/deftones/event/transport.rb +276 -0
  94. data/lib/deftones/instrument/am_synth.rb +56 -0
  95. data/lib/deftones/instrument/duo_synth.rb +68 -0
  96. data/lib/deftones/instrument/fm_synth.rb +60 -0
  97. data/lib/deftones/instrument/membrane_synth.rb +60 -0
  98. data/lib/deftones/instrument/metal_synth.rb +61 -0
  99. data/lib/deftones/instrument/mono_synth.rb +88 -0
  100. data/lib/deftones/instrument/noise_synth.rb +56 -0
  101. data/lib/deftones/instrument/pluck_synth.rb +41 -0
  102. data/lib/deftones/instrument/poly_synth.rb +96 -0
  103. data/lib/deftones/instrument/sampler.rb +97 -0
  104. data/lib/deftones/instrument/synth.rb +60 -0
  105. data/lib/deftones/io/buffer.rb +352 -0
  106. data/lib/deftones/io/buffers.rb +77 -0
  107. data/lib/deftones/io/recorder.rb +89 -0
  108. data/lib/deftones/listener.rb +120 -0
  109. data/lib/deftones/music/frequency.rb +128 -0
  110. data/lib/deftones/music/midi.rb +206 -0
  111. data/lib/deftones/music/note.rb +58 -0
  112. data/lib/deftones/music/ticks.rb +106 -0
  113. data/lib/deftones/music/time.rb +209 -0
  114. data/lib/deftones/music/transport_time.rb +94 -0
  115. data/lib/deftones/music/unit_helpers.rb +30 -0
  116. data/lib/deftones/offline_context.rb +46 -0
  117. data/lib/deftones/portaudio_support.rb +112 -0
  118. data/lib/deftones/source/am_oscillator.rb +42 -0
  119. data/lib/deftones/source/fat_oscillator.rb +49 -0
  120. data/lib/deftones/source/fm_oscillator.rb +47 -0
  121. data/lib/deftones/source/grain_player.rb +198 -0
  122. data/lib/deftones/source/karplus_strong.rb +51 -0
  123. data/lib/deftones/source/noise.rb +99 -0
  124. data/lib/deftones/source/omni_oscillator.rb +175 -0
  125. data/lib/deftones/source/oscillator.rb +74 -0
  126. data/lib/deftones/source/player.rb +228 -0
  127. data/lib/deftones/source/players.rb +133 -0
  128. data/lib/deftones/source/pulse_oscillator.rb +38 -0
  129. data/lib/deftones/source/pwm_oscillator.rb +49 -0
  130. data/lib/deftones/source/tone_buffer_source.rb +136 -0
  131. data/lib/deftones/source/tone_oscillator_node.rb +65 -0
  132. data/lib/deftones/source/user_media.rb +519 -0
  133. data/lib/deftones/version.rb +5 -0
  134. data/lib/deftones.rb +542 -0
  135. metadata +221 -0
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class Player < Core::Source
6
+ attr_reader :buffer, :playback_rate
7
+ attr_accessor :loop, :loop_start, :loop_end, :reverse, :fade_in, :fade_out, :curve
8
+ attr_accessor :autostart
9
+
10
+ def initialize(buffer:, playback_rate: 1.0, loop: false, loop_start: 0.0, loop_end: nil,
11
+ reverse: false, fade_in: 0.0, fade_out: 0.0, curve: :linear,
12
+ autostart: false, onstop: nil, context: Deftones.context)
13
+ super(context: context)
14
+ @buffer = buffer.is_a?(IO::Buffer) ? buffer : IO::Buffer.load(buffer)
15
+ @playback_rate = Core::Signal.new(value: playback_rate, units: :number, context: context)
16
+ @loop = loop
17
+ @loop_start = loop_start.to_f
18
+ @loop_end = loop_end
19
+ @reverse = reverse
20
+ @fade_in = fade_in.to_f
21
+ @fade_out = fade_out.to_f
22
+ @curve = curve.to_sym
23
+ @autostart = !!autostart
24
+ @onstop = onstop
25
+ @seek_position = 0.0
26
+ @start_time = Float::INFINITY
27
+ @stop_notified = false
28
+ start(0.0) if @autostart
29
+ end
30
+
31
+ def playback_rate=(value)
32
+ @playback_rate.value = value
33
+ end
34
+
35
+ def onstop
36
+ @onstop
37
+ end
38
+
39
+ def onstop=(callback)
40
+ @onstop = callback
41
+ end
42
+
43
+ def loaded?
44
+ !@buffer.nil?
45
+ end
46
+
47
+ def loaded
48
+ loaded?
49
+ end
50
+
51
+ def dispose
52
+ @buffer = nil
53
+ super
54
+ end
55
+
56
+ def start(time = nil, offset = nil, duration = nil)
57
+ seek(offset) if offset
58
+ @stop_notified = false
59
+ super(time)
60
+ self.stop(@start_time + Deftones::Music::Time.parse(duration)) if duration
61
+ self
62
+ end
63
+
64
+ def restart(time = nil, offset = nil, duration = nil)
65
+ stop(time)
66
+ start(time, offset, duration)
67
+ end
68
+
69
+ def state(time = context.current_time)
70
+ active_at?(resolve_time(time)) ? :started : :stopped
71
+ end
72
+
73
+ def seek(time = nil)
74
+ return @seek_position.to_f / @buffer.sample_rate if time.nil?
75
+
76
+ @seek_position = Deftones::Music::Time.parse(time) * @buffer.sample_rate
77
+ self
78
+ end
79
+
80
+ def process(_input_buffer, num_frames, start_frame, _cache)
81
+ rates = @playback_rate.process(num_frames, start_frame)
82
+ return process_multichannel_buffer(num_frames, start_frame, rates) if multichannel_process?
83
+
84
+ Array.new(num_frames) do |index|
85
+ current_time = (start_frame + index).to_f / context.sample_rate
86
+ notify_stop(current_time) if @stop_time && current_time >= @stop_time
87
+ next 0.0 unless active_at?(current_time)
88
+
89
+ sample_position = sample_position_for(current_time, rates[index])
90
+ if sample_position.negative?
91
+ @stop_time ||= current_time
92
+ notify_stop(current_time)
93
+ next 0.0
94
+ end
95
+
96
+ @buffer.sample_at(sample_position) * envelope_gain(current_time)
97
+ end
98
+ end
99
+
100
+ alias playbackRate playback_rate
101
+ alias loopStart loop_start
102
+ alias loopEnd loop_end
103
+ alias fadeIn fade_in
104
+ alias fadeOut fade_out
105
+
106
+ def playbackRate=(value)
107
+ self.playback_rate = value
108
+ end
109
+
110
+ def loopStart=(value)
111
+ self.loop_start = value
112
+ end
113
+
114
+ def loopEnd=(value)
115
+ self.loop_end = value
116
+ end
117
+
118
+ def fadeIn=(value)
119
+ self.fade_in = value
120
+ end
121
+
122
+ def fadeOut=(value)
123
+ self.fade_out = value
124
+ end
125
+
126
+ private
127
+
128
+ def multichannel_process?
129
+ @buffer && @buffer.channels > 1
130
+ end
131
+
132
+ def envelope_gain(current_time)
133
+ fade_in_gain(current_time) * fade_out_gain(current_time)
134
+ end
135
+
136
+ def fade_in_gain(current_time)
137
+ return 1.0 if @fade_in <= 0.0
138
+
139
+ shaped_gain((current_time - @start_time) / @fade_in)
140
+ end
141
+
142
+ def fade_out_gain(current_time)
143
+ return 1.0 unless @stop_time && @fade_out > 0.0
144
+
145
+ shaped_gain((@stop_time - current_time) / @fade_out)
146
+ end
147
+
148
+ def shaped_gain(progress)
149
+ bounded = progress.clamp(0.0, 1.0)
150
+ return bounded if @curve == :linear
151
+
152
+ Math.sin(bounded * Math::PI * 0.5)
153
+ end
154
+
155
+ def notify_stop(current_time)
156
+ return if @stop_notified
157
+
158
+ @stop_notified = true
159
+ @onstop&.call(current_time)
160
+ end
161
+
162
+ def process_multichannel_buffer(num_frames, start_frame, rates)
163
+ output = Array.new(@buffer.channels) { Array.new(num_frames, 0.0) }
164
+
165
+ num_frames.times do |index|
166
+ current_time = (start_frame + index).to_f / context.sample_rate
167
+ notify_stop(current_time) if @stop_time && current_time >= @stop_time
168
+ next unless active_at?(current_time)
169
+
170
+ sample_position = sample_position_for(current_time, rates[index])
171
+ if sample_position.negative?
172
+ @stop_time ||= current_time
173
+ notify_stop(current_time)
174
+ next
175
+ end
176
+
177
+ gain = envelope_gain(current_time)
178
+ @buffer.channels.times do |channel_index|
179
+ output[channel_index][index] = @buffer.sample_at(sample_position, channel_index) * gain
180
+ end
181
+ end
182
+
183
+ Core::AudioBlock.from_channel_data(output)
184
+ end
185
+
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)
190
+ end
191
+
192
+ def resolve_buffer_position(base_position)
193
+ return -1 if base_position.negative?
194
+ return reverse_position(base_position) if @reverse
195
+ return looped_position(base_position) if @loop
196
+
197
+ base_position < @buffer.frames ? base_position : -1
198
+ end
199
+
200
+ def reverse_position(base_position)
201
+ max_position = loop_end_frame || (@buffer.frames - 1)
202
+ min_position = @loop_start * @buffer.sample_rate
203
+ position = max_position - base_position
204
+ return looped_reverse_position(position, min_position, max_position) if @loop
205
+
206
+ position
207
+ end
208
+
209
+ def looped_position(position)
210
+ min_position = @loop_start * @buffer.sample_rate
211
+ max_position = loop_end_frame || @buffer.frames
212
+ span = [max_position - min_position, 1.0].max
213
+ min_position + ((position - min_position) % span)
214
+ end
215
+
216
+ def looped_reverse_position(position, min_position, max_position)
217
+ span = [max_position - min_position, 1.0].max
218
+ min_position + ((position - min_position) % span)
219
+ end
220
+
221
+ def loop_end_frame
222
+ return unless @loop_end
223
+
224
+ Deftones::Music::Time.parse(@loop_end) * @buffer.sample_rate
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class Players
6
+ include Enumerable
7
+
8
+ class VolumeProxy
9
+ attr_reader :players
10
+ attr_accessor :value
11
+
12
+ def initialize(players, value: 0.0)
13
+ @players = players
14
+ @value = value.to_f
15
+ end
16
+
17
+ def value=(new_value)
18
+ @value = new_value.to_f
19
+ players.send(:apply_controls!)
20
+ end
21
+
22
+ def ramp_to(target_value, _duration = nil)
23
+ self.value = target_value
24
+ self
25
+ end
26
+
27
+ alias linear_ramp_to ramp_to
28
+ alias exponential_ramp_to ramp_to
29
+ end
30
+
31
+ attr_reader :volume
32
+
33
+ def initialize(buffers = {}, context: Deftones.context)
34
+ @context = context
35
+ @players = {}
36
+ @mute = false
37
+ @volume = VolumeProxy.new(self)
38
+ @disposed = false
39
+ source_buffers = buffers.is_a?(IO::Buffers) ? buffers : IO::Buffers.new(buffers)
40
+ source_buffers.each { |name, buffer| add(name, buffer) }
41
+ end
42
+
43
+ def add(name, buffer)
44
+ player = Player.new(buffer: buffer, context: @context)
45
+ player.volume.value = @volume.value
46
+ player.mute = @mute
47
+ @players[name.to_sym] = player
48
+ player
49
+ end
50
+
51
+ def [](name)
52
+ get(name)
53
+ end
54
+
55
+ def get(name)
56
+ @players[name.to_sym]
57
+ end
58
+
59
+ def player(name)
60
+ get(name)
61
+ end
62
+
63
+ def has?(name)
64
+ @players.key?(name.to_sym)
65
+ end
66
+
67
+ def names
68
+ @players.keys
69
+ end
70
+
71
+ def loaded?
72
+ !@disposed
73
+ end
74
+
75
+ def loaded
76
+ loaded?
77
+ end
78
+
79
+ def mute
80
+ @mute
81
+ end
82
+
83
+ def mute=(value)
84
+ @mute = !!value
85
+ apply_controls!
86
+ end
87
+
88
+ def mute?
89
+ @mute
90
+ end
91
+
92
+ def volume=(value)
93
+ @volume.value = value
94
+ end
95
+
96
+ def stop_all(time = nil)
97
+ @players.each_value { |player| player.stop(time) }
98
+ self
99
+ end
100
+
101
+ def state(name = nil, time: @context.current_time)
102
+ return get(name)&.state(time) if name
103
+
104
+ @players.transform_values { |player| player.state(time) }
105
+ end
106
+
107
+ def dispose
108
+ @players.each_value(&:dispose)
109
+ @players.clear
110
+ @disposed = true
111
+ self
112
+ end
113
+
114
+ def each(&block)
115
+ return enum_for(:each) unless block
116
+
117
+ @players.each_value(&block)
118
+ end
119
+
120
+ alias stopAll stop_all
121
+
122
+ private
123
+
124
+ def apply_controls!
125
+ @players.each_value do |player|
126
+ player.volume.value = @volume.value
127
+ player.mute = @mute
128
+ end
129
+ self
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class PulseOscillator < Core::Source
6
+ attr_reader :frequency, :width, :detune
7
+
8
+ def initialize(frequency: 440.0, width: 0.5, detune: 0.0, phase: 0.0, context: Deftones.context)
9
+ super(context: context)
10
+ @frequency = Core::Signal.new(value: frequency, units: :frequency, context: context)
11
+ @width = Core::Signal.new(value: width, units: :number, context: context)
12
+ @detune = Core::Signal.new(value: detune, units: :number, context: context)
13
+ @phase = phase.to_f % 1.0
14
+ end
15
+
16
+ def detune=(value)
17
+ @detune.value = value
18
+ end
19
+
20
+ def process(_input_buffer, num_frames, start_frame, _cache)
21
+ frequencies = @frequency.process(num_frames, start_frame)
22
+ widths = @width.process(num_frames, start_frame)
23
+ detunes = @detune.process(num_frames, start_frame)
24
+
25
+ Array.new(num_frames) do |index|
26
+ current_time = (start_frame + index).to_f / context.sample_rate
27
+ next 0.0 unless active_at?(current_time)
28
+
29
+ duty = Deftones::DSP::Helpers.clamp(widths[index], 0.01, 0.99)
30
+ sample = @phase < duty ? 1.0 : -1.0
31
+ frequency = frequencies[index] * (2.0**(detunes[index].to_f / 1200.0))
32
+ @phase = (@phase + (frequency / context.sample_rate)) % 1.0
33
+ sample
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class PWMOscillator < Core::Source
6
+ attr_reader :frequency, :modulation_frequency, :modulation_depth, :detune
7
+
8
+ def initialize(frequency: 440.0, modulation_frequency: 0.5, modulation_depth: 0.4,
9
+ pulse_width: 0.5, detune: 0.0, context: Deftones.context)
10
+ super(context: context)
11
+ @frequency = Core::Signal.new(value: frequency, units: :frequency, context: context)
12
+ @modulation_frequency = Core::Signal.new(value: modulation_frequency, units: :frequency, context: context)
13
+ @modulation_depth = Core::Signal.new(value: modulation_depth, units: :number, context: context)
14
+ @detune = Core::Signal.new(value: detune, units: :number, context: context)
15
+ @base_width = pulse_width.to_f
16
+ @phase = 0.0
17
+ @modulation_phase = 0.0
18
+ end
19
+
20
+ def detune=(value)
21
+ @detune.value = value
22
+ end
23
+
24
+ def process(_input_buffer, num_frames, start_frame, _cache)
25
+ frequencies = @frequency.process(num_frames, start_frame)
26
+ mod_frequencies = @modulation_frequency.process(num_frames, start_frame)
27
+ mod_depths = @modulation_depth.process(num_frames, start_frame)
28
+ detunes = @detune.process(num_frames, start_frame)
29
+
30
+ Array.new(num_frames) do |index|
31
+ current_time = (start_frame + index).to_f / context.sample_rate
32
+ next 0.0 unless active_at?(current_time)
33
+
34
+ width = @base_width + (Math.sin(2.0 * Math::PI * @modulation_phase) * mod_depths[index] * 0.5)
35
+ duty = Deftones::DSP::Helpers.clamp(width, 0.05, 0.95)
36
+ sample = @phase < duty ? 1.0 : -1.0
37
+
38
+ frequency = frequencies[index] * (2.0**(detunes[index].to_f / 1200.0))
39
+ @phase = (@phase + (frequency / context.sample_rate)) % 1.0
40
+ @modulation_phase = (@modulation_phase + (mod_frequencies[index] / context.sample_rate)) % 1.0
41
+ sample
42
+ end
43
+ end
44
+
45
+ alias modulationFrequency modulation_frequency
46
+ alias modulationDepth modulation_depth
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class ToneBufferSource < Player
6
+ attr_accessor :curve, :fade_in, :fade_out, :onended
7
+ attr_reader :detune, :start_gain
8
+
9
+ def initialize(buffer:, playback_rate: 1.0, detune: 0.0, fade_in: 0.0, fade_out: 0.0, curve: :linear,
10
+ context: Deftones.context, **options)
11
+ super(buffer: buffer, playback_rate: playback_rate, context: context, **options)
12
+ @detune = Core::Signal.new(value: detune, units: :number, context: context)
13
+ @fade_in = fade_in.to_f
14
+ @fade_out = fade_out.to_f
15
+ @curve = curve.to_sym
16
+ @start_gain = 1.0
17
+ @onended = nil
18
+ @ended_notified = false
19
+ end
20
+
21
+ def playback_rate=(value)
22
+ @playback_rate.value = value
23
+ end
24
+
25
+ def detune=(value)
26
+ @detune.value = value
27
+ end
28
+
29
+ def start(time = nil, offset = nil, duration = nil, gain: 1.0)
30
+ seek(offset) if offset
31
+ @start_gain = gain.to_f
32
+ @ended_notified = false
33
+ super(time)
34
+ self.stop(@start_time + Deftones::Music::Time.parse(duration)) if duration
35
+ self
36
+ end
37
+
38
+ def cancel_stop
39
+ @stop_time = nil
40
+ self
41
+ end
42
+
43
+ def state(time = context.current_time)
44
+ active_at?(resolve_time(time)) ? :started : :stopped
45
+ end
46
+
47
+ def process(_input_buffer, num_frames, start_frame, _cache)
48
+ rates = @playback_rate.process(num_frames, start_frame)
49
+ detunes = @detune.process(num_frames, start_frame)
50
+ return process_multichannel_buffer(num_frames, start_frame, rates, detunes) if multichannel_process?
51
+
52
+ Array.new(num_frames) do |index|
53
+ current_time = (start_frame + index).to_f / context.sample_rate
54
+ notify_ended(current_time) if @stop_time && current_time >= @stop_time
55
+ next 0.0 unless active_at?(current_time)
56
+
57
+ rate = rates[index] * detune_ratio(detunes[index])
58
+ sample_position = sample_position_for(current_time, rate)
59
+ if sample_position.negative?
60
+ @stop_time ||= current_time
61
+ notify_ended(current_time)
62
+ next 0.0
63
+ end
64
+
65
+ @buffer.sample_at(sample_position) * envelope_gain(current_time)
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def process_multichannel_buffer(num_frames, start_frame, rates, detunes)
72
+ output = Array.new(@buffer.channels) { Array.new(num_frames, 0.0) }
73
+
74
+ num_frames.times do |index|
75
+ current_time = (start_frame + index).to_f / context.sample_rate
76
+ notify_ended(current_time) if @stop_time && current_time >= @stop_time
77
+ next unless active_at?(current_time)
78
+
79
+ rate = rates[index] * detune_ratio(detunes[index])
80
+ sample_position = sample_position_for(current_time, rate)
81
+ if sample_position.negative?
82
+ @stop_time ||= current_time
83
+ notify_ended(current_time)
84
+ next
85
+ end
86
+
87
+ gain = envelope_gain(current_time)
88
+ @buffer.channels.times do |channel_index|
89
+ output[channel_index][index] = @buffer.sample_at(sample_position, channel_index) * gain
90
+ end
91
+ end
92
+
93
+ Core::AudioBlock.from_channel_data(output)
94
+ end
95
+
96
+ def detune_ratio(cents)
97
+ 2.0**(cents.to_f / 1200.0)
98
+ end
99
+
100
+ def envelope_gain(current_time)
101
+ gain = @start_gain
102
+ gain *= fade_in_gain(current_time)
103
+ gain *= fade_out_gain(current_time)
104
+ gain
105
+ end
106
+
107
+ def fade_in_gain(current_time)
108
+ return 1.0 if @fade_in <= 0.0
109
+
110
+ elapsed = current_time - @start_time
111
+ shaped_gain(elapsed / @fade_in)
112
+ end
113
+
114
+ def fade_out_gain(current_time)
115
+ return 1.0 unless @stop_time && @fade_out > 0.0
116
+
117
+ remaining = @stop_time - current_time
118
+ shaped_gain(remaining / @fade_out)
119
+ end
120
+
121
+ def shaped_gain(progress)
122
+ bounded = progress.clamp(0.0, 1.0)
123
+ return bounded if @curve == :linear
124
+
125
+ Math.sin(bounded * Math::PI * 0.5)
126
+ end
127
+
128
+ def notify_ended(current_time)
129
+ return if @ended_notified
130
+
131
+ @ended_notified = true
132
+ @onended&.call(current_time)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class ToneOscillatorNode < Oscillator
6
+ attr_accessor :onended
7
+ attr_reader :detune
8
+
9
+ def initialize(detune: 0.0, context: Deftones.context, **options)
10
+ super(context: context, **options)
11
+ @detune = Core::Signal.new(value: detune, units: :number, context: context)
12
+ @onended = nil
13
+ @ended_notified = false
14
+ end
15
+
16
+ def detune=(value)
17
+ @detune.value = value
18
+ end
19
+
20
+ def state(time = context.current_time)
21
+ active_at?(resolve_time(time)) ? :started : :stopped
22
+ end
23
+
24
+ def cancel_stop
25
+ @stop_time = nil
26
+ self
27
+ end
28
+
29
+ def start(time = nil)
30
+ @ended_notified = false
31
+ super
32
+ end
33
+
34
+ def process(_input_buffer, num_frames, start_frame, _cache)
35
+ generator = GENERATORS.fetch(send(:normalize_type, @type))
36
+ frequencies = @frequency.process(num_frames, start_frame)
37
+ detunes = @detune.process(num_frames, start_frame)
38
+
39
+ Array.new(num_frames) do |index|
40
+ current_time = (start_frame + index).to_f / context.sample_rate
41
+ notify_ended(current_time) if @stop_time && current_time >= @stop_time
42
+ next 0.0 unless active_at?(current_time)
43
+
44
+ sample = generator.call(@phase)
45
+ frequency = frequencies[index] * detune_ratio(detunes[index])
46
+ @phase = (@phase + (frequency / context.sample_rate)) % 1.0
47
+ sample
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def detune_ratio(cents)
54
+ 2.0**(cents.to_f / 1200.0)
55
+ end
56
+
57
+ def notify_ended(current_time)
58
+ return if @ended_notified
59
+
60
+ @ended_notified = true
61
+ @onended&.call(current_time)
62
+ end
63
+ end
64
+ end
65
+ end