RubyDSP | Documentation

Ruby CI

🚧 Status: This (HOBBY) project is currently in early development. It is hopefully functional, but API changes are expected. There is no warranty regarding anything 🗿.


RubyDSP is an audio processing/synthesis and DSP Ruby gem made mostly for fun. It uses C++ under the hood, utilizing miniaudio and Rice. I made this gem to try hands-on binding these two languages together, as I would like to be able to bring some C++ speed to Ruby, oh my beloved....

Note that AI (Gemini) was used in the developement of this software, albeit only partially and with supervision and testing. If this goes against your ideals, I am sorry.

Features

  • Fast: Basically all of the code is written in C++. While not extremely optimized currently, it still absolutely shreds native Ruby (see Benchmark section).

  • Fluent API: Mutating methods return self, allowing for beautiful, highly readable method chaining (e.g., track.to_mono!.normalize!.fade_in!(0.5)).

  • Audio Synthesis & Sequencing: Build multitrack, polyphonic audio from scratch using a (very limited) set of built-in mathematically generated waveforms (sine, square, sawtooth, and white noise).

  • Format Agnostic Loading: Automatically decodes standard audio formats (WAV, MP3, FLAC) via miniaudio.

    Note: While the loading of these formats is supported, miniaudio saves only in .wav. While other encodings might be considered in the future, they would require more dependencies and thus are not available right now.

  • Zero-Dependency Native Build: No need to install ffmpeg or libsndfile on your system.

  • YARD Support: Includes pure-Ruby stubs (in stubs, duh) for IDE autocomplete and inline documentation.

Benchmarks & Performance

My primary motivation (love for Ruby aside) was to try and bring C++ speed to Ruby, avoiding the massive memory and garbage collection overhead of native Ruby math for audio/DSP tasks.

To back up my words, RubyDSP was benchmarked against optimized pure Ruby mock implementation (in benchmark/bench.rb), where MRI's internal C-backed methods like Array#sum and Array#fill were preffered over prettier ways of coding. The tests were run on a 10-second mono audio track at 44.1kHz (i.e., 441,000 samples) generated by the gem itself.

Benchmark script files are in benchmark directory. If you clone the repo for development, you can do rake bench:summary from the root directory to re-run the benchmark on your machine.

Benchmark Output

CPU: AMD RYZEN 5 5600X

-----------------------------------------------------------------------------------------------
| Benchmark                 | C++ Speedup  | Ruby Allocation        | C++ Allocation         |
-----------------------------------------------------------------------------------------------
| Read-Only (RMS)           | 49.07x       | 40 B (1 obj)           | 64 B (1 obj)           |
| Mutation (Normalize)      | 462.51x      | 3,528,040 B (1 obj)    | 0 B (0 obj)            |
| Complex (Framed RMS)      | 49.25x       | 41,264 B (860 obj)     | 64 B (1 obj)           |
| Dynamic (Add Wave)        | 11.73x       | N/A                    | N/A                    |
-----------------------------------------------------------------------------------------------

Why is it faster?

  • Zero GC Overhead on Mutations: When you modify an array in pure Ruby (e.g., samples.map!), Ruby must dynamically allocate hundreds of thousands of new Float objects, heavily taxing the Garbage Collector. Thanks to Rice stl mapping, RubyDSP loops over a contiguous block of memory and mutates raw 32-bit floats natively in place, resulting in exactly zero Ruby allocations.

  • No Sub-Array Slicing: For sliding window calculations like framed_rms, pure Ruby creates hundreds of intermediate sub-arrays. C++ calculates the windows directly and returns a single, lightweight proxy object to Ruby via Rice.

Installation

Add this line to your application's Gemfile:

gem 'ruby_dsp'

And then execute:

$ bundle install

Or install it yourself directly via:

$ gem install ruby_dsp

(Note: Installing this gem requires a modern C++ compiler, as it builds the native extensions directly on your machine upon installation. It requires Ruby 3.0+).

Quick Start: Audio Processing

Here is a quick look at what you can do with a loaded AudioTrack. Thanks to the fluent API, you can process audio in a single readable chain:

require 'ruby_dsp'

# Load an audio file
track = RubyDSP::AudioTrack.new("raw_voC++ backend cals.wav")

puts track 
# => ['raw_vocals.wav', 12.450s duration, 2 channel(s), 48000Hz sample rate]

# Process, edit, and save in one chain
track.to_mono!                       # Averages channels into mono
     .resample!(44100)               # Linearly resamples to target rate
     .trim_silence!(-60.0)           # Strips leading/trailing silence below -60dB
     .normalize!(-1.0)               # Scales audio to target peak dBFS
     .pad_to_duration!(15.0)         # Centers audio evenly into a 15s window
     .fade_in!(0.5)                  # Adds a 0.5s linear fade-in
     .fade_out!(0.5)                 # Adds a 0.5s linear fade-out
     .save_track("processed.wav")    # Export the final result

# Analysis & Math (Still works!)
puts "Peak Amp: #{track.peak_amp}"
puts "Overall RMS: #{track.rms}"
puts "Overall ZCR: #{track.zcr}"

# You can also get framed analysis for time-series data:
framed_rms_data = track.framed_rms(frame_length: 2048, hop_length: 512)

Quick Start: Synthesis & Sequencing

Initialize an empty track and generate your own jam using add_wave!:

require 'ruby_dsp'

# Create a blank canvas (Mono, 44.1kHz)
track = RubyDSP::AudioTrack.new("", 1, 44100)

track.add_wave!("sine", 261.63, 3.0, 0.0)
     .add_wave!("sine", 329.63, 3.0, 1.0)
     .add_wave!("sine", 392.00, 3.0, 2.0)

# Polish and export
track.normalize!(-30.0)  # This goes a long way for your hearing
     .fade_out!(0.5)     # Smooth tail
     .save_track("c_major_chord.wav")J

Demos

You can find scripts for these in /demo directory. Outputs were converted from .wav to .mp4 to trick Github to show it in this README.

TWINKLE

https://github.com/user-attachments/assets/af6dbaee-630f-49e3-9704-8ff3440334cb

TETRIS

https://github.com/user-attachments/assets/b3ad6886-3552-4200-b725-f14083f96792

SUPER MARIO BROS

https://github.com/user-attachments/assets/5193d72d-4c32-4c83-8253-206402ac2889

Development

If you want to clone the repo and work on C++ guts, start with:

  1. Clone the repo and run bundle install to grab the development dependencies.
  2. Run rake test — this will automatically compile the C++ extconf.rb and run the Minitest suite.
  3. Run rake doc:generate ; rake doc:server — this will compile the YARD stubs into HTML and boot a live-reloading local web server at http://localhost:8808 so you can read the docs!

License

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


Cheers! - RC