Project

sound_util

0.0
No release in over 3 years
Lightweight audio buffer utilities for manipulating sound in memory using IO::Buffer.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Runtime

>= 0.5.0
~> 1.2
 Project Readme

SoundUtil

CI

SoundUtil is a lightweight Ruby library focused on manipulating sound data directly in memory. Its primary goal is to help scripts process and analyze audio buffers using IO::Buffer. The API is still evolving and should be considered unstable until version 1.0.

Installation

Add this line to your application's Gemfile:

gem "sound_util"

And then execute:

bundle install

Usage

Generate a 440 Hz stereo sine wave and stream it to stdout (pipeable into aplay, ffplay, etc.):

require "sound_util"

duration    = 2.0
sample_rate = 44_100
channels    = 2

wave = SoundUtil::Wave.sine(
  duration_seconds: duration,
  sample_rate: sample_rate,
  channels: channels,
  frequency: 440.0,
  amplitude: 0.6
)

wave.pipe($stdout)

Blocks passed to SoundUtil::Wave.new yield the frame index and may return a scalar (applied to all channels) or an array of per-channel sample values. Floats are treated as -1.0..1.0 amplitudes and integers are clamped to the target PCM range.

Filters

Waves provide mutable/immutable filters similar to image_util:

wave = SoundUtil::Wave.sine(duration_seconds: 1, frequency: 220)

wave = wave.gain(0.25)          # return a quieter copy
wave.fade_in!(seconds: 0.1)     # in-place fade-in over the first 0.1s
wave.fade_out!(seconds: 0.1)    # in-place fade-out over the last 0.1s
wave = wave.resample(48_000)         # adjust sample rate with linear interpolation

Wave#resample(rate, frames: nil) performs linear interpolation and updates both sample rate and frame count (pass frames: to override duration).

WAV input/output

wave  = SoundUtil::Wave.from_file("input.wav")
bytes = wave.to_string(:wav)               # defaults to source format

wave = SoundUtil::Wave.new(frames: 2) { 0.2 }
wave.to_file("output.wav")                # writes RIFF/WAVE by default

io = StringIO.new
wave.to_file(io, :wav, sample_format: :f32le) # encode with custom format

SoundUtil::Wave.from_data(data, format: :wav) decodes in-memory WAV blobs, while from_file accepts paths or IO objects (auto-detecting formats via the Magic helper). Encoding supports the common PCM/float variants (:u8, :s16le, :s24le, :s32le, :f32le, :f64le). Pass sample_format: when you need to transcode to a different width before writing.

Combining waves

intro = SoundUtil::Wave.sine(duration_seconds: 0.5, frequency: 440)
outro = SoundUtil::Wave.silence(duration_seconds: 0.25)

full  = intro + outro           # append, returns a new wave
intro << outro                  # append in place

vocals = SoundUtil::Wave.sine(duration_seconds: 0.5, frequency: 220)
mix    = vocals | intro         # mix with sample clamping

stereo = vocals & intro         # stack channels together
mono   = stereo.channel(0)      # extract an individual channel

#+ and #<< append clips end-to-end (requiring matching sample rates, formats, and channel counts). #| mixes two clips frame-by-frame with automatic clamping, and #& stacks their channels using the longest duration from either wave. Use Wave#channel(index) to materialize a single channel as its own wave.

wave.play                         # pipes to the default `aplay` command
wave.play(command: ["ffplay", "-autoexit", "-nodisp", "-f", "s16le", "-ar", wave.sample_rate.to_s, "-ac", wave.channels.to_s, "-"])

Wave#play shells out to aplay by default. Provide command: with a string/array when targeting other tools (e.g. ffplay, afplay).

Indexing

wave[0]          # => [Float, Float] for all channels in the first frame
wave[0, 1]       # => Float for a specific channel
wave[100..200]   # => new Wave containing that frame range
wave[0..-1, 0]   # => mono Wave extracted from channel 0

wave[0] = [0.5, -0.5]
wave[10..20] = SoundUtil::Wave.sine(duration_seconds: 0.25, frequency: 880)

Values are normalised to the -1.0..1.0 range when read and clamped when written.

Preview

SoundUtil::Wave.sine(duration_seconds: 1, frequency: 440).preview

Renders a compact Sixel chart (requires ImageUtil and terminal support). Pass width:/height: options to adjust the preview

Wave instances now implement inspect_image, so pretty-printing in IRB (pp wave) renders the same preview via ImageUtil's inspectable interface.

CLI

The Thor CLI emits raw PCM suitable for piping into a sound device:

bundle exec exe/sound_util generate sine \
  --seconds 2 --rate 44100 --channels 2 --frequency 440 --amplitude 0.6 \
  | aplay -f S16_LE -c 2 -r 44100

Use --output path.pcm to write to a file instead of stdout.

Development

After checking out the repo, run bundle install to install dependencies. Then, run rake to run the tests and linter.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/rbutils/sound_util.

License

The gem is available as open source under the terms of the MIT License.