見出し画像

Cline×Deepseekを使った自然言語BGM生成

前提として、pythonを使えば音声データは作成可能です。
そのため、LLMでの音声データの作成は可能です。

今回の構成では、ClineというAI agentを使って、プログラムの修正・実行を可能にし、Deepseekという格安LLMでpythonを作成することで、自然言語での音声データ作成が可能になります。

つまり、作曲をLLMに依頼し、その成果物に対して、人間が口を出す構成となります。

Clineを使わなくても、以下のテキスト(「★TODO」のところはコピペの必要あり)をChatGPTやClaude、Geminiなどに入力することで、BGMを作成するプログラムを作成してもらうことは可能です。

README.mdsound_generator.pyを参照し、
ここちのよい3分程度のBGMを作成するプログラムを作成してください。
まずはコンセプトを出力し、プログラムを作成してください。


■README.md
---TODO:ここにREADME.mdをコピペ


■sound_generator.py
---TODO:ここにsound_generator.pyをコピペ


■config.json
---TODO:ここにconfig.jsonをコピペ

ただし、エラーになることもあるので、Clineの方が楽です。


環境構築

Cline×DeepSeekの環境構築については、別の方の記事を共有させていただきます。(従量課金が気になる方もいると思いますが、この記事の内容を検証するまでにもいろいろCline試しながら、この記事の内容を検証、記事の作成までをするのにかかったコストは1ドルです。)

pythonとFFmpegを準備します。

- Python 3.8以上
- 以下のライブラリが必要です:
  ```bash
  pip install pydub numpy scipy ffmpeg-python
  ```
- MP3/OGG出力にはffmpegが必要です:
  1. [ffmpeg公式サイト](https://ffmpeg.org/download.html)からインストーラーをダウンロード
  2. インストール後、システムの環境変数PATHにffmpegのパスを追加

以下のフォルダ構成でファイルを配置します。

├── sound_generator/
│   ├── sound_generator.py  # メインプログラム
│   ├── config.json         # 設定ファイル
│   ├── README.md           # 設計書
│   └── sounds/             # 生成された効果音の保存先

sound_generator.py

# sound_generator.py

import os
import numpy as np
import soundfile as sf
from scipy import signal
from pydub import AudioSegment
import subprocess

class SoundGenerator:
    def __init__(self, sample_rate=44100):
        self.sample_rate = sample_rate
        self.output_dir = "sounds"
        os.makedirs(self.output_dir, exist_ok=True)

    def generate_tone(self, frequency=440, duration=1.0, wave_type='sine', volume=0.5, 
                    fm_ratio=1.0, fm_depth=0.0, am_ratio=1.0, am_depth=0.0,
                    pulse_width=0.5, noise_amount=0.0):
        """指定された波形で単一トーンを生成(FM/AM合成、パルス幅調整、ノイズ追加対応)"""
        t = np.linspace(0, duration, int(self.sample_rate * duration), False)
        
        # 基本波形生成
        if wave_type == 'sine':
            wave = np.sin(frequency * 2 * np.pi * t)
        elif wave_type == 'square':
            wave = signal.square(frequency * 2 * np.pi * t, duty=pulse_width)
        elif wave_type == 'sawtooth':
            wave = signal.sawtooth(frequency * 2 * np.pi * t)
        elif wave_type == 'triangle':
            wave = signal.sawtooth(frequency * 2 * np.pi * t, width=0.5)
        elif wave_type == 'noise':
            wave = np.random.uniform(-1, 1, len(t))
        else:
            wave = np.sin(frequency * 2 * np.pi * t)  # デフォルトはサイン波
            
        # FM合成
        if fm_depth > 0:
            modulator_freq = frequency * fm_ratio
            fm_wave = fm_depth * np.sin(modulator_freq * 2 * np.pi * t)
            wave = np.sin((frequency * 2 * np.pi * t) + fm_wave)
            
        # AM合成
        if am_depth > 0:
            modulator_freq = frequency * am_ratio
            am_wave = 1.0 + am_depth * np.sin(modulator_freq * 2 * np.pi * t)
            wave *= am_wave
            
        # ノイズ追加
        if noise_amount > 0:
            noise = np.random.uniform(-1, 1, len(t)) * noise_amount
            wave = wave * (1 - noise_amount) + noise
            
        # 音量調整とクリッピング防止
        wave = np.clip(wave * volume, -0.99, 0.99)
            
        return wave

    def generate_chord(self, frequencies, duration=1.0, wave_type='sine', volume=0.5):
        """複数周波数を組み合わせてコードを生成"""
        chord_wave = np.zeros(int(self.sample_rate * duration))
        
        for freq in frequencies:
            tone = self.generate_tone(freq, duration, wave_type, volume/len(frequencies))
            chord_wave += tone
            
        # 正規化
        max_val = np.max(np.abs(chord_wave))
        if max_val > 0:
            chord_wave = chord_wave / max_val * volume
        
        return chord_wave

    def generate_sequence(self, notes, durations, wave_type='sine', volume=0.5):
        """ノートのシーケンスを生成"""
        sequence = np.array([])
        
        for note, duration in zip(notes, durations):
            tone = self.generate_tone(note, duration, wave_type, volume)
            sequence = np.append(sequence, tone)
            
        return sequence

    def apply_envelope(self, wave, attack=0.1, decay=0.1, sustain_level=0.7, release=0.2):
        """ADSRエンベロープを波形に適用"""
        total_samples = len(wave)
        attack_samples = int(attack * self.sample_rate)
        decay_samples = int(decay * self.sample_rate)
        release_samples = int(release * self.sample_rate)
        sustain_samples = total_samples - (attack_samples + decay_samples + release_samples)
        
        # エンベロープ時間が長すぎる場合、スケーリング
        if sustain_samples < 0:
            total_env_samples = attack_samples + decay_samples + release_samples
            if total_env_samples == 0:
                return wave
            scale = total_samples / total_env_samples
            attack_samples = int(attack_samples * scale)
            decay_samples = int(decay_samples * scale)
            release_samples = int(release_samples * scale)
            sustain_samples = 0  # 持続フェーズなし
        
        # 攻撃フェーズ
        if attack_samples > 0:
            attack_env = np.linspace(0, 1, attack_samples)
            wave[:attack_samples] *= attack_env
        
        # 減衰フェーズ
        if decay_samples > 0:
            decay_env = np.linspace(1, sustain_level, decay_samples)
            wave[attack_samples:attack_samples + decay_samples] *= decay_env
        
        # 持続フェーズ
        if sustain_samples > 0:
            wave[attack_samples + decay_samples:attack_samples + decay_samples + sustain_samples] *= sustain_level
        
        # リリースフェーズ
        if release_samples > 0:
            release_env = np.linspace(sustain_level, 0, release_samples)
            wave[-release_samples:] *= release_env
        
        return wave
        
    def apply_distortion(self, wave, gain=2.0, mix=0.5):
        """ディストーションエフェクトを適用"""
        distorted = np.tanh(wave * gain)
        return wave * (1 - mix) + distorted * mix
        
    def apply_delay(self, wave, delay_time=0.5, feedback=0.5, mix=0.3):
        """ディレイエフェクトを適用"""
        delay_samples = int(delay_time * self.sample_rate)
        output = np.zeros_like(wave)
        
        for i in range(len(wave)):
            output[i] += wave[i]
            if i >= delay_samples:
                output[i] += output[i - delay_samples] * feedback
                
        return wave * (1 - mix) + output * mix
        
    def apply_reverb(self, wave, decay=0.7, mix=0.3):
        """シンプルなリバーブエフェクトを適用"""
        impulse_length = int(self.sample_rate * 1.5)  # 1.5秒のインパルス
        impulse = np.random.uniform(-1, 1, impulse_length)
        impulse *= np.exp(-np.linspace(0, 10, impulse_length)) * decay
        
        # 畳み込みでリバーブを適用
        reverb_wave = np.convolve(wave, impulse, mode='same')
        
        # 正規化
        reverb_wave = reverb_wave / np.max(np.abs(reverb_wave))
        
        return wave * (1 - mix) + reverb_wave * mix

    def save_sound(self, wave, filename):
        """生成された音声をファイルに保存"""
        try:
            filepath = os.path.join(self.output_dir, filename)
            print(f"Saving sound to: {filepath}")
            
            # ステレオデータの場合、データを2チャンネルに分割
            if wave.ndim == 2 and wave.shape[1] == 2:
                # 正規化
                max_val = np.max(np.abs(wave))
                if max_val > 0:
                    wave = wave / max_val * 0.9  # 少し余裕を持たせる
                sf.write(filepath, wave, self.sample_rate)
            else:
                # モノラルデータ
                sf.write(filepath, wave, self.sample_rate)
            
            # ファイルが正しく作成されたか確認
            if os.path.exists(filepath):
                file_size = os.path.getsize(filepath)
                print(f"Successfully saved: {filename} (Size: {file_size} bytes)")
                return filepath
            else:
                print(f"Error: File was not created at {filepath}")
                return None
                
        except Exception as e:
            print(f"Error saving sound file: {str(e)}")
            import traceback
            traceback.print_exc()
            return None

    def has_ffmpeg(self):
        """ffmpegがインストールされているか確認"""
        try:
            subprocess.run(["ffmpeg", "-version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            return True
        except FileNotFoundError:
            return False

if __name__ == "__main__":
    generator = SoundGenerator()
    
    # テスト用の複雑な音を生成
    wave = generator.generate_tone(
        frequency=440,
        duration=2.0,
        wave_type='square',
        volume=0.8,
        fm_ratio=1.5,
        fm_depth=0.3,
        am_ratio=2.0,
        am_depth=0.2,
        pulse_width=0.3,
        noise_amount=0.05
    )
    
    # エンベロープを適用
    wave = generator.apply_envelope(wave, attack=0.05, decay=0.1, sustain_level=0.8, release=0.2)
    
    # エフェクトを適用
    wave = generator.apply_distortion(wave, gain=3.0, mix=0.4)
    wave = generator.apply_delay(wave, delay_time=0.3, feedback=0.5, mix=0.3)
    wave = generator.apply_reverb(wave, decay=0.7, mix=0.3)
    
    # 保存
    generator.save_sound(wave, "complex_sound.wav")

config.json

{
    "audio_settings": {
        "sample_rate": {
            "default": 44100,
            "min": 8000,
            "max": 192000
        },
        "channels": {
            "default": 1,
            "options": [1, 2]
        },
        "bit_depth": {
            "default": 16,
            "options": [8, 16, 24, 32]
        },
        "output_format": {
            "default": "wav",
            "options": ["wav", "mp3", "ogg"]
        }
    },
    "effects": {
        "beep": {
            "frequency": {
                "default": 440,
                "min": 20,
                "max": 20000
            },
            "duration": {
                "default": 0.5,
                "min": 0.1,
                "max": 10.0
            },
            "wave_type": {
                "default": "sine",
                "options": ["sine", "square", "sawtooth", "triangle"]
            },
            "effects": {
                "fade_in": {
                    "default": 0.1,
                    "min": 0.0,
                    "max": 1.0
                },
                "fade_out": {
                    "default": 0.1,
                    "min": 0.0,
                    "max": 1.0
                }
            }
        },
        "whoosh": {
            "start_freq": {
                "default": 100,
                "min": 20,
                "max": 20000
            },
            "end_freq": {
                "default": 1000,
                "min": 20,
                "max": 20000
            },
            "duration": {
                "default": 1.0,
                "min": 0.1,
                "max": 10.0
            },
            "effects": {
                "reverb": {
                    "default": 0.2,
                    "min": 0.0,
                    "max": 1.0
                }
            }
        }
    }
}

README.md

# ローカル効果音生成システム

## 概要
このシステムはPythonを使用して、ローカル環境で高品質な効果音を自動生成するツールです。基本的な波形生成から、複雑なエフェクト処理まで幅広い音声生成が可能です。

## 主な機能
- 基本波形生成(サイン波、矩形波、ノコギリ波)
- エフェクト処理(フェードイン/アウト、パン、リバーブ)
- 複数フォーマット出力(WAV, MP3, OGG)
- 設定ファイルによる柔軟なカスタマイズ

## システム構成
```
.
├── sound_generator/
│   ├── sound_generator.py  # メインプログラム
│   ├── config.json         # 設定ファイル
│   ├── README.md           # 設計書
│   └── sounds/             # 生成された効果音の保存先
```

## 前提条件
- Python 3.8以上
- 以下のライブラリが必要です:
  ```bash
  pip install pydub numpy scipy ffmpeg-python
  ```
- MP3/OGG出力にはffmpegが必要です:
  1. [ffmpeg公式サイト](https://ffmpeg.org/download.html)からインストーラーをダウンロード
  2. インストール後、システムの環境変数PATHにffmpegのパスを追加

## 使用方法
1. config.jsonを編集して効果音のパラメータを設定
2. 以下のコマンドで実行
```bash
python sound_generator.py
```

## 注意事項
- 出力ディレクトリ(sounds/)は自動生成されます
- 長時間の音声生成には大量のメモリを消費する可能性があります
- 高品質なMP3出力にはffmpegのインストールが必要です

## 拡張方法
新しい効果音を追加する手順:
1. config.jsonの"effects"セクションに新しいエントリを追加
2. SoundGeneratorクラスに新しいeffect_typeの処理を実装
3. 必要に応じて新しい波形生成関数を追加

## ライセンス
MIT License

## 新しいBGMの作成手順
1. sound_generator.pyをコピーして新しいファイルを作成(例: fantasy_bgm.py)
2. 新しいファイル内でgenerate_fantasy_bgm()のパラメータを調整
3. ファイルを実行して新しいBGMを生成
4. 生成されたファイルはsounds/ディレクトリに保存されます

例:
```bash
cp sound_generator.py fantasy_bgm.py
python fantasy_bgm.py
```


BGM作成

以下のようなプロンプトで、ClineにBGM作成を依頼します。

sound_generator」フォルダに音声を作成するためのプログラムを用意しています。詳細はreadme.mdを参照してください。

fantasyゲームの教会的な3分程度のBGMを作成してください。

BGM①:ClineとDeepseekで作成したBGM


より複雑なBGM作成

o1やGemini 2.0 Flash Experimentalなどの高度推論モデルに以下のプロンプト(「★TODO」のところはコピペの必要あり)を投げて、clineに投げるためのプロンプトを作成してもらいます。

ただし、複雑になればなるほどCPUでの処理に時間がかかります。

README.mdsound_generator.pyconfig.jsonを参照し、
ここちのよいパスるゲームのような3分程度のBGMを作成したいです。
単調なリズムにならないようにLLMにプログラム修正とプログラムの実行を依頼します。
プロンプトを作成してください。

■README.md
---TODO:ここにREADME.mdをコピペ


■sound_generator.py
---TODO:ここにsound_generator.pyをコピペ


■config.json
---TODO:ここにconfig.jsonをコピペ

BGM②:ClineとDeepseekとo1で作成したBGM(リラックス)

https://youtu.be/9zRGJPHJVy0
※たぶん載せられる動画の上限で表示されず


BGM➂:ClineとDeepseekとo1で作成したBGM(教会)


BGM④:ClineとDeepseekとo1で作成したBGM(パズル)


感想

昔のゲームのBGM的な曲は割と作成できそうな雰囲気がしてます。
今回は雑に作ったため、単調になる部分の制御が不十分ですが、Clineであれば今回の設定やソースコードをベースに、より複雑でリズムカルな曲が作成できると思います。

ちなみに、Clineにソースコードなどの作成はお任せしているので、ソースコードの内容は一切見ていません。

いいなと思ったら応援しよう!