Quando eu li pela primeira vez sobre o MRuby, eu só queria brincar com ele. A primeira coisa que veio à minha mente foi um plugin VST. Eu poderia construir um pequeno wrapper e passar toda a função para o interpretador Ruby. Vou descrever como fiz isso.
Preparação
Antes de tudo, precisamos configurar algumas coisas. Comecemos com o MRuby.
MRuby
Precisamos clonar o repo do MRuby:
$ git clone <a href="https://github.com/mruby/mruby">https://github.com/mruby/mruby</a> $ cd mruby
Depois disso, precisamos configurar nossa build MRuby. Devemos modificar build_config.rb. No final do arquivo, vamos adicionar nossas especificações de build.
MRuby::Build.new('mrubyvst') do |conf|
toolchain :gcc
conf.gembox 'default'
conf.gem :core => 'mruby-eval'
conf.gem :github => 'iij/mruby-dir'
conf.gem :github => 'iij/mruby-io'
end
Neste caso, especificamos que usaremos uma cadeia de ferramentas gcc. Além disso, incluiremos todas as gems padrão e algumas outras. Em Mruby, gems são compiladas. Então, se você quiser mudar um conjunto de gems, deve recompilar o mruby build. Nós precisaremos de mruby-eval para permitir que o script Ruby carregue outros scripts ruby, mruby-dir para listar o conteúdo do diretório e mruby-io para acessar arquivos, etc.
Terminamos, devemos compilá-lo.
$ rake
No final do processo de build, veremos:
================================================
Config Name: mrubyvst
Output Directory: build/mrubyvst
Included Gems:
mruby-sprintf - standard Kernel#sprintf method
mruby-print - standard print/puts/p
mruby-math - standard Math module
mruby-time - standard Time class
mruby-struct - standard Struct class
mruby-enum-ext - Enumerable module extension
mruby-string-ext - String class extension
mruby-numeric-ext - Numeric class extension
mruby-array-ext - Array class extension
mruby-hash-ext - Hash class extension
mruby-range-ext - Range class extension
mruby-proc-ext - Proc class extension
mruby-symbol-ext - Symbol class extension
mruby-random - Random class
mruby-object-ext - Object class extension
mruby-objectspace - ObjectSpace class
mruby-fiber - Fiber class
mruby-enumerator - Enumerator class
mruby-enum-lazy - Enumerator::Lazy class
mruby-toplevel-ext - toplevel object (main) ...
mruby-compiler - mruby compiler library
mruby-bin-mirb - mirb command
- Binaries: mirb
mruby-bin-mruby - mruby command
- Binaries: mruby
mruby-bin-strip - irep dump debug section ...
- Binaries: mruby-strip
mruby-kernel-ext - Kernel module extension
mruby-class-ext - class/module extension
mruby-eval - standard Kernel#eval method
mruby-dir
mruby-io
Ótimo, acabamos de construir o MRuby. Vamos continuar.
SDK VST
Queremos criar o plugin VST. Nós temos que baixar o SDK do site Steinberg e descompactá-lo em algum lugar do computador.
DAW
Também precisamos de um DAW, que carregará nosso plugin. Eu o testei com Ableton Live 9. Existe uma versão de teste, mas também uma demo. No modo demo, você não pode salvar seu projeto, mas não precisamos desse recurso. Existem builds de 32 e 64 bits – vamos construir uma versão de 64 bits, então precisaremos de um daw de 64 bits.
Construindo o VST
A próxima coisa a ser feita, é um plugin VST em si. Vamos clonar o repositório, configurá-lo e iniciá-lo dentro do DAW.
$ git clone https://github.com/fazibear/mrubyvst $ cd mrubyvst $ rake init
Dê uma olhada no Rakefile. No topo há constantes.
MRUBY_DIR = File.expand_path('../mruby')
VST_SDK_DIR = File.expand_path('../vst-sdk')
SCRIPT_PATH = File.expand_path('./mrubyvst.rb')
VST_CLASS = 'MRubyVST'
PROGRAMS_COUNT = 10
PARAMETERS_COUNT = 4
Temos que mudar MRUBY_DIR e VST_SDK_DIR para corrigir o local dessas bibliotecas no seu computador. SCRIPT_PATH aponta para um arquivo que será carregado na VM Ruby na inicialização, e VST_CLASS é o nome da classe que o MRuby instanciará. Agora podemos construí-lo!
$ rake
Nós apenas o construímos. Funciona? Não. Precisamos copiá-lo ou vinculá-lo a um diretório especial, então o DAW pode encontrá-lo. Nos plugins Mac VST, estão em ~/Library/Audio/Plug-Ins/VST/. Mas espere. Existe um script que fará isso por você.
$ rake link
Vamos linkar sua build VST a esse diretório. Você também pode desfazer o link.
$ rake unlink
Começando um DAW
Estamos prontos para lançar o DAW. Se você não sabe como usar o plugin VST, aqui está um pequeno tutorial. Boa sorte.
É assim que o VST se parece no Ableton Live (em outro DAW, parecerá diferente).

Ótimo! O plugin funciona. Agora vamos analisar os detalhes da implementação.
Implementação
Dê uma olhada no arquivo mrubyvst.h.
No VST, existem quatro grupos de coisas que precisamos implementar.
O mais importante é o processamento. Nesse método, mudaremos os dados de áudio de entrada. Mas vamos abordar esse método mais tarde. O segundo grupo é “programas”. O programa é um estado de todos os parâmetros. E os parâmetros são variáveis únicas das quais o processamento de som depende. O último é informação. VST tem que devolver algumas informações para se identificar.
Vamos visualizá-los. No topo, vemos mrubyvst, é um nome do nosso plugin. Dropdown com empty.rb é um campo de programas. Existe uma lista de programas. E, à direita, existem quatro parâmetros com nomes e valores. Precisamos decidir quantos programas e parâmetros precisamos antes da compilação. No nosso exemplo, existem 10 programas e quatro parâmetros.
Mas espere. E quanto ao Ruby? Esse é o arquivo de cabeçalho C++! Isso está correto, precisamos criar um pequeno wrapper que transmita informações para e a partir da VM Ruby. Fique calmo.
Inicialização
O construtor é um ótimo lugar para inicializar nossa terra do Ruby. Depois disso, precisamos carregar o script Ruby e instanciar nossa classe MRubyVST. Além disso, precisamos definir algumas constantes nessa classe.
MRubyVst::MRubyVst(audioMasterCallback audioMaster): AudioEffectX(audioMaster, PROGRAMS_COUNT, PARAMETERS_COUNT) {
setUniqueID(666);
canProcessReplacing();
setNumInputs(2);
setNumOutputs(2);
mrb = mrb_open();
FILE *file = fopen(SCRIPT_PATH, "r");
if (file != NULL) {
mrb_load_file(mrb, file);
mrb_value vst_class = mrb_vm_const_get(mrb, mrb_intern_lit(mrb, VST_CLASS));
mrb_const_set(mrb, vst_class, mrb_intern_lit(mrb, "PROGRAMS_COUNT"), mrb_fixnum_value(PROGRAMS_COUNT));
mrb_const_set(mrb, vst_class, mrb_intern_lit(mrb, "PARAMETERS_COUNT"), mrb_fixnum_value(PARAMETERS_COUNT));
mrb_const_set(mrb, vst_class, mrb_intern_lit(mrb, "SAMPLE_RATE"), mrb_float_value(mrb, getSampleRate()));
mrb_const_set(mrb, vst_class, mrb_intern_lit(mrb, "SCRIPT_PATH"), mrb_str_new_cstr(mrb, (SCRIPT_PATH)));
vst_instance = mrb_instance_new(mrb, vst_class);
fclose(file);
}
}
A parte mais importante é a linha 8. Aqui é onde iniciamos a VM Ruby embutida. Em seguida, abrimos um arquivo e, se ele existe, é carregado em nossa VM.
Agora o nosso script Ruby está carregado. A linha 14 receberá nossa classe Ruby e nos permitirá estabelecer algumas constantes. Depois disso, podemos instanciá-lo e fechar o arquivo.
O script Ruby
class MRubyVST
attr_reader :vendor, :product, :effect_name, :version
def initialize
@vendor = 'Mruby'
@product = 'MrubyVST'
@effect_name = 'MRubyEffects'
@version = 0
@programs_dir = "#{File.dirname(SCRIPT_PATH)}/programs"
end
def programs
Dir.entries(@programs_dir) - ['.', '..']
end
def load_program(path)
Module.new.instance_eval(
File.open(path).read
)
end
def change_program(index)
@program = load_program("#{@programs_dir}/#{programs[index]}") if programs[index]
end
def program_name(index)
programs[index] || "-empty-"
end
def set_parameter(index, value)
@program.set_parameter(index, value) if @program
end
def parameter_name(index)
@program.parameter_name(index) if @program
end
def parameter_value(index)
@program.parameter_value(index) if @program
end
def parameter_display_value(index)
@program.parameter_display_value(index) if @program
end
def parameter_label(index)
@program.parameter_label(index) if @program
end
def process(data)
@program.process(data) if @program
end
def log(str)
io = File.open('/tmp/mrubyvst.log', 'a')
io.write(str + "\n")
io.close
end
end
Esse é o script Ruby. Todas as informações estão aqui. Fornecedor, produto, versão, etc. Nosso plugin usará programas para carregar vários módulos Ruby. Quando o usuário mudar um programa, instanciaremos a nova classe e passaremos a maioria dos métodos para esse módulo. A lista de programas é formada apenas por arquivos do diretório de programas. O submódulo fica assim:
class GainVST
def initialize
@gain = 1.0
end
def set_parameter(index, value)
@gain = value if index = 0
end
def parameter_name(index)
index == 0 ? 'Gain' : 'empty'
end
def parameter_value(index)
@gain if index == 0
end
def parameter_display_value(index)
@gain.to_s if index == 0
end
def parameter_label(index)
'dB' if index == 0
end
def process(data)
data[0].map!{ |left | left * @gain}
data[1].map!{ |right| right * @gain }
data
end
end
GainVST.new
É um plugin de ganho simples. Isso alterará o volume de áudio de entrada, dependendo de como o ganho é definido. O padrão é 1. O que esses métodos fazem? Usaremos apenas um parâmetro com índice 0 (zero).
- parameter_name: retorna um nome de parâmetro com um dado índice.
- parameter_value: retorna um valor de parâmetro com dado índice.
- parameter_display_value: retorna um valor de parâmetro com um dado índice como uma string, e você pode formatá-lo como quiser.
- parameter_label: retorna uma etiqueta do nosso parâmetro.
- set_parameter: estabelece um valor de parâmetro com um dado índice.
- process: processa dados de entrada e retorna a saída. Neste caso, irá multiplicar todos os valores de entrada com um valor de ganho.
Wrapper MRuby
Agora precisamos voltar para o nosso wrapper e implementar métodos que passem dados para Ruby e de volta. A maioria dos métodos é muito semelhante. Veja um aqui:
bool MRubyVst::getProgramNameIndexed(VstInt32 category, VstInt32 index, char* text) {
m.lock();
if(!mrb_nil_p(vst_instance) && mrb_respond_to(mrb, vst_instance, mrb_intern_lit(mrb, "program_name"))){
mrb_value mrb_name = mrb_funcall(mrb, vst_instance, "program_name", 1, mrb_fixnum_value(index));
if (!mrb_nil_p(mrb_name)) {
vst_strncpy(text, RSTRING_PTR(mrb_name), kVstMaxProgNameLen);
m.unlock();
return true;
}
}
m.unlock();
return false;
}
Como o MRuby não é thread-safe, precisamos usar o Mutex. Bloqueie-o quando começar a usar o MRuby e o desbloqueie no final. Verifique se a instância VST não é nula e se ela responde ao método program_name. Agora podemos invocá-lo e se o resultado não for nulo, podemos decodificá-lo e copiá-lo para que o VST possa lê-lo. Retornar verdadeiro significa que copiamos algo. Não é muito complicado descobrir quando e como os dados são convertidos. Agora, dê uma olhada no mais importante – processamento de áudio.
void MRubyVst::processReplacing(float** inputs, float** outputs, VstInt32 sampleFrames) {
m.lock();
if(!mrb_nil_p(vst_instance) && mrb_respond_to(mrb, vst_instance, mrb_intern_lit(mrb, "process"))){
int ai = mrb_gc_arena_save(mrb);
float* in1 = inputs[0];
float* in2 = inputs[1];
float* out1 = outputs[0];
float* out2 = outputs[1];
mrb_value mrb_inputs = mrb_ary_new(mrb);
mrb_value mrb_input_1 = mrb_ary_new(mrb);
mrb_value mrb_input_2 = mrb_ary_new(mrb);
for (int i=0;i<sampleFrames;i++) {
mrb_ary_push(mrb, mrb_input_1, mrb_float_value(mrb, (*in1++)));
mrb_ary_push(mrb, mrb_input_2, mrb_float_value(mrb, (*in2++)));
}
mrb_ary_push(mrb, mrb_inputs, mrb_input_1);
mrb_ary_push(mrb, mrb_inputs, mrb_input_2);
mrb_value mrb_outputs = mrb_funcall(mrb, vst_instance, "process", 1, mrb_inputs);
//mrb_value mrb_outputs = mrb_inputs;
if (!mrb_nil_p(mrb_outputs)) {
mrb_value mrb_output_1 = mrb_ary_shift(mrb, mrb_outputs);
mrb_value mrb_output_2 = mrb_ary_shift(mrb, mrb_outputs);
for (int i=0;i<sampleFrames;i++) {
(*out1++) = mrb_float(mrb_ary_shift(mrb, mrb_output_1));
(*out2++) = mrb_float(mrb_ary_shift(mrb, mrb_output_2));
}
}
mrb_gc_arena_restore(mrb, ai);
}
m.unlock();
}
Poucas coisas acontecem aqui. Precisamos instruir o Ruby Garbage Collector para remover os dados não utilizados. Isso é o que fazem as linhas 4 e 36. Todos os objetos criados entre essas linhas podem ser coletados pelo coletor de lixo, portanto não teremos vazamentos de memória. Em seguida, precisamos converter um array de flutuadores no array de flutuadores do Ruby. Crie arrays Ruby para cada entrada e adicione um flutuador convertido a ele. Agora, podemos invocar um método de processo com parâmetros. O resultado desse método é convertido de volta para o array C de flutuadores e depois passado para o DAW.
Palavras finais
Sim, funciona. Claro, o plugin de ganho simples leva muito mais CPU do que o escrito em C. Mas, como você sabe, precisamos empacotá-lo no objeto Ruby e extrair no final. Agora você pode fazer o que quiser com o fluxo de áudio em Ruby. Existem alguns exemplos de plugins como pan e stereo enhancer. Mas você também pode criar o seu próprio. Tente executar o servidor web dentro dele. Qualquer coisa. Em Ruby, isso é mais simples e mais divertido.
MRubyByExample
Foi difícil fazer o debug do comportamento do MRuby no VST. Então fiz um pequeno script para testá-lo. Se você quiser brincar com o MRuby, você pode verificar este repo e observar esta documentação. Talvez você encontre algo interessante lá.
Esperando que tenha sido útil. Obrigado por ler! Se você gostou, click em curtir.
Obrigado!
***
Michał Kalbarczyk faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela Redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://blog.fazibear.me/processing-audio-with-ruby-330796afd06




