Project

portaudio

0.0
No release in over 3 years
PortAudio V19 binding with both low-level C API mapping and high-level Ruby wrappers.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 1.15
 Project Readme

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.dll
  • libportaudio.dll
  • libportaudio-2.dll
  • portaudio_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 portaudio

or:

gem install portaudio

Configuration

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.so

or 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 = false is 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 = true

or 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
end

PortAudio.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
end

Useful helpers:

  • PortAudio::Device.count
  • PortAudio::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:

  • device accepts either a PortAudio::Device object or a device index
  • channels defaults to 1
  • format defaults to :float32
  • latency defaults to 0.0
  • host_api_stream_info must be FFI::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
end

write accepts either:

  • A packed binary String
  • An Array of 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)
end

read 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
end

Callback return values can be either PortAudio symbols or the normalized Ruby aliases:

  • :paContinue or :continue
  • :paComplete or :complete
  • :paAbort or :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
end

Buffer helper

PortAudio::Buffer allocates a PortAudio-sized memory region and exposes a few convenience methods:

  • pointer
  • bytesize
  • write_floats
  • to_float_array
  • clear

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::InvalidDeviceError
  • PortAudio::FormatNotSupportedError
  • PortAudio::DeviceUnavailableError
  • PortAudio::InputOverflowError
  • PortAudio::OutputUnderflowError
  • PortAudio::HostApiError

PortAudio::HostApiError also exposes:

  • code
  • host_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::Functions
  • PortAudio::C::Constants
  • PortAudio::C::Enums
  • PortAudio::C::Structs

Platform-specific host API extension bindings are available under PortAudio::C::HostAPI:

  • ALSA
  • ASIO
  • CoreAudio
  • DirectSound
  • JACK
  • PulseAudio
  • WASAPI
  • WDMKS
  • WaveFormat
  • WMME

If the loaded PortAudio library does not export a specific extension symbol, calling that Ruby method raises PortAudio::UnsupportedFunctionError.

Development

Install dependencies:

bundle install

Run the default test suite:

bundle exec rspec
bundle exec rake

Integration specs use real audio hardware and are opt-in:

PORTAUDIO_RUN_INTEGRATION=1 bundle exec rspec spec/integration

License

MIT