portaudio
portaudio is a Ruby FFI binding for PortAudio V19.
It exposes the low-level PortAudio API under PortAudio::C and a small set of Ruby wrappers for common tasks such as device discovery, host API discovery, blocking streams, callback streams, and buffer conversion.
Public API overview
| API | Purpose |
|---|---|
PortAudio |
Lifecycle helpers such as init, terminate, with_portaudio, and check_error!
|
PortAudio::Device |
Enumerate devices, inspect defaults, and search by name |
PortAudio::HostAPI |
Enumerate host APIs and access their devices |
PortAudio::BlockingStream |
Blocking read / write streams |
PortAudio::Stream |
Callback-based streams |
PortAudio::Buffer |
Convert between raw PortAudio bytes and Ruby float arrays |
PortAudio::C |
Low-level functions, constants, enums, and structs |
PortAudio::C::HostAPI |
Low-level host API extension bindings |
Portaudio remains available as a compatibility alias for PortAudio.
Requirements
- Ruby
>= 3.1 ffi >= 1.15- A PortAudio shared library installed on the system
Install PortAudio
Install the runtime and development files first:
- macOS:
brew install portaudio - Ubuntu/Debian:
sudo apt-get install -y libportaudio2 portaudio19-dev - Windows (RubyInstaller2 UCRT):
ridk exec pacman -S mingw-w64-ucrt-x86_64-portaudio - Windows (RubyInstaller2 MINGW):
ridk exec pacman -S mingw-w64-x86_64-portaudio
On Windows, make one of these DLLs available on PATH:
portaudio.dlllibportaudio.dlllibportaudio-2.dllportaudio_x64.dll
You can also point the gem at a specific library file with PORTAUDIO_LIBRARY_PATH or PortAudio::Configuration.library_path.
Install the gem
bundle add portaudioor:
gem install portaudioConfiguration
The shared library is loaded lazily on first use. If loading fails, the gem raises PortAudio::LibraryNotLoadedError.
Override the library path
Use an environment variable:
export PORTAUDIO_LIBRARY_PATH=/custom/path/libportaudio.soor configure it in Ruby:
PortAudio::Configuration.library_path = "/custom/path/libportaudio.so"Set the path before the first PortAudio call when possible.
Warning handling for blocking streams
PortAudio::BlockingStream#read and #write treat overflow/underflow warning codes specially.
-
PortAudio::Configuration.strict_warnings = falseis the default - When strict mode is off, warning codes are stored in
stream.last_warning - When strict mode is on, warning codes raise normal Ruby exceptions such as
PortAudio::InputOverflowError
You can set this globally:
PortAudio::Configuration.strict_warnings = trueor per call:
chunk = stream.read(256, strict: false)Lifecycle
Use PortAudio.with_portaudio for normal code. It initializes PortAudio, yields to your block, and terminates it in ensure.
PortAudio.with_portaudio do
puts PortAudio.initialized? # => true
endPortAudio.init / PortAudio.terminate are reference-counted, so nested usage is safe.
Device and host API discovery
PortAudio.with_portaudio do
puts "Host APIs:"
PortAudio::HostAPI.all.each do |host_api|
puts "#{host_api.index}: #{host_api.name} (#{host_api.type_symbol})"
end
puts
puts "Devices:"
PortAudio::Device.all.each do |device|
puts [
"#{device.index}: #{device.name}",
"host_api=#{device.host_api.name}",
"in=#{device.max_input_channels}",
"out=#{device.max_output_channels}",
"rate=#{device.default_sample_rate}"
].join(" ")
end
default_input = PortAudio::Device.default_input
default_output = PortAudio::Device.default_output
p default_input
p default_output
endUseful helpers:
PortAudio::Device.countPortAudio::Device.find_by_name("built-in")PortAudio::HostAPI.default-
PortAudio::HostAPI.find_by_type(:paCoreAudio)or another PortAudio host API type symbol
Stream parameter hashes
Both PortAudio::BlockingStream and PortAudio::Stream accept input: and output: hashes with the same shape:
{
device: device_or_index,
channels: 2,
format: :float32,
latency: 0.0,
host_api_stream_info: pointer_or_struct
}Notes:
-
deviceaccepts either aPortAudio::Deviceobject or a device index -
channelsdefaults to1 -
formatdefaults to:float32 -
latencydefaults to0.0 -
host_api_stream_infomust beFFI::Pointer-compatible
Supported sample format symbols:
:float32:int32:int24:int16:int8:uint8
Blocking streams
Use PortAudio::BlockingStream for explicit reads and writes.
Write audio to the default output device
PortAudio.with_portaudio do
output = PortAudio::Device.default_output
raise "No default output device" unless output
stream = PortAudio::BlockingStream.new(
output: { device: output, channels: 1, format: :float32 },
sample_rate: output.default_sample_rate,
frames_per_buffer: 256
)
samples = Array.new(256, 0.0)
stream.start
100.times { stream.write(samples) }
stream.stop
stream.close
endwrite accepts either:
- A packed binary
String - An
Arrayof sample values, which is converted according to the configured output format
Read input and decode it with PortAudio::Buffer
PortAudio.with_portaudio do
input = PortAudio::Device.default_input
raise "No default input device" unless input
stream = PortAudio::BlockingStream.new(
input: { device: input, channels: 1, format: :float32 },
sample_rate: input.default_sample_rate,
frames_per_buffer: 128
)
stream.start
raw = stream.read(128)
stream.stop
stream.close
buffer = PortAudio::Buffer.new(frames: 128, channels: 1, format: :float32)
buffer.pointer.put_bytes(0, raw)
p buffer.to_float_array.first(8)
endread returns raw bytes. PortAudio::Buffer is the convenience wrapper for converting those bytes into floats.
Callback streams
Use PortAudio::Stream when PortAudio should pull audio from a callback.
PortAudio.with_portaudio do
output = PortAudio::Device.default_output
raise "No default output device" unless output
remaining_frames = (output.default_sample_rate * 0.2).round
stream = PortAudio::Stream.new(
output: { device: output, channels: 1, format: :float32 },
sample_rate: output.default_sample_rate,
frames_per_buffer: 128
) do |_input_ptr, output_ptr, frame_count, _time_info, _status_flags, _user_data|
samples = Array.new(frame_count) do
remaining_frames -= 1 if remaining_frames.positive?
0.0
end
output_ptr.put_array_of_float(0, samples)
remaining_frames.zero? ? :complete : :continue
end
stream.on_finished { puts "stream finished" }
stream.start
sleep 0.1 while stream.active?
stream.close
raise stream.callback_error if stream.callback_error
endCallback return values can be either PortAudio symbols or the normalized Ruby aliases:
-
:paContinueor:continue -
:paCompleteor:complete -
:paAbortor:abort
If the callback raises, the stream aborts and the exception is stored in stream.callback_error.
Stream.open convenience helper
Use PortAudio::Stream.open to start a stream inside a block and always close it afterward:
PortAudio.with_portaudio do
output = PortAudio::Device.default_output
raise "No default output device" unless output
remaining_frames = (output.default_sample_rate * 0.2).round
callback = proc do |_input_ptr, output_ptr, frame_count, *_rest|
samples = Array.new(frame_count) do
remaining_frames -= 1 if remaining_frames.positive?
0.0
end
output_ptr.put_array_of_float(0, samples)
remaining_frames.zero? ? :complete : :continue
end
PortAudio::Stream.open(
output: { device: output, channels: 1, format: :float32 },
sample_rate: output.default_sample_rate,
frames_per_buffer: 128,
callback: callback
) do |stream|
stream.start
sleep 0.1 while stream.active?
end
endBuffer helper
PortAudio::Buffer allocates a PortAudio-sized memory region and exposes a few convenience methods:
pointerbytesizewrite_floatsto_float_arrayclear
to_float_array and write_floats currently support :float32, :int16, and :int32.
Errors
All negative PaError codes are mapped to Ruby exceptions through PortAudio.check_error!.
Common examples:
PortAudio::InvalidDeviceErrorPortAudio::FormatNotSupportedErrorPortAudio::DeviceUnavailableErrorPortAudio::InputOverflowErrorPortAudio::OutputUnderflowErrorPortAudio::HostApiError
PortAudio::HostApiError also exposes:
codehost_error_info
If you call low-level functions directly, use PortAudio.check_error!(code) on the return value.
Low-level bindings
The gem exposes low-level PortAudio bindings under:
PortAudio::C::FunctionsPortAudio::C::ConstantsPortAudio::C::EnumsPortAudio::C::Structs
Platform-specific host API extension bindings are available under PortAudio::C::HostAPI:
ALSAASIOCoreAudioDirectSoundJACKPulseAudioWASAPIWDMKSWaveFormatWMME
If the loaded PortAudio library does not export a specific extension symbol, calling that Ruby method raises PortAudio::UnsupportedFunctionError.
Development
Install dependencies:
bundle installRun the default test suite:
bundle exec rspec
bundle exec rakeIntegration specs use real audio hardware and are opt-in:
PORTAUDIO_RUN_INTEGRATION=1 bundle exec rspec spec/integrationLicense
MIT