portaudio 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/.github/workflows/ci.yml +42 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +388 -0
- data/Rakefile +8 -0
- data/examples/host_api_info.rb +16 -0
- data/examples/level_meter.rb +39 -0
- data/examples/list_devices.rb +17 -0
- data/examples/passthrough.rb +39 -0
- data/examples/record_to_file.rb +42 -0
- data/examples/sine_wave.rb +46 -0
- data/examples/sine_wave_blocking.rb +45 -0
- data/lib/portaudio/blocking_stream.rb +182 -0
- data/lib/portaudio/buffer.rb +61 -0
- data/lib/portaudio/c/constants.rb +53 -0
- data/lib/portaudio/c/enums.rb +84 -0
- data/lib/portaudio/c/functions.rb +93 -0
- data/lib/portaudio/c/host_api/alsa.rb +40 -0
- data/lib/portaudio/c/host_api/asio.rb +51 -0
- data/lib/portaudio/c/host_api/core_audio.rb +54 -0
- data/lib/portaudio/c/host_api/direct_sound.rb +26 -0
- data/lib/portaudio/c/host_api/jack.rb +28 -0
- data/lib/portaudio/c/host_api/pulse_audio.rb +28 -0
- data/lib/portaudio/c/host_api/wasapi.rb +238 -0
- data/lib/portaudio/c/host_api/wave_format.rb +105 -0
- data/lib/portaudio/c/host_api/wdm_ks.rb +73 -0
- data/lib/portaudio/c/host_api/wmme.rb +54 -0
- data/lib/portaudio/c/library.rb +122 -0
- data/lib/portaudio/c/structs.rb +66 -0
- data/lib/portaudio/configuration.rb +20 -0
- data/lib/portaudio/device.rb +87 -0
- data/lib/portaudio/error.rb +131 -0
- data/lib/portaudio/host_api.rb +89 -0
- data/lib/portaudio/stream.rb +177 -0
- data/lib/portaudio/stream_parameters_builder.rb +59 -0
- data/lib/portaudio/version.rb +5 -0
- data/lib/portaudio.rb +79 -0
- data/spec/integration/blocking_playback_spec.rb +21 -0
- data/spec/integration/callback_playback_spec.rb +26 -0
- data/spec/integration/device_enumeration_spec.rb +11 -0
- data/spec/integration/recording_spec.rb +25 -0
- data/spec/portaudio_spec.rb +43 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/unit/blocking_stream_spec.rb +66 -0
- data/spec/unit/buffer_spec.rb +25 -0
- data/spec/unit/c/constants_spec.rb +13 -0
- data/spec/unit/c/enums_spec.rb +18 -0
- data/spec/unit/c/functions_spec.rb +14 -0
- data/spec/unit/c/host_api/asio_spec.rb +14 -0
- data/spec/unit/c/host_api/core_audio_spec.rb +18 -0
- data/spec/unit/c/host_api/direct_sound_spec.rb +9 -0
- data/spec/unit/c/host_api/jack_spec.rb +9 -0
- data/spec/unit/c/host_api/pulse_audio_spec.rb +9 -0
- data/spec/unit/c/host_api/wasapi_spec.rb +34 -0
- data/spec/unit/c/host_api/wave_format_spec.rb +17 -0
- data/spec/unit/c/host_api/wdm_ks_spec.rb +13 -0
- data/spec/unit/c/host_api/wmme_spec.rb +16 -0
- data/spec/unit/c/library_spec.rb +18 -0
- data/spec/unit/device_spec.rb +54 -0
- data/spec/unit/error_spec.rb +32 -0
- data/spec/unit/host_api_spec.rb +53 -0
- data/spec/unit/stream_parameters_builder_spec.rb +32 -0
- data/spec/unit/stream_spec.rb +76 -0
- metadata +122 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "portaudio"
|
|
4
|
+
|
|
5
|
+
frequency = 440.0
|
|
6
|
+
phase = 0.0
|
|
7
|
+
duration_seconds = 2.0
|
|
8
|
+
|
|
9
|
+
PortAudio.with_portaudio do
|
|
10
|
+
output = PortAudio::Device.default_output
|
|
11
|
+
unless output
|
|
12
|
+
warn "No default output device available"
|
|
13
|
+
next
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
sample_rate = output.default_sample_rate
|
|
17
|
+
frames = 256
|
|
18
|
+
remaining_frames = (sample_rate * duration_seconds).round
|
|
19
|
+
|
|
20
|
+
stream = PortAudio::BlockingStream.new(
|
|
21
|
+
output: { device: output.index, channels: 1, format: :float32 },
|
|
22
|
+
sample_rate: sample_rate,
|
|
23
|
+
frames_per_buffer: frames
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
stream.start
|
|
28
|
+
|
|
29
|
+
while remaining_frames.positive?
|
|
30
|
+
frame_count = [remaining_frames, frames].min
|
|
31
|
+
chunk = Array.new(frame_count) do
|
|
32
|
+
value = Math.sin(phase * 2.0 * Math::PI)
|
|
33
|
+
phase += frequency / sample_rate
|
|
34
|
+
phase -= 1.0 if phase >= 1.0
|
|
35
|
+
value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
stream.write(chunk, frame_count)
|
|
39
|
+
remaining_frames -= frame_count
|
|
40
|
+
end
|
|
41
|
+
ensure
|
|
42
|
+
stream.stop if stream.active?
|
|
43
|
+
stream.close unless stream.closed?
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PortAudio
|
|
4
|
+
class BlockingStream
|
|
5
|
+
attr_reader :sample_rate,
|
|
6
|
+
:frames_per_buffer,
|
|
7
|
+
:input_channels,
|
|
8
|
+
:output_channels,
|
|
9
|
+
:input_format,
|
|
10
|
+
:output_format,
|
|
11
|
+
:last_warning
|
|
12
|
+
|
|
13
|
+
def initialize(
|
|
14
|
+
input: nil,
|
|
15
|
+
output: nil,
|
|
16
|
+
sample_rate:,
|
|
17
|
+
frames_per_buffer: 1024,
|
|
18
|
+
flags: C::Constants::PA_NO_FLAG
|
|
19
|
+
)
|
|
20
|
+
@stream_ptr = FFI::MemoryPointer.new(:pointer)
|
|
21
|
+
@input_channels = Integer(input&.fetch(:channels, 1) || 0)
|
|
22
|
+
@output_channels = Integer(output&.fetch(:channels, 1) || 0)
|
|
23
|
+
@input_format = input&.fetch(:format, :float32)
|
|
24
|
+
@output_format = output&.fetch(:format, :float32)
|
|
25
|
+
@sample_rate = Float(sample_rate)
|
|
26
|
+
@frames_per_buffer = Integer(frames_per_buffer)
|
|
27
|
+
|
|
28
|
+
code = C::Functions.Pa_OpenStream(
|
|
29
|
+
@stream_ptr,
|
|
30
|
+
pointer_or_null(StreamParametersBuilder.build(input)),
|
|
31
|
+
pointer_or_null(StreamParametersBuilder.build(output)),
|
|
32
|
+
@sample_rate,
|
|
33
|
+
@frames_per_buffer,
|
|
34
|
+
Integer(flags),
|
|
35
|
+
nil,
|
|
36
|
+
FFI::Pointer::NULL
|
|
37
|
+
)
|
|
38
|
+
PortAudio.check_error!(code)
|
|
39
|
+
|
|
40
|
+
@handle = @stream_ptr.read_pointer
|
|
41
|
+
@last_warning = nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def start
|
|
45
|
+
PortAudio.check_error!(C::Functions.Pa_StartStream(handle))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def stop
|
|
49
|
+
PortAudio.check_error!(C::Functions.Pa_StopStream(handle))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def abort
|
|
53
|
+
PortAudio.check_error!(C::Functions.Pa_AbortStream(handle))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def close
|
|
57
|
+
return if closed?
|
|
58
|
+
|
|
59
|
+
PortAudio.check_error!(C::Functions.Pa_CloseStream(handle))
|
|
60
|
+
@handle = nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def closed?
|
|
64
|
+
@handle.nil?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def active?
|
|
68
|
+
return false if closed?
|
|
69
|
+
|
|
70
|
+
C::Functions.Pa_IsStreamActive(handle) == 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def stopped?
|
|
74
|
+
return true if closed?
|
|
75
|
+
|
|
76
|
+
C::Functions.Pa_IsStreamStopped(handle) == 1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def read(frames = @frames_per_buffer, strict: Configuration.strict_warnings)
|
|
80
|
+
ensure_input_stream!
|
|
81
|
+
|
|
82
|
+
frame_count = Integer(frames)
|
|
83
|
+
sample_size = StreamParametersBuilder.sample_size_for(@input_format)
|
|
84
|
+
byte_size = frame_count * @input_channels * sample_size
|
|
85
|
+
buffer = FFI::MemoryPointer.new(:char, byte_size)
|
|
86
|
+
|
|
87
|
+
code = C::Functions.Pa_ReadStream(handle, buffer, frame_count)
|
|
88
|
+
handle_stream_result(code, strict: strict)
|
|
89
|
+
buffer.read_bytes(byte_size)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def write(data, frames = nil, strict: Configuration.strict_warnings)
|
|
93
|
+
ensure_output_stream!
|
|
94
|
+
|
|
95
|
+
payload = coerce_output_data(data)
|
|
96
|
+
sample_size = StreamParametersBuilder.sample_size_for(@output_format)
|
|
97
|
+
frame_count = frames || (payload.bytesize / (@output_channels * sample_size))
|
|
98
|
+
pointer = FFI::MemoryPointer.new(:char, payload.bytesize)
|
|
99
|
+
pointer.put_bytes(0, payload)
|
|
100
|
+
|
|
101
|
+
code = C::Functions.Pa_WriteStream(handle, pointer, Integer(frame_count))
|
|
102
|
+
handle_stream_result(code, strict: strict)
|
|
103
|
+
frame_count
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def read_available
|
|
107
|
+
C::Functions.Pa_GetStreamReadAvailable(handle)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def write_available
|
|
111
|
+
C::Functions.Pa_GetStreamWriteAvailable(handle)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def each_frame(frames = @frames_per_buffer, strict: Configuration.strict_warnings)
|
|
115
|
+
return enum_for(:each_frame, frames, strict: strict) unless block_given?
|
|
116
|
+
|
|
117
|
+
loop do
|
|
118
|
+
yield read(frames, strict: strict)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def pointer_or_null(stream_parameters)
|
|
125
|
+
return FFI::Pointer::NULL if stream_parameters.nil?
|
|
126
|
+
|
|
127
|
+
stream_parameters.pointer
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def coerce_output_data(data)
|
|
131
|
+
return pack_array_data(data) if data.is_a?(Array)
|
|
132
|
+
return data if data.is_a?(String)
|
|
133
|
+
|
|
134
|
+
raise ArgumentError, "write data must be String or Array"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def pack_array_data(array)
|
|
138
|
+
case StreamParametersBuilder.resolve_sample_format(@output_format)
|
|
139
|
+
when C::Constants::PA_FLOAT32
|
|
140
|
+
array.pack("e*")
|
|
141
|
+
when C::Constants::PA_INT16
|
|
142
|
+
array.map { |value| (Float(value) * 32_767).round.clamp(-32_768, 32_767) }.pack("s<*")
|
|
143
|
+
when C::Constants::PA_INT32
|
|
144
|
+
array.map { |value| (Float(value) * 2_147_483_647).round.clamp(-2_147_483_648, 2_147_483_647) }.pack("l<*")
|
|
145
|
+
when C::Constants::PA_INT8
|
|
146
|
+
array.map { |value| (Float(value) * 127).round.clamp(-128, 127) }.pack("c*")
|
|
147
|
+
when C::Constants::PA_UINT8
|
|
148
|
+
array.map { |value| (Float(value) * 255).round.clamp(0, 255) }.pack("C*")
|
|
149
|
+
else
|
|
150
|
+
raise ArgumentError, "Array write is unsupported for format #{@output_format.inspect}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def handle_stream_result(code, strict:)
|
|
155
|
+
@last_warning = nil
|
|
156
|
+
|
|
157
|
+
return code if code >= 0
|
|
158
|
+
return PortAudio.check_error!(code) if strict || !Error.warning_code?(code)
|
|
159
|
+
|
|
160
|
+
@last_warning = code
|
|
161
|
+
code
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def ensure_input_stream!
|
|
165
|
+
return if @input_channels.positive?
|
|
166
|
+
|
|
167
|
+
raise CannotReadFromOutputOnlyStreamError, "input stream is not configured"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def ensure_output_stream!
|
|
171
|
+
return if @output_channels.positive?
|
|
172
|
+
|
|
173
|
+
raise CannotWriteToInputOnlyStreamError, "output stream is not configured"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def handle
|
|
177
|
+
raise StreamStoppedError, "stream is closed" if @handle.nil?
|
|
178
|
+
|
|
179
|
+
@handle
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PortAudio
|
|
4
|
+
class Buffer
|
|
5
|
+
attr_reader :frames, :channels, :format
|
|
6
|
+
|
|
7
|
+
def initialize(frames:, channels:, format: :float32)
|
|
8
|
+
@frames = Integer(frames)
|
|
9
|
+
@channels = Integer(channels)
|
|
10
|
+
@format = format
|
|
11
|
+
@sample_format = StreamParametersBuilder.resolve_sample_format(format)
|
|
12
|
+
@sample_size = StreamParametersBuilder.sample_size_for(@sample_format)
|
|
13
|
+
@data = FFI::MemoryPointer.new(:char, bytesize)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def pointer
|
|
17
|
+
@data
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def bytesize
|
|
21
|
+
@frames * @channels * @sample_size
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_float_array
|
|
25
|
+
total_samples = @frames * @channels
|
|
26
|
+
|
|
27
|
+
case @sample_format
|
|
28
|
+
when C::Constants::PA_FLOAT32
|
|
29
|
+
@data.read_array_of_float(total_samples)
|
|
30
|
+
when C::Constants::PA_INT16
|
|
31
|
+
@data.read_array_of_int16(total_samples).map { |sample| sample / 32_768.0 }
|
|
32
|
+
when C::Constants::PA_INT32
|
|
33
|
+
@data.read_array_of_int32(total_samples).map { |sample| sample / 2_147_483_648.0 }
|
|
34
|
+
else
|
|
35
|
+
raise ArgumentError, "Unsupported format for to_float_array: #{@format.inspect}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def write_floats(floats)
|
|
40
|
+
case @sample_format
|
|
41
|
+
when C::Constants::PA_FLOAT32
|
|
42
|
+
@data.write_array_of_float(floats.map(&:to_f))
|
|
43
|
+
when C::Constants::PA_INT16
|
|
44
|
+
int_samples = floats.map { |sample| (sample.to_f * 32_767).round.clamp(-32_768, 32_767) }
|
|
45
|
+
@data.write_array_of_int16(int_samples)
|
|
46
|
+
when C::Constants::PA_INT32
|
|
47
|
+
int_samples = floats.map { |sample| (sample.to_f * 2_147_483_647).round.clamp(-2_147_483_648, 2_147_483_647) }
|
|
48
|
+
@data.write_array_of_int32(int_samples)
|
|
49
|
+
else
|
|
50
|
+
raise ArgumentError, "Unsupported format for write_floats: #{@format.inspect}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def clear
|
|
57
|
+
@data.put_bytes(0, "\x00" * bytesize)
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PortAudio
|
|
4
|
+
module C
|
|
5
|
+
module Constants
|
|
6
|
+
PA_NO_DEVICE = -1
|
|
7
|
+
PA_USE_HOST_API_SPECIFIC_DEVICE_SPECIFICATION = -2
|
|
8
|
+
|
|
9
|
+
PA_FLOAT32 = 0x00000001
|
|
10
|
+
PA_INT32 = 0x00000002
|
|
11
|
+
PA_INT24 = 0x00000004
|
|
12
|
+
PA_INT16 = 0x00000008
|
|
13
|
+
PA_INT8 = 0x00000010
|
|
14
|
+
PA_UINT8 = 0x00000020
|
|
15
|
+
PA_CUSTOM_FORMAT = 0x00010000
|
|
16
|
+
PA_NON_INTERLEAVED = 0x80000000
|
|
17
|
+
|
|
18
|
+
PA_NO_FLAG = 0
|
|
19
|
+
PA_CLIP_OFF = 0x00000001
|
|
20
|
+
PA_DITHER_OFF = 0x00000002
|
|
21
|
+
PA_NEVER_DROP_INPUT = 0x00000004
|
|
22
|
+
PA_PRIME_OUTPUT_BUFFERS_USING_STREAM_CALLBACK = 0x00000008
|
|
23
|
+
PA_PLATFORM_SPECIFIC_FLAGS = 0xFFFF0000
|
|
24
|
+
|
|
25
|
+
PA_INPUT_UNDERFLOW = 0x00000001
|
|
26
|
+
PA_INPUT_OVERFLOW = 0x00000002
|
|
27
|
+
PA_OUTPUT_UNDERFLOW = 0x00000004
|
|
28
|
+
PA_OUTPUT_OVERFLOW = 0x00000008
|
|
29
|
+
PA_PRIMING_OUTPUT = 0x00000010
|
|
30
|
+
|
|
31
|
+
PA_FORMAT_IS_SUPPORTED = 0
|
|
32
|
+
PA_FRAMES_PER_BUFFER_UNSPECIFIED = 0
|
|
33
|
+
|
|
34
|
+
SAMPLE_SIZE = {
|
|
35
|
+
PA_FLOAT32 => 4,
|
|
36
|
+
PA_INT32 => 4,
|
|
37
|
+
PA_INT24 => 3,
|
|
38
|
+
PA_INT16 => 2,
|
|
39
|
+
PA_INT8 => 1,
|
|
40
|
+
PA_UINT8 => 1
|
|
41
|
+
}.freeze
|
|
42
|
+
|
|
43
|
+
SYMBOL_TO_SAMPLE_FORMAT = {
|
|
44
|
+
float32: PA_FLOAT32,
|
|
45
|
+
int32: PA_INT32,
|
|
46
|
+
int24: PA_INT24,
|
|
47
|
+
int16: PA_INT16,
|
|
48
|
+
int8: PA_INT8,
|
|
49
|
+
uint8: PA_UINT8
|
|
50
|
+
}.freeze
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
module PortAudio
|
|
6
|
+
module C
|
|
7
|
+
module Enums
|
|
8
|
+
extend FFI::Library
|
|
9
|
+
|
|
10
|
+
PA_ERROR_CODE_VALUES = [
|
|
11
|
+
:paNoError, 0,
|
|
12
|
+
:paNotInitialized, -10000,
|
|
13
|
+
:paUnanticipatedHostError, -9999,
|
|
14
|
+
:paInvalidChannelCount, -9998,
|
|
15
|
+
:paInvalidSampleRate, -9997,
|
|
16
|
+
:paInvalidDevice, -9996,
|
|
17
|
+
:paInvalidFlag, -9995,
|
|
18
|
+
:paSampleFormatNotSupported, -9994,
|
|
19
|
+
:paBadIODeviceCombination, -9993,
|
|
20
|
+
:paInsufficientMemory, -9992,
|
|
21
|
+
:paBufferTooBig, -9991,
|
|
22
|
+
:paBufferTooSmall, -9990,
|
|
23
|
+
:paNullCallback, -9989,
|
|
24
|
+
:paBadStreamPtr, -9988,
|
|
25
|
+
:paTimedOut, -9987,
|
|
26
|
+
:paInternalError, -9986,
|
|
27
|
+
:paDeviceUnavailable, -9985,
|
|
28
|
+
:paIncompatibleHostApiSpecificStreamInfo, -9984,
|
|
29
|
+
:paStreamIsStopped, -9983,
|
|
30
|
+
:paStreamIsNotStopped, -9982,
|
|
31
|
+
:paInputOverflowed, -9981,
|
|
32
|
+
:paOutputUnderflowed, -9980,
|
|
33
|
+
:paHostApiNotFound, -9979,
|
|
34
|
+
:paInvalidHostApi, -9978,
|
|
35
|
+
:paCanNotReadFromACallbackStream, -9977,
|
|
36
|
+
:paCanNotWriteToACallbackStream, -9976,
|
|
37
|
+
:paCanNotReadFromAnOutputOnlyStream, -9975,
|
|
38
|
+
:paCanNotWriteToAnInputOnlyStream, -9974,
|
|
39
|
+
:paIncompatibleStreamHostApi, -9973,
|
|
40
|
+
:paBadBufferPtr, -9972,
|
|
41
|
+
:paCanNotInitializeRecursively, -9971
|
|
42
|
+
].freeze
|
|
43
|
+
|
|
44
|
+
PA_HOST_API_TYPE_VALUES = [
|
|
45
|
+
:paInDevelopment, 0,
|
|
46
|
+
:paDirectSound, 1,
|
|
47
|
+
:paMME, 2,
|
|
48
|
+
:paASIO, 3,
|
|
49
|
+
:paSoundManager, 4,
|
|
50
|
+
:paCoreAudio, 5,
|
|
51
|
+
:paOSS, 7,
|
|
52
|
+
:paALSA, 8,
|
|
53
|
+
:paAL, 9,
|
|
54
|
+
:paBeOS, 10,
|
|
55
|
+
:paWDMKS, 11,
|
|
56
|
+
:paJACK, 12,
|
|
57
|
+
:paWASAPI, 13,
|
|
58
|
+
:paAudioScienceHPI, 14,
|
|
59
|
+
:paAudioIO, 15,
|
|
60
|
+
:paPulseAudio, 16,
|
|
61
|
+
:paSndio, 17
|
|
62
|
+
].freeze
|
|
63
|
+
|
|
64
|
+
PA_STREAM_CALLBACK_RESULT_VALUES = [
|
|
65
|
+
:paContinue, 0,
|
|
66
|
+
:paComplete, 1,
|
|
67
|
+
:paAbort, 2
|
|
68
|
+
].freeze
|
|
69
|
+
|
|
70
|
+
PaErrorCode = enum(:pa_error_code, PA_ERROR_CODE_VALUES)
|
|
71
|
+
PaHostApiTypeId = enum(:pa_host_api_type_id, PA_HOST_API_TYPE_VALUES)
|
|
72
|
+
PaStreamCallbackResult = enum(:pa_stream_callback_result, PA_STREAM_CALLBACK_RESULT_VALUES)
|
|
73
|
+
|
|
74
|
+
ERROR_CODE_SYMBOL_TO_CODE = PA_ERROR_CODE_VALUES.each_slice(2).to_h.freeze
|
|
75
|
+
ERROR_CODE_CODE_TO_SYMBOL = ERROR_CODE_SYMBOL_TO_CODE.invert.freeze
|
|
76
|
+
|
|
77
|
+
HOST_API_TYPE_SYMBOL_TO_CODE = PA_HOST_API_TYPE_VALUES.each_slice(2).to_h.freeze
|
|
78
|
+
HOST_API_TYPE_CODE_TO_SYMBOL = HOST_API_TYPE_SYMBOL_TO_CODE.invert.freeze
|
|
79
|
+
|
|
80
|
+
STREAM_CALLBACK_RESULT_SYMBOL_TO_CODE = PA_STREAM_CALLBACK_RESULT_VALUES.each_slice(2).to_h.freeze
|
|
81
|
+
STREAM_CALLBACK_RESULT_CODE_TO_SYMBOL = STREAM_CALLBACK_RESULT_SYMBOL_TO_CODE.invert.freeze
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
module PortAudio
|
|
6
|
+
module C
|
|
7
|
+
module Functions
|
|
8
|
+
extend FFI::Library
|
|
9
|
+
extend PortAudio::C::BindingHelpers
|
|
10
|
+
|
|
11
|
+
PaStreamCallback = callback(
|
|
12
|
+
:pa_stream_callback,
|
|
13
|
+
%i[pointer pointer ulong pointer ulong pointer],
|
|
14
|
+
:int
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
PaStreamFinishedCallback = callback(
|
|
18
|
+
:pa_stream_finished_callback,
|
|
19
|
+
[:pointer],
|
|
20
|
+
:void
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
SIGNATURES = [
|
|
24
|
+
{ c_name: :Pa_GetVersion, args: [], returns: :int },
|
|
25
|
+
{ c_name: :Pa_GetVersionText, args: [], returns: :string },
|
|
26
|
+
{ c_name: :Pa_GetVersionInfo, args: [], returns: :pointer },
|
|
27
|
+
|
|
28
|
+
{ c_name: :Pa_Initialize, args: [], returns: :int },
|
|
29
|
+
{ c_name: :Pa_Terminate, args: [], returns: :int },
|
|
30
|
+
|
|
31
|
+
{ c_name: :Pa_GetHostApiCount, args: [], returns: :int },
|
|
32
|
+
{ c_name: :Pa_GetDefaultHostApi, args: [], returns: :int },
|
|
33
|
+
{ c_name: :Pa_GetHostApiInfo, args: [:int], returns: :pointer },
|
|
34
|
+
{ c_name: :Pa_HostApiTypeIdToHostApiIndex, args: [:int], returns: :int },
|
|
35
|
+
{ c_name: :Pa_HostApiDeviceIndexToDeviceIndex, args: %i[int int], returns: :int },
|
|
36
|
+
|
|
37
|
+
{ c_name: :Pa_GetErrorText, args: [:int], returns: :string },
|
|
38
|
+
{ c_name: :Pa_GetLastHostErrorInfo, args: [], returns: :pointer },
|
|
39
|
+
|
|
40
|
+
{ c_name: :Pa_GetDeviceCount, args: [], returns: :int },
|
|
41
|
+
{ c_name: :Pa_GetDefaultInputDevice, args: [], returns: :int },
|
|
42
|
+
{ c_name: :Pa_GetDefaultOutputDevice, args: [], returns: :int },
|
|
43
|
+
{ c_name: :Pa_GetDeviceInfo, args: [:int], returns: :pointer },
|
|
44
|
+
|
|
45
|
+
{ c_name: :Pa_IsFormatSupported, args: %i[pointer pointer double], returns: :int },
|
|
46
|
+
{
|
|
47
|
+
c_name: :Pa_OpenStream,
|
|
48
|
+
args: %i[pointer pointer pointer double ulong ulong pa_stream_callback pointer],
|
|
49
|
+
returns: :int
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
c_name: :Pa_OpenDefaultStream,
|
|
53
|
+
args: %i[pointer int int ulong double ulong pa_stream_callback pointer],
|
|
54
|
+
returns: :int
|
|
55
|
+
},
|
|
56
|
+
{ c_name: :Pa_CloseStream, args: [:pointer], returns: :int },
|
|
57
|
+
{
|
|
58
|
+
c_name: :Pa_SetStreamFinishedCallback,
|
|
59
|
+
args: %i[pointer pa_stream_finished_callback],
|
|
60
|
+
returns: :int
|
|
61
|
+
},
|
|
62
|
+
{ c_name: :Pa_StartStream, args: [:pointer], returns: :int },
|
|
63
|
+
{ c_name: :Pa_StopStream, args: [:pointer], returns: :int },
|
|
64
|
+
{ c_name: :Pa_AbortStream, args: [:pointer], returns: :int },
|
|
65
|
+
{ c_name: :Pa_IsStreamStopped, args: [:pointer], returns: :int },
|
|
66
|
+
{ c_name: :Pa_IsStreamActive, args: [:pointer], returns: :int },
|
|
67
|
+
{ c_name: :Pa_GetStreamInfo, args: [:pointer], returns: :pointer },
|
|
68
|
+
{ c_name: :Pa_GetStreamTime, args: [:pointer], returns: :double },
|
|
69
|
+
{ c_name: :Pa_GetStreamCpuLoad, args: [:pointer], returns: :double },
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
c_name: :Pa_ReadStream,
|
|
73
|
+
args: %i[pointer pointer ulong],
|
|
74
|
+
returns: :int,
|
|
75
|
+
options: { blocking: true }
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
c_name: :Pa_WriteStream,
|
|
79
|
+
args: %i[pointer pointer ulong],
|
|
80
|
+
returns: :int,
|
|
81
|
+
options: { blocking: true }
|
|
82
|
+
},
|
|
83
|
+
{ c_name: :Pa_GetStreamReadAvailable, args: [:pointer], returns: :long },
|
|
84
|
+
{ c_name: :Pa_GetStreamWriteAvailable, args: [:pointer], returns: :long },
|
|
85
|
+
|
|
86
|
+
{ c_name: :Pa_GetSampleSize, args: [:ulong], returns: :int },
|
|
87
|
+
{ c_name: :Pa_Sleep, args: [:long], returns: :void }
|
|
88
|
+
].freeze
|
|
89
|
+
|
|
90
|
+
define_lazy_functions(SIGNATURES)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
|
|
6
|
+
module PortAudio
|
|
7
|
+
module C
|
|
8
|
+
module HostAPI
|
|
9
|
+
module ALSA
|
|
10
|
+
extend FFI::Library
|
|
11
|
+
extend PortAudio::C::BindingHelpers
|
|
12
|
+
|
|
13
|
+
SUPPORTED = /linux/i.match?(RbConfig::CONFIG["host_os"])
|
|
14
|
+
|
|
15
|
+
class PaAlsaStreamInfo < FFI::Struct
|
|
16
|
+
layout :size, :ulong,
|
|
17
|
+
:hostApiType, :int,
|
|
18
|
+
:version, :ulong,
|
|
19
|
+
:deviceString, :string
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
SIGNATURES = [
|
|
23
|
+
{ c_name: :PaAlsa_InitializeStreamInfo, args: [:pointer], returns: :void },
|
|
24
|
+
{ c_name: :PaAlsa_EnableRealtimeScheduling, args: %i[pointer int], returns: :void },
|
|
25
|
+
{ c_name: :PaAlsa_GetStreamInputCard, args: %i[pointer pointer], returns: :int },
|
|
26
|
+
{ c_name: :PaAlsa_GetStreamOutputCard, args: %i[pointer pointer], returns: :int },
|
|
27
|
+
{ c_name: :PaAlsa_SetNumPeriods, args: [:int], returns: :void },
|
|
28
|
+
{ c_name: :PaAlsa_SetRetriesBusy, args: [:int], returns: :void },
|
|
29
|
+
{ c_name: :PaAlsa_SetLibraryPathName, args: [:string], returns: :void }
|
|
30
|
+
].freeze
|
|
31
|
+
|
|
32
|
+
if SUPPORTED
|
|
33
|
+
define_lazy_functions(SIGNATURES)
|
|
34
|
+
else
|
|
35
|
+
define_unsupported_functions(SIGNATURES, "ALSA extension is available only on Linux")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
|
|
6
|
+
module PortAudio
|
|
7
|
+
module C
|
|
8
|
+
module HostAPI
|
|
9
|
+
module ASIO
|
|
10
|
+
extend FFI::Library
|
|
11
|
+
extend PortAudio::C::BindingHelpers
|
|
12
|
+
|
|
13
|
+
SUPPORTED = /mingw|mswin/i.match?(RbConfig::CONFIG["host_os"])
|
|
14
|
+
|
|
15
|
+
PA_ASIO_USE_CHANNEL_SELECTORS = 0x01
|
|
16
|
+
|
|
17
|
+
class PaAsioStreamInfo < FFI::Struct
|
|
18
|
+
layout :size, :ulong,
|
|
19
|
+
:hostApiType, :int,
|
|
20
|
+
:version, :ulong,
|
|
21
|
+
:flags, :ulong,
|
|
22
|
+
:channelSelectors, :pointer
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
SIGNATURES = [
|
|
26
|
+
{
|
|
27
|
+
c_name: :PaAsio_GetAvailableBufferSizes,
|
|
28
|
+
args: %i[int pointer pointer pointer pointer],
|
|
29
|
+
returns: :int
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
c_name: :PaAsio_GetAvailableBufferSizes,
|
|
33
|
+
ruby_name: :PaAsio_GetAvailableLatencyValues,
|
|
34
|
+
args: %i[int pointer pointer pointer pointer],
|
|
35
|
+
returns: :int
|
|
36
|
+
},
|
|
37
|
+
{ c_name: :PaAsio_ShowControlPanel, args: %i[int pointer], returns: :int },
|
|
38
|
+
{ c_name: :PaAsio_GetInputChannelName, args: %i[int int pointer], returns: :int },
|
|
39
|
+
{ c_name: :PaAsio_GetOutputChannelName, args: %i[int int pointer], returns: :int },
|
|
40
|
+
{ c_name: :PaAsio_SetStreamSampleRate, args: %i[pointer double], returns: :int }
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
if SUPPORTED
|
|
44
|
+
define_lazy_functions(SIGNATURES)
|
|
45
|
+
else
|
|
46
|
+
define_unsupported_functions(SIGNATURES, "ASIO extension is available only on Windows")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
|
|
6
|
+
module PortAudio
|
|
7
|
+
module C
|
|
8
|
+
module HostAPI
|
|
9
|
+
module CoreAudio
|
|
10
|
+
extend FFI::Library
|
|
11
|
+
extend PortAudio::C::BindingHelpers
|
|
12
|
+
|
|
13
|
+
SUPPORTED = /darwin/i.match?(RbConfig::CONFIG["host_os"])
|
|
14
|
+
|
|
15
|
+
PA_MAC_CORE_CHANGE_DEVICE_PARAMETERS = 0x01
|
|
16
|
+
PA_MAC_CORE_FAIL_IF_CONVERSION_REQUIRED = 0x02
|
|
17
|
+
PA_MAC_CORE_CONVERSION_QUALITY_MIN = 0x0100
|
|
18
|
+
PA_MAC_CORE_CONVERSION_QUALITY_MEDIUM = 0x0200
|
|
19
|
+
PA_MAC_CORE_CONVERSION_QUALITY_LOW = 0x0300
|
|
20
|
+
PA_MAC_CORE_CONVERSION_QUALITY_HIGH = 0x0400
|
|
21
|
+
PA_MAC_CORE_CONVERSION_QUALITY_MAX = 0x0000
|
|
22
|
+
PA_MAC_CORE_PLAY_NICE = 0x00
|
|
23
|
+
PA_MAC_CORE_PRO = 0x01
|
|
24
|
+
PA_MAC_CORE_MINIMIZE_CPU_BUT_PLAY_NICE = 0x0100
|
|
25
|
+
PA_MAC_CORE_MINIMIZE_CPU = 0x0101
|
|
26
|
+
|
|
27
|
+
class PaMacCoreStreamInfo < FFI::Struct
|
|
28
|
+
layout :size, :ulong,
|
|
29
|
+
:hostApiType, :int,
|
|
30
|
+
:version, :ulong,
|
|
31
|
+
:flags, :ulong,
|
|
32
|
+
:channelMap, :pointer,
|
|
33
|
+
:channelMapSize, :ulong
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
SIGNATURES = [
|
|
37
|
+
{ c_name: :PaMacCore_SetupStreamInfo, args: %i[pointer ulong], returns: :void },
|
|
38
|
+
{ c_name: :PaMacCore_SetupChannelMap, args: %i[pointer pointer ulong], returns: :void },
|
|
39
|
+
{ c_name: :PaMacCore_GetBufferSizeRange, args: %i[int pointer pointer], returns: :int },
|
|
40
|
+
{ c_name: :PaMacCore_GetStreamInputDevice, args: [:pointer], returns: :uint },
|
|
41
|
+
{ c_name: :PaMacCore_GetStreamOutputDevice, args: [:pointer], returns: :uint },
|
|
42
|
+
{ c_name: :PaMacCore_GetChannelName, args: %i[int int int], returns: :string },
|
|
43
|
+
{ c_name: :PaMacCore_GetOSWorkgroup, args: %i[int pointer], returns: :int }
|
|
44
|
+
].freeze
|
|
45
|
+
|
|
46
|
+
if SUPPORTED
|
|
47
|
+
define_lazy_functions(SIGNATURES)
|
|
48
|
+
else
|
|
49
|
+
define_unsupported_functions(SIGNATURES, "CoreAudio extension is available only on macOS")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
|
|
6
|
+
module PortAudio
|
|
7
|
+
module C
|
|
8
|
+
module HostAPI
|
|
9
|
+
module DirectSound
|
|
10
|
+
SUPPORTED = /mingw|mswin/i.match?(RbConfig::CONFIG["host_os"])
|
|
11
|
+
|
|
12
|
+
PA_WIN_DIRECT_SOUND_USE_LOW_LEVEL_LATENCY_PARAMETERS = 0x01
|
|
13
|
+
PA_WIN_DIRECT_SOUND_USE_CHANNEL_MASK = 0x04
|
|
14
|
+
|
|
15
|
+
class PaWinDirectSoundStreamInfo < FFI::Struct
|
|
16
|
+
layout :size, :ulong,
|
|
17
|
+
:hostApiType, :int,
|
|
18
|
+
:version, :ulong,
|
|
19
|
+
:flags, :ulong,
|
|
20
|
+
:framesPerBuffer, :ulong,
|
|
21
|
+
:channelMask, :ulong
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|