よくもまぁこんな面倒臭い大変なものを作ったなと自分で思う。
RubyのEnumerator::Yielderとlambdaを多用したリアルタイム処理とも親和性が高い作りをしてる。
だいぶ前に書いた文章が発掘されたので手直しして公開。
供養、ナムナム。
パッケージ
すべてRubyGemsで公開してる。
vdsp
Rubyでデジタル信号処理を出来るようにした。
MacOSのvDSP frameworkのbinding。
GitHub - yoshida-eth0/ruby-vdsp: Perform basic arithmetic operations and common digital signal processing routines on large vectors.
vdsp | RubyGems.org | your community gem host
audio_stream
Rubyで音楽制作できるようにした。
DAWとFXみたいなもの。
依存:vdsp coreaudio
GitHub - yoshida-eth0/ruby-audio_stream: AudioStream is a Digital Audio Workstation for CLI
audio_stream | RubyGems.org | your community gem host
synthesizer
シンセサイザーとステップエディタみたいなもの。
依存:audio_stream
GitHub - yoshida-eth0/ruby-synthesizer: Synthesizer implemented in Ruby.
synthesizer | RubyGems.org | your community gem host
インストール
coreaudio gemsのコンパイルで、Xcode 12系だと以下のエラーが出る。
compiling coreaudio.m coreaudio.m:785:12: error: implicit declaration of function 'rb_thread_call_without_gvl' is invalid in C99 [-Werror,-Wimplicit-function-declaration] return rb_thread_call_without_gvl(ca_buffer_wait, ptr, ^ 1 error generated.
なので以下のようにしてまずはcoreaudio gemsをインストールしてからsynthesizer gemsをインストール。
gem install coreaudio -- --with-cflags="-Wno-error=implicit-function-declaration" gem install synthesizer
audio_streamの機能と仕様
Audio Busの接続
オーディオファイルやらオーディオデバイスやらのAudioInputをObservableとして、Audio Busなど後続のObserverにBufferを通知する。
FXの接続、ステレオ/モノラル変換、Send ToでのBusの合流などもこのObserverパターンで実装してる。
ruby-audio_stream/audio_observable.rb at master · yoshida-eth0/ruby-audio_stream · GitHub
トラック間の同期
AudioInputはEnumerableを実装していて、Enumerator::Yielderがバッファを生成し続ける。
全てのトラックが同じタイミングで同じ回数Enumerator#nextを呼ぶためにSizedQueueを使って1回の呼び出しごとにThreadを止める。
そして全トラックのバッファが揃ったタイミングで、止めていたThreadを起動させて次のバッファを生成させる。
ruby-audio_stream/conductor.rb at master · yoshida-eth0/ruby-audio_stream · GitHub
FX
FXを自力実装することで今までなんとなく雰囲気で掛けてたFXの意味を正しく理解することが出来たのは良かった。
当初VST2相当の仕組みで作っていたけど、途中でヴォコーダーを作る時にVST2相当だとLRでキャリアとモジュレーションを分けるというあの懐かしい様式になってしまうのが嫌でサイドチェインに対応してVST3相当の汎用性を得たので満足感高い。
Rubyは数値解析系が充実してなくてリバーブの実装で一部SciPyの再開発をすることになったのは想定外で割と苦戦した。
BiquadFilterの美しさ
BiquadFilterには数学的美しさが詰まってると思う。
フィルター系のグラフ。
上から順に、LowPassFilter、HighPassFilter、LowShelfFilter、HighShelfFilter、BandPassFilter、PeakingFilter。
上記フィルターを複数使ったイコライザー系のグラフ。
上から順に、2BandEqualizer、3BandEqualizer、GraphicEqualizer。
オクターブ差の16帯域のBandPassFilterのグラフ。
周波数軸を対数表示にすることで等間隔のキレイなグラフになる。
440Hzを基準とした平均律の計算しやすさは数学的にも浮動小数点演算的にも美しい。
synthesizerの機能と仕様
Oscillator、Amplifier、Filter、とシンセサイザーをいじったことのある人なら音を想像できるくらい直感的なインターフェイスになっていると思う。
VSTi開発の知識がある人であれば、自分でシンセサイザーのロジック部分だけを実装して使える。
シンセサイザーといえばC++が基本みたいなところあるけど、カジュアルにRubyで書けるというのは結構な利点だと思ってる。
波形バッファの生成
バッファ分の波形を生成するlambdaをEnumerator::Yielderで実行してる。
NoteOnのタイミングでOscillator、Amplifier、Filterを初期化して、Enumerator#nextされる度に続きの波形を生成し続ける。
Oscillatorで生成する波形の角度はクロージャのような仕組みで保持してる。
なのでバッファが分割されても途切れることなく続きの波形を延々生成し続けることが出来る。
また、NoteOnから時間の推移とともに変化していくパラメータも同じ仕組みで状態保持しているのでADSRやLFOなどのエンベロープが掛けられるようになっている。
ruby-synthesizer/note_perform.rb at master · yoshida-eth0/ruby-synthesizer · GitHub
ruby-synthesizer/low.rb at master · yoshida-eth0/ruby-synthesizer · GitHub
NoteOn/NoteOffでやっていること
以下の設定や状態を計算して最終的な値を算出する。
・シンセサイザー固有の設定や状態(PitchBend、Glide)
・Note固有の状態(Volume、Panなど)
・Oscillator固有の状態(Volume、Panなど)
NoteOnのタイミングでやることは以下。
・Oscillator、Amplifier、Filterを初期化。
NoteOnからNoteOffまでの間に延々やることは以下。
・Oscillator、Amplifierの各種パラメータのエンベロープを更新。
・VolumeにNoteOnのVelocityを反映。
・Tuneのエンベロープを更新してPitchBendを反映し、生成する波形の周波数を算出。
・上記の周波数とパラメータからOscillatorが波形バッファを生成。
・Filterのエンベロープを更新。
・Oscillatorが生成した波形バッファにFilterを掛ける。
NoteOff以降にやることは以下。
・ADSRの残響の計算。
・ADSRでRelease途中でVolumeが0を超えていればNoteOn中と同等の処理をする。
・ADSRでReleaseし終えてVolumeが0になったらNoteを削除。
この辺はもう普通にシンセサイザー。
理論上は普通のGUIや物理ボタンを持つシンセサイザーと同等のことが出来る。
ruby-synthesizer/low.rb at master · yoshida-eth0/ruby-synthesizer · GitHub
エンベロープ
ADSR、LFO、Glide、Curve、を実装した。
これらを各種パラメータに掛けることができる。
エンベロープを掛けられるOscillatorのパラメータはVolume、Pan、Tune(semi / cent)、Sym、Sync、Unison(num / detune / stereo)など。
エンベロープを掛けられるAmplifierのパラメータはVolume、Pan、Tune(semi / cent)、Unison(num / detune / stereo)など。
ADSRのグラフ。
ruby-synthesizer/modulation_value.rb at master · yoshida-eth0/ruby-synthesizer · GitHub
ruby-synthesizer/adsr.rb at master · yoshida-eth0/ruby-synthesizer · GitHub
Oscillator
波形生成ロジックは独立して外側に置けるので、sine、triangle、saw、squareなどの基本的な波形以外にも、フォルマントヴォコーダーをOscillatorとして実装するなんてことも出来るようにしてある。
実際にフォルマントヴォコーダーは実装して使えるようにした。
デモのCinemaではフォルマントヴォコーダーを使ってる。
ruby-synthesizer/base.rb at master · yoshida-eth0/ruby-synthesizer · GitHub
ruby-synthesizer/shape.rb at master · yoshida-eth0/ruby-synthesizer · GitHub
Amplifier
AmplifierにはADSRやLFOなどのエンベロープが指定できる。
ADSRの減衰やLFOの角度の状態は、Oscillatorの波形生成と同様にクロージャのような仕組みで保持してる。
Filter
FXに準ずるフィルターの実装。
パラレル/シリアルでのフィルター処理にも対応した。
ステップエディタ
ST/GT方式のステップエディタを実装した。
分解能とBPMを指定することで、Tickでの指定も出来るようにもした。(sync機能)
ダブステップとかでLFOをBPMに同期させる時とかに便利。
ruby-synthesizer/st_gt.rb at master · yoshida-eth0/ruby-synthesizer · GitHub