In this article I’ll present some bits of the work we’ve been doing daniel309 (author of jVSTwRapper) and I, around the topic: is it possible to use Ruby to make it easier to prototype VST plugins?
It looks like yes – here’s the most simple example:
class RubyGain < OpazPlug
plugin "RubyGain", "Opaz", "LoGeek"
can_do "1in1out", "plugAsChannelInsert", "plugAsSend"
unique_id "RGaN"
param :gain, "Gain", 1.0, "dB"
def process(inputs, outputs, sampleFrames)
inBuffer, outBuffer = inputs[0], outputs[0]
for i in (0..sampleFrames-1)
outBuffer[i] = inBuffer[i] * gain
end
end
end
We’ve been working iteratively using Git, and a few things came out:
- a DSL that allows to use declarative syntax for parameters handling (which usually involves a lot of boilerplate code)
- the possibility to mix and match Java (for performance) and JRuby (for conciseness and declarativeness) as needed
- a set of rake tasks to make it easier to bundle the plugins for the 3 platforms (Windows, Mac OS X, Linux)
- some simple but working, portable plugins
- other promising things like UI DSL, IRB console for live code hacking (!)
- a lot of fun :)
I’ll keep how things work internally for a later post (a lot of glueing), here I’ll present the basics and examples of plugins, but now I’m totally sold on the fact that JRuby is a great way to leverage the existing JVM infrastructure™.
The overall performance is very slow compared to the Java or C++ equivalent, but it provides a very agile way to try out ideas before optimizing and picking either Java or C++ once the idea is mostly there.
Some definitions to get started
- VST – one of the most common norms to create audio plugins, supported by hosts like Ableton Live, Reaper and many more
- VST SDK – a C++ SDK provided by Steinberg to develop VST plugins
- jVSTwRapper – a Java VST framework, sitting on top of the VST SDK and allowing to create plugins with Java in a portable fashion
- Opaz-PlugDK – a JRuby DSL + set of rake tasks, sitting on top of jVSTwRapper, allowing to create portable VST plugins with JRuby (and Java when better performance is needed)
How to use the simple gain
If you git clone the opaz-plugdk repo, you’ll see that all plugins are under a plugins folder.
The following task (you’ll need javac in the path) will build the 3 versions of the plugins:
$ rake package plugin=RubyGain
[snip]
$ ls plugins/RubyGain/build/
linux osx win
These three folders contain a RubyGain.vst folder ready to be added to the VST folder configured in your host.
Improving the performance: HybridGain
Our rake tasks automatically compile the .java files when rake package is called. Here’s a notably faster version of the previous gain – it mixes JRuby code (for the declaration and glue code) with Java (for CPU intensive code).
HybridGain.rb
include_class Java::HybridGainTools
class HybridGain < OpazPlug
plugin "HybridGain", "Opaz", "LoGeek"
can_do "1in1out", "plugAsChannelInsert", "plugAsSend"
unique_id 9876549
param :gain, "Gain", 1.0
def process(inputs, outputs, sampleFrames)
HybridGainTools.applyGain(inputs[0], outputs[0], sampleFrames, gain)
end
end
HybridGain.java
// factor out computation-intensive stuff to java
public class HybridGainTools {
public static void applyGain(float[] input, float[] output, int sampleFrames, float gain) {
for (int i=0; i < sampleFrames; i++) {
output[i] = gain * input[i];
}
}
}
Thanks to JRuby, it’s quite easy to delegate the CPU intensive stuff to Java – we can use the java classes straight from the ruby code.
A more useful example: HybridFilta
The hybrid filter is a LP/HP cut-off resonance filter (quite used in “filtered house” for instance). Although longer, the code is still an order of magnitude shorter than a full Java or full C++ equivalent plugin.
HybridFilta.rb
include_class Java::FilterTools
class HybridFilta < OpazPlug
plugin "HybridFilta", "Opaz", "LoGeek"
can_do "1in1out", "plugAsChannelInsert", "plugAsSend"
unique_id "hflt"
param :cut_off, "Cut Off", 1.0
param :resonance, "Resonance", 0.1
param :mode, "Mode (LP or HP)", 0
def filter
@filter ||= FilterTools.new
end
def use_low_pass?
mode < 0.5
end
def process(inputs, outputs, sampleFrames)
filter.recomputeParameters(cut_off, resonance, use_low_pass?, sample_rate)
filter.apply(inputs, outputs, sampleFrames)
end
end
HybridFilta.java
public class FilterTools {
float f,r,c,a1,a2,a3,b1,b2;
public void recomputeParameters(float cutoff, float resonance, boolean lowPassMode, float sampleRate) {
r = (1-resonance) * 10f; // r = rez amount, from sqrt(2) to ~ 0.1
if (r<0.1f)
r=0.1f;
f = cutoff * sampleRate/4; // replace 44100 by context.samplerate later (or /4 ?)
if (f<40f)
f=40f;
if (lowPassMode) // lowpass mode
{
c = (float)(1.0 / Math.tan(Math.PI * f / sampleRate));
a1 = 1.0f / ( 1.0f + r * c + c * c);
a2 = 2* a1;
a3 = a1;
b1 = 2.0f * ( 1.0f - c*c) * a1;
b2 = ( 1.0f - r * c + c * c) * a1;
}
else // hipass mode
{
c = (float)Math.tan(Math.PI * f / sampleRate);
a1 = 1.0f / ( 1.0f + r * c + c * c);
a2 = -2*a1;
a3 = a1;
b1 = 2.0f * ( c*c - 1.0f) * a1;
b2 = ( 1.0f - r * c + c * c) * a1;
}
}
// history elements ih(x)_(y) : input sample n-y on voice x, oh(x)_y : output sample n-y on voice x
float ih1_1=0,ih1_2=0,oh1_1=0,oh1_2=0;
float computeFilter(float inp0,float inp1,float inp2,float outp1,float outp2)
{
return a1 * inp0 + a2 * inp1 + a3 * inp2 - b1*outp1 - b2*outp2;
}
public void apply(float[][] inputs, float[][] outputs, int sampleFrames) {
// one channel only for the moment
float[] input0 = inputs[0];
float[] output0 = outputs[0];
// TODO: move the whole following code to some more functional-styled code, if possible
output0[0] = computeFilter( input0[0], ih1_1, ih1_2, oh1_1,oh1_2);
output0[1] = computeFilter( input0[1], input0[0], ih1_1, output0[0], oh1_1);
for (int sample=2;sample<sampleFrames;sample++)
output0[sample] = a1*input0[sample] + a2*input0[sample-1] + a3*input0[sample-2] - b1*output0[sample-1] - b2*output0[sample-2];
// save history
ih1_1 = input0[sampleFrames-1];
ih1_2 = input0[sampleFrames-2];
oh1_1 = output0[sampleFrames-1];
oh1_2 = output0[sampleFrames-2];
}
}
Going further: implementing VST instruments
Instruments are plugins that take midi in and generate audio (short version). We implemented a simple synth here – overall I find that Ruby is nice to handle the midi events and logic.
Where do we go from here ?
Well it’s been an interesting road :) Getting access to the libraries provided by both Java and Ruby, coding in JRuby as long as it’s beneficial and optimizing when needed is definitely interesting. And I wouldn’t have achieved anything if I hadn’t reused the existing infrastructure (jVSTwRapper makes all the heavy lifting here).
At some point I’ll write a sequel post to explain how things work underneath – I think we’re going to meet this kind of pattern a lot in the coming years, working either with JRuby or IronRuby.
The comments system is brand new - don't be afraid to comment!
- Monitoring File Changes and Getting Notified via Growl (February 14th, 2010)
- How to use Google Calendar and Rufus-Google for Basic Time Tracking (November 27th, 2009)
- Introducing Learnivore.com (September 15th, 2009)
- How to create small, unique tokens in Ruby (July 2nd, 2009)
- Detecting Which Ruby Interpreter is Running (JRuby, IronRuby) (March 4th, 2009)
- How to create an empty Rails Edge application (January 28th, 2009)
- How to Freeze Gems with Rails >= 2.1 (December 23rd, 2008)
- Thoughts on IronRuby and .Net Testing (December 1st, 2008)
- How to Retrieve Delicious Tags and Number of Bookmarks for a Given Url (November 30th, 2008)
- Fixing Symbol not found _rl_filename_completion_function (November 6th, 2008)
- How to Generate a Gradient for your CSS using RMagick (October 21st, 2008)
- Analyzing Your GMail History (September 18th, 2008)
- Data Visualization with Ruby and RMagick - Where Are Those Bikes ? (March 31st, 2008)
- All-Time Classics: a Selection of Recommended Books for Software Developers (February 23rd, 2008)

