こんにちは。エンジニアの遠藤です。
この記事は自分一人ではなく、タナカさん、マツモトさん、カネマツさんとの共著という形を取らせていただきます。
また、この記事では音楽理論について触れていますが、音楽に関しては素人なので何か間違いなどがあればご指摘いただければ幸いです。
先に制作したページへのリンクをこちらに記載しておきます。 ※ 制作物は音が出るので注意してください。
- 習作1: https://enkatsu.github.io/chord-progression-graph/
- 習作2: https://enkatsu.github.io/page-progression/playback
はじめに
この記事には、シンプルな操作でそれっぽいコード進行を作り出すWebアプリを作った際の過程をまとめています。
実はこのアプリは二つの裏テーマを持って制作しました。
それは、「スタジオ・アルカナにいる豊富なタレントにフォーカスする」「生成AIを使って他分野を学習する」という二点です。
スタジオ・アルカナにいる豊富なタレントにフォーカスする
スタジオ・アルカナには、様々な人たちがいます。 今回はその中でも、バンドで作曲をしていたエンジニアのタナカさん、音楽の知見があるデザイナーのマツモトさん、HCI(ヒューマン・コンピュータ・インタラクション)とメディアアートを専門として情報学の修士号を持つエンジニアのカネマツさんに意見をいただきながら制作しました。
たまに友人から言われるのですが、弊社には面白いタレントが揃っていると思いますが、通常の業務ではそのような個性が注目されることは多くありません。 これらのエンジニア/デザイナーの個性をうまいこと組み合わせられたら良いなと思い、今回は共著に取り組んでみました。
これにより、社内のタレントに対して通常の業務からは少し離れた視点でフォーカスできればと思います。
生成AIを使って他分野を学習する
自分は楽器は弾けないのですが、最近は音系(Sound)の制作をすることが多いです。 今までは音楽系(Music)には手をつけられていなかったので、これを機に少し勉強してみようと思いました。
そんな音楽については素人の自分ですが、プログラムは多少書くことができます。 また、生成AIの有用な用途の一つとして「広い意味での翻訳」が挙げられると考えています。
なので、ここ最近は自分の専門外の分野を勉強する際に、生成AIを使って専門外の知識をコードに翻訳することで、コードを読みつついじくり回して学習しています。
自分は、プログラムは実行可能なメディアであるという点に特徴があると思っています。また、多くの分野にテクノロジーが関与する現代において、コードを読み書きできる人間には、この方法は割と有用なのでは?と考えています。
よって、少し意識してコードを通した他分野の学習について書いていこうと思います。
そこで、この記事では社内の人たちとコミュニケーションを取りながら、専門外のことを学んだ過程について書いていこうと思います。 しかし、自分はプログラマーなので、だらだらと文章ばかりを書いても仕方がないと思い、自分なりにコード進行をWebページの遷移として表現した習作を作りました。
音楽理論に関する注意事項
コード理論について学習を進めている最中に、Xで興味深いポストを見かけました。
音楽やってない人には分かりにくい時思うんだけど、音楽理論って自然科学的な「理論」とは全く違うんだよな。理論と言いながらも経験から抽出された「体系的整理」であって、予測モデルでもなければ反証可能でもない。実態は「analysis frameworks」みたいな感じ。 @kosekiseki
https://x.com/kosekiseki/status/1993705319755833670?s=46&t=7j17GvG2nSpm21lo6-_nLQ
これは、コード理論を勉強しながら薄々感じていたことですが、この方がおっしゃる通り音楽理論は自然科学における理論とは異なり、既存の音楽作品から抽出・抽象化されたモデルのようなものです。
思い返すと、様々な文献に書かれていたことなのですが、調べているうちにすっかり頭から抜け落ちていました。 これから音楽理論を勉強し始める人は、納得しながら学習するためにも、この点は留意しておくと良いかと思います。
ざっくりコード理論
まずは、コード理論について簡単に書いていこうと思います。
ですが、詳しく調べたい方はSoundQuestさんや、YAMAHAさんの記事を読んでいただければと思います。
まず、コード(Chord)とは和音のことです。
和音は、複数のピッチの音が混ざった音の固まりのことを指します。
例えばCメジャーというコードは、ドミソの音の固まりになります。
また、コードはある調(主音と音階の種類)において、トニック(T)、サブドミナント(SD)、ドミナント(D)というコード機能に分類されます。
※ この記事ではサブドミナントのことをSDと書いていますが、YAMAHAさんの記事などではSと表記されています。読みにくいかもしれませんがご容赦ください。
これらの説明は、YAMAHAさんの記事を引用させていただきます。
Tはトニック: 始まりや終止の和音となります。言葉に置き変えると、主語のようなものです。Dはドミナント: 最も緊張状態にある和音です。言葉に置き変えると、述語のようなものです。Sはサブドミナント: D(ドミナント)を飾る和音です。言葉に置き変えると補語のようなものです。
さらに、曲の中でのコードの移り変わりをコード進行と言います。 コード進行は、T、SD、Dの移り変わりを基本形にしています。
一般に、音楽の文章の型のことを、カデンツ(ケーデンス)といいます。
- カデンツ第1型(K1) T-D-T
- カデンツ第2型(K2) T-S-D-T
- カデンツ第3型(K3) T-S-T
さらに、音楽のジャンルによって、コード機能のグループの中からどのコードを選びやすいかは変わってきます。
例えば、Cメジャーのジャズでは
Dm7(SD) → G7(D) → Cmaj7(T) という進行がよく使われるそうです。
これはいわゆる Ⅱ-Ⅴ-Ⅰ と呼ばれるコード進行になります。
また、これらのコードには7という数字がついていますが、これらは7thコードと言います。 例えばCmaj7というコードは、Cのコード(ドミソ)に7度の音(シ)を足した和音になります。
最後に、絶対表記(C, Dm7, G7…)と 相対表記(Ⅰ, ii7, V7…)の2つがあることを押さえておきます。
- 絶対表記(ルート名で書く)
- 例:
C → Dm7 → G7 - そのまま「キーC」の曲であれば
CはCですがキーが変わると同じ役割のコードでも表記が変わります - 例: キーGなら
G → Am7 → D7
- 例:
- 相対表記(度数で書く)
- 例:
Ⅰ → ii7 → V7(いわゆるⅡ-Ⅴ-Ⅰ) - これは「そのキーの主音をⅠ(トニック)として、そこから何度上か」で書く表記です
- キーが変わっても表記は変わらないので、進行の型(役割)として扱いやすいのが利点です
- 例:
基本的な内容はこのくらいにして、実装の話に移ります。
習作1: コード進行をグラフ構造で表現する
今回は学習が目的なので、「『今のコード』と『次に向かいやすいコード』しか考えない」という、かなり限定的な内容で作ろうと思います。
よって、Ⅱ-Ⅴ-Ⅰのような、3つ以上のコードにまたがる文脈は考慮しません。 具体的な実装方法としては、「コードをノード」「コード間の移動確率をエッジ」とする 重み付き有向グラフとしてコード進行を構造化しました。
このグラフデータは、ひとまずClaude Codeを使って作成しました。 そして、このデータの妥当性について検討するために、グラフ構造をもとに自動演奏するシステムを実装することで、有識者の同僚タナカさんに検証してもらったところ
「それっぽいんじゃないですかね?ジャズはもう少し9thとかを使うとオシャレになるかも」
とのことでした。
そこでClaude Codeに9thを追加して重みを調整するように指示を出しました。 この過程の中で、「長調/短調でコードの機能(T, SD, D)は変わってくるの?」「音楽に使われる主要なコードは何種類?」「コードを表す時にCとかDじゃなくて、ⅠとかⅡって表記することを何て呼ぶの?」「度数表記のⅡとⅱはどう違うの?」などの質問をしつつ調査することで音楽について理解を深めていきました。何度もClaude Codeのリミットに引っかかったので、コードベースでなくてもいい内容は、ChatGPTなどに質問しつつ、YAMAHAさんのページなどを読んで学習しました。 これは余談ですが、コードという言葉を使うと生成AIがCodeなのかChordなのか判断できない時もあったので、適宜「和音」としました。
このような調整作業を経て作成した習作がこちらになります。
https://enkatsu.github.io/chord-progression-graph/
操作方法は以下のようになっています。
- p: 再生/停止
- s: style変更
- l: レイアウト変更
レイアウトは円形配置の他にも、D3.jsなどでよくある力学レイアウトにも対応しています。
重みの強いエッジほど濃く表示され、向先となっているノードほど大きく表示されるようになっています。
こちらはViteとTypeScriptで簡易的に実装しました。 ソースコードはこちらになります。
https://github.com/enkatsu/chord-progression-graph
使用したライブラリは以下の通りです。
- p5.js
- グラフィックのレンダリングに使用
- グラフデータの可視化はD3.jsでも良かったが、可視化を試行錯誤したかったので採用
- Tone.js
- 音周りの処理に使用
- p5.soundを検討したが、p5.soundはTone.jsをベースにしているため、自由度を上げるために採用
- tonal
- 音名などの記号変換
- 以下のようなコードの構成音を取得したりできる
▪Scale.get("C major").notes; // => ["C", "D", "E", "F", "G", "A", "B"];▪この構成音を元にTone.jsで和音を出力する - こんなライブラリもあるのかーと感心した
重み付き有向グラフのデータフォーマットは、D3.jsなどでよく使われるJSONのフォーマットにしました。
これは、のちのちNeo4jなどのグラフDBに格納したり、PythonのNetworkXで分析しても面白そうだと思ったからです。
{
"nodes": [
{ "id": "I7" },
{ "id": "Imaj7" },
{ "id": "I6" },
{ "id": "I9" },
// ...
],
"links": [
{ "source": "Imaj7", "target": "vi7", "weight": 0.25 },
{ "source": "Imaj7", "target": "vi9", "weight": 0.20 },
{ "source": "Imaj7", "target": "ii7", "weight": 0.20 },
// ...
]
}
{
"nodes": [
{ "id": "I7" },
{ "id": "Imaj7" },
{ "id": "I6" },
{ "id": "I9" },
// ...
],
"links": [
{ "source": "Imaj7", "target": "vi7", "weight": 0.25 },
{ "source": "Imaj7", "target": "vi9", "weight": 0.20 },
{ "source": "Imaj7", "target": "ii7", "weight": 0.20 },
// ...
]
}
各ジャンル毎にデータを用意して、コードを順番に演奏します。
この習作の実装を通して、コードの機能や種類、曲のジャンル毎のコード進行の違いについて理解を深めることができました。
習作2: ユーザのページ遷移で伴奏を作成する
次に、コードの移り変わりやすさをWebページのUIに落とし込んでいきます。 イメージとしては、仮想空間での動きが伴奏になっていくイメージです。 たとえば、押しやすいボタンを押していけば、良く言えば聞き心地の良い、悪く言えば無難なコード進行に、逆に押しにくいボタンを押していけば、良く言えば斬新な、悪く言えば違和感のあるコード進行にできればなと思いました。
そこで、音楽経験のあるデザイナーのマツモトさんに相談しました。
自分のイメージをお話ししても伝えきれないので、まずは叩き台のFigmaを作成してマツモトさんに相談しました。
そこで、いくつかアドバイスと、Figmaでデザインを作っていただきました。
- 作成したコード進行をシェアできるようにする
- URLパラメータにコード進行を持たせた
- スタート画面、終了画面を用意してゲームみたいにする
- 画面を追加
- コードの表記はフォントはもう少し目立たせる
- TODO
- コード選択は4の倍数で終わるようにする
- 現在のデータでは最後にTが来るとは限らない
- 8個目のコード選択ではT以外のコードをフィルタリング
- もしTが1つも存在しなければ、グラフ構造に存在する全てのTを表示
こちらがマツモトさんに作っていただいたFigmaになります。
また、ユーザインタラクションとメディアアートを専門としている後輩のカネマツさんにも意見をいただきました。
- ユーザに進行であることを意識させる
- 画面上部に作成したコード進行を表示
- 色と音の関係性があるなら明確にする
- T, SD, D毎に色を変える
- Blob(和音が割り振られた丸)の動きが速いとタップを躊躇う
- T, SD, D毎に速度を変える
■Tはタップしやすいようにゆっくり動く
■SDはタップを躊躇うように速く動く
- T, SD, D毎に速度を変える
また、タナカさんからは、最後はTで終止した方が良いとの意見をいただきました。
それらのアドバイスをもとに制作した習作がこちらになります。
https://enkatsu.github.io/page-progression/playback
こちらは、ページ遷移など実装する必要があるのでNext.jsで実装しました。 ソースコードはこちらになります。
https://github.com/enkatsu/page-progression
使用したライブラリは以下の通りです。
こちらでは、習作1で作成したジャズのデータのみを使用しました。
8個目のコード選択では、T以外のコードをフィルタリングし、もしTが1つも存在しなければ、グラフ構造に存在する全てのTを表示するようにしています。
今回、一番詰まったポイントは、Paper.jsを使っている際のNext.jsのビルドでした。 調べてみると、Paper.jsはブラウザだけでなく、Node.jsで動くことも想定して作られていました。
https://github.com/paperjs/paper.js?tab=readme-ov-file#installing-nodejs-and-npm Next.jsプロジェクトでビルドする際に、これが原因で転けていましたが、以下に同様のIssueを発見しました。
https://github.com/paperjs/paper.js/issues/2050
対応としては、GitHubのIssueにもある通り、Webpackのビルド設定でサーバサイドのビルドではPaperを無効化しつつ、Paper.jsを使っているコンポーネントをSSRではスキップするようにしたら解決しました。
const nextConfig = {
output: 'export',
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
webpack: (config, { isServer }) => {
if (isServer) {
// サーバーサイドでは paper を無効化
config.resolve.alias = {
...config.resolve.alias,
paper: false,
};
}
return config;
},
turbopack: {},
};
const nextConfig = {
output: 'export',
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
webpack: (config, { isServer }) => {
if (isServer) {
// サーバーサイドでは paper を無効化
config.resolve.alias = {
...config.resolve.alias,
paper: false,
};
}
return config;
},
turbopack: {},
};
"use client";
import dynamic from "next/dynamic";
const PlayPageContent = dynamic(() => import("./PlayPageContent"), {
ssr: false,
loading: () => (
<main className="w-screen h-screen overflow-hidden bg-black" />
),
});
export default function PlayPage() {
return <PlayPageContent />;
}
"use client";
import dynamic from "next/dynamic";
const PlayPageContent = dynamic(() => import("./PlayPageContent"), {
ssr: false,
loading: () => (
<main className="w-screen h-screen overflow-hidden bg-black" />
),
});
export default function PlayPage() {
return <PlayPageContent />;
}
おわりに
この記事では、まず生成AIを使った他分野の学習方法と、ざっくりしたコード理論について触れました。さらに、コード進行を重みつき有向グラフとして表現した習作と、コード進行をWebページの動線として解釈して演奏する習作を制作して紹介しました。
もっと、ハイパーリンクらしさを出して導線がコード進行に変換される様子を表現したかったのですが、それは今後の展望とします。仮想空間の構造を音楽として表現する際には、ヤニス・クセナキスの音楽と建築あたりが参考になるのかもしれないと考えています。
また、今回は自分の思いつきに付き合っていただいた、タナカさんとマツモトさん、カネマツさんに深く感謝いたします。
おまけ
この記事を書いた後に、協力してくれた三人に確認のお願いをしたのですが、カネマツさんから「自然科学の中にもサンプリングしたデータからパターンを見出してモデルを構築するアプローチはあるのではないか?」とのコメントがありました。カネマツさんは、大学院でサウンド系の研究をしたことがあるから、このような疑問が出たのかもしれません。
確かに、地学を専門としていたグループ会社の方と雑談をしている際に、地学や生物学はフィールドワークが中心となっていると聞いた時に、似たようなことを考えたことがありました。音楽理論は、音という物理現象と、人間が生み出した文化を対象としているからこそ、このような議論が生まれるのかもしれません。
社内でこういった議論ができるのはいいなと思いました。