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,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Music
5
+ class Ticks
6
+ attr_reader :value, :transport
7
+
8
+ def initialize(value, transport: Deftones.transport)
9
+ @value = value
10
+ @transport = transport
11
+ @disposed = false
12
+ end
13
+
14
+ def to_i
15
+ self.class.parse(
16
+ value,
17
+ bpm: transport.bpm,
18
+ time_signature: transport.time_signature,
19
+ ppq: transport.ppq
20
+ ).round
21
+ end
22
+
23
+ def to_seconds
24
+ transport.ticks_to_seconds(to_i)
25
+ end
26
+
27
+ def to_ticks
28
+ to_i
29
+ end
30
+
31
+ def to_bars_beats_sixteenths
32
+ transport.seconds_to_position(to_seconds)
33
+ end
34
+
35
+ def to_frequency
36
+ 1.0 / [to_seconds, 1.0e-6].max
37
+ end
38
+
39
+ def to_midi
40
+ Note.to_midi(Note.from_frequency(to_frequency))
41
+ end
42
+
43
+ def to_milliseconds
44
+ to_seconds * 1000.0
45
+ end
46
+
47
+ def to_samples(sample_rate = Deftones.context.sample_rate)
48
+ UnitHelpers.samples_for_seconds(to_seconds, sample_rate)
49
+ end
50
+
51
+ def to_notation
52
+ UnitHelpers.closest_notation(to_seconds, transport: transport)
53
+ end
54
+
55
+ def quantize(subdiv, percent = 1.0)
56
+ quantized_seconds = UnitHelpers.quantize_seconds(to_seconds, subdiv, transport: transport, percent: percent)
57
+ transport.seconds_to_ticks(quantized_seconds).round
58
+ end
59
+
60
+ def from_type(type)
61
+ @value =
62
+ if type.respond_to?(:to_ticks)
63
+ type.to_ticks
64
+ elsif type.respond_to?(:value_of)
65
+ type.value_of
66
+ else
67
+ type
68
+ end
69
+ self
70
+ end
71
+
72
+ def dispose
73
+ @disposed = true
74
+ self
75
+ end
76
+
77
+ def disposed?
78
+ @disposed
79
+ end
80
+
81
+ def to_s
82
+ value.to_s
83
+ end
84
+
85
+ alias toString to_s
86
+
87
+ def value_of
88
+ to_i
89
+ end
90
+
91
+ class << self
92
+ def parse(value, bpm: Deftones.transport.bpm, time_signature: Deftones.transport.time_signature,
93
+ ppq: Deftones.transport.ppq)
94
+ return value.to_f if value.is_a?(Numeric)
95
+
96
+ string_value = value.to_s
97
+ direct_ticks = string_value.match(/\A(-?\d+(?:\.\d+)?)i\z/i)
98
+ return direct_ticks[1].to_f if direct_ticks
99
+
100
+ seconds = Time.parse(value, bpm: bpm, time_signature: time_signature, ppq: ppq)
101
+ (seconds / (60.0 / bpm.to_f)) * ppq.to_f
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Music
5
+ class Time
6
+ attr_reader :value, :transport
7
+
8
+ def initialize(value, transport: Deftones.transport)
9
+ @value = value
10
+ @transport = transport
11
+ @disposed = false
12
+ end
13
+
14
+ def to_seconds
15
+ self.class.parse(
16
+ value,
17
+ bpm: transport.bpm,
18
+ time_signature: transport.time_signature,
19
+ ppq: transport.ppq
20
+ )
21
+ end
22
+
23
+ def to_ticks
24
+ transport.seconds_to_ticks(to_seconds)
25
+ end
26
+
27
+ def to_bars_beats_sixteenths
28
+ transport.seconds_to_position(to_seconds)
29
+ end
30
+
31
+ def to_frequency
32
+ 1.0 / [to_seconds, 1.0e-6].max
33
+ end
34
+
35
+ def to_midi
36
+ Note.to_midi(Note.from_frequency(to_frequency))
37
+ end
38
+
39
+ def to_milliseconds
40
+ to_seconds * 1000.0
41
+ end
42
+
43
+ def to_samples(sample_rate = Deftones.context.sample_rate)
44
+ UnitHelpers.samples_for_seconds(to_seconds, sample_rate)
45
+ end
46
+
47
+ def to_notation
48
+ UnitHelpers.closest_notation(to_seconds, transport: transport)
49
+ end
50
+
51
+ def quantize(subdiv, percent = 1.0)
52
+ UnitHelpers.quantize_seconds(to_seconds, subdiv, transport: transport, percent: percent)
53
+ end
54
+
55
+ def from_type(type)
56
+ @value = type.respond_to?(:value_of) ? type.value_of : type
57
+ self
58
+ end
59
+
60
+ def dispose
61
+ @disposed = true
62
+ self
63
+ end
64
+
65
+ def disposed?
66
+ @disposed
67
+ end
68
+
69
+ def to_s
70
+ value.to_s
71
+ end
72
+
73
+ alias toString to_s
74
+
75
+ def value_of
76
+ to_seconds
77
+ end
78
+
79
+ SYMBOL_MAP = {
80
+ whole: "1n",
81
+ half: "2n",
82
+ quarter: "4n",
83
+ eighth: "8n",
84
+ sixteenth: "16n",
85
+ quarter_triplet: "4t",
86
+ eighth_triplet: "8t",
87
+ dotted_quarter: "4n.",
88
+ dotted_eighth: "8n.",
89
+ measure: "1m"
90
+ }.freeze
91
+
92
+ class << self
93
+ def parse(value, bpm: Deftones.transport.bpm, time_signature: Deftones.transport.time_signature,
94
+ ppq: Deftones.transport.ppq)
95
+ case value
96
+ when Numeric
97
+ value.to_f
98
+ when Symbol
99
+ parse(SYMBOL_MAP.fetch(value), bpm: bpm, time_signature: time_signature, ppq: ppq)
100
+ when String
101
+ return evaluate_expression(value, bpm: bpm, time_signature: time_signature, ppq: ppq) if arithmetic_expression?(value)
102
+
103
+ parse_literal(value, bpm: bpm, time_signature: time_signature, ppq: ppq)
104
+ when /\A(\d+)n\z/
105
+ parse_literal(value, bpm: bpm, time_signature: time_signature, ppq: ppq)
106
+ else
107
+ return value.to_seconds if value.respond_to?(:to_seconds)
108
+ end
109
+ rescue KeyError, ArgumentError
110
+ raise ArgumentError, "Unknown time format: #{value}"
111
+ end
112
+
113
+ private
114
+
115
+ def parse_literal(value, bpm:, time_signature:, ppq:)
116
+ case value
117
+ when /\A(\d+)n\z/
118
+ beats = 4.0 / Regexp.last_match(1).to_f
119
+ beats * beat_duration(bpm)
120
+ when /\A(\d+)t\z/
121
+ beats = (4.0 / Regexp.last_match(1).to_f) * (2.0 / 3.0)
122
+ beats * beat_duration(bpm)
123
+ when /\A(\d+)n\.\z/
124
+ parse("#{Regexp.last_match(1)}n", bpm: bpm, time_signature: time_signature, ppq: ppq) * 1.5
125
+ when /\A(\d+)m\z/i
126
+ measures = Regexp.last_match(1).to_f
127
+ beats_per_measure = Array(time_signature).first || 4
128
+ measures * beats_per_measure * beat_duration(bpm)
129
+ when /\A(\d+):(\d+):(\d+)\z/
130
+ bars = Regexp.last_match(1).to_f
131
+ beats = Regexp.last_match(2).to_f
132
+ sixteenths = Regexp.last_match(3).to_f
133
+ beats_per_measure = Array(time_signature).first || 4
134
+ ((bars * beats_per_measure) + beats + (sixteenths * 0.25)) * beat_duration(bpm)
135
+ when /\A(\d+(?:\.\d+)?)hz\z/i
136
+ 1.0 / Regexp.last_match(1).to_f
137
+ when /\A(-?\d+(?:\.\d+)?)i\z/i
138
+ (Regexp.last_match(1).to_f / ppq.to_f) * beat_duration(bpm)
139
+ else
140
+ Float(value)
141
+ end
142
+ end
143
+
144
+ def arithmetic_expression?(value)
145
+ value.match?(/[+\-*\/()]/) && !value.match?(/\A-?\d+(?:\.\d+)?\z/)
146
+ end
147
+
148
+ def beat_duration(bpm)
149
+ 60.0 / bpm.to_f
150
+ end
151
+
152
+ def evaluate_expression(value, bpm:, time_signature:, ppq:)
153
+ compute_rpn(to_rpn(tokenize(value)), bpm: bpm, time_signature: time_signature, ppq: ppq)
154
+ end
155
+
156
+ def tokenize(expression)
157
+ expression.scan(/\d+:\d+:\d+|\d+(?:\.\d+)?hz|-?\d+(?:\.\d+)?i|\d+n\.?|\d+t|\d+m|[()+\-*\/]|\d+(?:\.\d+)?/)
158
+ end
159
+
160
+ def to_rpn(tokens)
161
+ output = []
162
+ operators = []
163
+
164
+ tokens.each do |token|
165
+ if operator?(token)
166
+ while operators.any? && operator?(operators.last) && precedence(operators.last) >= precedence(token)
167
+ output << operators.pop
168
+ end
169
+ operators << token
170
+ elsif token == "("
171
+ operators << token
172
+ elsif token == ")"
173
+ output << operators.pop until operators.last == "("
174
+ operators.pop
175
+ else
176
+ output << token
177
+ end
178
+ end
179
+
180
+ output.concat(operators.reverse)
181
+ end
182
+
183
+ def compute_rpn(tokens, bpm:, time_signature:, ppq:)
184
+ stack = []
185
+
186
+ tokens.each do |token|
187
+ if operator?(token)
188
+ right = stack.pop
189
+ left = stack.pop
190
+ stack << left.public_send(token, right)
191
+ else
192
+ stack << parse_literal(token, bpm: bpm, time_signature: time_signature, ppq: ppq)
193
+ end
194
+ end
195
+
196
+ stack.first
197
+ end
198
+
199
+ def operator?(token)
200
+ %w[+ - * /].include?(token)
201
+ end
202
+
203
+ def precedence(token)
204
+ %w[* /].include?(token) ? 2 : 1
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Music
5
+ class TransportTime
6
+ attr_reader :value, :transport
7
+
8
+ def initialize(value, transport: Deftones.transport)
9
+ @value = value
10
+ @transport = transport
11
+ @disposed = false
12
+ end
13
+
14
+ def to_seconds
15
+ self.class.parse(
16
+ value,
17
+ bpm: transport.bpm,
18
+ time_signature: transport.time_signature,
19
+ ppq: transport.ppq
20
+ )
21
+ end
22
+
23
+ def to_ticks
24
+ transport.seconds_to_ticks(to_seconds)
25
+ end
26
+
27
+ def to_bars_beats_sixteenths
28
+ transport.seconds_to_position(to_seconds)
29
+ end
30
+
31
+ def to_frequency
32
+ 1.0 / [to_seconds, 1.0e-6].max
33
+ end
34
+
35
+ def to_midi
36
+ Note.to_midi(Note.from_frequency(to_frequency))
37
+ end
38
+
39
+ def to_milliseconds
40
+ to_seconds * 1000.0
41
+ end
42
+
43
+ def to_samples(sample_rate = Deftones.context.sample_rate)
44
+ UnitHelpers.samples_for_seconds(to_seconds, sample_rate)
45
+ end
46
+
47
+ def to_notation
48
+ UnitHelpers.closest_notation(to_seconds, transport: transport)
49
+ end
50
+
51
+ def quantize(subdiv, percent = 1.0)
52
+ UnitHelpers.quantize_seconds(to_seconds, subdiv, transport: transport, percent: percent)
53
+ end
54
+
55
+ def from_type(type)
56
+ @value =
57
+ if type.respond_to?(:to_bars_beats_sixteenths)
58
+ type.to_bars_beats_sixteenths
59
+ elsif type.respond_to?(:value_of)
60
+ type.value_of
61
+ else
62
+ type
63
+ end
64
+ self
65
+ end
66
+
67
+ def dispose
68
+ @disposed = true
69
+ self
70
+ end
71
+
72
+ def disposed?
73
+ @disposed
74
+ end
75
+
76
+ def to_s
77
+ value.to_s
78
+ end
79
+
80
+ alias toString to_s
81
+
82
+ def value_of
83
+ to_seconds
84
+ end
85
+
86
+ class << self
87
+ def parse(value, bpm: Deftones.transport.bpm, time_signature: Deftones.transport.time_signature,
88
+ ppq: Deftones.transport.ppq)
89
+ Time.parse(value, bpm: bpm, time_signature: time_signature, ppq: ppq)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Music
5
+ module UnitHelpers
6
+ NOTATION_CANDIDATES = %w[1m 1n 2n. 2n 4n. 4n 8n. 8n 8t 16n. 16n 16t 32n 64n 128n].freeze
7
+
8
+ class << self
9
+ def closest_notation(seconds, transport:)
10
+ NOTATION_CANDIDATES.min_by do |notation|
11
+ duration = Time.parse(notation, bpm: transport.bpm, time_signature: transport.time_signature, ppq: transport.ppq)
12
+ (duration - seconds).abs
13
+ end
14
+ end
15
+
16
+ def quantize_seconds(seconds, subdivision, transport:, percent: 1.0)
17
+ quantum = Time.parse(subdivision, bpm: transport.bpm, time_signature: transport.time_signature, ppq: transport.ppq)
18
+ return seconds unless quantum.positive?
19
+
20
+ target = (seconds / quantum).round * quantum
21
+ seconds + ((target - seconds) * percent.to_f.clamp(0.0, 1.0))
22
+ end
23
+
24
+ def samples_for_seconds(seconds, sample_rate)
25
+ (seconds * sample_rate.to_f).round
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ class OfflineContext < Context
5
+ attr_reader :channels, :duration, :total_frames
6
+
7
+ def initialize(duration:, channels: 2, sample_rate: DEFAULT_SAMPLE_RATE,
8
+ buffer_size: DEFAULT_BUFFER_SIZE)
9
+ super(sample_rate: sample_rate, buffer_size: buffer_size, channels: channels, autostart: false)
10
+ @duration = duration.to_f
11
+ @total_frames = (@duration * sample_rate).ceil
12
+ end
13
+
14
+ def current_time
15
+ 0.0
16
+ end
17
+
18
+ def state
19
+ "suspended"
20
+ end
21
+
22
+ def render
23
+ Deftones.transport.prepare_render(@duration)
24
+ Deftones.draw.prepare_render(@duration)
25
+ samples = Array.new(@total_frames * @channels, 0.0)
26
+ frames_processed = 0
27
+
28
+ while frames_processed < @total_frames
29
+ chunk_frames = [buffer_size, @total_frames - frames_processed].min
30
+ interleaved = render_block_frames(chunk_frames, frames_processed).fit_channels(@channels).interleaved
31
+ start_index = frames_processed * @channels
32
+
33
+ samples[start_index, interleaved.length] = interleaved
34
+ frames_processed += chunk_frames
35
+ end
36
+
37
+ IO::Buffer.new(samples, channels: @channels, sample_rate: sample_rate)
38
+ end
39
+
40
+ def render_to_file(path, format: nil)
41
+ rendered_buffer = render
42
+ rendered_buffer.save(path, format: format)
43
+ rendered_buffer
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+
5
+ module Deftones
6
+ module PortAudioSupport
7
+ class << self
8
+ def available?
9
+ !!defined?(PortAudio)
10
+ end
11
+
12
+ def acquire!
13
+ raise Deftones::MissingRealtimeBackendError, "PortAudio backend is unavailable" unless available?
14
+
15
+ PortAudio.init
16
+ rescue StandardError => error
17
+ raise Deftones::MissingRealtimeBackendError, error.message
18
+ end
19
+
20
+ def release
21
+ return unless available?
22
+
23
+ PortAudio.terminate
24
+ rescue StandardError
25
+ nil
26
+ end
27
+
28
+ def output_parameters(channels)
29
+ build_stream_parameters(direction: :output, channels: channels)
30
+ end
31
+
32
+ def input_parameters(channels, device_id: nil, label: nil)
33
+ build_stream_parameters(direction: :input, channels: channels, device_id: device_id, label: label)
34
+ end
35
+
36
+ def check_error!(result, fallback: nil)
37
+ PortAudio.check_error!(result)
38
+ rescue StandardError => error
39
+ raise Deftones::MissingRealtimeBackendError, fallback || error.message
40
+ end
41
+
42
+ private
43
+
44
+ def build_stream_parameters(direction:, channels:, device_id: nil, label: nil)
45
+ device =
46
+ resolve_device(direction: direction, device_id: device_id, label: label)
47
+
48
+ raise Deftones::MissingRealtimeBackendError, "No default #{direction} device available" unless device
49
+
50
+ {
51
+ device: device,
52
+ channels: channels,
53
+ format: :float32,
54
+ latency: suggested_latency(device, direction)
55
+ }
56
+ rescue StandardError => error
57
+ raise Deftones::MissingRealtimeBackendError, error.message
58
+ end
59
+
60
+ def resolve_device(direction:, device_id:, label:)
61
+ return default_device(direction) if device_id.nil? && label.nil?
62
+
63
+ devices =
64
+ if PortAudio::Device.respond_to?(:all)
65
+ Array(PortAudio::Device.all)
66
+ elsif PortAudio::Device.respond_to?(:devices)
67
+ Array(PortAudio::Device.devices)
68
+ else
69
+ []
70
+ end
71
+
72
+ matched = devices.find do |device|
73
+ matches_device_id?(device, device_id) || matches_device_label?(device, label)
74
+ end
75
+ return matched if matched
76
+
77
+ raise Deftones::MissingRealtimeBackendError, "No matching #{direction} device available"
78
+ end
79
+
80
+ def default_device(direction)
81
+ case direction
82
+ when :input then PortAudio::Device.default_input
83
+ when :output then PortAudio::Device.default_output
84
+ else raise ArgumentError, "Unsupported PortAudio direction: #{direction}"
85
+ end
86
+ end
87
+
88
+ def matches_device_id?(device, device_id)
89
+ return false if device_id.nil?
90
+
91
+ candidates = []
92
+ candidates << device.device_id if device.respond_to?(:device_id)
93
+ candidates << device.index if device.respond_to?(:index)
94
+ candidates << device.device_index if device.respond_to?(:device_index)
95
+ candidates.compact.any? { |candidate| candidate.to_s == device_id.to_s }
96
+ end
97
+
98
+ def matches_device_label?(device, label)
99
+ return false if label.nil?
100
+
101
+ candidates = []
102
+ candidates << device.label if device.respond_to?(:label)
103
+ candidates << device.name if device.respond_to?(:name)
104
+ candidates.compact.any? { |candidate| candidate.to_s == label.to_s }
105
+ end
106
+
107
+ def suggested_latency(device, direction)
108
+ direction == :input ? device.default_low_input_latency : device.default_low_output_latency
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class AMOscillator < Core::Source
6
+ attr_reader :frequency, :harmonicity, :detune
7
+
8
+ def initialize(frequency: 440.0, harmonicity: 2.0, 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
+ @harmonicity = Core::Signal.new(value: harmonicity, units: :number, context: context)
12
+ @detune = Core::Signal.new(value: detune, units: :number, context: context)
13
+ @carrier_phase = phase.to_f % 1.0
14
+ @modulator_phase = phase.to_f % 1.0
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
+ frequencies = @frequency.process(num_frames, start_frame)
23
+ harmonicities = @harmonicity.process(num_frames, start_frame)
24
+ detunes = @detune.process(num_frames, start_frame)
25
+
26
+ Array.new(num_frames) do |index|
27
+ current_time = (start_frame + index).to_f / context.sample_rate
28
+ next 0.0 unless active_at?(current_time)
29
+
30
+ carrier_frequency = frequencies[index] * (2.0**(detunes[index].to_f / 1200.0))
31
+ modulator_frequency = carrier_frequency * harmonicities[index]
32
+ carrier = Math.sin(2.0 * Math::PI * @carrier_phase)
33
+ modulator = (Math.sin(2.0 * Math::PI * @modulator_phase) + 1.0) * 0.5
34
+
35
+ @carrier_phase = (@carrier_phase + (carrier_frequency / context.sample_rate)) % 1.0
36
+ @modulator_phase = (@modulator_phase + (modulator_frequency / context.sample_rate)) % 1.0
37
+ carrier * modulator
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Source
5
+ class FatOscillator < Core::Source
6
+ attr_reader :frequency
7
+ attr_accessor :count, :spread, :type
8
+
9
+ def initialize(type: :sawtooth, frequency: 440.0, count: 3, spread: 20.0, context: Deftones.context)
10
+ super(context: context)
11
+ @type = type.to_sym
12
+ @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)
16
+ end
17
+
18
+ def process(_input_buffer, num_frames, start_frame, _cache)
19
+ frequencies = @frequency.process(num_frames, start_frame)
20
+ generator = Oscillator::GENERATORS.fetch(@type) { Oscillator::GENERATORS[:sawtooth] }
21
+
22
+ Array.new(num_frames) do |index|
23
+ current_time = (start_frame + index).to_f / context.sample_rate
24
+ next 0.0 unless active_at?(current_time)
25
+
26
+ detuned = detune_frequencies(frequencies[index])
27
+ 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
30
+ sample
31
+ end
32
+ samples.sum / samples.length.to_f
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def detune_frequencies(base_frequency)
39
+ offsets = Array.new(@count) do |index|
40
+ position = @count == 1 ? 0.0 : (index.to_f / (@count - 1)) - 0.5
41
+ cents = position * @spread
42
+ base_frequency * (2.0**(cents / 1200.0))
43
+ end
44
+ @phases = Array.new(@count, 0.0) if @phases.length != @count
45
+ offsets
46
+ end
47
+ end
48
+ end
49
+ end