The Bach Generator That Stopped Repairing
バッハの器楽曲を生成するプログラムを書いている。オルガンのフーガ、無伴奏チェロ組曲、シャコンヌ。譜面ではなくMIDIを吐く。最初のアーキテクチャは、いま思えば素直すぎた。まず音を生成して、ルール違反をあとから直す。 これで動くと思っていた。
動いた。並行5度は消えた。声部交差も直った。跳躍も収まった。テストは緑になった。
でも、鳴らすと音楽ではなかった。
直せば直すほど音楽でなくなる、という奇妙な手応えがあった。リペアをいくら足しても、吐き出されるのは不協和音の嵐だ。子どもがオルガンをでたらめに叩いているような、聞くに堪えないデータ。それが延々と出てくるのを見て、この先にゴールはないと悟った。わたしはエンジンを丸ごと捨てた。リペアという発想そのものを捨てた。この記事は、その方針転換の記録だ。
対位法を、まず3分で
先に最低限の言葉を用意しておく。これを読んでいるエンジニアの多くは、たぶん対位法とは縁がなかっただろう。読み飛ばしてもいいが、ここを通しておくと後半が一気に読めるようになる。
対位法(counterpoint)とは、独立した複数の旋律を同時に鳴らして、それでも調和させる書法だ。メロディに伴奏のコードを付けるのとは違う。主旋律と和音、ではなく、どの線もそれ自体が旋律として成立していて、しかも重ねて鳴らしても破綻しない。各々の線を声部(voice)と呼ぶ。フーガなら3声、4声と重なる。
軸は二つある。横——一本の線が、それ単独で歌える旋律になっていること。縦——ある瞬間に同時に鳴っている音どうしが、濁らずに響くこと。作曲とは、この縦と横を同時に満たし続ける作業だ。
たとえるなら、数人が同時に喋る会話に近い。各人が筋の通った話をしていて(横)、かつ全員が声をそろえて同じ言葉を唱和したりはしない(縦の独立)。誰か一人に全員が飲み込まれた瞬間、それは会話ではなく斉唱になる。
協和と不協和
二つの音の隔たりを音程(interval)という。隔たりによって、響きは安定したり緊張したりする。ここから先の譜面はどれも再生できる。再生ボタンを押して、耳で確かめてほしい。
まず完全協和音程——完全5度や完全8度。
なぜ「完全」と呼ぶのか
「完全」は出来栄えの話ではない。ドからソまでが完全5度、ドから1オクターヴ上のドまでが完全8度——どちらも溶け合う力が強く、二つの音がほとんど一つに聞こえる。3度や6度には長・短の二種類があって響きが揺れるのに、5度と8度にはその揺れがなく一種類しかない。だから「完全」と呼ぶ。そしてこの溶け合いの強さこそが、のちに「並行させてはいけない」と禁じられる理由になる。
次に不完全協和音程——3度・6度・10度。
2度・7度・三全音(トライトーン)は不協和音程で、緊張を生む。
運動
二つの声部が動くとき、関係は三通りだ。同じ方向に動く並行(parallel)、逆方向に動く反行(contrary)、片方が止まってもう片方だけが動く斜行(oblique)。声部の独立は、常に並行しないことから生まれる。
拍
もう一つだけ。拍には強弱の序列がある。各小節の頭が強拍で、それ以外は弱拍だ。さっきの不協和は、弱拍でなら通り抜けてよいが、強拍に置けば構造の誤りに聞こえる。どこに緊張を置けるかは、拍の位置で決まる。
これで、声部・協和と不協和・運動・拍の強弱がそろった。道具はこれで足りる。ここからが本題だ——なぜ、リペアでは音楽にならないのか。
学習して、生成して、直す
捨てた側のエンジン——コミットログで「legacy generation system」と呼んでいたもの——は、当時のわたしとしてはよく出来ていたと思う。三段構えだった。
まず学習。本物のバッハを448曲ぶんJSON化したコーパスを用意し、そこからMarkov連鎖の遷移表や5-gramの語彙テーブルを抽出した。「この音型のあとには、この音型が来やすい」という統計だ。
次に生成。語彙テーブルを引きながら音を並べる。声部の動きには制約ソルバを噛ませた。ConstraintStateという三層モデル——果たすべき義務(Obligation)、破ってはならない不変条件(Invariant)、各音の引力(Gravity)——を持ち、FeasibilityEstimatorが「この先まだ解けるか」を見積もる。それなりに大掛かりな仕組みだった。
そして採点。bach-mcpという姉妹プロジェクトのスコアラに生成結果を渡し、本物のコーパスとの近さで点をつける。点が低ければ作り直す。といっても自動のループではなく、スコアを見てわたしが手で回していた。
要するに、データからバッハを学習し、模倣して生成し、おかしいところを直す。今のAI生成の素朴な発想とそう変わらない。問題は「直す」のところに全部詰まっていた。
リペアが積み上がる
生成された音には、必ずルール違反が混じる。そのたびにリペアのパスを足した。用語は流し読みでいい——見てほしいのは、コミットログに残った積み上がりそのものだ。
- 並行5度・8度を消す(
parallel repair) - 跳躍——音が大きく飛ぶこと——を直す(
leap resolution module) - 同じ音の繰り返しを直す(
repeated-note repair) - 隣り合う声部の音のぶつかりを直す(
tritone repair、cross-relation fix) - 外声がうっかり完全音程に着地するのを止める(
hidden-perfect outer-voice rule) - 声部どうしの衝突を時間軸ごと解く(
harmonic-timeline-aware collision resolution) - 曲の終わり(コーダ)の声部進行を作り直す(
coda voice-leading search)
一つひとつは正しい。並行5度は対位法で禁じられている。直すべきだ。でも、リペアを足すほど、別の問題が顔を出す。並行5度を消そうと1音ずらすと、そのずらしが跳躍を生む。跳躍を直すと同じ音の繰り返しになる。それを直すと、今度はまた別の声部と並行8度になる。モグラ叩きだった。
そして致命的だったのは、リペアは規則違反を消せても、音楽を作れないということだ。
リペアが音楽を壊す
なぜ局所的なリペアでは音楽にならないのか。具体的に、耳で確かめながら見ていく。
並行5度が禁じられるのは、道徳の問題ではない。さっきの会話のたとえで言えば、二人が完全5度のまま声をそろえて動いた瞬間に、二声が一声へ溶けてしまうからだ。
リペアの発想だと、これは「片方の音を1個ずらせば消える」問題に見える。実際、消える。でも、ずらした1音は旋律の論理の中にいる。前後の音となめらかに繋がっていた線が、リペアの都合で折れる。違反は消えても、線が死ぬ。
隠伏5度はもっと厄介だ。今その瞬間は5度になっていなくても、外声が同じ方向から完全5度に「到達」すると、耳には並行5度の残響が残る。
ここで効いてくるのが、禁じられているのは完全音程の並行だけという事実だ。3度や6度——10度も含めて——の並行はむしろバッハの常套手段で、二声がきれいに寄り添って動く。
つまり「並行を消す」リペアは粗すぎる。完全音程か不完全音程かで扱いが正反対なのだから、消すべきものと残すべきものを、音を置く前に判断していなければならない。
掛留は、あとから作れない
リペアの限界がいちばんはっきり出るのが掛留(サスペンション)だ。
掛留は、前の拍で協和していた音をそのまま次の拍へ「保留」し、その拍では不協和になり、続けて2度下行して解決する。準備—保留—解決の三拍子。不協和になる前に、協和として準備されていなければならない。
問題はここだ。リペアは「いま鳴っている不協和音」を見つけてから動く。でも掛留の準備は、不協和が鳴る一つ前の拍で済んでいなければならない。リペアが違反を見つけた時点で、もう手遅れなのだ。時間を巻き戻して前の拍を協和に書き換えれば、今度は前の拍の整合が崩れる。
準備なしにいきなり不協和を置くと、それはただの濁りであって掛留ではない。
バッハの掛留の連鎖を聴くと、不協和が緊張と解決のリズムを生んでいるのがわかる。これは生成後の修正では絶対に届かない。設計の段階で時間軸ごと組み立てるしかない。
転回しても成り立つ、という制約
もう一つ、局所リペアが原理的に届かない例がある。転回対位法だ。
ここでいう「転回」は、下の声部をまるごとオクターヴ上げて、上と下を入れ替えることだ。入れ替えると、二声の音程が変わる。たとえば下が C、上が G なら完全5度。その C を G の上へ持ち上げると、今度は G と C で完全4度になる。5度が4度に化けたわけだ。同じように3度は6度に、6度は3度になる。上下の数字を足すと必ず9になる、と覚えておけばいい。
下のトグルで、それを目で確かめられる。「原型」と「転回」を切り替えると、上の線A は動かないまま、下の線B(青)だけが1オクターヴ上がり、3度が6度に変わる。
ここに罠がある。二声だけで鳴るとき、完全4度は不協和として扱われる——完全5度は協和なのに、だ。つまり、入れ替える前は安定していた5度が、入れ替えた瞬間に不協和の4度へ反転してしまう。
4度が不協和、という違和感
ソからその上のドまでが完全4度。耳には落ち着いて聞こえるのに、二声だけで鳴らすと不協和として扱われる——昔からの対位法の決まりだ。理由は、下のソが「土台」のように聞こえてしまい、上のドが宙ぶらりんに感じられるから。下にもう一声足して三声にすれば、4度はちゃんと協和に戻る。だから困るのは、二声を上下そっくり入れ替えるこの場面だけだ。
これを満たすには、二つの線を「上にあっても下にあっても通用する」よう最初から設計しなければならない。生成してからリペアで近づけられる性質のものではない。バッハのフーガでは、主題と対主題がこの制約を当然のように満たしていて、だからこそ展開部で上下を入れ替えても崩れない。
声部交差も同じ匂いがする。旧エンジンは交差を「検出して直す」対象にしていた。でもバッハは意図的に声部を交差させて、線の独立性をむしろ際立たせる。一律に直してはいけないものを、リペアは一律に直してしまう。
最初から正しく置く
結論はシンプルだった。生成してから直すのをやめて、置く瞬間に正しい音を選ぶ。
新しいエンジン——コミットでは「composer subsystem」と名付けた——は、和声プラン(harmonic plan)と各声部の意図(voice intent)から直接MIDIを組み立てる。中心にあるのはcandidate_searchだ。ある位置に音を置くとき、候補となる音を一つずつ評価する。並行完全音程になっていないか、声部交差を起こさないか、跳躍は適切か、不協和は準備・解決されているか、経過音として正当か、フーガの応唱を運べているか。各ルールでスコアをつけ、最も高い候補を採る。
違反が「出てから」直すのではない。違反になる候補は、そもそも選ばれない。リペアのパスは要らなくなった。生成後に走るのはvalidatorだけ——構造(structural)、横の流れ(horizontal)、縦の響き(vertical)の最終チェックで、これは直す装置ではなく、置き方が正しかったかを確かめる装置だ。
新エンジンが立ったところで、退路を断った。
- まずバッハ参照コーパス448曲のJSONを、スコアラごとbach-mcp側へ移した。生成エンジンはこのデータに依存していない。本物のバッハを参照するのは、最後の検証のときだけだ。
- C API・CLI・WASMビルドの全10形式を、新しい
composer経由に切り替えた。 - そして旧エンジンを丸ごと削除した。counterpoint、fugue、forms、ornament、instrument、transform、solo-stringの各サブシステムと、それらを束ねていたGeneratorごと。
削除後の検証は、ctest 920件、WASMビルドとJSテスト217件、Pythonテスト146件、すべて緑。捨てたコード量のわりに、壊れたものは何もなかった。リペアの山は、実は本質ではなかったのだ。
規則は、耳で確かめる
candidate_searchが見ているルールを、耳で聞ける教材にしてみたかった。そこで対位法講座をサイトに併設した。この記事に貼った再生可能な譜面は、すべてその講座から持ってきたものだ。
講座はバッハの作例で文法を教える。たとえばフーガの応唱。主題をそのまま属調に移すと「真正応唱(real answer)」、調的なつじつまを保つために一部を調整すると「変応唱(tonal answer)」になる。どちらを選ぶかは局所の問題ではなく、曲全体の調的設計の問題だ。
属調(ドミナント)とは
主調——曲のホームになる調——の5度上の調を属調という。ハ長調がホームなら、属調はト長調。フーガでは主題がまずホームの主調で現れ、応唱が属調でそれに答える。このホームと属調の「呼びかけと応え」が、フーガという形式の背骨になっている。
主題が入ったら、それに寄り添う対主題が要る。さらに、主題の入りが重なり合うとストレッタになる。
そして終止。完全終止だけでなく、期待を裏切る偽終止、短調の曲を長三和音で閉じるピカルディの三度。終止は和声の文法であって、これも音を置く前に決まっていなければ書けない。
講座の章立ては、そのままcandidate_searchが見ているルールの一覧でもある——音程と協和、運動と禁則並行、不協和の処理、旋律法、調的文法、フーガの技法、形式ごとの制約。記事の制約と講座の章が一対一で対応している。理屈に興味が湧いたら、対位法講座を最初から辿ってもらうのが早い。どの譜面もブラウザでそのまま鳴らせる。
何がわかったか
「バッハは論理だ」と思って始めた。違った。
正確には、論理ではあるのだが、論理をあとから当てはめることはできない。並行5度を消す、跳躍を直す、不協和を解決する——一つずつなら全部できる。でも音楽は、そういう局所的な正しさの寄せ集めではない。掛留は鳴る前に準備されていなければならず、転回対位法は上下を入れ替えても成り立っていなければならず、応唱は曲全体の調的設計の中にいなければならない。どれも「置いたあとで直す」では原理的に届かない。
生成してから直すのではなく、最初から正しく置く。当たり前のことを、エンジンを一つ捨てるまで理解できなかった。そして生成された曲を鳴らすたびに、バッハの偉大さをかみしめている。一音も後から直さずに、これだけのものを書いた人がいる。