Chromed Shark

My various ramblings about programming

Update on Ruby-audio

As a lot has changed over the past two weeks with ruby-audio, I thought an update would be in order.

After trying to put the existing version (0.2.0) into production code, I ran into a whole bunch of issues with the API. It was not ruby-like at all, which made the code I was writing look ugly. In addition, reading into a small buffer and writing out to a new sound wasn’t possible to do without a lot of unnecessary object instantiation, as there was no API for writing only a portion of a buffer out to the sound. Hence, a rewrite was in order. Armed with copies of the ruby 1.8 and 1.9 source code, I set out to re-write the C extension with a prettier API and without the previous version’s issues. The result is ruby-audio version 1.0 (now 1.2.0 as of this writing).

ruby-audio now has three data classes - Sound, Buffer, and SoundInfo. These correspond to their C parents - CSound, CBuffer, and CSoundInfo. SoundInfo maps to the SF_INFO struct, providing information like sound length, channel count, and format. Buffer is a thin wrapper around a C array of one of the four datatypes supported for read and write by libsndfile. Finally, Sound provides all the standard functions you would expect from an IO object, including seeks, reads, and writes.

With that out of the way, let’s look at some code. The following example takes an array of compatible sound files and numbers and turns it into a single one-channel wav.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
require 'rubygems'
require 'ruby-audio'

# Create wav output file
info = RubyAudio::SoundInfo.new :channels => 1, :samplerate => 44100,
                                :format => RubyAudio::FORMAT_WAV|RubyAudio::FORMAT_PCM_16
out = RubyAudio::Sound.open('out.wav', 'w', info)

# Initialize read/write and pause buffers
one_sec = RubyAudio::Buffer.double(44100)
one_sec.real_size = 44100
buf = RubyAudio::Buffer.double(10000)

# For numbers, insert a pause for the given number of milliseconds
# For strings, open the sound file and append
wavs.each do |wav|
  if wav.is_a?(Numeric) # Pause
    secs = (wav/1000).to_i
    millisecs = wav % 1000

    # Handle milliseconds
    if millisecs > 0
      one_sec.real_size = (44100 * millisecs/1000).to_i
      out.write(one_sec)
      one_sec.real_size = 44100
    end
    secs.times { out.write(one_sec) }
  else
    RubyAudio::Sound.open(wav, 'r') do |snd|
      out.write(buf) while snd.read(buf) > 0
    end
  end
end

out.close

If you have any issues with the API or features you’d like to see implemented, don’t hesitate to fork and fix it on github, add it to the issues, or send me an e-mail.