Pure Dataで作るシンプルなVAシンセサイザ


drumlogue向けexternal generatorのサンプル用に、簡単なVAシンセサイザをPure Dataで作ってみました。これをベースにオシレータ部分だけを入れ替えれば、とりあえずオリジナルのシンセサイザが作れるようになる、ということを意図しています。

drumlogue用のhvcc external generatorでは、これまでよりも大きなパッチを変換できるようになります。これで開発上の制約は大幅に小さくなりますが、一方でdrumlogueではOSCユニットがありません。代わりにSynthユニットで、シンセサイザ全体を作る必要があり、これは開発のハードルを少し上げそうです。

また、私自身、drumlogue以外のプラットホームではサイズの制約があったために、あまり大きなPure Dataパッチは作ってきませんでした。そこで練習の意味も含めて作ってみたのが今回紹介するVAシンセサイザin Pure Dataです。

全体像

下図はこのシンセサイザのブロック図です。OSC、VCF、EG、VCAを1つずつ使うミニマルなVAシンセです。OSCはSAWとSQUAREをミックスできます。VCFはカットオフをEGでモジュレーションできます。LFOはありません。

下図がこれをPure Dataで実装したものの全体像です。解りやすいようにパラメータを垂直スライダで表現しています。(実際にdrumlogue用に変換する際は[r paramName @hv_param]という形式のオブジェクトに置き換えます。)

大体の流れとしては、[notein]でMIDIノートイベントを受け、ノートナンバーは周波数に変換してからオシレータとフィルタへ、ベロシティはEGへ送っています。
周波数をフィルタへ送っているのは、カットオフ周波数の基準を今鳴らしている音高に合わせるためです。
この周波数はスライダの数値と掛け算されていますが、このスライダは対数目盛で値域は[1/8,8]、つまり[2^(-3), 2^3]で上下3オクターブ相当です。

フィルタの出力は[*~]へ送られ、そこでEGからのエンベロープカーブと掛け算されてから、音として出力されます。

オシレータ、フィルタ、EGはそれぞれサブパッチになっています。

オシレータ

下図はオシレータ部のサブパッチです。簡易的に鋸波と矩形波を生成しています。

[phasor~]は0から1まで上昇して0に戻る、という信号を生成しますので、これを鋸波として使っています。値域を-1.0から1.0とするため、2倍して1を引いています。

[phasor~]から矩形波を生成する部分は、イメージとしては鋸波の高さのちょうど真ん中あたりだけを薄く切り出して、それを拡大しています(下図)。
厳密には傾いているのですが、縦方向を10,000倍しているので、まあまあ垂直になります。
切り出す位置を縦方向にずらせば、PWMも実現できます。

最後にこれら2つの信号を
$$(1-k)Saw + kSquare$$
という形で比率\(k\)でミックスしています。

ローパスフィルタ

Pure Data Vanillaで使えるローパスフィルタは[lop~][vcf~][bob~]があります。
[bob~]はmoogフィルタのシミュレーションですが、hvccではサポートされていません。また、[lop~]はレゾナンスが使えません。ですので、今回は[vcf~]を使っています。1poleフィルタですので、同じものを2段連結して2poleにしています。

レゾナンスはリニアなパラメータを指数形式に変換しています。ここではパラメータは[-1,1]の範囲を想定し、これを4^(-1)=0.25から4^1=4.0まで変化させています。

また、カットオフ周波数をエンベロープでモジュレーションするための入力を付けています。この入力も[-1,1]の範囲を想定しています。
モジュレーション信号は2^(-3) = 1/8から2^3 = 8まで変化します。つまり上下に3オクターブ分ということです。
このモジュレーション信号は、EG Intesityのパラメータ([-1,1]の範囲を想定)を掛けて、それを元のカットオフ周波数に掛け算していますので、カットオフ周波数も最大3オクターブ分、上下させることができます。

エンベロープジェネレータ

今回のパッチの中では、このEGが一番複雑です。EGではパラメータやイベントが多いので考慮すべきケースが多いためです。

今回作成したのはADSRタイプのエンベロープジェネレータで、本家のチュートリアルにあるものを参考に簡略化しています。

信号自体は[line~]で生成していますので、指数関数カーブではなく直線で構成された信号になります。エンベロープジェネレータは出力の連続性を維持するために、常に「現在の値」から始めて信号を生成する必要がありますが、[line~]は始点を省略して終点だけを指定できます。

下図がパッチの中身です。最下段に[line~]オブジェクトがあり、これに状態に応じたメッセージを送信して信号を生成させます。

[line~]が受け取るメッセージは2つの数値のペアで、1つ目が出力レベルの目標値、2つ目がその目標値に到達するまでにかける時間(msec単位)です。数値が1つの場合は、2つ目はゼロとみなされます。

このパッチでは[line~]に送られるメッセージは4つで、

・[0] (リセット時)
・[1 attack-time (
・[sustain-level decay-time (
・[0 release-time (

となっています。

AttackとReleaseはNote OnとNote Offでキックされますが、これらのイベントはベロシティから生成します。ベロシティがゼロならNote Off、非ゼロならNote Onです。この判定は[sel 0]オブジェクトが行います。[sel x]は入力がxに等しければ左からban、等しくなければ右からxを出力します。

このパッチでは、[sel 0]の左の出力はReleaseをキックし、[sel 0]の右の出力はbangに変換してからAttackをキックしています。

一方、Decayの開始のためのイベントは自分で生成する必要があります。

そのために使えるのが[delay]オブジェクトで、これはbangを受け取ると指定時間後にbangを送出します。Attackがキックされたときに、Attackタイムを与えて[delay]にbangを送れば、Attackフェーズが終了したときにbangが出力されるので、これをDecayフェーズをキックするために使うことができます。

ただし、Note On、Decay、Note Offの順序は様々なパターンがありうることを考慮する必要があります。

・Note On ⇒ Decay Start ⇒ Note Off

というのが最も標準的な順序ですが、

・Note On ⇒ (Decayより先に)Note Off(Attackの途中でキーを離した場合)
・Note On ⇒ Decay Start ⇒ (Note Offより先に)Note On(キーを押したまま別のキーを押した場合)

といった信号も普通に起こりえます。

そのため、上のパッチではNote OnとNote Offのそれぞれが起きたときに、[stop ( メッセージを送っていったん[delay]の動作を中止させています。Note OffのあとでDecayが始まったり、Note Onの後で前のNote Onに由来するDecayが始まっては困るからです。

なお、今回の実装では、キーを押した状態でSustainレベルを変更することはできません。

これは[line~]を使った実装の欠点です。Decay/Sustain期間中にSustainレベルを変更できるようにするには、SustainレベルをDecayカーブの目標地点ではなく、信号レベルに掛かる係数として計算する必要があります。また別の欠点として、[line~]は線の傾きではなく所要時間を固定しているため、開始時点のエンベロープの値に関わらず、目標値に到達するまで指定の所要時間をかけてしまいます。(参照

デモ

下のビデオはスライダをreceiveオブジェクトに換えてdrumlogue用にビルドしたものを動作させています。


一応シンセサイザっぽくなっているので、サンプルとしては十分かなと思います。

コメント