ABEJA Tech Blog

中の人の興味のある情報を発信していきます

生成AI時代にあえて一からプログラミングで作曲してみた

本記事はABEJAアドベントカレンダー2024 12日目の記事です。

こんにちは!データサイエンティストの安倍(あんばい)です。 競馬事業部部長を勝手に名乗り、社内にて競馬布教活動に従事しています。今年も順調に収支はマイナスです。

さて、今回のテーマは「作曲」になります。生成AIブームの昨今、誰でも簡単に文章や画像、音楽、コードetc. 何もかも生成できる時代になりました。 このタイミングであえて時代に逆行し、「理論ベースで一からプログラミングで曲を作る」という逆張りの取り組みをしたので紹介したいと思います。

目次

音とは何か

ChatGPT o1先生曰く、下記が音の定義です。

音とは、振動する物体が空気などの媒質を介して伝わる圧力変動であり、それを耳が拾い、脳が信号として解釈することで「音」として知覚される現象です。以下の要素で構成されます。

  1. 振動源(音源):振動して音を発生させる物体。
  2. 媒質(伝播経路):空気や水など、音を伝える物質。
  3. 音波特性:周波数(高さ)、振幅(大きさ)、波形(音色)といった要素。
  4. 受容器(聴覚器官):音波を捉え、神経信号に変換し脳へ伝える器官。

本記事は 3. 音波特性 に着目をし、作曲をしていきます。普段私たちが耳にする音は、様々な波の足し合わせから成り立っています。疑似データを作成し例を見ていきましょう。

疑似データ生成

261.626Hz(音階ド C4)と391.955Hz(音階ソ G4)の2つの音を生成します。

Signal C4
Signal G4

上記2つの波形を足し合わせると下記のような音になります。音量注意

C4 + G4

音量注意と前置きした通り、音を加算したことでAmplitude(音の大きさ)が2倍になっています。波形を見るとsine波が変形し、いびつな形になっています。現実ではもっと様々な波が複雑に重なり合い、私たちの耳に届いている訳です。

複雑な波を分解する

先程の例では周波数が異なるsine波を加算することで新しい音を作り出しました。音はsine波やcosine波といった基本的な波形の重ね合わせの上で出来ているため、複雑な波形であっても理論的に最小単位の波に分割することが可能です。フーリエ変換を使い、先程の合成した波形が、どの周波数帯の音からなるかを調べてみます。(フーリエ変換の説明は割愛致します。)

import matplotlib.pyplot as plt
import numpy as np

amplitude = 1
freq_c = 261.626
freq_g = 391.955
sampling_rate = 44100
duration = 1

# サイン波の生成
time = np.arange(0, duration, 1 / sampling_rate)
signal_c = amplitude * np.sin(2 * np.pi * freq_c * time)  # C4
signal_g = amplitude * np.sin(2 * np.pi * freq_g * time)  # G4
signal = signal_c + signal_g  # C4 + G4

# フーリエ変換
fft_result = np.fft.fft(signal)
frequencies = np.fft.fftfreq(len(fft_result), 1 / sampling_rate)
fft_magnitude = np.abs(fft_result)

plt.plot(frequencies[: len(frequencies) // 2], fft_magnitude[: len(frequencies) // 2])
plt.xlim(200, 500)
plt.title("FFT result")
plt.xlabel("Frequency (Hz)")
plt.ylabel("Magnitude")

FFT result

加算前のC4とG4の周波数帯に分割出来ているのが分かります。簡単ですね!(筆者は学生時代にこの内容をC言語で実装するという課題に大苦戦しました。便利な時代になりましたね。)

基本波形の紹介

最後に、シンセサイザーの基本波形、sine・triangle・saw・squareを紹介して本セクションを締めたいと思います。

sine wave

triangle wave

saw wave

square wave

サイン波で曲を作っていく

音が単純な波形の組み合わせで出来ているというのが理解頂けたかと思います。であるならば、加算やら乗算やら頑張って音を作っていけば曲も作れるという訳です! 作曲ソフトを使えば簡単に実装できますし、こんな知識が無くても生成AIに曲の雰囲気を伝えればそれっぽいものが出来てしまう時代なのですが、あえて挑戦していきます。

Csound

今回はCsoundという言語を用いて作曲に挑戦していきます。 csound.com

本編に入る前に、何故Csoundなのかを少しだけ語らせて下さい。 シンセサイザーの魔術師ことBTがCsoundを使って「All That Makes Us Human Continues」という名曲を生み出したのは一部界隈では有名な話しです。 学生時代に拝聴し、もの凄い衝撃を受けすぐにCsoundをインストールしました。ただ、独特な記法やマイナー言語故の情報の少なさ、当時の能力の低さですぐに断念してしまいました。 そこから約6年、最強の勉強ツールChatGPTを手に入れたので、リベンジも兼ねて挑戦をしていきます。

チュートリアル

下記がCsoundで作成したチュートリアルのコードになります。

<CsoundSynthesizer>
<CsOptions>
-odac -o ../output/hello_csound.mp3
</CsOptions>
<CsInstruments>
sr = 44100 ; sampling rate
kr = 4410 ; control rate
ksamps = 32 ; number of samples in a control period (sr/kr)
nchnls = 2 ; output channeli
0dbfs = 1 ; sets value of 0 decibels

instr 1
    a1 oscil p4, p5, 1
    out a1
endin

</CsInstruments>
<CsScore>
; f: function number, load-time, table-size, GEN
; sine wave
f1 0 4096 10 1

; i: instrument number, Start-time, Duration, Amplitude
i1 0.0 0.4 0.5 261.63 ; C4
i1 0.5 0.4 0.5 261.63 ; C4
i1 1.0 0.4 0.5 392.00 ; G4
i1 1.5 0.4 0.5 392.00 ; G4

i1 2.0 0.4 0.5 440.00 ; A4
i1 2.5 0.4 0.5 440.00 ; A4
i1 3.0 0.9 0.5 392.00 ; G4

i1 4.0 0.4 0.5 349.23 ; F4
i1 4.5 0.4 0.5 349.23 ; F4
i1 5.0 0.4 0.5 329.63 ; E4
i1 5.5 0.4 0.5 329.63 ; E4

i1 6.0 0.4 0.5 293.66 ; D4
i1 6.5 0.4 0.5 293.66 ; D4
i1 7.0 0.9 1.0 261.63 ; C4
e
</CsScore>
</CsoundSynthesizer>

ぱっと見て分かりそうで分からないコードですね。詳細の説明は割愛し、パーツごとにざっくり説明をしていきます。

  • CsInstruments
    • Pythonのコードでも登場したサンプリング周波数や最大振幅数、出力チャンネル数など、音に関する初期設定を行う。
    • instr: 一般的な言語で言う型。instrument型。音を生成して出力する箱みたいなもの。サンプルコードではsine波を出力している。
  • CsScore
    • 音をどう鳴らすかを定義している楽譜のようなもの。音の長さや音の高さを数字で細かく指定している。

Csoundの記法は癖があり、p・i・k・a等が型として予約されておりiGuitarkCountのような形で変数を宣言するという非常に理解しづらい言語です。また、波形の組み合わせで音を作っていくため音階は全てHzで記載する必要があります。詳細はこちら。 github.com

生成AIに質問しながらコードを書いていく

結論から言います。マイナー言語すぎて、9割動きませんでした(笑)

「特定のプログラミング言語を理解している人が、初めて触る言語を何となくで弄ってエラーが出てしまっている。」くらいの内容でした。 特に多かったのは、ありそうな名前の関数を勝手に生み出し利用するケースです。配列の長さを取得するためにlen()を使うみたいなイメージですね。

上記チュートリアルコードは初手ChatGPT 4oを利用して作成しましたが、音声に関する設定が一部抜けていて音が出なかったり、スコアの引数が誤っていたりと残念ながらそのまま利用することは出来ませんでした。 もっと簡単なsine波を生成して再生するというタスクですらエラーが出たり音が鳴らなかったりと使い物にならない。。。

さて。困った。とても困った。ここまで来てコア部分の実装ができない可能性が出てきた。やばい(笑)

頑張った

頑張りました。とにかく色々調べ「真似できる所は真似る。諦める所は諦める。」の精神で何とか形になりました。下記が作成した音の一覧とコードになります。

  • ドラム
    • キック: サンプリング音源を使用
    • スネア: サンプリング音源を使用
    • ハイハット: ホワイトノイズを生成し、音量や音の変化のさせ方を調整
  • ベース
    • 周波数やら振幅やら色々と調整し生成
  • PAD
    • ホワイトノイズを生成し、共振フィルターをかけたり周波数やら振幅やら色々と調整し生成
  • メロディ
    • 自分でMIDIで打ち込み生成

終わりに

ということで、最後に作成したコードをライブで動かした動画をお見せして本記事を締めたいと思います。最後までご覧頂きありがとうございました!

X: @bai2_25 github.com

謝辞

作成にあたり、byulparan様のコードを参考にさせて頂きました。ハイハットやPAD等は完成度が非常に高く、ほぼそのまま利用させて頂いております。この場をお借りして感謝申し上げます。 github.com

We Are Hiring!

ABEJAは、テクノロジーの社会実装に取り組んでいます。 技術はもちろん、技術をどのようにして社会やビジネスに組み込んでいくかを考えるのが好きな方は、下記採用ページからエントリーください! (新卒の方やインターンシップのエントリーもお待ちしております!)

careers.abejainc.com

特に下記ポジションの募集を強化しています!ぜひ御覧ください!

プラットフォームグループ:シニアソフトウェアエンジニア | 株式会社ABEJA

トランスフォーメーション領域:ソフトウェアエンジニア(リードクラス) | 株式会社ABEJA

トランスフォーメーション領域:データサイエンティスト(シニアクラス) | 株式会社ABEJA