The Audio Diaries Ch. 2 – Using RubyAudio

By Loren Segal on June 06, 2013 at 620:855:226 PM

I’m still working on embedding an interpreter into a VST 2.x plugin. I have most of the code, in place, but surprisingly, finding a simple VST host to test my plugin with is proving much harder; I’m trying to stick to open source tools. Once loaded, the other challenge will be how to get Ruby code into the plugin, and then fixing up the processReplacing function such that whatever Ruby code is loaded can read and write float Array buffers.

Until then, I’ve decided to post some of my early audio tests with the ruby-audio gem. Installing it is pretty easy, but it relies on libsndfile which you will need to brew install (or other package manager). This worked for me:

brew install libsndfile
gem install ruby-audio

Then it’s just a matter of using the API, which is not very heavily documented but not too hard to figure out. I managed to create a simple sine wave with the following code:

require 'ruby-audio'
include RubyAudio

sample_rate = 48000
len = 10   # 10 seconds
freq = 440 # Play the C note
amp = 0.9  # Almost full volume

# The sample_rate dictates how many samples (or data points) make up
# ONE SECOND of audio. To get 10 seconds of data points we multiply
# our duration by the sample_rate.
buf = Buffer.float(sample_rate * len)
buf.size.times do |i| # for each data point
  sine_rad = ((freq * Math::PI * 2) / sample_rate) * i
  buf[i] = amp * Math.sin(sine_rad)
end

# Create the .wav file and write it with our buffer
format = FORMAT_WAV | FORMAT_PCM_16
info = SoundInfo.new(channels: 1, samplerate: sample_rate, format: format)
snd = Sound.new('out.wav', 'w', info)
snd.write(buf)
snd.close

After reading Robin Schmidt’s tutorial on Sinusoids and the Frequency Domain, I was recalled back to my algebra days when we talked about sinusoidal periods, at which point I remembered that the sine function has a natural frequency of 2π (6.28…). That means if we want to generate a sine wave at 440Hz we first have to normalize the period to 1, and that is done by multiplying the frequency by 2π. Robin explains it better than me. Unfortunately, the part about sample rates was omitted from that document, and we need to take that into account too. Multiplying by 2π normalizes the function so that a period occurs from t=0..1, t=1..2, etc. (“one second”), but our one second is actually at sample_rate on the x axis, so we have to re-normalize for that by dividing. That’s what our sine_rad variable refers to.

Writing the wave file is fairly straightforward. You give it a sample rate, number of channels (our buffer only contains one channel, which means the file is mono, not stereo). The only somewhat confusing part is the format, which I took from the documentation.

For reference, here is how a square wave could be created:

buf.size.times do |i|
  buf[i] = amp * (-1) ** (freq * i.to_f / sample_rate).floor
end

Using (–1)x is a nifty little trick to oscillate from [-1,+1] as long as x is an integer (the value is a complex number if x is not an integer, and that’s complicated!).

The next post will hopefully have more progress on the VST plugin.

Questions? Comments? Follow me on Twitter (@lsegal) or email me.