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.
Add this line to your application's Gemfile:
gem "sound_util"And then execute:
bundle installGenerate 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.
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 interpolationWave#resample(rate, frames: nil) performs linear interpolation and updates both sample rate and frame count (pass frames: to override duration).
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 formatSoundUtil::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.
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).
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.
SoundUtil::Wave.sine(duration_seconds: 1, frequency: 440).previewRenders 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.
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 44100Use --output path.pcm to write to a file instead of stdout.
After checking out the repo, run bundle install to install dependencies. Then, run rake to run the tests and linter.
Bug reports and pull requests are welcome on GitHub at https://github.com/rbutils/sound_util.
The gem is available as open source under the terms of the MIT License.