2022年9月30日

CAL スクリプトを書く 中級編


Cakewalk のプログラミング言語 CAL を使ってスクリプトを自作する方法をメモメモ…



このページの内容の動作確認には Cakewalk by BandLab 2022.06 を使用しています。その後のアップデートで仕様が変更されている場合があります。



初級編では、CAL ファイルを作成して実行し、MIDI ノートのタイミングやキー(高さ)を変更できるようになりました。中級編では、最終的にノート以外の MIDI イベントに対しても自由な操作ができるように解説していきたいと思います。


ではさっそく始めましょう!


ユーザーからの入力を受け取る


前回作成したスクリプトでは、変数宣言時に指定した初期値をそのまま使っていました。毎回全く同じ操作を繰り返す場合はそれでもよいのですが、実行時にユーザーが好きな値を設定できるようにしたほうが便利なこともありますよね。ためしに前回作ったヒューマナイザーのスクリプトを書き換えて、ノートの移動量の範囲を指定できるようにしてみましょう。

(int minOffset -10)
(getInt minOffset "Input MIN Offset" -200 0)
(int maxOffset 30)
(getInt maxOffset "Input MAX Offset" 0 200)

対象の変数が int 型か long 型の場合はgetInt関数を使い、word 型か dword 型の場合はgetWord関数を使います。getLong や getDword、getString などは存在しないので注意。あとは上記の例のように

(getInt/getWord 変数名 表示メッセージ 最小値 最大値)

と指定すれば、この式が実行されるタイミングでウィンドウが表示されて数値入力が可能になります。ちなみに最小値と最大値の範囲外の数値を入力しようとするとエラーが出ます。

すみません…


条件分岐


続いて、スクリプト制御に欠かせない条件分岐について簡単に説明します。

基本的な構文は(if 条件式 A B)です。まず「条件式」の真偽が判定されて、もし真であれば A が、偽であれば B が実行されます。とりあえず実例を見てみましょう。

(if (> Note.Vel 100) (= Note.Vel 100))

この例の場合、最初の(> Note.Vel 100)条件式です。「Note.Vel の値が 100 よりも大きければ」という意味ですね。もしこの条件を満たしていれば、次の(= Note.Vel 100)が実行されます。つまりこれは「100 よりも大きいベロシティを 100 に揃える」という操作になるわけです。なお、条件を満たしていない場合の式は省略可能で、この例では省略されています。

比較演算子を使って真偽値どうしを比べることもできます。

(if (&& (>= Note.Key 60) (< Note.Key 72))   ; 条件式
  (= Note.Vel 100)                          ; 真の場合の処理
  (= Note.Vel 60)                           ; 偽の場合の処理
)

キーが60以上かつ72未満、つまりC4~B4の範囲内にあるノートはベロシティを100に、それ以外のノートは60に設定しています。

もちろん前回解説した(do ...)を使えば複数の式を実行することも可能です。

(if (|| (== (% Note.Key 12) 0) (== (% Note.Key 12) 7))   ; 条件式
  (do                                                    ; 真の場合の処理
    (+= Note.Vel 10)
    (*= Note.Dur 2)
  )
)

条件式が少し複雑ですが、これは「対象のノートがド(C)かソ(G)だった場合」という意味です。%は剰余を返す演算子なので、(% Note.Key 12)を計算するとオクターブに関係なく、たとえば Note.Key が48(C3)でも60(C4)でも72(C5)でもド(C)の音であるかぎりは0を返します。これを使えば、12音階の中でド(C)から半音いくつぶん離れているかを求めることができるというわけです。

もし「誤った個数の引数」とか「括弧が不一致」といったエラーが表示される場合はdoで1つの式にまとめるのを忘れていたり、インデントされていなかったり、)が抜けていたりする可能性が高いです。

ちなみに、CAL ではifの他にswtichも使えます。

(switch (% Note.Key 12)
  0 (pause "C!")
  2 (pause "D!")
  4 (pause "E!")
  5 (pause "F!")
  7 (pause "G!")
  9 (pause "A!")
  11 (pause "B!")
)

これだけでも大体使い方はわかると思いますが、swtichの後に置いた式や変数の値に応じて処理を分岐させることができます。上の例ではキーに応じて音名を表示していますね。これをifだけで書こうとするととんでもないネストになってしまうので、swtichが使える場面では積極的に利用していきましょう。


Event.Kind


ifを説明したついでに、前回の最後に少しだけ触れたこの式↓についても解説します。

(if (== Event.Kind NOTE) ...)

Event.Time は対象の MIDI イベントの開始位置を示すものでしたが、Event.Kind というのは、対象の MIDI イベントの種類を示すものです。えっ?ノート以外にも選択できるものがあるの?と思う方もいるでしょうが、実はあるのです。ピアノロールビューの下の「コントローラ表示部」で編集できる「コントローラ(CC)」や「ピッチベンド」といった項目で設定された各値も MIDI イベントになっています。

コントローラ表示部のイベントも選択可能

ちなみに、オートメーションレーンに表示されているエンベロープ(各トラックの折れ線グラフ)はそのままでは扱えませんが、MIDI イベント⇔エンベロープは自由に変換できるので、オートメーションレーン上で編集したデータであっても CAL を使って処理することは可能です。

また、メニューの「表示」から「イベントリスト」を選択すると、トラックごとのイベント一覧を表示させることもできます。さすがにこのリストを見ながら作曲するのは厳しいですが、各 MIDI イベントの中身にどんなデータが入っているか一発でわかるので、スクリプト作成時には利用することをおすすめします。

イベントリストでは生のイベントデータを確認できる

対象のイベントがノートなのかコントローラなのかピッチベンドなのかは、イベントの種類ごとに割り振られた定数と比較すれば簡単に知ることができます。イベントの種類を表す定数には次のようなものがあります。

NOTEおなじみのノートイベント。音の高さは Note.Key、ベロシティは Note.Vel、長さは Note.Dur でそれぞれ参照可能。
CONTROLコントローラ(CC)イベント。コントローラの内容は Control.Num から参照できる。CC#7がボリューム、CC#10がパン、CC#64がペダル…など、どの値がどの機能に対応するかは定義を確認のこと(参考)。値は Control.Val で取得可能。
WHEELピッチベンドホイールイベント。ピッチ(音の高さ)を微調整する機能。値は Wheel.Val で取得できる。
CHANAFTチャンネルアフタータッチイベント。「アフタータッチ」とは、シンセサイザーなどに搭載されている、鍵盤を押した後さらに押し込むことで様々な効果を生み出す機能のこと。ChanAft.Val の値を書き換えれば押し込み度(圧力)を自由にシミュレートできる。
KEYAFTキーアフタータッチイベント。CHANAFT とほぼ同じだがチャンネル全体ではなくキーごとに個別に圧力を指定することが可能。KeyAft.Key と KeyAft.Val でそれぞれキーとその圧力値を設定できる。
PATCHパッチチェンジイベント。MIDI 音源の音色やバンクを切り替える機能…だと思うが使ったことがない。Patch.Num と Patch.Bank を使用可能。
RPNコントローラ(CC)の番号は通常 119 までしか割り当てられないが、その範囲を拡張し 16383 まで割り当てられるようにするための仕組み。基本的に CAL では扱えない。ピッチベンドの感度調整機能くらいしか使い道がないので気にしなくて良い。
NRPNRPN と同じく基本的に CAL では扱えない

MIDI イベントを識別する定数が用意されていても CAL で扱えるとは限らないので注意。また、当たり前ですが音源(プラグイン)側が対応していなければいくら CAL で MIDI イベントを編集しても音色は変わりません。

ちなみに、内部的には Event.Kind の中身はただの整数値で、NOTE は 144、CONTROL は 176 …というように定数の値と比較して種類を判別しているだけです。


イベントの挿入と削除


コントローラ(CC)系のイベントは値を調整するだけではなく「ノートに合わせて新たにイベントを追加する」という操作をする場合が多いので、ここでイベントの挿入方法をまとめておきます。

(insert 挿入位置 チャンネル NOTE Key Vel Dur)
(insert 挿入位置 チャンネル CONTROL Num Val)
(insert 挿入位置 チャンネル WHEEL Val)
(insert 挿入位置 チャンネル CHANAFT Val)
(insert 挿入位置 チャンネル KEYAFT Key Val)
(insert 挿入位置 チャンネル PATCH Num Bank)

insert関数を使って、挿入位置、チャンネル、イベント識別用定数、各パラメータの値を順に指定します。たとえば選択したノートを根音にしてセブンスコードを作りたければ次のようにすればOK。

(if (== Event.Kind NOTE)
  (do
    (insert Event.Time Event.Chan NOTE (+ Note.Key 4) Note.Vel Note.Dur)
    (insert Event.Time Event.Chan NOTE (+ Note.Key 7) Note.Vel Note.Dur)
    (insert Event.Time Event.Chan NOTE (+ Note.Key 10) Note.Vel Note.Dur)
  )
)

位置や長さはすべて同じで、キーだけ変更したノートを新たに重ねています。マイナーセブンスにしたければ(+ Note.Key 4)ではなく(+ Note.Key 3)にすればいいですね。

イベントを削除する方法はもっと簡単です。

(delete)

これだけで現在対象になっているイベントを完全に削除できます。


作ってみよう! ~サステインペダル~


恒例の作ってみよう!のコーナーです。今回は簡単な自動サステインペダルを作成します。

仕組みは単純で、選択された各ノートの直前でサステイン(CC#64)をオフ(0)にする、ノートが発音された直後にオン(127)にして音を伸ばす、という処理をやっているだけです。ピアノやシンセ系の楽器なら概ね対応していると思います。

; SustainPedal.cal

(do
  (word onOffset 120)  ; ノートが発音してからサステインがオンになるまでの時間
  (word offOffset 120) ; サステインがオフになってからノートが発音するまでの時間
  (dword lastTime 0)   ; 最後にイベントを追加した時間

  (getWord onOffset "Input ON Offset" 0 960)
  (getWord offOffset "Input OFF Offset" 0 960)
)

(do
  (if (&& (== Event.Kind NOTE) (> Event.Time lastTime))
    (do
      ; ノートの直前にサステインをオフにする
      (if (>= Event.Time offOffset)
        (insert (- Event.Time offOffset) 0 CONTROL 64 0)
      )
      ; ノートの直後にサステインをオンにする
      (insert (+ Event.Time onOffset) 0 CONTROL 64 127)

      (= lastTime Event.Time)
    )
  )
)

同じ位置にあるノートが複数選択されているとその数だけコントローライベントも挿入されてしまうので、それを防ぐために13行目では「最後にイベントを挿入した位置よりも後にあるノートに対してのみ実行する」と条件をつけています。


おわりに


そろそろ長くなってきたので中級編はここまで!条件分岐と MIDI イベントの追加と削除をマスターすれば、もうたいていの操作は実現できるはずです。上級編では自分でも理解しているかどうか怪しいforEachEventを解説してみます。余裕があれば読んでくださいね!



…あ、それから言い忘れていましたが、特定の CAL スクリプトの実行には環境設定からショートカットキーを割り当てることができます。作成した .cal ファイルをCakewalk Content\Cakewalk Core\CAL Scriptsの中に移動させるか、環境設定→「保存フォルダの指定」から CAL ファイルの保存先を変更することで、ショートカットの機能一覧に表示されるようになります。頻繁に使うスクリプトはぜひ登録しておきましょう!


0 件のコメント:

コメントを投稿