0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonによる自動作曲

Posted at

設計したプロンプト

このままコピー&ペーストして、ChatGPT(GPT-4o推奨)やGeminiに入力してください。

# 命令書:
あなたは「熟練したクラシック作曲家」かつ「優秀なPythonエンジニア」です。
以下の要件に基づき、美しいピアノソロ曲のMIDIデータを生成するための**実行可能なPythonコード**を作成してください。

# 目的:
Pythonライブラリ(`midiutil` または `mido` 推奨)を使用して、聴く人の心を動かすような「シンプルで美しいピアノ・インストゥルメンタル」のMIDIファイルを生成すること。

# 楽曲の要件:
1.  **ジャンル**: クラシック / 現代音楽(久石譲や坂本龍一のような、ミニマルで叙情的なスタイル)
2.  **楽器**: Acoustic Grand Piano (General MIDI Program #0)
3.  **構成**: イントロ → Aメロ → Bメロ(サビ) → アウトロ の展開を持たせること。
4.  **音楽理論の適用**:
    * ランダムな音の生成は禁止します。
    * 特定のキー(例: イ短調 A Minor または ト長調 G Major)を設定すること。
    * 左手(伴奏)はアルペジオやコード進行(王道の進行:IV-V-iii-viなど)を使用すること。
    * 右手(メロディ)は、スケールに基づいた自然なフレージングにすること。
    * ベロシティ(音の強弱)に変化をつけ、人間味のある演奏をシミュレートすること。

# 技術的要件:
1.  Google Colabやローカル環境でエラーなく動作するコードであること。
2.  必要なライブラリのインストールコマンド(`!pip install midiutil` など)を冒頭に含めること。
3.  生成されたMIDIファイル(output.mid)を保存する処理を含めること。

# 出力形式:
1.  **Pythonコード**: コピーしてすぐに実行可能なコードブロック。
2.  **楽曲解説**: コード内でどのような音楽理論(キー、コード進行、リズム)を使用したかの簡潔な説明。

# トーン:
クリエイティブ、論理的、専門的
# 必要なライブラリのインストール(Colab等で初回のみ必要)
# !pip install midiutil

import random
from midiutil import MIDIFile

class SentimentalComposer:
    def __init__(self, filename="sentimental_piano.mid"):
        self.filename = filename
        self.midi = MIDIFile(1)  # 1トラック
        self.track = 0
        self.time = 0   # 現在の小節位置
        self.tempo = 76 # ゆったりとしたテンポ
        
        # 楽器設定 (0: Acoustic Grand Piano)
        self.midi.addTrackName(self.track, 0, "Grand Piano")
        self.midi.addTempo(self.track, 0, self.tempo)
        self.midi.addProgramChange(self.track, 0, 0, 0)

        # 音楽理論設定: Key = A Minor (イ短調) / C Major (ハ長調)
        # スケールノート (A Minor Natural)
        self.scale_notes = [57, 59, 60, 62, 64, 65, 67, 69, 71, 72, 74, 76, 77] 
        # 57=A3, 60=C4, 69=A4...
        
        # コード構成音 (Bass + Chord tones)
        # 進行: FM7 -> G7 -> Em7 -> Am (王道進行 IV-V-iii-vi)
        self.chord_progression = [
            {"root": 41, "type": "F",  "notes": [53, 57, 60, 64]}, # FM7 (F2ベース)
            {"root": 43, "type": "G",  "notes": [55, 59, 62, 65]}, # G7  (G2ベース)
            {"root": 40, "type": "Em", "notes": [52, 55, 59, 62]}, # Em7 (E2ベース)
            {"root": 45, "type": "Am", "notes": [57, 60, 64, 67]}, # Am7 (A2ベース)
        ]

    def add_note_humanized(self, note, duration, velocity_base=60):
        """人間味のある演奏のためにベロシティとタイミングを微調整"""
        # ベロシティのゆらぎ
        vel = max(40, min(127, int(random.gauss(velocity_base, 5))))
        # タイミングの微細なズレ (0.01拍〜0.03拍)
        offset = random.uniform(0, 0.03)
        
        self.midi.addNote(self.track, 0, note, self.time + offset, duration, vel)

    def play_arpeggio(self, chord, duration=4):
        """左手: アルペジオ伴奏 (1小節分)"""
        # パターン: Root -> 5th -> Octave -> 3rd/5th...
        notes = chord["notes"]
        # 8分音符で分散和音
        pattern = [0, 1, 2, 3, 1, 2, 3, 1] # インデックス
        step = duration / len(pattern)
        
        current_local_time = self.time
        for idx in pattern:
            note = notes[idx % len(notes)]
            # 左手は少し弱めに
            self.add_note_humanized(note, step * 1.1, velocity_base=50)
            self.time += step
            
        self.time = current_local_time # 時間ポインタを戻さない(和音とメロディを並行処理するため呼び出し元で管理すべきだが、今回は簡易化)

    def generate_section(self, section_name, num_bars, intensity="low"):
        """セクションごとの生成"""
        print(f"Generating {section_name}...")
        
        start_time = self.time
        
        for i in range(num_bars):
            # コード進行の選択 (王道進行をループ)
            chord_idx = i % len(self.chord_progression)
            chord = self.chord_progression[chord_idx]
            
            # -------------------------------
            # 左手: アルペジオ
            # -------------------------------
            # アルペジオ用の時間を進めるが、メロディ用に開始時間を保持しておく
            bar_start = self.time
            
            # アルペジオパターンの生成
            notes = chord["notes"]
            bass_note = chord["root"]
            
            # ベース音 (全音符)
            self.midi.addNote(self.track, 0, bass_note, bar_start, 4, 55)
            
            # 分散和音 (8分音符 x 8)
            for j in range(8):
                # 音の選び方: 下から上へ、揺らぎを持たせる
                if j < 4:
                    note_idx = j % len(notes)
                else:
                    note_idx = (7 - j) % len(notes) # 折り返し
                
                note_num = notes[note_idx]
                self.add_note_humanized(note_num, 0.5, velocity_base=45 if intensity=="low" else 55)
                self.time += 0.5

            # -------------------------------
            # 右手: メロディ (スケールから選択)
            # -------------------------------
            # 時間を小節の頭に戻してメロディを重ねる
            current_melody_time = bar_start
            remaining_duration = 4.0
            
            while remaining_duration > 0:
                # 音価の決定 (intensityによって変化)
                if intensity == "low":
                    durations = [1.0, 2.0] # ゆったり
                    vel_base = 65
                    octave_offset = 0
                else: # high (サビ)
                    durations = [0.5, 1.0, 1.5] # 動きがある
                    vel_base = 80
                    octave_offset = 12 # 1オクターブ上げる

                dur = random.choice(durations)
                if dur > remaining_duration:
                    dur = remaining_duration
                
                # 音高の決定 (前の音に近い音を選びやすくする「ランダムウォーク」)
                # コードトーンを優先すると綺麗に響く
                is_chord_tone = random.random() > 0.3
                if is_chord_tone:
                    # その瞬間のコードに合う音
                    candidates = [n + octave_offset for n in chord["notes"]]
                else:
                    # スケール内の音(経過音)
                    candidates = [n + octave_offset for n in self.scale_notes if 60 <= n + octave_offset <= 84]
                
                if not candidates: candidates = [69] # フォールバック(A4)
                
                note = random.choice(candidates)
                
                # 休符を入れる確率 (サビは少なく、Aメロは適度に)
                rest_prob = 0.1 if intensity == "high" else 0.2
                if random.random() > rest_prob:
                    self.add_note_humanized(note, dur, velocity_base=vel_base)
                
                current_melody_time += dur
                remaining_duration -= dur

            # 次の小節へ
            self.time = bar_start + 4

    def compose(self):
        # 1. Intro (4小節): 静かで、ルート音中心
        self.generate_section("Intro", 4, intensity="low")
        
        # 2. Aメロ (8小節): 物語の始まり、中音域
        self.generate_section("Verse (A)", 8, intensity="low")
        
        # 3. Bメロ/サビ (8小節): 感情の高まり、高音域、強め
        self.generate_section("Chorus (B)", 8, intensity="high")
        
        # 4. Outro (4小節): フェードアウト、主音(Am)で終わる
        print("Generating Outro...")
        # 最後はAmのアルペジオで終わる
        chord = self.chord_progression[3] # Am
        self.midi.addNote(self.track, 0, 45, self.time, 4, 50) # Bass A
        self.midi.addNote(self.track, 0, 57, self.time, 4, 45) # A
        self.midi.addNote(self.track, 0, 60, self.time+0.5, 3.5, 45) # C
        self.midi.addNote(self.track, 0, 64, self.time+1.0, 3.0, 45) # E
        self.midi.addNote(self.track, 0, 69, self.time+1.5, 2.5, 40) # A (High)
        
        # ファイル保存
        with open(self.filename, "wb") as output_file:
            self.midi.writeFile(output_file)
        print(f"Done! Saved to {self.filename}")

# 実行部
if __name__ == "__main__":
    composer = SentimentalComposer()
    composer.compose()
    
    # Google Colabの場合、自動ダウンロードさせるコード
    try:
        from google.colab import files
        files.download('sentimental_piano.mid')
    except ImportError:
        print("Local environment detected. File is saved in the current directory.")
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?