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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +42 -0
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +10 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +388 -0
  7. data/Rakefile +8 -0
  8. data/examples/host_api_info.rb +16 -0
  9. data/examples/level_meter.rb +39 -0
  10. data/examples/list_devices.rb +17 -0
  11. data/examples/passthrough.rb +39 -0
  12. data/examples/record_to_file.rb +42 -0
  13. data/examples/sine_wave.rb +46 -0
  14. data/examples/sine_wave_blocking.rb +45 -0
  15. data/lib/portaudio/blocking_stream.rb +182 -0
  16. data/lib/portaudio/buffer.rb +61 -0
  17. data/lib/portaudio/c/constants.rb +53 -0
  18. data/lib/portaudio/c/enums.rb +84 -0
  19. data/lib/portaudio/c/functions.rb +93 -0
  20. data/lib/portaudio/c/host_api/alsa.rb +40 -0
  21. data/lib/portaudio/c/host_api/asio.rb +51 -0
  22. data/lib/portaudio/c/host_api/core_audio.rb +54 -0
  23. data/lib/portaudio/c/host_api/direct_sound.rb +26 -0
  24. data/lib/portaudio/c/host_api/jack.rb +28 -0
  25. data/lib/portaudio/c/host_api/pulse_audio.rb +28 -0
  26. data/lib/portaudio/c/host_api/wasapi.rb +238 -0
  27. data/lib/portaudio/c/host_api/wave_format.rb +105 -0
  28. data/lib/portaudio/c/host_api/wdm_ks.rb +73 -0
  29. data/lib/portaudio/c/host_api/wmme.rb +54 -0
  30. data/lib/portaudio/c/library.rb +122 -0
  31. data/lib/portaudio/c/structs.rb +66 -0
  32. data/lib/portaudio/configuration.rb +20 -0
  33. data/lib/portaudio/device.rb +87 -0
  34. data/lib/portaudio/error.rb +131 -0
  35. data/lib/portaudio/host_api.rb +89 -0
  36. data/lib/portaudio/stream.rb +177 -0
  37. data/lib/portaudio/stream_parameters_builder.rb +59 -0
  38. data/lib/portaudio/version.rb +5 -0
  39. data/lib/portaudio.rb +79 -0
  40. data/spec/integration/blocking_playback_spec.rb +21 -0
  41. data/spec/integration/callback_playback_spec.rb +26 -0
  42. data/spec/integration/device_enumeration_spec.rb +11 -0
  43. data/spec/integration/recording_spec.rb +25 -0
  44. data/spec/portaudio_spec.rb +43 -0
  45. data/spec/spec_helper.rb +17 -0
  46. data/spec/unit/blocking_stream_spec.rb +66 -0
  47. data/spec/unit/buffer_spec.rb +25 -0
  48. data/spec/unit/c/constants_spec.rb +13 -0
  49. data/spec/unit/c/enums_spec.rb +18 -0
  50. data/spec/unit/c/functions_spec.rb +14 -0
  51. data/spec/unit/c/host_api/asio_spec.rb +14 -0
  52. data/spec/unit/c/host_api/core_audio_spec.rb +18 -0
  53. data/spec/unit/c/host_api/direct_sound_spec.rb +9 -0
  54. data/spec/unit/c/host_api/jack_spec.rb +9 -0
  55. data/spec/unit/c/host_api/pulse_audio_spec.rb +9 -0
  56. data/spec/unit/c/host_api/wasapi_spec.rb +34 -0
  57. data/spec/unit/c/host_api/wave_format_spec.rb +17 -0
  58. data/spec/unit/c/host_api/wdm_ks_spec.rb +13 -0
  59. data/spec/unit/c/host_api/wmme_spec.rb +16 -0
  60. data/spec/unit/c/library_spec.rb +18 -0
  61. data/spec/unit/device_spec.rb +54 -0
  62. data/spec/unit/error_spec.rb +32 -0
  63. data/spec/unit/host_api_spec.rb +53 -0
  64. data/spec/unit/stream_parameters_builder_spec.rb +32 -0
  65. data/spec/unit/stream_spec.rb +76 -0
  66. 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