制作ツール

Pythonで音楽生成。動画のBGMと効果音を、素材なしでコードだけで作った

公開 約6分で読めます

TL;DRこの記事の要点

  • BGMも効果音も、numpyで波の数字を並べてWAVに保存すれば作れる
  • 小節ごとに楽器を足し引きして、映像の展開に曲を合わせた
  • 途切れの原因は、効果音のぶつ切りと約33dB下がる無音のブレイク
  • 今回Claude Codeには音を聞いてもらえないので、確認は数字で、最後は自分の耳で
目次7項目
  1. なんでコードで作ったの?
  2. 音は、波の数式でできている
  3. 動画の場面に合わせて、小節ごとに足し引きした
  4. 効果音は、低い音だけで作り直した
  5. 音が途切れて聞こえた原因は2つあった
  6. Claude Codeには、音を聞いてもらえなかった
  7. まとめ

動画のBGM、フリー素材じゃなくて、Pythonのコードだけで作りました。
コードを書いたのはClaude Codeで、自分は「こういう曲にして」と頼む係です。
使ったのはnumpyとscipy。
音源ファイルは1つも使っていません。

🎥 この記事の内容は動画でも解説しています。

作ったのは、受託のお仕事を紹介する43秒の縦動画のBGMです。
テンポは128BPM、23小節。
効果音も、同じやり方でコードから作りました。

先に結論です。

  • キックもベースも、波の数式で鳴らせる
  • 動画の場面に合わせて、小節ごとに楽器を足したり抜いたりできる
  • ただし、今回Claude Codeには音を聞いてもらえない。途切れに気づいたのは自分の耳だった

なんでコードで作ったの?

理由は2つです。

1つめは、自分だけの曲がほしかったから。
受託の紹介に使う動画なので、ほかの動画とかぶらない曲にしたかったんです。

2つめは、前に試したPremiere(Beta版)のAI音楽生成が、うまくいかなかったから。
イメージと違う曲が出てきて、そのうちクレジットも切れました。

で、Claude Codeに「BGMをコードで作れる?」と聞いたら、作れるとのこと。
じゃあお願い、となりました。

音は、波の数式でできている

音の正体は、空気の振動です。
なので、波の形を数字で並べてWAVファイルに保存すれば、再生したときに音になります。
今回は、波の高さを1秒に44,100回ぶんの数字にして保存しています(CDと同じ細かさ)。

たとえばキック(バスドラムの「ドッ」)は、こう作っています。
実際のコードから、キック1発をWAVに書き出すところまでを抜き出しました。
pip install numpy scipyで2つを入れれば、そのまま試せます。
コードをkick.pyとして保存して、そのフォルダでpython3 kick.pyを実行すると、同じフォルダにkick.wavができます。

import numpy as np
from scipy.io import wavfile

SR = 44100
rng = np.random.default_rng(7)

def tt(d):
    return np.arange(int(SR * d)) / SR

def kick():
    t = tt(0.42)
    f = 46 + 130 * np.exp(-t / 0.035)
    ph = 2 * np.pi * np.cumsum(f) / SR
    body = np.sin(ph) * np.exp(-t / 0.16)
    click = rng.standard_normal(len(t)) * np.exp(-t / 0.003) * 0.35
    return np.tanh(2.2 * (body + click)) * 0.9

wavfile.write("kick.wav", SR, (kick() * 32767).astype(np.int16))

やっていることは3つ。

  • 高い音から低い音へ、一瞬で下がる正弦波を鳴らす(これが「ドッ」の胴の部分)
  • 頭にごく短いノイズを足す(叩いた瞬間の「カッ」)
  • 少しだけ音を歪ませて、太くする

ほかの楽器も同じ考え方です。

  • ハイハット: ノイズから高い音だけを残す
  • ベース: ノコギリ波と正弦波を混ぜて、高い音を削る
  • パッド(後ろでずっと鳴っている和音): 少しずつ音程をずらしたノコギリ波を重ねる
  • アルペジオ: 短く「ポン」と鳴る音を、左右に振り分けて並べる
  • ライザー: 盛り上がる直前の「シューッ」。ノイズを少しずつ高く、大きくしていく

動画の場面に合わせて、小節ごとに足し引きした

コードで作ってよかったのは、ここです。
動画の構成に合わせて、小節ごとに鳴らす楽器を決められます。

  • 冒頭: パッドとハイハットだけ。少し遅れてキックが静かに入る
  • サイトを作る場面: 4つ打ちのキックとベース
  • 画面がばらける場面: 頭に「ドーン」と低い音を1発
  • 重いページの場面: いったん音を落とす
  • 速くなった数字を出す場面: ハイハットを細かくして、勢いをつける
  • 締め: 静かにしていって、最後はフェードアウト

組み立て方は単純です。
128BPMなら1拍は約0.47秒、1小節(4拍)は約1.88秒。
曲全体ぶんの空の配列を用意して、「何小節目の何拍目」を秒に直した位置に、キックやベースの音を足し込んでいきます。
最後に全体の音量を調整して、WAVに書き出せば完成です。

さっきのkick.pyの最後の行を、これに置きかえると、4つ打ちを2小節ぶん鳴らせます。

BPM = 128
BEAT = 60 / BPM          # 1拍の秒数
out = np.zeros(int(SR * BEAT * 8) + SR)  # 8拍ぶん+余白
k = kick()
for b in range(8):       # 1拍ごとにキックを足す
    i = int(b * BEAT * SR)
    out[i:i + len(k)] += k
out = out / np.abs(out).max() * 0.9
wavfile.write("beat.wav", SR, (out * 32767).astype(np.int16))

ベースを足すなら、out = out / ...の行の前に、これを入れます。
キックとキックの間(裏拍)に、低いノコギリ波を鳴らす形です。

def bass(freq, dur):
    t = tt(dur)
    saw = 2 * ((t * freq) % 1.0) - 1          # ノコギリ波
    fade = np.minimum(1, t / 0.005) * np.minimum(1, (dur - t) / 0.01)  # 頭とおしりを短くフェード
    return saw * np.exp(-t / 0.12) * fade * 0.4  # だんだん小さく

for b in range(8):       # 拍の半分ずらした位置に足す
    n = bass(55.0, BEAT / 2)                  # 55Hz = 低いラ
    i = int((b + 0.5) * BEAT * SR)
    out[i:i + len(n)] += n

実際のBGMは、ハイハットやパッドも同じように足していって、小節ごとに鳴らすかどうかを切りかえています。

既製の曲だと、映像に合う曲を探したり、曲に合わせて映像を調整したりすることになります。
コードなら、映像の展開に合わせて曲のほうを組めるんですよね。

効果音は、低い音だけで作り直した

効果音も最初は、フリー素材サイト(効果音ラボ)の音を使っていました。
でも、「シャキーン」みたいな高い音が、動画の黒っぽい雰囲気に合わなかったんです。

そこで、効果音も重心の低い音だけでコードから作り直しました。
作り方はBGMと同じで、低い正弦波とノイズの組み合わせです。

音が途切れて聞こえた原因は2つあった

できた動画を聞いて、自分が「音が途切れてる」と指摘しました。
Claude Codeに調べてもらうと、原因は2つありました。

1つめは、効果音の終わりがぶつ切りになっていたこと。
音が鳴っている途中でいきなり0になると、「ブツッ」と切れて聞こえます。
終わりに短いフェードを入れて直しました。

2つめは、BGMの「いったん音を落とす」場面。
最初は、ここをほぼ無音にしていました。
区間ごとの音の大きさ(RMS)で比べると、前の小節より約33dB小さくなっていました。
今回の動画では、耳でもほぼ無音に聞こえました。
演出のつもりが、聞いている側には「音が止まった」になっていました。

そこで、無音にするのはやめました。
キックやベースは鳴らしたまま、こもった音から少しずつ明るくなっていく形(フィルターが開いていく音)に変えています。
これで、落としても流れは止まらなくなりました。

Claude Codeには、音を聞いてもらえなかった

今回のやり方だと、Claude Codeは作った音を耳で聞けません。

なので、音の確認は数字でやってもらいました。
区間ごとの音の大きさ(RMS)を計算して、急に落ちているところがないかを見る、というやり方です。
33dBの差も、この計算で見つかりました。

でも、「途切れてる」と最初に気づいたのは自分の耳です。
数字で確かめるのはAIに任せて、聞いて変だと思うのは人がやる。
音のものは、この分担がいいなと思いました。

まとめ

  • BGMも効果音も、numpyで波を並べれば作れる。素材ファイルはいらない
  • 小節ごとに楽器を足し引きすれば、映像の展開に曲を合わせられる
  • 無音のブレイクやぶつ切りの終わりは、「途切れた」に聞こえやすい
  • 今回のやり方ではClaude Codeに音を聞いてもらえなかったので、最後は自分の耳で聞く

できた曲は、制作のご依頼ページの動画で流れています。
自分だけの曲、けっこう気に入ってます!

この記事が参考になったら、応援クリックをお願いします

Cat's Handこの記事を書いた人

Web制作歴10年以上。WordPressのテーマカスタマイズやコーディングを中心に、副業・本業ともにweb制作に携わっています。このブログでは、実際に手を動かして分かったことを書いています。