JavaScriptでライブコーディングするためのライブラリを作っている話

ブログ
プログラミング
JavaScript
ライブコーディング
Mage
npm

自作ライブラリ「Mage」

1 年ぐらい前から「ブラウザ上でライブコーディングがしたいなぁ」と思っていて、Web Audio APIを使って色々やっていたのですが、最低限動くものになったので、多分私しか使わないライブラリもしかしたら私も使わないかもしれないですが、npm にパブリッシュしてしまいました。

npm : @millstones/mage

ソース : GitHub

DSL を覚えるのはしんどいので、多少冗長でも JavaScript のシンタクスの範囲で実現するのがコンセプトです。 (逆にいうと、ある程度 JavaScript でプログラミングができる人でないと、まともに扱えないと思います。)

Mage は「Making music, generated with code.」の略です。

TidalCyclesSonic Piといった偉大な先人がある中でなんでこんなことやってるのかというのはありますが、別に比肩する存在になろうとか大それたことは考えていなくて、要は私が書き慣れた JavaScript(というか TypeScript)を使って、コードベースで作曲のアイディア出しに使えるものができないかなと思ったのがきっかけでした。

まあ行き詰まってくると、いろんなことを試してみたくなるわけですよ。

使い方

こんな感じで使う。

仕組み

createMageがこのライブラリの中心となるmageオブジェクトを返します。 beatsPerCycleは Mage 上で 1 サイクルとする拍数で、 例えば、{ Tempo : 120, beatsPerCycle : 4 } なら、120bpm で 4 拍が 1 サイクルになります。 後述するcast()メソッドは、サイクルの終わりまで呼び出しが先送りされるようになっています。色々変更されるタイミングの拍がこれ。

Mage では音源とシーケンスの 2 つの要素と組み合わせたSpellオブジェクトを作って、ループさせることで音を重ねていきます。

音源(Source)はmage.createSynth()またはmage.createSampler()という Factory 関数を用意しているので、これで作ります。ただ、所定のplay()メソッドが実装されていればなんでもいいので、p5.js とか Tone.js とかのオブジェクトをラップしてどうにかすることもできるかもしれません(未確認)。

シーケンス(Sequence)は、実際のノートの配列になります。以下の型に合致するものを 1 ステップとして、Step の配列で定義します。

export type Step = {
  noteNumber: number | number[]; // 配列の場合は同時に鳴る
  volume: number;
  duration: number;
};

ただ、これを地道に書いてたら音が鳴る前に日が暮れてしまうので、createSequence()という関数を用意しています。これは、スケール(scale)とパターン(pattern)からシーケンスを作る関数です。

  • スケールは MIDI ノートナンバーの配列です。例えば、C4 を根音とする CMaj7 の構成音であれば[60, 64, 67, 71]になります。

  • パターンは Mage で唯一ちょっと DSL っぽい部分で、各ステップの内容を文字リテラルで決められるようになっています。

    • 0-9a-f はスケール中のインデックスを 16 進数表記で指定するもので、例えばスケールが[60, 64, 67, 71]の時、0601なら64になります。
      • つまり、1 スケールで使える音程は 16 種類まで。
        • 7 音 2 オクターブでほとんど埋まってしまうので、これは何か別途対策を考えるかもしれない
    • スペース( )は休符を表します。
    • ピリオド(.)は、休符でない直前のステップを繰り返します。
    • 疑問符(?)はスケールの中からランダムに 1 音選びます。
    • ハイフン(-)は直前の音の長さを 2 倍にして、そのステップ自体は音を鳴らしません。
    • 大カッコ([])は連符です。当該ステップの中でカッコ内の音を等間隔で鳴らします。
      • 例えば、"000[00]"とすると、4 拍のシーケンスで、最後だけ 1 拍に 2 回音が鳴ります。
        • 四つ打ちキックの「ドン|ドン|ドン|ドド」というやつです。
          • 入れ子にすることもできます。
    • 中カッコ({})は和音です。カッコ内の音を同時に鳴らします。
      • つまり、"{0123}"は CMaj7 のコードに、"[0123]"は CMaj7 のアルペジオになります。
      • 中カッコは入れ子にすることはできず、中カッコの中で出てきたカッコ類は全て無視されます。

スケールとパターンを組み合わせてシーケンスを作るので、パターンが同じでもスケールを差し替えることができます。例えば、"{0123}"はスケールが[60, 64, 67, 71]なら CMaj7 になりますが、[62, 65, 69, 72]なら Dm7 になります。ここが Mage の肝心です。

SourceSequenceが準備できたところで、これをmage.cast()することで、いよいよ音が鳴ります。 spellには名前が必要になります。トラック名とかフレーズ名みたいなもので、同じ名前で後から cast すると上書きします。これはライブコーディングを想定した仕様のつもり。

propsにはsound,sequence,durationを渡す必要がありますが、sound は音源Sourceそのものではなく、Sourceを返す関数を、sequence も同様に、シーケンス(Stepの配列)を返す関数を渡します。これらの関数には引数として{ cycles, beats, loopCount }が渡されるのですが、要は曲の進み具合や、に応じて音色やフレーズが変わるようにできます。

  • cyclesは開始してからのサイクル数
  • beatsはサイクル内の何拍目かを示す数
  • loopCountは、そのSpellが何回実行されてるかを示す数
    • なので、 繰り返しのたびにフレーズを変えたり、拍に応じて音色を変えたりできます。

これも Mage のもう一つの肝の部分です。

durationSpellの長さを拍数で指定します。これは Mage 本体のbeatsPerCycleとは無関係で、この長さをシーケンスのステップで等分して鳴らします。なので、5 連符とか 7 連符のようなリズムを組み合わせた複雑なポリリズムや、フレーズごとに長さが違うポリメーターが簡単に実現できます。やってみるとわかりますが、すぐにわけわかんなくなります

これも Mage の肝と言っていいでしょう。一体いくつ肝があるんだ。

いろいろ命名が整理しきれない感はありますが、構成要素としてはこんな感じです。

問題点

デモ用に CodeSandbox にコードを書きましたが、これリアルタイムで書くの大変すぎるな。 専用のスニペット集みたいなのをエディタ上で呼び出せるようにしないと厳しそう。

API ドキュメント

未整備。

  • TSDoc は ChatGPT に書いてもらったので、もし使おうという奇特な御仁がいればリポジトリのdocsフォルダを参照のこと。
    • せいぜい私しか使わないと思うので、あんまり整備するモチベーションがない。

今後

  • エフェクターの実装と、ビジュアライズ用のインターフェイスを追加したい。

  • Mage だけ独立してライブラリ化しているけど、元々はブラウザでライブコーディングできる Web アプリとして開発中の「Nimbus」の副産物です。

    • Nimbus もある程度動かせる状態になったら、そのうち Vercel あたりでホストする予定。
  • ちなみに、2022 年 4 月にツイートしたこれは、その Nimbus の前身。

    • この頃はライブラリと Web アプリを区別してなくて、これ全体が「Mage」だった。
      • その後、ライブラリを「Mage」、 Web アプリを「Cauldron」として切り分けたのだが、ごちゃごちゃしてわけわかんなくなったので、全部捨てて書き直しました。