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,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class FMOscillator < Core::Source
6
+ attr_reader :frequency, :harmonicity, :modulation_index, :detune
7
+
8
+ def initialize(frequency: 440.0, harmonicity: 2.0, modulation_index: 5.0,
9
+ detune: 0.0, phase: 0.0, context: Deftones.context)
10
+ super(context: context)
11
+ @frequency = Core::Signal.new(value: frequency, units: :frequency, context: context)
12
+ @harmonicity = Core::Signal.new(value: harmonicity, units: :number, context: context)
13
+ @modulation_index = Core::Signal.new(value: modulation_index, units: :number, context: context)
14
+ @detune = Core::Signal.new(value: detune, units: :number, context: context)
15
+ @carrier_phase = phase.to_f % 1.0
16
+ @modulator_phase = phase.to_f % 1.0
17
+ end
18
+
19
+ def detune=(value)
20
+ @detune.value = value
21
+ end
22
+
23
+ def process(_input_buffer, num_frames, start_frame, _cache)
24
+ frequencies = @frequency.process(num_frames, start_frame)
25
+ harmonicities = @harmonicity.process(num_frames, start_frame)
26
+ modulation_indices = @modulation_index.process(num_frames, start_frame)
27
+ detunes = @detune.process(num_frames, start_frame)
28
+
29
+ Array.new(num_frames) do |index|
30
+ current_time = (start_frame + index).to_f / context.sample_rate
31
+ next 0.0 unless active_at?(current_time)
32
+
33
+ carrier_frequency = frequencies[index] * (2.0**(detunes[index].to_f / 1200.0))
34
+ modulator_frequency = carrier_frequency * harmonicities[index]
35
+ modulation = Math.sin(2.0 * Math::PI * @modulator_phase) * modulation_indices[index]
36
+ sample = Math.sin((2.0 * Math::PI * @carrier_phase) + modulation)
37
+
38
+ @carrier_phase = (@carrier_phase + (carrier_frequency / context.sample_rate)) % 1.0
39
+ @modulator_phase = (@modulator_phase + (modulator_frequency / context.sample_rate)) % 1.0
40
+ sample
41
+ end
42
+ end
43
+
44
+ alias modulationIndex modulation_index
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class GrainPlayer < Player
6
+ attr_reader :detune
7
+ attr_accessor :grain_size, :overlap, :jitter
8
+
9
+ def initialize(grain_size: 0.05, overlap: 0.5, jitter: 0.002, detune: 0.0, **options)
10
+ super(**options)
11
+ @grain_size = grain_size.to_f
12
+ @overlap = overlap.to_f
13
+ @jitter = jitter.to_f
14
+ @detune = Core::Signal.new(value: detune, units: :number, context: context)
15
+ end
16
+
17
+ def detune=(value)
18
+ @detune.value = value
19
+ end
20
+
21
+ def process(_input_buffer, num_frames, start_frame, _cache)
22
+ rates = @playback_rate.process(num_frames, start_frame)
23
+ detunes = @detune.process(num_frames, start_frame)
24
+ grain_duration = grain_duration_seconds
25
+ overlap_duration = overlap_duration_seconds(grain_duration)
26
+ grain_interval = grain_interval_seconds(grain_duration, overlap_duration)
27
+ active_grains = active_grain_count(grain_duration, grain_interval)
28
+ return process_multichannel_buffer(num_frames, start_frame, rates, detunes, grain_duration, overlap_duration, grain_interval, active_grains) if multichannel_process?
29
+
30
+ Array.new(num_frames) do |index|
31
+ current_time = (start_frame + index).to_f / context.sample_rate
32
+ notify_stop(current_time) if @stop_time && current_time >= @stop_time
33
+ next 0.0 unless active_at?(current_time)
34
+
35
+ rate = positive_rate(rates[index])
36
+ output = granular_sample(
37
+ current_time,
38
+ playback_rate: rate,
39
+ detune_ratio: detune_ratio(detunes[index]),
40
+ grain_duration: grain_duration,
41
+ overlap_duration: overlap_duration,
42
+ grain_interval: grain_interval,
43
+ active_grains: active_grains
44
+ )
45
+
46
+ if naturally_finished?(current_time, rate, grain_duration)
47
+ @stop_time ||= current_time
48
+ notify_stop(current_time)
49
+ next 0.0
50
+ end
51
+
52
+ output * envelope_gain(current_time)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def process_multichannel_buffer(num_frames, start_frame, rates, detunes, grain_duration, overlap_duration, grain_interval, active_grains)
59
+ output = Array.new(@buffer.channels) { Array.new(num_frames, 0.0) }
60
+
61
+ num_frames.times do |index|
62
+ current_time = (start_frame + index).to_f / context.sample_rate
63
+ notify_stop(current_time) if @stop_time && current_time >= @stop_time
64
+ next unless active_at?(current_time)
65
+
66
+ rate = positive_rate(rates[index])
67
+ detune = detune_ratio(detunes[index])
68
+
69
+ @buffer.channels.times do |channel_index|
70
+ output[channel_index][index] = granular_sample(
71
+ current_time,
72
+ playback_rate: rate,
73
+ detune_ratio: detune,
74
+ grain_duration: grain_duration,
75
+ overlap_duration: overlap_duration,
76
+ grain_interval: grain_interval,
77
+ active_grains: active_grains,
78
+ channel_index: channel_index
79
+ )
80
+ end
81
+
82
+ if naturally_finished?(current_time, rate, grain_duration)
83
+ @stop_time ||= current_time
84
+ notify_stop(current_time)
85
+ else
86
+ gain = envelope_gain(current_time)
87
+ @buffer.channels.times do |channel_index|
88
+ output[channel_index][index] *= gain
89
+ end
90
+ end
91
+ end
92
+
93
+ Core::AudioBlock.from_channel_data(output)
94
+ end
95
+
96
+ def granular_sample(current_time, playback_rate:, detune_ratio:, grain_duration:, overlap_duration:, grain_interval:,
97
+ active_grains:, channel_index: nil)
98
+ current_grain = grain_index_at(current_time, grain_interval)
99
+ first_grain = [current_grain - active_grains, 0].max
100
+
101
+ (first_grain..current_grain).sum(0.0) do |grain_index|
102
+ grain_elapsed = current_time - grain_start_time(grain_index, grain_interval)
103
+ next 0.0 if grain_elapsed.negative? || grain_elapsed >= grain_duration
104
+
105
+ logical_position = grain_logical_position(grain_index, grain_elapsed, playback_rate, detune_ratio, grain_interval)
106
+ sample_position = resolve_buffer_position(logical_position)
107
+ next 0.0 if sample_position.negative?
108
+
109
+ sample =
110
+ if channel_index.nil?
111
+ @buffer.sample_at(sample_position)
112
+ else
113
+ @buffer.sample_at(sample_position, channel_index)
114
+ end
115
+ sample * grain_window_gain(grain_elapsed, grain_duration, overlap_duration)
116
+ end
117
+ end
118
+
119
+ def grain_duration_seconds
120
+ [@grain_size.to_f, context.sample_time].max
121
+ end
122
+
123
+ def overlap_duration_seconds(grain_duration)
124
+ @overlap.to_f.clamp(0.0, grain_duration)
125
+ end
126
+
127
+ def grain_interval_seconds(grain_duration, overlap_duration)
128
+ [grain_duration - overlap_duration, context.sample_time].max
129
+ end
130
+
131
+ def active_grain_count(grain_duration, grain_interval)
132
+ [(grain_duration / grain_interval).ceil + 1, 1].max
133
+ end
134
+
135
+ def grain_index_at(current_time, grain_interval)
136
+ ((current_time - @start_time) / grain_interval).floor
137
+ end
138
+
139
+ def grain_start_time(grain_index, grain_interval)
140
+ @start_time + (grain_index * grain_interval)
141
+ end
142
+
143
+ def grain_logical_position(grain_index, grain_elapsed, playback_rate, detune_ratio, grain_interval)
144
+ anchor = @seek_position + grain_anchor_offset(grain_index, playback_rate, grain_interval)
145
+ anchor + grain_jitter_offset(grain_index) + (grain_elapsed * @buffer.sample_rate * detune_ratio)
146
+ end
147
+
148
+ def grain_anchor_offset(grain_index, playback_rate, grain_interval)
149
+ grain_index * grain_interval * @buffer.sample_rate * playback_rate
150
+ end
151
+
152
+ def grain_jitter_offset(grain_index)
153
+ return 0.0 if @jitter.zero?
154
+
155
+ ((grain_random(grain_index) * 2.0) - 1.0) * @jitter * @buffer.sample_rate
156
+ end
157
+
158
+ def grain_random(grain_index)
159
+ Math.sin((grain_index + 1) * 12_989.0).abs % 1.0
160
+ end
161
+
162
+ def grain_window_gain(grain_elapsed, grain_duration, overlap_duration)
163
+ return 1.0 if overlap_duration <= 0.0
164
+
165
+ fade_in = (grain_elapsed / overlap_duration).clamp(0.0, 1.0)
166
+ fade_out = ((grain_duration - grain_elapsed) / overlap_duration).clamp(0.0, 1.0)
167
+ [fade_in, fade_out].min
168
+ end
169
+
170
+ def naturally_finished?(current_time, playback_rate, grain_duration)
171
+ return false if @loop
172
+ return false unless playback_rate.positive?
173
+
174
+ current_time >= natural_stop_time(playback_rate, grain_duration)
175
+ end
176
+
177
+ def natural_stop_time(playback_rate, grain_duration)
178
+ @start_time + (playable_frame_span / (@buffer.sample_rate * playback_rate)) + grain_duration
179
+ end
180
+
181
+ def playable_frame_span
182
+ return [@buffer.frames - @seek_position, 0.0].max unless @reverse
183
+
184
+ min_position = @loop_start * @buffer.sample_rate
185
+ max_position = loop_end_frame || (@buffer.frames - 1)
186
+ [max_position - min_position - @seek_position, 0.0].max
187
+ end
188
+
189
+ def positive_rate(value)
190
+ [value.to_f.abs, Float::EPSILON].max
191
+ end
192
+
193
+ def detune_ratio(cents)
194
+ 2.0**(cents.to_f / 1200.0)
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class KarplusStrong < Core::Source
6
+ def initialize(decay: 0.995, damping: 0.5, context: Deftones.context)
7
+ super(context: context)
8
+ @decay = decay.to_f
9
+ @damping = damping.to_f
10
+ @events = []
11
+ @buffer = []
12
+ @buffer_index = 0
13
+ end
14
+
15
+ def trigger(note, time = nil, velocity = 1.0)
16
+ @events << {
17
+ time: resolve_time(time),
18
+ frequency: Deftones::Music::Note.to_frequency(note),
19
+ velocity: velocity.to_f
20
+ }
21
+ @events.sort_by! { |event| event[:time] }
22
+ self
23
+ end
24
+
25
+ def process(_input_buffer, num_frames, start_frame, _cache)
26
+ Array.new(num_frames) do |index|
27
+ time = (start_frame + index).to_f / context.sample_rate
28
+ consume_events(time)
29
+ next 0.0 if @buffer.empty?
30
+
31
+ current = @buffer[@buffer_index]
32
+ following = @buffer[(@buffer_index + 1) % @buffer.length]
33
+ @buffer[@buffer_index] = ((current + following) * 0.5 * @decay) + ((following - current) * @damping * 0.01)
34
+ @buffer_index = (@buffer_index + 1) % @buffer.length
35
+ current
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def consume_events(time)
42
+ while @events.any? && @events.first[:time] <= time
43
+ event = @events.shift
44
+ delay_length = [1, (context.sample_rate / event[:frequency]).round].max
45
+ @buffer = Array.new(delay_length) { ((rand * 2.0) - 1.0) * event[:velocity] }
46
+ @buffer_index = 0
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class Noise < Core::Source
6
+ TYPES = %i[white pink brown].freeze
7
+
8
+ attr_accessor :type, :fade_in, :fade_out
9
+ attr_reader :playback_rate
10
+
11
+ def initialize(type: :white, playback_rate: 1.0, fade_in: 0.0, fade_out: 0.0, context: Deftones.context)
12
+ super(context: context)
13
+ @type = normalize_type(type)
14
+ @playback_rate = playback_rate.to_f
15
+ @fade_in = fade_in.to_f
16
+ @fade_out = fade_out.to_f
17
+ @pink_state = 0.0
18
+ @brown_state = 0.0
19
+ @held_sample = next_noise_sample
20
+ @playback_phase = 0.0
21
+ end
22
+
23
+ def playback_rate=(value)
24
+ @playback_rate = value.to_f
25
+ end
26
+
27
+ def process(_input_buffer, num_frames, start_frame, _cache)
28
+ Array.new(num_frames) do |index|
29
+ current_time = (start_frame + index).to_f / context.sample_rate
30
+ next 0.0 unless active_at?(current_time)
31
+
32
+ next_sample * envelope_gain(current_time)
33
+ end
34
+ end
35
+
36
+ alias fadeIn fade_in
37
+ alias fadeIn= fade_in=
38
+ alias fadeOut fade_out
39
+ alias fadeOut= fade_out=
40
+
41
+ private
42
+
43
+ def next_sample
44
+ sample = @held_sample
45
+ advance_playback
46
+ sample
47
+ end
48
+
49
+ def advance_playback
50
+ @playback_phase += [@playback_rate, 1.0e-6].max
51
+ steps = @playback_phase.floor
52
+ return if steps <= 0
53
+
54
+ steps.times { @held_sample = next_noise_sample }
55
+ @playback_phase -= steps
56
+ end
57
+
58
+ def envelope_gain(current_time)
59
+ fade_in_gain(current_time) * fade_out_gain(current_time)
60
+ end
61
+
62
+ def fade_in_gain(current_time)
63
+ return 1.0 if @fade_in <= 0.0
64
+
65
+ ((current_time - @start_time) / @fade_in).clamp(0.0, 1.0)
66
+ end
67
+
68
+ def fade_out_gain(current_time)
69
+ return 1.0 unless @stop_time && @fade_out > 0.0
70
+
71
+ ((@stop_time - current_time) / @fade_out).clamp(0.0, 1.0)
72
+ end
73
+
74
+ def next_noise_sample
75
+ white = (rand * 2.0) - 1.0
76
+
77
+ case normalize_type(@type)
78
+ when :white
79
+ white
80
+ when :pink
81
+ @pink_state = (0.98 * @pink_state) + (0.02 * white)
82
+ @pink_state * 3.5
83
+ when :brown
84
+ @brown_state = Deftones::DSP::Helpers.clamp(@brown_state + (white * 0.02), -1.0, 1.0)
85
+ end
86
+ end
87
+
88
+ def normalize_type(type)
89
+ normalized = type.to_sym
90
+ return normalized if TYPES.include?(normalized)
91
+
92
+ raise ArgumentError, "Unsupported noise type: #{type}"
93
+ end
94
+
95
+ alias playbackRate playback_rate
96
+ alias playbackRate= playback_rate=
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class OmniOscillator < Core::Source
6
+ SNAPSHOT_OPTION_KEYS = %i[
7
+ count
8
+ detune
9
+ frequency
10
+ harmonicity
11
+ modulation_depth
12
+ modulation_frequency
13
+ modulation_index
14
+ phase
15
+ spread
16
+ type
17
+ width
18
+ ].freeze
19
+
20
+ TYPE_MAP = {
21
+ sine: Oscillator,
22
+ square: Oscillator,
23
+ triangle: Oscillator,
24
+ sawtooth: Oscillator,
25
+ pulse: PulseOscillator,
26
+ pwm: PWMOscillator,
27
+ fm: FMOscillator,
28
+ am: AMOscillator,
29
+ fat: FatOscillator
30
+ }.freeze
31
+
32
+ attr_reader :source
33
+ attr_accessor :type
34
+
35
+ def initialize(type: :sine, context: Deftones.context, **options)
36
+ super(context: context)
37
+ @type = type.to_sym
38
+ @options = options
39
+ rebuild_source
40
+ end
41
+
42
+ def type=(value)
43
+ @type = value.to_sym
44
+ rebuild_source
45
+ end
46
+
47
+ def frequency
48
+ @source.frequency if @source.respond_to?(:frequency)
49
+ end
50
+
51
+ def start(time = nil)
52
+ @source.start(time)
53
+ self
54
+ end
55
+
56
+ def stop(time = nil)
57
+ @source.stop(time)
58
+ self
59
+ end
60
+
61
+ def restart(time = nil)
62
+ @source.restart(time)
63
+ self
64
+ end
65
+
66
+ def cancel_stop
67
+ @source.cancel_stop
68
+ self
69
+ end
70
+
71
+ def sync
72
+ @source.sync
73
+ self
74
+ end
75
+
76
+ def unsync
77
+ @source.unsync
78
+ self
79
+ end
80
+
81
+ def synced?
82
+ @source.synced?
83
+ end
84
+
85
+ def state(time = context.current_time)
86
+ @source.state(time)
87
+ end
88
+
89
+ def onstop
90
+ @source.onstop
91
+ end
92
+
93
+ def onstop=(callback)
94
+ @source.onstop = callback
95
+ end
96
+
97
+ def process(_input_buffer, num_frames, start_frame, cache)
98
+ @source.render(num_frames, start_frame, cache)
99
+ end
100
+
101
+ alias cancelStop cancel_stop
102
+
103
+ def method_missing(method_name, *arguments, &block)
104
+ return super unless @source.respond_to?(method_name)
105
+
106
+ @source.public_send(method_name, *arguments, &block)
107
+ end
108
+
109
+ def respond_to_missing?(method_name, include_private = false)
110
+ @source.respond_to?(method_name, include_private) || super
111
+ end
112
+
113
+ private
114
+
115
+ def rebuild_source
116
+ source_class = TYPE_MAP.fetch(@type) { Oscillator }
117
+ snapshot = snapshot_source(@source)
118
+ source_options = build_source_options(source_class, snapshot.fetch(:options, {}))
119
+ @source = source_class.new(**source_options)
120
+ apply_snapshot(@source, snapshot)
121
+ end
122
+
123
+ def build_source_options(source_class, snapshot_options)
124
+ allowed_keys = source_class.instance_method(:initialize).parameters.filter_map do |kind, name|
125
+ name if %i[key keyreq].include?(kind)
126
+ end
127
+
128
+ @options
129
+ .merge(snapshot_options)
130
+ .merge(context: context)
131
+ .then do |options|
132
+ options[:type] = @type if source_class == Oscillator
133
+ options.select { |key, _value| allowed_keys.include?(key) }
134
+ end
135
+ end
136
+
137
+ def snapshot_source(source)
138
+ return { options: {} } unless source
139
+
140
+ {
141
+ options: snapshot_source_options(source),
142
+ mute: source.respond_to?(:mute?) ? source.mute? : false,
143
+ onstop: source.respond_to?(:onstop) ? source.onstop : nil,
144
+ start_time: source.instance_variable_get(:@start_time),
145
+ stop_notified: source.instance_variable_get(:@stop_notified),
146
+ stop_time: source.instance_variable_get(:@stop_time),
147
+ synced: source.respond_to?(:synced?) ? source.synced? : false,
148
+ volume: source.respond_to?(:volume) ? source.volume.value : 0.0
149
+ }
150
+ end
151
+
152
+ def snapshot_source_options(source)
153
+ SNAPSHOT_OPTION_KEYS.each_with_object({}) do |key, options|
154
+ next unless source.respond_to?(key)
155
+
156
+ value = source.public_send(key)
157
+ options[key] = value.respond_to?(:value) ? value.value : value
158
+ end
159
+ end
160
+
161
+ def apply_snapshot(source, snapshot)
162
+ return source if snapshot.empty?
163
+
164
+ source.volume.value = snapshot[:volume] if source.respond_to?(:volume) && snapshot.key?(:volume)
165
+ source.mute = snapshot[:mute] if source.respond_to?(:mute=) && snapshot.key?(:mute)
166
+ source.onstop = snapshot[:onstop] if source.respond_to?(:onstop=) && snapshot.key?(:onstop)
167
+ source.instance_variable_set(:@start_time, snapshot[:start_time]) if snapshot.key?(:start_time)
168
+ source.instance_variable_set(:@stop_time, snapshot[:stop_time]) if snapshot.key?(:stop_time)
169
+ source.instance_variable_set(:@stop_notified, snapshot[:stop_notified]) if snapshot.key?(:stop_notified)
170
+ source.sync if snapshot[:synced] && source.respond_to?(:sync)
171
+ source
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class Oscillator < Core::Source
6
+ 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
+ }
19
+ }.freeze
20
+
21
+ attr_reader :frequency, :detune
22
+ attr_accessor :type
23
+
24
+ def initialize(type: :sine, frequency: 440.0, detune: 0.0, phase: 0.0, context: Deftones.context)
25
+ super(context: context)
26
+ @type = normalize_type(type)
27
+ @frequency = Core::Signal.new(value: frequency, units: :frequency, context: context)
28
+ @detune = Core::Signal.new(value: detune, units: :number, context: context)
29
+ self.phase = phase
30
+ end
31
+
32
+ def detune=(value)
33
+ @detune.value = value
34
+ end
35
+
36
+ def phase
37
+ @phase
38
+ end
39
+
40
+ def phase=(value)
41
+ @phase = value.to_f % 1.0
42
+ end
43
+
44
+ def process(_input_buffer, num_frames, start_frame, _cache)
45
+ generator = GENERATORS.fetch(normalize_type(@type))
46
+ frequencies = @frequency.process(num_frames, start_frame)
47
+ detunes = @detune.process(num_frames, start_frame)
48
+
49
+ Array.new(num_frames) do |index|
50
+ current_time = (start_frame + index).to_f / context.sample_rate
51
+ next 0.0 unless active_at?(current_time)
52
+
53
+ sample = generator.call(@phase)
54
+ frequency = frequencies[index] * detune_ratio(detunes[index])
55
+ @phase = (@phase + (frequency / context.sample_rate)) % 1.0
56
+ sample
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def detune_ratio(cents)
63
+ 2.0**(cents.to_f / 1200.0)
64
+ end
65
+
66
+ def normalize_type(type)
67
+ normalized = type.to_sym
68
+ return normalized if GENERATORS.key?(normalized)
69
+
70
+ raise ArgumentError, "Unsupported oscillator type: #{type}"
71
+ end
72
+ end
73
+ end
74
+ end