はじめに
レトロゲームの魅力の一つである8ビットサウンド。そのノスタルジックな音色は、多くのゲーマーの心を掴んで離しません。今回は、Pythonを使って8ビット風のゲーム音楽を生成する方法を紹介します。特に、スペースシューターゲームをイメージした約45秒の曲を作成していきます。
要件と仕様
このプロジェクトを始める前に、明確な要件と仕様を定義しました。これにより、目的に沿った実装が可能になり、また読者の皆さんにもプロジェクトの意図をより良く理解していただけると思います。
要件:
- Pythonを使用して、8ビット風の音楽を生成すること。
- スペースシューターゲームに適した雰囲気の音楽を作ること。
- 楽曲の長さは約45秒とし、ゲームの進行に合わせたストーリー性を持たせること。
- 生成した音楽をMP3形式で保存できること。
- コードは再利用可能で、カスタマイズしやすい構造にすること。
仕様:
-
音声合成:
- 矩形波を使用して8ビット風の音を生成する。
- ADSRエンベロープを適用して、音の立ち上がりと減衰を自然にする。
-
音楽構成:
- イントロ(宇宙船の出発):約8秒
- メイン戦闘シーン:約16秒
- ボス登場のジングル:約4秒
- ボス戦:約12秒
- エンディング(勝利のファンファーレ):約5秒
-
使用音階:
- C3からG6までの音符を使用可能とする。
-
テンポ:
- 基本テンポを150 BPMとする。
-
トラック構成:
- メロディトラックとベースライントラックの2トラックを使用。
-
出力形式:
- 44.1kHz、16ビット、ステレオのMP3ファイル。
-
ライブラリ:
- numpy: 数値計算用
- scipy: 信号処理用
- pydub: オーディオセグメント操作用
- simpleaudio: オーディオ再生用
これらの要件と仕様に基づいて実装を行いました。では、具体的な実装の詳細を見ていきましょう。
必要な環境
まず、以下のライブラリをインストールする必要があります:
pip install numpy scipy pydub simpleaudio
実装の解説
1. 音符と周波数の定義
まず、音符とその周波数を定義します。これにより、音楽理論の知識がなくても、直感的に曲を作ることができます。
NOTES = {
'C3': 130.81, 'C#3': 138.59, 'D3': 146.83,
# ... 中略 ...
'F#6': 1479.98, 'G6': 1567.98
}
2. 波形の生成
8ビット風の音を作るために、矩形波を使用します。
def create_square_wave(frequency, duration, duty_cycle=0.5, sample_rate=44100):
t = np.linspace(0, duration, int(sample_rate * duration), False)
wave = signal.square(2 * np.pi * frequency * t, duty=duty_cycle)
return wave
3. エンベロープの適用
音の立ち上がりと減衰を自然にするために、ADSR(Attack, Decay, Sustain, Release)エンベロープを適用します。
def apply_envelope(wave, attack=0.01, decay=0.1, sustain=0.7, release=0.1):
# ... エンベロープの計算と適用 ...
4. メロディの作成
メロディは音符と長さのペアのリストとして定義します。ここでは、ゲームの各シーンに合わせてメロディを構成しています。
intro_melody = [
('C5', 0.25), ('E5', 0.25), ('G5', 0.25), ('C6', 0.25),
('B5', 0.5), ('G5', 0.5),
# ... 続く ...
]
# メイン戦闘シーン、ボス登場ジングル、ボス戦、エンディングも同様に定義
5. ベースラインの追加
メロディに深みを加えるために、シンプルなベースラインを追加します。
def create_bass_line(tempo=120):
bass_pattern = [
('C3', 0.5), ('G3', 0.5), ('C4', 0.5), ('G3', 0.5),
# ... 続く ...
]
return create_melody(bass_pattern, tempo)
6. トラックのミキシング
メロディとベースラインを組み合わせて、最終的な音楽トラックを作成します。
def mix_tracks(*tracks):
mixed = AudioSegment.silent(duration=max(track.duration_seconds for track in tracks) * 1000)
for track in tracks:
mixed = mixed.overlay(track)
return mixed
使用方法
完全な実装を space_shooter_music_generator.py
として保存し、以下のように実行します:
python space_shooter_music_generator.py
実行すると、約45秒のスペースシューターゲーム風の音楽が生成され、再生された後、MP3ファイルとして保存されます。
コードを表示
import numpy as np
from scipy import signal
from pydub import AudioSegment
import simpleaudio as sa
# 音符と周波数の定義(E6まで拡張)
NOTES = {
'C3': 130.81, 'C#3': 138.59, 'D3': 146.83, 'D#3': 155.56, 'E3': 164.81, 'F3': 174.61,
'F#3': 185.00, 'G3': 196.00, 'G#3': 207.65, 'A3': 220.00, 'A#3': 233.08, 'B3': 246.94,
'C4': 261.63, 'C#4': 277.18, 'D4': 293.66, 'D#4': 311.13, 'E4': 329.63, 'F4': 349.23,
'F#4': 369.99, 'G4': 392.00, 'G#4': 415.30, 'A4': 440.00, 'A#4': 466.16, 'B4': 493.88,
'C5': 523.25, 'C#5': 554.37, 'D5': 587.33, 'D#5': 622.25, 'E5': 659.25, 'F5': 698.46,
'F#5': 739.99, 'G5': 783.99, 'G#5': 830.61, 'A5': 880.00, 'A#5': 932.33, 'B5': 987.77,
'C6': 1046.50, 'C#6': 1108.73, 'D6': 1174.66, 'D#6': 1244.51, 'E6': 1318.51, 'F6': 1396.91,
'F#6': 1479.98, 'G6': 1567.98
}
def create_square_wave(frequency, duration, duty_cycle=0.5, sample_rate=44100):
t = np.linspace(0, duration, int(sample_rate * duration), False)
wave = signal.square(2 * np.pi * frequency * t, duty=duty_cycle)
return wave
def apply_envelope(wave, attack=0.01, decay=0.1, sustain=0.7, release=0.1):
total_length = len(wave)
attack_length = int(attack * total_length)
decay_length = int(decay * total_length)
release_length = int(release * total_length)
sustain_length = total_length - attack_length - decay_length - release_length
envelope = np.concatenate([
np.linspace(0, 1, attack_length),
np.linspace(1, sustain, decay_length),
np.ones(sustain_length) * sustain,
np.linspace(sustain, 0, release_length)
])
return wave * envelope
def create_tone(note, duration, volume=-20, duty_cycle=0.5):
sample_rate = 44100
try:
frequency = NOTES[note]
except KeyError:
print(f"警告: 未定義の音符 '{note}' です。C4を代わりに使用します。")
frequency = NOTES['C4'] # デフォルトの音符として C4 を使用
wave = create_square_wave(frequency, duration, duty_cycle, sample_rate)
wave = apply_envelope(wave)
wave = np.int16(wave * 32767 * 10**(volume/20))
return AudioSegment(wave.tobytes(), frame_rate=sample_rate, sample_width=2, channels=1)
def create_melody(notes, tempo=120):
melody = AudioSegment.silent(duration=0)
beat_duration = 60 / tempo
for note, duration in notes:
if note == 'R':
tone = AudioSegment.silent(duration=int(duration * beat_duration * 1000))
else:
tone = create_tone(note, duration * beat_duration)
melody += tone
return melody
# イントロ部分(宇宙船の出発)
intro_melody = [
('C5', 0.25), ('E5', 0.25), ('G5', 0.25), ('C6', 0.25),
('B5', 0.5), ('G5', 0.5),
('A5', 0.25), ('F5', 0.25), ('D5', 0.25), ('B4', 0.25),
('G4', 0.5), ('C5', 0.5),
] * 2
# メイン戦闘シーン
battle_melody = [
('D5', 0.25), ('F5', 0.25), ('A5', 0.25), ('C6', 0.25),
('B5', 0.5), ('G5', 0.5),
('A5', 0.25), ('F5', 0.25), ('D5', 0.25), ('B4', 0.25),
('C5', 0.5), ('E5', 0.5),
] * 4
# ボス登場のジングル
boss_jingle = [
('C5', 0.25), ('C5', 0.25), ('C5', 0.25), ('R', 0.25),
('G4', 0.5), ('G4', 0.5),
('C5', 0.25), ('B4', 0.25), ('A4', 0.25), ('G4', 0.25),
('F4', 0.5), ('R', 0.25), ('F5', 0.75),
]
# ボス戦
boss_battle = [
('C6', 0.25), ('A5', 0.25), ('F5', 0.25), ('C5', 0.25),
('D5', 0.5), ('G5', 0.5),
('B5', 0.25), ('G5', 0.25), ('E5', 0.25), ('C5', 0.25),
('A4', 0.5), ('D5', 0.5),
] * 3
# エンディング(勝利のファンファーレ)
ending = [
('C5', 0.5), ('E5', 0.5), ('G5', 0.5), ('C6', 0.5),
('G5', 0.5), ('C6', 0.5), ('E6', 1.0),
('C6', 0.25), ('C6', 0.25), ('C6', 0.25), ('C6', 0.25),
('C6', 1.0), ('R', 1.0)
]
# 完全なメロディ
full_melody = intro_melody + battle_melody + boss_jingle + boss_battle + ending
def create_bass_line(tempo=120):
bass_pattern = [
('C3', 0.5), ('G3', 0.5), ('C4', 0.5), ('G3', 0.5),
('A3', 0.5), ('E3', 0.5), ('A3', 0.5), ('E3', 0.5),
('F3', 0.5), ('C3', 0.5), ('F3', 0.5), ('C3', 0.5),
('G3', 0.5), ('D3', 0.5), ('G3', 0.5), ('D3', 0.5),
] * 6 # 6回繰り返して約45秒の長さにする
return create_melody(bass_pattern, tempo)
def mix_tracks(*tracks):
mixed = AudioSegment.silent(duration=max(track.duration_seconds for track in tracks) * 1000)
for track in tracks:
mixed = mixed.overlay(track)
return mixed
def play_audio(audio):
play_obj = sa.play_buffer(
audio.raw_data,
num_channels=audio.channels,
bytes_per_sample=audio.sample_width,
sample_rate=audio.frame_rate
)
play_obj.wait_done()
def save_audio_as_mp3(audio, filename):
audio.export(filename, format="mp3")
def main():
print("拡張版スペースシューターゲーム風の音楽を生成します...")
tempo = 150
# メロディの生成
melody = create_melody(full_melody, tempo=tempo)
# ベースラインの生成
bass_line = create_bass_line(tempo=tempo)
# メロディとベースラインをミックス
final_track = mix_tracks(melody, bass_line)
# 音量調整
final_track = final_track - 3 # 全体の音量を少し下げる
print("スペースシューターゲームの音楽を再生します...")
play_audio(final_track)
print("音楽をMP3ファイルとして保存します...")
save_audio_as_mp3(final_track, "extended_space_shooter_music.mp3")
print("処理が完了しました。extended_space_shooter_music.mp3が作成されました。")
if __name__ == "__main__":
main()
設計
クラス図
クラス概要
クラス名 | 説明 |
---|---|
Note | 音符の基本情報を表現 |
WaveGenerator | 異なる種類の波形を生成 |
Envelope | ADSRエンベロープを適用 |
Melody | メロディを作成・管理 |
Bassline | ベースラインを作成・管理 |
Mixer | 複数の音楽トラックを混合 |
クラス詳細
Note
属性/メソッド | 型 | 説明 |
---|---|---|
frequency | float | 音の高さ(周波数) |
duration | float | 音の長さ |
WaveGenerator
メソッド | 説明 |
---|---|
generate_square_wave() | 矩形波を生成 |
generate_triangle_wave() | 三角波を生成 |
generate_sawtooth_wave() | のこぎり波を生成 |
Envelope
属性/メソッド | 型/説明 |
---|---|
attack | float(音の立ち上がり時間) |
decay | float(最大音量から持続音量までの減衰時間) |
sustain | float(持続音量のレベル) |
release | float(キーを離してから音が消えるまでの時間) |
apply_envelope(wave) | 与えられた波形にエンベロープを適用 |
Melody
属性/メソッド | 型/説明 |
---|---|
notes | List[Note](メロディを構成する音符のリスト) |
create_melody() | 音符のリストからメロディを生成 |
Bassline
属性/メソッド | 型/説明 |
---|---|
pattern | List[Note](ベースラインのパターンを表す音符のリスト) |
create_bassline() | パターンからベースラインを生成 |
Mixer
属性/メソッド | 型/説明 |
---|---|
tracks | List[AudioSegment](混合する音楽トラックのリスト) |
add_track(track) | ミキサーに新しいトラックを追加 |
mix() | すべてのトラックを混合して最終的な音楽を生成 |
シーケンス図
発展の可能性
このコードをベースに、さまざまな拡張が可能です:
- 異なるゲームジャンル向けの音楽生成
- UIを追加して、インタラクティブな音楽作成ツールの開発
- 機械学習を使用した自動作曲機能の実装
- リアルタイムでのゲーム状況に応じた動的な音楽生成
まとめ
Pythonを使って8ビット風のゲーム音楽を生成する方法を紹介しました。明確な要件と仕様に基づいて実装することで、目的に沿った効果的な音楽生成が可能になりました。この手法を使えば、レトロゲーム風の音楽を手軽に作成できます。ぜひ自分好みにアレンジして、オリジナルの8ビットサウンドを作ってみてください!