Cakewalk Application Language (CAL) - DTMプログラミング言語探訪

[DTMプログラミング言語探訪](https://qiita.com/aike@github/items/77b3eccc6861de09513a)

# Cakewalk Application Language (CAL)

## 概要

DAWソフトウェアCakewalk by BandLabを操作するスクリプト言語。
Cakewalk、Roland、Gibson、BandLabと資本が次々と変わり、名前もそのたびにCakewalk、SONAR、Cakewalk by BandLabのように変わってきた数奇な運命のDAWだけに、スクリプト言語の情報も断片的で全貌を把握するのが比較的困難です。

## 用途

- DAW定型操作自動化

## 言語仕様

- Lisp系言語
- MIDIノート操作
- メニュー項目自動実行
  - FILEメニュー
  - EDITメニュー
  - GOTOメニュー
  - SETTINGSメニュー
  - TRACKメニュー

シンタックスはLisp系ですが。car/cdrのような基本的なリスト操作関数はありません。またdefunやlambdaなどユーザー関数定義機能も用意されておらず、ほとんどの場合do関数で逐次的に記述するスタイルのため、関数型言語らしさはあまりありません。対話的に実行するREPL環境もありません。

基本的に昔ながらのMIDIシーケンサーの操作を自動化する言語であるため、波形編集などオーディオを扱う機能はほぼありません。リアルタイムのオーディオエフェクト処理やMIDIエフェクトの機能もありません。

[英語版公式ドキュメント](https://www.cakewalk.com/Documentation?product=SONAR&help=Programming_CAL.01.html)
[日本語版公式ドキュメント](https://www.cakewalk.com/Documentation?product=SONAR&language=4&help=Programming_CAL.01.html)

言語仕様やAPIの網羅的な公式ドキュメントは現状ありません。「The Cakewalk Application Language Programming Guide for SONAR」という、有志により2010年に書かれたPDFが出回っており、これを参考にすることが多いようです。しかしながら最新のCakewalk by BandLabでは利用できない古い内容も含まれ、また配布サイトも今はなくなっています。

サンプルスクリプトがインストールフォルダの以下の場所にあるので参考になります。
```Cakewalk Content\Cakewalk Core\CAL Scripts```

## GUI仕様

- ユーザー入力用ダイアログ： getInt、getWord、getTime
- 情報表示用ダイアログ： pause

以前はステータスバーに情報を表示するmessage関数が多用されましたが、2010年SONARのUI更新によりステータスバーが削除されたためmessage関数の出力はどこにも表示されなくなりました。
また、DLL関数でDLLを読み込んで実行できる機能もあり、工夫次第で表示に限らず多くのことができましたが、2023年現在使えないようです。

## プログラム例

### Hello World

一番シンプルなハローワールドは以下のように書けます。

```lisp:hello.cal
(pause "hello, world")
```

<img width="300" alt="hello.cal" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/6c6fb6ed-6b7f-e6cd-14bb-c476d714d612.png">


文字コードSJISでスクリプトファイルを保存すると日本語も表示できます。

```lisp:hello_ja.cal
(pause "こんにちは、世界")
```

<img width="300" alt="hello_ja.cal" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/6b5f3077-32f8-5695-09cb-42a8d0ace8b9.png">

### プログラム構造

CALファイルには3つの式を書くことができます。
ひとつ目の式は変数宣言など初期化処理用で最初に1回だけ実行されます。
ふたつ目の式は選択しているノートの数だけ繰り返し実行されます。
みっつ目の式は終了処理用で最後に1回だけ実行されます。
それぞれプロローグ、ボディ、エピローグと呼ばれ、プロローグ以外は省略可能です。

```lisp:notenumber.cal
(pause "start")                    ; Prolog
(pause "note number = " Note.Key)  ; Body
(pause "end")                      ; Epilog
```

ただ、これだと繰り返し構造がわかりづらいので、doとforEachEventを使ってひとつの式として書くことも多いです。以下の例は前述のスクリプトと同じ挙動をします。

```lisp:notenumber2.cal
(do
  (pause "start")
  (forEachEvent
    (pause "note number = " Note.Key))
  (pause "end")
)
```

### スイング

裏拍のノートだけタイミングを後ろにずらすスイング処理は以下のように書けます。
intは変数宣言と初期化。TIMEBASEは4分音符の長さが格納されているシステム変数です。
(tick Event.Time)で表拍からの時間を取得して、8分音符より長ければ事前に定義したtimeShift時間だけ後ろにずらしています。

```lisp:swing.cal
(do
  (int len8thNote (/ TIMEBASE 2))              ; 8th note length
  (int swing 50)
  (int timeShift (/ (* swing len8thNote) 100)) ; 50% of 8th note length

  (forEachEvent
    (if (== Event.Kind NOTE)
      (do
        (if (>= (tick Event.Time) len8thNote) ; if back beat
          (+= Event.Time timeShift)           ; then shift timing
          NIL
        )
      )
      NIL
    )
  )
)
```


## 実行方法

処理対象のノートを選択した状態でProcessメニューの「Run CAL...」を選択、次に実行するCALファイルを選択します。


<img width="600" alt="Run CAL" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/63c98b80-3525-c72b-6a7a-3cf5aa122c37.png">


## 感想

公式のリファレンスマニュアルがない時点で、存続可能性に不安が生じます。
言語自体はLisp的で楽しいのですが、自作関数が作れないのは拡張性にも乏しく残念です。とはいえ制限が多くdoで逐次的に書く仕様は、プログラミングがさほど得意ではない音楽制作者にも扱いやすい言語とも言えます。
昔のアプリケーションソフトウェアでは、シンプルに軽く実装できるということでアプリケーション組み込み言語にLispを採用するケースがそれなりにありました。現代では組み込み言語としてはLuaが非常に優秀で他の言語はあまり見なくなり、そういう意味でもCALは歴史を感じる言語です。
DAWの機能を自動化して制御できるスクリプト言語というコンセプト自体はとても有用なので、機能やドキュメントなど公式にメンテナンスしてほしいとは思います。

[DTMプログラミング言語探訪](https://qiita.com/aike@github/items/77b3eccc6861de09513a)