2022年9月30日

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


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



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



CAL というのは Cakewalk Application Language の略で、Cakewalk by BandLab に搭載されているプログラミング言語のことです。この言語でスクリプトを書き .cal という拡張子をつけて保存しておくと、Cakewalk 内のメニュー(「プロセス」→「CALの実行」)からスクリプトで指定した処理を実行できるようになります。

CAL の役割は MIDI イベントに対する操作の自動化です。具体的にどんな操作が可能かというと…

  • 重複している MIDI ノートを取り除く
  • 選択した範囲の MIDI ノートのベロシティをだんだん強くする(クレッシェンド)
  • 指定した MIDI ノートの上に3度5度7度の和音を重ねてセブンスコードにする
  • なめらかなレガートになるように MIDI ノートの長さを調整する

…などなど、もちろんこれらはほんの一例で、スクリプトさえ書ければ好きな処理を自作できます。インストールフォルダ内Cakewalk Content\Cakewalk Core\CAL Scriptsの中にはあらかじめ様々な機能の .cal ファイルが用意されているので、試しにいろいろ実行してみてください。打ち込みメインで作曲している人にとっては強力な武器になると思います。

CAL スクリプトで操作を自動化

ただ、大きな問題が1つあります。CAL を学ぶハードルの高さです。そもそも CAL は1994年発売の Cakewalk Ver.3.01 (Windows 3.1 の時代です!)からサポートされている言語で、もう何年もバージョンアップされておらず半ば放置状態になっています。最近の言語なら1行で実装できる簡単な処理でも CAL では複数のループと変数を駆使してようやく実現…というパターンも少なくありません。公式マニュアルのような、体系的にまとめられたドキュメントも用意されていません。便利なのに気軽に試せない状況になってしまっているわけです。

そこで、CAL について自分なりにいろいろ調べた(上述の .cal ファイルを読みまくって仕様を推測したり、はるか昔の個人サイトを巡ったり、ネット空間を漂っている PDF を拾って読んだりした)内容を簡単に書き残しておこうと思います。間違っている可能性もあるので参考程度に読んでもらえると嬉しいです。


記法の基本


CAL は LISP に近い言語です。( )で囲まれた範囲を1つの式/文として扱います。

(+ 10 20)

たとえば上のコードは一般的な言語では10 + 20と表現されますが、CAL は前置記法なので、演算子や関数は式の先頭で指定することになっています。式は入れ子にすることも可能です。

(+= time (* 960 4))

このコードはtime += 960 * 4と同じ意味で、「time という名前の変数に960*4の結果(=3840)を足し合わせて代入する」という処理になります。最初は戸惑うかもしれませんが、とにかく「( )で囲んだ部分が1つの式になる」「演算子は前に置く」ということさえ理解できていれば大丈夫です。


変数


CAL で変数を宣言するには次のように書きます。

(int offset -20)

この例では int 型の変数 offset を初期値 -20 で宣言しています。つまり(データ型 変数名 初期値)と順に書けばいいわけです。ちなみに、初期値を省略した場合は自動的に 0 (string の場合は空文字)が設定されます。

CAL で扱えるデータ型は次の5つです。

int16ビット符号あり整数型。-32768 から 32767 までの範囲を表現できる。
long32ビット符合あり整数型。-2147483648 から 2147483647 までの範囲を表現できる。
word16ビット符号なし整数型。0 から 65535 までの範囲を表現できる。Note.Key や Note.Dur など内部の値はほぼ word 型として格納されているので、基本的に変数は word 型を使えばOK。どうしても負の数を扱いたい場合のみ int 型の変数を使う。
dword32ビット符号なし整数型。0 から 4294967295 までの範囲を表現できる。Event.Time はこの dword 型で格納されているので、Event.Time の値を代入する場合は dword 型の変数を使う。
string文字列型。"I AM ERROR" のようにダブルクォーテーションでくくって表現する。ただし文字列の操作はほぼできないので、わざわざ string 型の変数を使う機会は皆無。

基本的に数値はすべて整数型で、浮動小数点は存在しないことに注意しましょう。たとえば次のコードを見てください。このコードを実行すると、変数 var にはどんな値が代入されるでしょうか?

(= var (* (/ 6 12) 60))

えーと…まず内側の(/ 6 12)を計算すると 0.5 になって、次に(* 0.5 60)を計算するから…答えは 30 かな? いいえ、残念ながら違います。答えは 0 です。なぜかというと、式の値が浮動小数点になることはないので(/ 6 12)の計算結果も 0.5 ではなく 0 になってしまうからです。場合によってはハマるので、念のため覚えておいてください。

さて、上の説明でもチラッと触れましたが、CAL には宣言しなくても使える組み込みの変数や定数があらかじめいくつか用意されています。この値を参照したり書き換えたりすることで、MIDI イベントに対する何らかの操作を実行できます。特によく使うものを見てみましょう。

Event.Time対象の MIDI イベントの開始時間(位置)。これは秒数でも拍数でもなく「ティック数」であることに注意。ティック(分解能)とは、DAWアプリケーションの内部で処理をするときの最小単位のこと。この値を書き換えると MIDI イベントのタイミングが前後に移動する。
Event.Chan対象の MIDI イベントのチャンネル。Ch.1なら0、Ch.2なら1…というふうにずれるので注意。
Note.Key対象の MIDI イベントのノートの音の高さ。たとえば(= Note.Key 60)と書けば対象のノートがC4(真ん中のド)に移動する。(+= Note.Key 12)と書けば12半音つまり1オクターブ上に移動する。
Note.Vel対象の MIDI イベントのノートのベロシティ。0~127の値しかとらないので注意。「ここはフォルティッシッシッシモ(ffff)で行くぜ!」と気合いを入れて(= Note.Vel 200)などと書いても範囲外なのでエラーになる。
Note.Dur対象の MIDI イベントのノートの長さ。Event.Time と組み合わせて使うことが多い。たとえば(+= Note.Dur 10)と書くとノートがほんの少し(10ティックぶん)だけ長くなる。

そろそろ説明にも飽きてきました。必要な知識はこれで十分身についたと思うので、いよいよスクリプトを書いていきましょう!


CAL ファイルの作成と実行


CAL ファイルを作成する方法は簡単です。テキストエディタ(メモ帳)を開いて、スクリプトを記述し、適当な名前に .cal という拡張子をつけて保存するだけです。とりあえずこんなスクリプトを書いてみました。

; Delay.cal
(word delay 100)
(+= Event.Time delay)
(pause "MISSION COMPLETE! Delay: " delay)

なんか説明されてないものが出てきたぞ!と思った方、落ち着いてください。説明します。冒頭の;は「コメント」を示し、それ以降その行の末尾までの記述が無視されます。他の言語でいう//とか#と同じです。

最後の(pause ...)は「一旦スクリプト処理を停止して画面にメッセージを表示する」という処理をしてくれる関数です。pauseのあとに表示したい文字列や変数を指定すればプロンプトとして表示されるので、デバッグにめちゃくちゃ便利です。ここでは適当な文字列と delay 変数の中身(=100)を表示しています。

「キャンセル」を押すとスクリプトを中止できる

では上記の CAL スクリプトを実行してみてください。ピアノロールビューを表示して適当にいくつかノートを入力、それらのノートを選択した状態でメニューの「プロセス」→「CALの実行」からファイルを選びましょう。各ノートの開始位置(Event.Time)が100ティックぶんだけ後ろにずれれば成功です。


CAL スクリプトの実行順


どうやら、上記のファイル内のコードは次のようなルールで実行されるようです。

  • まず、最初の式(word delay 100)が1回実行される
  • 次に、選択されたすべてのイベントに対して2つ目の式(+= Event.Time delay)がイベント回数分実行される
  • 最後に、3つ目の式(pause "MISSION COMPLETE! Delay: " delay)が1回実行される
  • 4つ目以降の式はあっても実行されない

つまり最初の( )で変数宣言などの前処理を、2つ目の( )で各ノートへの実際の操作を、3つ目の( )で後処理を行う想定なのだと思います。

試しに次のようなスクリプトを実行してみると挙動がよくわかります。

(pause "[ 1 ]")
(pause "[ 2 ]: " Event.Time Event.Chan Note.Key Note.Vel Note.Dur)
(pause "[ 3 ]")
(pause "[ 4 ]")

特に重要なのは2つ目の式です。選択中の各 MIDI イベントに対して処理が1回ずつ実行され、1つ1つのイベントには Event.Time や Note.Key といった変数を通してアクセスすることができます。

…でも、この仕様だと1回の操作に対して1つの式しか実行できませんよね。複数のコマンドを使うような複雑な処理を実行したいときはどうすればいいのでしょうか?


(do ...)


複数の処理を実行したいときに使うのが(do ...)です。doは、そのあとに並べた処理を順番に実行するというだけの単純な関数なのですが、重要なのは、どれだけ( )を並べても結果的に(do ...)という1つの式として扱われるということです。

; この処理は1つの式!
(do
  (word key 0)
  (word vel 0)
  (word dur 0)
  (dword time 0)
)

これを使えば複雑な処理でもひとまとめにして実行させることができるようになります。もちろん(do ...)の中にさらに(do ...)を入れて入れ子にしてもOK。ただし、(do ...)の中は必ずインデントするようにしてください。これは単に字下げしないとコードが読みにくいからという理由だけではなく、実行したい式と式の間にホワイトスペースがないとエラーが発生するためです。

; おそらくエラーになる
(do
(+= Note.Key 1)
(/= Note.Vel 2)
)

「不明な手順」「括弧が不一致」「誤った個数の引数」といったエラーメッセージが表示される場合は、多くの場合)をつけ忘れているか、空白文字が挿入されていないかのどちらかです。あと、ほかの言語に慣れているとついdo(...)と書いてしまいがちなので注意。


作ってみよう! ~ヒューマナイザー~


それでは最後に何か実用的なスクリプトを書いてみます。ここでは簡単なヒューマナイザーを作成してみましょう。ヒューマナイザーとは、各ノートの位置をわずかにずらしたりベロシティにランダムな強弱をつけたりすることで、人間が演奏したような「雑さ」を再現する機能のことです。HUMANIZE.CAL という同じ機能のファイルがすでにデフォルトのフォルダの中に存在するのですが、そちらが若干使いにくかったので、もっとシンプルに移動量の範囲を直接指定できるようにしてみました。

; Humanizer.cal

; 1つ目の式 1回実行される
(do
  (int minOffset -10)      ; ノートの移動量 下限
  (int maxOffset 30)       ; ノートの移動量 上限
  (int randomOffset 0)     ; ノートの移動量 計算用

  (int minVelOffset -12)   ; ベロシティの変化量 下限
  (int maxVelOffset 0)     ; ベロシティの変化量 上限
)

; 2つ目の式 選択したMIDIイベントすべてに対して実行される
(do
  (if (== Event.Kind NOTE)
    (do
      (= randomOffset (random minOffset maxOffset))
      (+= Event.Time randomOffset)
      (-= Note.Dur randomOffset)
      (+= Note.Vel (random minVelOffset maxVelOffset))
    )
  )
)

; 3つ目の式 今回は何もしない
NIL

15行目の(if (== Event.Kind NOTE) ...)についてはまた次回説明しようと思いますが、とりあえずここでは「対象の MIDI イベントがノートだった場合は ... を実行する」という意味だと解釈しておいてください。

ランダムな値を取得するには(random 最小値 最大値)という関数を使います。この関数は、指定した最小値から最大値までの範囲の疑似乱数(整数)を返します。たとえば(random -1 2)であれば -1、0、1、2 のいずれかの値になるわけです。

最後のNILは正直よくわかりません…。「何もしない」という意味で( )などと書いてしまうとエラーになるので、代わりに置いておくためのいわば空の式なのだと思います。なお、CAL ファイルは3つ目の式がなくても普通に動作するため、本来この状況でNILを使う必要はないはずなのですが、書いているスクリプトを多く見かけたので一応紹介しておきました。

試しに適当なフレーズを作って上記のスクリプトを適用するとこうなります。

Before:
After:

…はい、ほとんど一緒ですね。ベロシティが抑えられて若干音が丸くなってるかな~程度の差です。露骨にかけすぎると下手な演奏になってしまうので、このくらいの微妙なかけ具合でもいいかなと思ったのですが、ちょっと微妙すぎたかも。単純なランダム処理ではなくもう少し工夫しても良いかもしれません。

結果が気に入らなかった場合は「編集」→「元に戻す」(あるいは「履歴」)で気軽に元の状態に戻せます。ただ、MIDI ノートそのものに手を加えて変えてしまう処理(いわゆる「破壊的」な操作)なので「やっぱり前の方がよかった!」となったときのためにバックアップは取っておいたほうが無難です。


おわりに


…ここまで説明しておいて大変言いにくいのですが、もし全く同じ機能のプラグインが存在するなら CAL スクリプトよりもそちらを使った方が便利です。なぜなら、プラグインであれば有効化/無効化を簡単に切り替えられるからです。VST でも MIDI データの送受信を(一応)行えますし、少し古いですが MIDI FX プラグインを探すという手もあります(たとえばヒューマナイザーなら CSHumanize)。あくまでも CAL は MIDI 編集の補助として割り切るのがベストだと思います。

というわけで、以上で初級編はおしまいです。次回、中級編では入力プロンプトの表示や条件分岐、ノート以外の MIDI イベントを扱います。出来ることがぐぐっと広がるはずなので、ぜひ読んでくださいね!



0 件のコメント:

コメントを投稿