(本記事はZennに投稿したものと同一です)
TL; DR
- ソースコードは和音!実行しながら演奏を聴こう♪
- brainf*ck風の構文
- コード進行によってポインタと値を操作
A A A A A A A A A |: F G E Am :| F Fm |: C C C C C C C C A A A B Em :| C Cm X |: Db Eb Eb Eb Eb Eb C C C Fm :| C C X |: C C#m :| Cm Cm X X C C C X Ebm X D Dm A A A A A X |: F G Cm :| Gm X F Fm X F F F X Gm Gm D F# Bm |: Gm D F# Bm :| Gm X |: Gm Dm :| G X A X
Hello, world!
はじめに
コーディング中、音楽を聴くとテンションが上がりますよね1!
でも、それだけで満足ですか?
Cholcなら、なんと 音楽でプログラミングができます!あなたの「コード」を演奏してみましょう♪
Playground
以下のリンクから、ブラウザ上で実行できます。
まずはソースコードを打ち込みます。コードのボタンを押す(弾く?)と音を鳴らしながら入力できます。(もちろんtextareaへコピペも可能です)
入力がある場合はinputも入れてから、Playを押すと「演奏」開始します。順番に実行される和音を聴きながら完了を待ちましょう。メモリの様子もあわせて表示されます。
手っ取り早く結果が知りたい場合は Run
で即時実行もできます。
言語仕様
命令
Cholcの命令はbrainf*ckとよく似ています。パク(ry
- 処理系は1つのポインタとバイト配列、入出力からなる
- 命令によってポインタのアドレスや指す値を増減させられる
ソースコードは以下の6種類の命令からなります2。
operator | role |
---|---|
{メジャーコード} |
ポインタが指す値をインクリメント |
{マイナーコード} |
ポインタが指す値をデクリメント |
|: |
ポインタが指す値が0のとき、対応する :| の直後へ飛ぶ |
:| |
ポインタが指す値が0でないとき、対応する |: の直後へ飛ぶ |
v |
入力から1バイト読み込み(asciiとして)ポインタへ保存する |
X |
ポインタが指す値を(asciiとして)出力へ書き出す |
シャープには #
, フラットには b
(アルファベットの「B」)を使用します。
ポインタの移動
brainf*ckに馴染みのある方は、上記の表にポインタの移動が含まれていないことを疑問に思ったかもしれません。Cholcのポインタ移動は少し特殊で、コード進行によって決定されます。
強進行
ポインタ移動の仕組みの前に、少しだけ音楽の話をします。
ある和音の次に完全5度下(=完全4度上=半音5個上)の和音が続くコード進行は「強進行」と呼ばれます。
例えば、お辞儀の和音(C G C
)の最後の2つが強進行です。落ち着いたところに戻ってくるような感覚があると思います。
5▼は機能論の観点から見てもT–S–D–Tのサイクルを“順行”することになるので、緊張/弛緩の分かりやすい流れを作ることに長けています。
コード進行とCholcのポインタ移動量
そこで、Cholcでは、コードが強進行(完全5度下降)のときにポインタをデクリメントさせています。
(インクリメントではなくデクリメントなのは完全に主観です。戻ってくる感覚があるのでマイナスにしました。)
G C
逆方向の完全5度上がる場合(弱進行)はインクリメントします。
C G
その他のコード進行についても、ポインタ移動量は和音が完全5度下降何個分離れているかで決定されます。
コード進行 | 移動量 |
---|---|
C C |
0 |
C G |
+1 |
C D |
+2 |
C A |
+3 |
C E |
+4 |
C B |
+5 |
C Gb |
-6 |
C Db |
-5 |
C Ab |
-4 |
C Eb |
-3 |
C Bb |
-3 |
C F |
-1 |
また、ポインタ移動においてはメジャーコード、マイナーコードを区別しません。
コード進行 | 移動量 |
---|---|
C F |
1 |
C Fm |
1 |
Cm F |
1 |
Cm Fm |
1 |
補足:なぜ半音ではなく強進行?
ポインタ移動量を「半音何個分か」で定義した方が簡単な言語仕様になったと思います。一方、強進行に基づいたおかげでよく使うコード進行を近場のアドレスに集めることができます3。
C G Am Em F C F G
- コード進行とポインタ移動量
進行 | 移動量 |
---|---|
C G |
1 |
G Am |
2 |
Am Em |
1 |
Em F |
-5 |
F C |
1 |
C F |
-1 |
F G |
2 |
- 半音によってポインタ移動が定義されていた場合の移動量
進行 | 移動量 |
---|---|
C G |
7 |
G Am |
2 |
Am Em |
7 |
Em F |
1 |
F C |
7 |
C F |
5 |
F G |
2 |
ポインタ移動と値の増減の処理順
コード進行によるポインタ移動の評価は、前のコードの評価と後のコードの評価の間に行われます。
例えば、 C G
は以下の順序で評価されます。
- 初期状態ではすべてのメモリが
0
埋めされており、ポインタのアドレスは0
アドレス | 0 | 1 |
---|---|---|
ポインタ | ★ | |
値 | 0 | 0 |
- コード
C
が評価され、値をインクリメント
アドレス | 0 | 1 |
---|---|---|
ポインタ | ★ | |
値 | 1 | 0 |
- コード進行
C G
が評価され、ポインタのアドレスをインクリメント
アドレス | 0 | 1 |
---|---|---|
ポインタ | ★ | |
値 | 1 | 0 |
- コード
G
が評価され、値をインクリメント
アドレス | 0 | 1 |
---|---|---|
ポインタ | ★ | |
値 | 1 | 1 |
Cholcはチューリング完全?
チューリング完全です。任意のbrainf*ckソースコードを以下の変換表でCholcに変換することが可能です4。
brainf*ck | Cholc |
---|---|
+ |
C |
- |
Cm |
> |
C Cm E Em Ab Abm C Cm |
< |
C Cm Ab Abm E Em C Cm |
[ |
|: |
] |
:| |
, |
v |
. |
X |
変換後のソースコードでは演奏が単調になるので、実用性は微妙です
内部実装
最後に、PlaygroundのUIや処理系の実装について軽く紹介します。アトミックデザインを試みたけどぐちゃぐちゃになったのでいつか直す
UI
ポインタの移動が分かりやすいよう、各和音を時計回りにポインタがインクリメントされるように配置しました3。
また、この配置にすることで、
- 一緒に使われることの多いコードが近くに集まる
- コード進行を転調しても回転するだけで位置関係が変わらない
というねらいもあります。
音
Tone.jsを使用して和音を鳴らしています。
export function playChord(synth: Tone.PolySynth<Tone.Synth<Tone.SynthOptions>>, notes: string[], duration: string = "4n"): void {
synth.releaseAll()
synth.triggerAttackRelease(notes, duration);
}
個人的なこだわりポイントとして、音のつながりが滑らかになるよう、和音の配置(ボイシング)をすべてC4~C5の1オクターブ内に収めました。
インタプリタ
和音を鳴らしながら実行するという特性上、インタプリタも1命令ずつ評価できるように設計しています。
構成は以下の通りです。
パーサ
まずはパーサでソースコードを命令列に変換します。単純な文法なので、ASTは作らずシンプルな配列で表現しています。
後でポインタ移動の計算をしやすいよう、和音の命令は音程の順に並べています5。
和音 | 命令(バイトコード) |
---|---|
C |
0 |
C# |
1 |
D |
2 |
... | ... |
B |
11 |
Cm |
12 |
... | ... |
Bm |
23 |
評価器
通常のevaluatorではプログラム完了まで一気に評価しますが、Cholcでは1和音分(=1ステップ)評価をしたところで中断します。こうすることでUIと協調し
- 最初の和音を評価
- 評価された和音を鳴らす
- UI上のメモリと出力を更新
- 次の和音を評価
- ...
という要領でプログラムを実行することができます。
// 1ステップ評価を進め、和音やメモリの状態を返す
const state = evaluator.step()
// UIへ反映
setMemoryView(state.memory)
setResult(state.output)
おわりに
以上、コード進行でプログラミングができる言語、Cholcの紹介でした。みなさんもプログラム、もとい作曲(?)をしてみてはいかがでしょうか?
(ちなみに、share link
ボタンを押すとソースコードをURLで共有できます!)
次回はサンプルコードについて書いていく予定です。
(1/28追記)書きました