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.
- checksums.yaml +7 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +197 -0
- data/Rakefile +8 -0
- data/examples/poly_chord.rb +14 -0
- data/examples/render_sampler.rb +16 -0
- data/examples/render_sequence.rb +13 -0
- data/examples/render_synth.rb +18 -0
- data/lib/deftones/analysis/analyser.rb +162 -0
- data/lib/deftones/analysis/dc_meter.rb +56 -0
- data/lib/deftones/analysis/fft.rb +128 -0
- data/lib/deftones/analysis/meter.rb +83 -0
- data/lib/deftones/analysis/waveform.rb +93 -0
- data/lib/deftones/component/amplitude_envelope.rb +8 -0
- data/lib/deftones/component/biquad_filter.rb +7 -0
- data/lib/deftones/component/channel.rb +109 -0
- data/lib/deftones/component/compressor.rb +64 -0
- data/lib/deftones/component/convolver.rb +99 -0
- data/lib/deftones/component/cross_fade.rb +67 -0
- data/lib/deftones/component/envelope.rb +160 -0
- data/lib/deftones/component/eq3.rb +73 -0
- data/lib/deftones/component/feedback_comb_filter.rb +75 -0
- data/lib/deftones/component/filter.rb +75 -0
- data/lib/deftones/component/follower.rb +55 -0
- data/lib/deftones/component/frequency_envelope.rb +24 -0
- data/lib/deftones/component/gate.rb +46 -0
- data/lib/deftones/component/lfo.rb +88 -0
- data/lib/deftones/component/limiter.rb +11 -0
- data/lib/deftones/component/lowpass_comb_filter.rb +43 -0
- data/lib/deftones/component/merge.rb +45 -0
- data/lib/deftones/component/mid_side_compressor.rb +43 -0
- data/lib/deftones/component/mid_side_merge.rb +53 -0
- data/lib/deftones/component/mid_side_split.rb +54 -0
- data/lib/deftones/component/mono.rb +8 -0
- data/lib/deftones/component/multiband_compressor.rb +70 -0
- data/lib/deftones/component/multiband_split.rb +133 -0
- data/lib/deftones/component/one_pole_filter.rb +71 -0
- data/lib/deftones/component/pan_vol.rb +26 -0
- data/lib/deftones/component/panner.rb +75 -0
- data/lib/deftones/component/panner3d.rb +322 -0
- data/lib/deftones/component/solo.rb +56 -0
- data/lib/deftones/component/split.rb +47 -0
- data/lib/deftones/component/volume.rb +31 -0
- data/lib/deftones/context.rb +213 -0
- data/lib/deftones/core/audio_block.rb +82 -0
- data/lib/deftones/core/audio_node.rb +262 -0
- data/lib/deftones/core/clock.rb +91 -0
- data/lib/deftones/core/computed_signal.rb +69 -0
- data/lib/deftones/core/delay.rb +44 -0
- data/lib/deftones/core/effect.rb +66 -0
- data/lib/deftones/core/emitter.rb +51 -0
- data/lib/deftones/core/gain.rb +39 -0
- data/lib/deftones/core/instrument.rb +109 -0
- data/lib/deftones/core/param.rb +31 -0
- data/lib/deftones/core/signal.rb +452 -0
- data/lib/deftones/core/signal_operator_methods.rb +73 -0
- data/lib/deftones/core/signal_operators.rb +138 -0
- data/lib/deftones/core/signal_shapers.rb +83 -0
- data/lib/deftones/core/source.rb +213 -0
- data/lib/deftones/core/synced_signal.rb +88 -0
- data/lib/deftones/destination.rb +132 -0
- data/lib/deftones/draw.rb +100 -0
- data/lib/deftones/dsp/biquad.rb +129 -0
- data/lib/deftones/dsp/delay_line.rb +41 -0
- data/lib/deftones/dsp/helpers.rb +25 -0
- data/lib/deftones/effect/auto_filter.rb +92 -0
- data/lib/deftones/effect/auto_panner.rb +57 -0
- data/lib/deftones/effect/auto_wah.rb +98 -0
- data/lib/deftones/effect/bit_crusher.rb +38 -0
- data/lib/deftones/effect/chebyshev.rb +36 -0
- data/lib/deftones/effect/chorus.rb +73 -0
- data/lib/deftones/effect/distortion.rb +22 -0
- data/lib/deftones/effect/feedback_delay.rb +38 -0
- data/lib/deftones/effect/freeverb.rb +11 -0
- data/lib/deftones/effect/frequency_shifter.rb +89 -0
- data/lib/deftones/effect/jc_reverb.rb +11 -0
- data/lib/deftones/effect/modulation_control.rb +159 -0
- data/lib/deftones/effect/phaser.rb +72 -0
- data/lib/deftones/effect/ping_pong_delay.rb +40 -0
- data/lib/deftones/effect/pitch_shift.rb +156 -0
- data/lib/deftones/effect/reverb.rb +71 -0
- data/lib/deftones/effect/stereo_widener.rb +34 -0
- data/lib/deftones/effect/tremolo.rb +52 -0
- data/lib/deftones/effect/vibrato.rb +47 -0
- data/lib/deftones/event/callback_behavior.rb +61 -0
- data/lib/deftones/event/loop.rb +53 -0
- data/lib/deftones/event/part.rb +51 -0
- data/lib/deftones/event/pattern.rb +94 -0
- data/lib/deftones/event/sequence.rb +87 -0
- data/lib/deftones/event/tone_event.rb +77 -0
- data/lib/deftones/event/transport.rb +276 -0
- data/lib/deftones/instrument/am_synth.rb +56 -0
- data/lib/deftones/instrument/duo_synth.rb +68 -0
- data/lib/deftones/instrument/fm_synth.rb +60 -0
- data/lib/deftones/instrument/membrane_synth.rb +60 -0
- data/lib/deftones/instrument/metal_synth.rb +61 -0
- data/lib/deftones/instrument/mono_synth.rb +88 -0
- data/lib/deftones/instrument/noise_synth.rb +56 -0
- data/lib/deftones/instrument/pluck_synth.rb +41 -0
- data/lib/deftones/instrument/poly_synth.rb +96 -0
- data/lib/deftones/instrument/sampler.rb +97 -0
- data/lib/deftones/instrument/synth.rb +60 -0
- data/lib/deftones/io/buffer.rb +352 -0
- data/lib/deftones/io/buffers.rb +77 -0
- data/lib/deftones/io/recorder.rb +89 -0
- data/lib/deftones/listener.rb +120 -0
- data/lib/deftones/music/frequency.rb +128 -0
- data/lib/deftones/music/midi.rb +206 -0
- data/lib/deftones/music/note.rb +58 -0
- data/lib/deftones/music/ticks.rb +106 -0
- data/lib/deftones/music/time.rb +209 -0
- data/lib/deftones/music/transport_time.rb +94 -0
- data/lib/deftones/music/unit_helpers.rb +30 -0
- data/lib/deftones/offline_context.rb +46 -0
- data/lib/deftones/portaudio_support.rb +112 -0
- data/lib/deftones/source/am_oscillator.rb +42 -0
- data/lib/deftones/source/fat_oscillator.rb +49 -0
- data/lib/deftones/source/fm_oscillator.rb +47 -0
- data/lib/deftones/source/grain_player.rb +198 -0
- data/lib/deftones/source/karplus_strong.rb +51 -0
- data/lib/deftones/source/noise.rb +99 -0
- data/lib/deftones/source/omni_oscillator.rb +175 -0
- data/lib/deftones/source/oscillator.rb +74 -0
- data/lib/deftones/source/player.rb +228 -0
- data/lib/deftones/source/players.rb +133 -0
- data/lib/deftones/source/pulse_oscillator.rb +38 -0
- data/lib/deftones/source/pwm_oscillator.rb +49 -0
- data/lib/deftones/source/tone_buffer_source.rb +136 -0
- data/lib/deftones/source/tone_oscillator_node.rb +65 -0
- data/lib/deftones/source/user_media.rb +519 -0
- data/lib/deftones/version.rb +5 -0
- data/lib/deftones.rb +542 -0
- metadata +221 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Deftones
|
|
4
|
+
module Source
|
|
5
|
+
class UserMedia < Core::Source
|
|
6
|
+
DeviceInfo = Struct.new(:device_id, :label, :group_id, :input_channels, :output_channels, keyword_init: true)
|
|
7
|
+
|
|
8
|
+
attr_reader :buffer, :provider, :device_id, :group_id, :label
|
|
9
|
+
|
|
10
|
+
def initialize(buffer: nil, provider: nil, loop: false, live: false, capture_backend: nil,
|
|
11
|
+
device_id: nil, group_id: nil, label: nil, channels: nil, context: Deftones.context)
|
|
12
|
+
super(context: context)
|
|
13
|
+
@buffer = normalize_buffer(buffer)
|
|
14
|
+
@provider = normalize_provider(provider)
|
|
15
|
+
@loop = loop
|
|
16
|
+
@live_requested = live || !capture_backend.nil?
|
|
17
|
+
@capture_backend = normalize_capture_backend(capture_backend, live, channels)
|
|
18
|
+
@device_id = device_id
|
|
19
|
+
@group_id = group_id
|
|
20
|
+
@label = label
|
|
21
|
+
@channels = normalize_channel_count(channels)
|
|
22
|
+
@sample_cursor = 0
|
|
23
|
+
@opened = false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def start(time = nil)
|
|
27
|
+
rewind
|
|
28
|
+
@capture_backend&.start
|
|
29
|
+
@opened = true
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def stop(time = nil)
|
|
34
|
+
@capture_backend&.stop
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def open(time = nil, device_id: nil, group_id: nil, label: nil)
|
|
39
|
+
@device_id = device_id unless device_id.nil?
|
|
40
|
+
@group_id = group_id unless group_id.nil?
|
|
41
|
+
@label = label unless label.nil?
|
|
42
|
+
open_capture_backend!
|
|
43
|
+
sync_capture_metadata!
|
|
44
|
+
self.class.grant_permissions!
|
|
45
|
+
start(time)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def close(time = nil)
|
|
49
|
+
stop(time)
|
|
50
|
+
@capture_backend&.close if @capture_backend.respond_to?(:close)
|
|
51
|
+
@opened = false
|
|
52
|
+
self
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def rewind
|
|
56
|
+
@sample_cursor = 0
|
|
57
|
+
@provider.rewind if @provider.respond_to?(:rewind)
|
|
58
|
+
@capture_backend&.rewind if @capture_backend.respond_to?(:rewind)
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def live?
|
|
63
|
+
@live_requested
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def opened?
|
|
67
|
+
@opened
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def permission_state
|
|
71
|
+
self.class.permission_state
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
alias permissionState permission_state
|
|
75
|
+
|
|
76
|
+
def state(time = context.current_time)
|
|
77
|
+
return :stopped unless @opened
|
|
78
|
+
|
|
79
|
+
active_at?(resolve_time(time)) ? :started : :stopped
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def dispose
|
|
83
|
+
close
|
|
84
|
+
super
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class << self
|
|
88
|
+
def permission_state
|
|
89
|
+
@permission_state ||= :prompt
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def grant_permissions!
|
|
93
|
+
@permission_state = :granted
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def reset_permissions!
|
|
98
|
+
@permission_state = :prompt
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def supported?
|
|
103
|
+
true
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def input_devices
|
|
107
|
+
portaudio_devices.filter_map do |device|
|
|
108
|
+
info = build_device_info(device)
|
|
109
|
+
info if info.input_channels.positive?
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def enumerate_devices
|
|
114
|
+
input_devices
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
alias supported supported?
|
|
118
|
+
alias enumerateDevices enumerate_devices
|
|
119
|
+
alias permissionState permission_state
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def permissions_granted?
|
|
124
|
+
permission_state == :granted
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def portaudio_devices
|
|
128
|
+
return [] unless Deftones.portaudio_available?
|
|
129
|
+
return [] unless defined?(PortAudio::Device)
|
|
130
|
+
|
|
131
|
+
return Array(PortAudio::Device.all) if PortAudio::Device.respond_to?(:all)
|
|
132
|
+
return Array(PortAudio::Device.devices) if PortAudio::Device.respond_to?(:devices)
|
|
133
|
+
|
|
134
|
+
[]
|
|
135
|
+
rescue StandardError
|
|
136
|
+
[]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def build_device_info(device)
|
|
140
|
+
DeviceInfo.new(
|
|
141
|
+
device_id: extract_device_id(device),
|
|
142
|
+
label: exposed_device_label(device),
|
|
143
|
+
group_id: exposed_device_group_id(device),
|
|
144
|
+
input_channels: extract_device_channels(device, :input),
|
|
145
|
+
output_channels: extract_device_channels(device, :output)
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def extract_device_id(device)
|
|
150
|
+
return device.device_id if device.respond_to?(:device_id)
|
|
151
|
+
return device.index if device.respond_to?(:index)
|
|
152
|
+
return device.device_index if device.respond_to?(:device_index)
|
|
153
|
+
|
|
154
|
+
extract_device_label(device)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def extract_device_label(device)
|
|
158
|
+
return device.label if device.respond_to?(:label)
|
|
159
|
+
return device.name if device.respond_to?(:name)
|
|
160
|
+
|
|
161
|
+
device.to_s
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def extract_device_group_id(device)
|
|
165
|
+
return device.group_id if device.respond_to?(:group_id)
|
|
166
|
+
return device.host_api if device.respond_to?(:host_api)
|
|
167
|
+
return device.host_api_name if device.respond_to?(:host_api_name)
|
|
168
|
+
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def exposed_device_label(device)
|
|
173
|
+
permissions_granted? ? extract_device_label(device) : ""
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def exposed_device_group_id(device)
|
|
177
|
+
permissions_granted? ? extract_device_group_id(device) : nil
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def extract_device_channels(device, direction)
|
|
181
|
+
methods =
|
|
182
|
+
case direction
|
|
183
|
+
when :input then %i[max_input_channels input_channels channels]
|
|
184
|
+
when :output then %i[max_output_channels output_channels channels]
|
|
185
|
+
else []
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
methods.each do |method_name|
|
|
189
|
+
return device.public_send(method_name).to_i if device.respond_to?(method_name)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
0
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def process(_input_buffer, num_frames, start_frame, _cache)
|
|
197
|
+
return render_buffer_block(num_frames, start_frame) if @buffer && @buffer.channels > 1
|
|
198
|
+
return render_capture_block(num_frames, start_frame) if @capture_backend && capture_channels > 1
|
|
199
|
+
return render_buffer(num_frames, start_frame) if @buffer
|
|
200
|
+
return render_capture(num_frames, start_frame) if @capture_backend
|
|
201
|
+
|
|
202
|
+
Array.new(num_frames) do |index|
|
|
203
|
+
current_time = (start_frame + index).to_f / context.sample_rate
|
|
204
|
+
next 0.0 unless active_at?(current_time)
|
|
205
|
+
|
|
206
|
+
next_provider_sample
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
private
|
|
211
|
+
|
|
212
|
+
def multichannel_process?
|
|
213
|
+
(@buffer && @buffer.channels > 1) || capture_channels > 1
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def capture_channels
|
|
217
|
+
return @buffer.channels if @buffer
|
|
218
|
+
return normalize_channel_count(@capture_backend.channels) if @capture_backend.respond_to?(:channels)
|
|
219
|
+
|
|
220
|
+
@channels
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def normalize_buffer(buffer)
|
|
224
|
+
return if buffer.nil?
|
|
225
|
+
|
|
226
|
+
buffer.is_a?(IO::Buffer) ? buffer : IO::Buffer.load(buffer)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def normalize_provider(provider)
|
|
230
|
+
return if provider.nil?
|
|
231
|
+
return provider if provider.respond_to?(:call)
|
|
232
|
+
|
|
233
|
+
provider.to_enum
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def normalize_capture_backend(capture_backend, live, channels)
|
|
237
|
+
return capture_backend if capture_backend
|
|
238
|
+
return unless live && Deftones.portaudio_available?
|
|
239
|
+
|
|
240
|
+
PortAudioCapture.new(
|
|
241
|
+
sample_rate: context.sample_rate,
|
|
242
|
+
buffer_size: context.buffer_size,
|
|
243
|
+
channels: normalize_channel_count(channels || context.channels)
|
|
244
|
+
)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def normalize_channel_count(channels)
|
|
248
|
+
[channels.to_i, 1].max
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def open_capture_backend!
|
|
252
|
+
return self unless live?
|
|
253
|
+
|
|
254
|
+
raise Deftones::MissingRealtimeBackendError, "UserMedia live capture backend is unavailable" unless @capture_backend
|
|
255
|
+
|
|
256
|
+
if @capture_backend.respond_to?(:open)
|
|
257
|
+
@capture_backend.open(
|
|
258
|
+
device_id: @device_id,
|
|
259
|
+
group_id: @group_id,
|
|
260
|
+
label: @label,
|
|
261
|
+
channels: capture_channels
|
|
262
|
+
)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
self
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def sync_capture_metadata!
|
|
269
|
+
return self unless @capture_backend
|
|
270
|
+
|
|
271
|
+
@device_id = @capture_backend.device_id if @capture_backend.respond_to?(:device_id) && !@capture_backend.device_id.nil?
|
|
272
|
+
@group_id = @capture_backend.group_id if @capture_backend.respond_to?(:group_id) && !@capture_backend.group_id.nil?
|
|
273
|
+
@label = @capture_backend.label if @capture_backend.respond_to?(:label) && !@capture_backend.label.nil?
|
|
274
|
+
self
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def render_buffer(num_frames, start_frame)
|
|
278
|
+
Array.new(num_frames) do |index|
|
|
279
|
+
current_time = (start_frame + index).to_f / context.sample_rate
|
|
280
|
+
next 0.0 unless active_at?(current_time)
|
|
281
|
+
|
|
282
|
+
sample_position = (current_time - @start_time) * @buffer.sample_rate
|
|
283
|
+
if @loop && @buffer.frames.positive?
|
|
284
|
+
sample_position %= @buffer.frames
|
|
285
|
+
elsif sample_position >= @buffer.frames
|
|
286
|
+
@opened = false
|
|
287
|
+
next 0.0
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
@buffer.sample_at(sample_position)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def render_buffer_block(num_frames, start_frame)
|
|
295
|
+
output = Array.new(@buffer.channels) { Array.new(num_frames, 0.0) }
|
|
296
|
+
|
|
297
|
+
num_frames.times do |index|
|
|
298
|
+
current_time = (start_frame + index).to_f / context.sample_rate
|
|
299
|
+
next unless active_at?(current_time)
|
|
300
|
+
|
|
301
|
+
sample_position = (current_time - @start_time) * @buffer.sample_rate
|
|
302
|
+
if @loop && @buffer.frames.positive?
|
|
303
|
+
sample_position %= @buffer.frames
|
|
304
|
+
elsif sample_position >= @buffer.frames
|
|
305
|
+
@opened = false
|
|
306
|
+
next
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
@buffer.channels.times do |channel_index|
|
|
310
|
+
output[channel_index][index] = @buffer.sample_at(sample_position, channel_index)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
Core::AudioBlock.from_channel_data(output)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def render_capture(num_frames, start_frame)
|
|
318
|
+
Array.new(num_frames) do |index|
|
|
319
|
+
current_time = (start_frame + index).to_f / context.sample_rate
|
|
320
|
+
next 0.0 unless active_at?(current_time)
|
|
321
|
+
|
|
322
|
+
@capture_backend.next_sample
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def render_capture_block(num_frames, start_frame)
|
|
327
|
+
output = Array.new(capture_channels) { Array.new(num_frames, 0.0) }
|
|
328
|
+
|
|
329
|
+
num_frames.times do |index|
|
|
330
|
+
current_time = (start_frame + index).to_f / context.sample_rate
|
|
331
|
+
next unless active_at?(current_time)
|
|
332
|
+
|
|
333
|
+
frame = next_capture_frame
|
|
334
|
+
capture_channels.times do |channel_index|
|
|
335
|
+
output[channel_index][index] = frame[channel_index] || 0.0
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
Core::AudioBlock.from_channel_data(output)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def next_capture_frame
|
|
343
|
+
return Array.new(capture_channels, 0.0) unless @capture_backend
|
|
344
|
+
|
|
345
|
+
frame = if @capture_backend.respond_to?(:next_frame)
|
|
346
|
+
@capture_backend.next_frame
|
|
347
|
+
else
|
|
348
|
+
@capture_backend.next_sample
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
frame = [frame] unless frame.is_a?(Array)
|
|
352
|
+
normalized = frame.map(&:to_f)
|
|
353
|
+
normalized.fill(0.0, normalized.length...capture_channels)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def next_provider_sample
|
|
357
|
+
return 0.0 unless @provider
|
|
358
|
+
|
|
359
|
+
sample = if @provider.respond_to?(:call)
|
|
360
|
+
@provider.call(@sample_cursor)
|
|
361
|
+
else
|
|
362
|
+
next_enumerator_sample
|
|
363
|
+
end
|
|
364
|
+
@sample_cursor += 1
|
|
365
|
+
sample.to_f
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def next_enumerator_sample
|
|
369
|
+
@provider.next
|
|
370
|
+
rescue StopIteration
|
|
371
|
+
return 0.0 unless @loop && @provider.respond_to?(:rewind)
|
|
372
|
+
|
|
373
|
+
@provider.rewind
|
|
374
|
+
@provider.next
|
|
375
|
+
rescue StopIteration
|
|
376
|
+
@opened = false
|
|
377
|
+
0.0
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
class PortAudioCapture
|
|
381
|
+
attr_reader :channels, :device_id, :group_id, :label
|
|
382
|
+
|
|
383
|
+
def initialize(sample_rate:, buffer_size:, channels: 1)
|
|
384
|
+
@sample_rate = sample_rate
|
|
385
|
+
@buffer_size = buffer_size
|
|
386
|
+
@channels = [channels.to_i, 1].max
|
|
387
|
+
@queue = Queue.new
|
|
388
|
+
@max_frames = buffer_size * 64
|
|
389
|
+
@stream = nil
|
|
390
|
+
@device_id = nil
|
|
391
|
+
@group_id = nil
|
|
392
|
+
@label = nil
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def open(device_id: nil, group_id: nil, label: nil, channels: nil)
|
|
396
|
+
@device_id = device_id unless device_id.nil?
|
|
397
|
+
@group_id = group_id unless group_id.nil?
|
|
398
|
+
@label = label unless label.nil?
|
|
399
|
+
@channels = [channels.to_i, 1].max unless channels.nil?
|
|
400
|
+
close if @stream
|
|
401
|
+
self
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def start
|
|
405
|
+
open_stream unless @stream
|
|
406
|
+
@stream.start
|
|
407
|
+
self
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def stop
|
|
411
|
+
return self unless @stream
|
|
412
|
+
return self if @stream.stopped?
|
|
413
|
+
|
|
414
|
+
@stream.stop
|
|
415
|
+
self
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def rewind
|
|
419
|
+
clear_queue
|
|
420
|
+
self
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def close
|
|
424
|
+
return self unless @stream
|
|
425
|
+
|
|
426
|
+
stream = @stream
|
|
427
|
+
@stream = nil
|
|
428
|
+
clear_queue
|
|
429
|
+
stream.close
|
|
430
|
+
self
|
|
431
|
+
ensure
|
|
432
|
+
Deftones::PortAudioSupport.release
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def next_sample
|
|
436
|
+
frame = next_frame
|
|
437
|
+
frame.sum / [frame.length, 1].max.to_f
|
|
438
|
+
rescue ThreadError
|
|
439
|
+
0.0
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def next_frame
|
|
443
|
+
@queue.pop(true)
|
|
444
|
+
rescue ThreadError
|
|
445
|
+
Array.new(@channels, 0.0)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
private
|
|
449
|
+
|
|
450
|
+
def open_stream
|
|
451
|
+
Deftones::PortAudioSupport.acquire!
|
|
452
|
+
input_parameters = Deftones::PortAudioSupport.input_parameters(
|
|
453
|
+
@channels,
|
|
454
|
+
device_id: @device_id,
|
|
455
|
+
label: @label
|
|
456
|
+
)
|
|
457
|
+
assign_device_metadata(input_parameters[:device])
|
|
458
|
+
@stream = PortAudio::Stream.new(
|
|
459
|
+
input: input_parameters,
|
|
460
|
+
sample_rate: @sample_rate.to_f,
|
|
461
|
+
frames_per_buffer: @buffer_size,
|
|
462
|
+
&method(:process)
|
|
463
|
+
)
|
|
464
|
+
rescue StandardError
|
|
465
|
+
Deftones::PortAudioSupport.release
|
|
466
|
+
raise
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def process(input, _output, frame_count, _time_info, _status_flags, _user_data)
|
|
470
|
+
return :continue if input.null?
|
|
471
|
+
|
|
472
|
+
input.read_array_of_float(frame_count * @channels).each_slice(@channels) do |frame|
|
|
473
|
+
@queue << frame.map(&:to_f)
|
|
474
|
+
end
|
|
475
|
+
trim_queue!
|
|
476
|
+
:continue
|
|
477
|
+
rescue StandardError
|
|
478
|
+
:abort
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def assign_device_metadata(device)
|
|
482
|
+
return unless device
|
|
483
|
+
|
|
484
|
+
@device_id ||= if device.respond_to?(:device_id)
|
|
485
|
+
device.device_id
|
|
486
|
+
elsif device.respond_to?(:index)
|
|
487
|
+
device.index
|
|
488
|
+
elsif device.respond_to?(:device_index)
|
|
489
|
+
device.device_index
|
|
490
|
+
end
|
|
491
|
+
@label ||= if device.respond_to?(:label)
|
|
492
|
+
device.label
|
|
493
|
+
elsif device.respond_to?(:name)
|
|
494
|
+
device.name
|
|
495
|
+
end
|
|
496
|
+
@group_id ||= if device.respond_to?(:group_id)
|
|
497
|
+
device.group_id
|
|
498
|
+
elsif device.respond_to?(:host_api)
|
|
499
|
+
device.host_api
|
|
500
|
+
elsif device.respond_to?(:host_api_name)
|
|
501
|
+
device.host_api_name
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def trim_queue!
|
|
506
|
+
@queue.pop(true) while @queue.size > @max_frames
|
|
507
|
+
rescue ThreadError
|
|
508
|
+
nil
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def clear_queue
|
|
512
|
+
@queue.pop(true) while true
|
|
513
|
+
rescue ThreadError
|
|
514
|
+
nil
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
end
|