見出し画像

【ChatGPT×Python×Logic Pro】music21にAIが生成したJSONファイルを読み込ませ、MIDIファイルを生成

今回Pythonの音楽ライブラリmusic21を活用して、ChatGPTで音楽を作るプログラムを作ったので、そのログを残す。

まずはpipのインストール

pip3 install music21
pip3 install pretty_midi //今回は使用しないが、こんなのもある

リラックスできるゆっくりな音楽を、JSONファイルとして作成してください。
(以降はこのJSONファイルをそのままコピーして、フォーマットを維持してもらう)

ソースコードは記事の一番下に添付してあります。

{
  "library": "music21",
  "settings": {
    "key": "C",
    "tempo": 60
  },
  "repeat_count": 2,
  "instruments": [
    {
      "name": "Piano",
      "actions": [
        {
          "type": "add_note",
          "pitch": "C3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "E3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "G3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "A3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "F3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "D3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "B2",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "G2",
          "quarterLength": 2.0
        }
      ]
    }
  ]
}

Pythonで実行するための環境ファイル設定

{
  "library": "music21",
  "file": "gpt.mid",
  "mode": "g"
}

libraryは今後ライブラリを追加した時のために設定、fileはMIDIのファイル名。
modeをgにするとgenerateで新規作成、eはeditの編集。

最初にgで実行してみる。

VS Codeを利用。

MIDIファイルが生成された。
Logic Proで開いてみる。

音が追加されている。

{  
   "library": "music21",
  "file": "gpt.mid",
  "mode": "e"
}

次に肉付けを行う。

{
  "library": "music21",
  "settings": {
    "key": "C",
    "tempo": 60
  },
  "repeat_count": 2,
  "instruments": [
    {
      "name": "Piano",
      "actions": [
        {
          "type": "add_note",
          "pitch": "C3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "E3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "G3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "A3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "F3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "D3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "B2",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "G2",
          "quarterLength": 2.0
        }
      ]
    },
    {
      "name": "Flute",
      "actions": [
        {
          "type": "add_note",
          "pitch": "C5",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "E5",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "G5",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "A5",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "F5",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "D5",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "B4",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "G4",
          "quarterLength": 2.0
        }
      ]
    },
    {
      "name": "Violin",
      "actions": [
        {
          "type": "add_note",
          "pitch": "C4",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "E4",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "G4",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "A4",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "F4",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "D4",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "B3",
          "quarterLength": 2.0
        },
        {
          "type": "add_note",
          "pitch": "G3",
          "quarterLength": 2.0
        }
      ]
    }
  ]
}

Pythonを実行


編集ようのファイルが生成された。

ピアノ以外にもバイオリンや、フルートなども追加された。

https://www.youtube.com/shorts/T3KGYSaC7PY

現在作成段階だが、とりあえず「music21」の新規作成と修正だけは利用できるようになっている。

#main.py
import json 
#import pretty_midi
from music21 import *

# JSONデータをロード
with open('type.json', 'r') as f:
    type_data = json.load(f)

# ファイル名
file = type_data['file']

# 新しいMIDIファイルを生成する機能→基盤となる一つの楽器だけを設定
def generate_pretty_midi():
    # MIDIファイルを読み込む
    midi_data = pretty_midi.PrettyMIDI('gpt_based_magenta_output.mid')
    
    # 楽器を設定
    instrument_name = edit_data.get('instrument', 'Acoustic Grand Piano')
    instrument_program = pretty_midi.instrument_name_to_program(instrument_name)
    new_instrument = pretty_midi.Instrument(program=instrument_program)
    
    for action in edit_data['actions']:
        if action['type'] == 'add_note':
            new_note = pretty_midi.Note(
                velocity=action['velocity'],
                pitch=action['pitch'],
                start=action['start'],
                end=action['end']
            )
            new_instrument.notes.append(new_note)
        elif action['type'] == 'change_tempo':
            # pretty_midiでのテンポ変更処理
            new_tempo = action['new_tempo']
            current_tempo = midi_data.estimate_tempo()
            ratio = new_tempo / current_tempo
            for tempo_change in midi_data.get_tempo_changes()[1]:
                tempo_change *= ratio
            for instrument in midi_data.instruments:
                for note in instrument.notes:
                    note.start *= ratio
                    note.end *= ratio
    
    # 新しい楽器トラックを追加
    midi_data.instruments.append(new_instrument)
    
    # MIDIファイルとして保存
    midi_data.write(file)

def edit_pretty_midi():
    # MIDIファイルを読み込む
    midi_data = pretty_midi.PrettyMIDI('gpt_based_magenta_output.mid')
    
    # 楽器を設定
    instrument_name = edit_data.get('instrument', 'Acoustic Grand Piano')
    instrument_program = pretty_midi.instrument_name_to_program(instrument_name)
    new_instrument = pretty_midi.Instrument(program=instrument_program)
    
    # 既存のノートを編集する(例:全てのノートのピッチを+1する)
    for instrument in midi_data.instruments:
        for note in instrument.notes:
            note.pitch += 1
    
    # 新しい楽器トラックを追加
    midi_data.instruments.append(new_instrument)
    
    # MIDIファイルとして保存
    midi_data.write(file)

# 「ドレミファソラシド」で音楽を生成する機能
def generate_pretty_doremi():
    # ここにドレミで音楽生成のコードを書く
    pass

def generate_music21_midi(edit_data):
    try:
        # Create a new score
        midi_stream = stream.Score()
        # If a file exists, it would be parsed into midi_stream here
        midi_stream = converter.parse(file)
    except FileNotFoundError:
        midi_stream = stream.Score()
    
    # Setting the repeat_count
    repeat_count = edit_data.get('repeat_count')
    
    # Create a new score to hold the repeated sections
    repeated_score = stream.Score()
    
    for i in range(repeat_count):
        for instr_data in edit_data.get('instruments', []):
            instrument_name = instr_data.get('name', 'Piano')
            new_instrument = instrument.fromString(instrument_name)
            
            # Create a new part and add the instrument to it
            new_part = stream.Part()
            new_part.insert(0, new_instrument)
            
            # Create a new measure and add a time signature to it
            new_measure = stream.Measure(number=1)
            new_measure.append(meter.TimeSignature('4/4'))
            
            for action in instr_data.get('actions', []):
                if action['type'] == 'add_note':
                    new_note = note.Note(action['pitch'])
                    new_note.quarterLength = action.get('quarterLength', 1.0)
                    new_measure.append(new_note)
                elif action['type'] == 'change_tempo':
                    new_tempo = tempo.MetronomeMark(number=action['new_tempo'])
                    new_measure.insert(0, new_tempo)
            
            # Append the measure to the part
            new_part.append(new_measure)
            
            # Append the part to the repeated_score
            repeated_score.append(new_part)
    
    # Append the repeated_score to the main score
    midi_stream.append(repeated_score)
    
    # Save as a MIDI file
    midi_stream.write('midi', fp=file)


from collections import defaultdict

def edit_music21_midi(edit_data):
    try:
        midi_stream = converter.parse(file)
    except FileNotFoundError:
        print("MIDI file not found. Exiting.")
        return

    parts_by_instrument = defaultdict(lambda: stream.Part())

    for instr_data in edit_data.get('instruments', []):
        instrument_name = instr_data.get('name', 'Piano')
        new_part = parts_by_instrument[instrument_name]
        
        # Create a new measure and add a time signature to it
        new_measure = stream.Measure(number=1)
        new_measure.append(meter.TimeSignature('4/4'))

        for action in instr_data.get('actions', []):
            action_type = action.get('type')
        
            if action_type == 'add_note':
                pitch = action.get('pitch', 'C4')
                new_note = note.Note(pitch)
                new_note.quarterLength = action.get('duration', 1.0)
                new_note.offset = action.get('start_time', 0.0)
                new_measure.append(new_note)
            elif action_type == 'add_rest':
                new_rest = note.Rest()
                new_rest.quarterLength = action.get('duration', 1.0)
                new_rest.offset = action.get('start_time', 0.0)
                new_measure.append(new_rest)
                
        new_part.append(new_measure)
        
        # Insert the instrument at the beginning of the part
        new_instrument = instrument.fromString(instrument_name)
        new_part.insert(0, new_instrument)

    # This should only happen once, outside of the loop
    for instrument_name, new_part in parts_by_instrument.items():
        midi_stream.append(new_part)
        
    # Write the MIDI file
    midi_stream.write('midi', fp='edit_music21_' + file)




# 「ドレミファソラシド」で音楽を生成する機能
def generate_music21_doremi():
    # ここにドレミで音楽生成のコードを書く
    pass

# pretty_midiを使用する場合
if type_data['library'] == 'pretty_midi':

    if type_data['mode'] == 'g':

        # JSONデータをロード
        with open('generate_pretty_midi.json', 'r') as f:
            edit_data = json.load(f)

        generate_pretty_midi()
    elif type_data['mode'] == 'e':

        # JSONデータをロード
        with open('edit_pretty_midi.json', 'r') as f:
            edit_data = json.load(f)

        edit_pretty_midi()
    elif type_data['mode'] == 'd':
        generate_pretty_doremi()
    else:
        print("Invalid mode")

# music21を使用する場合
elif type_data['library'] == 'music21':

    if type_data['mode'] == 'g':
        # JSONデータをロード
        with open('generate_music21_midi.json', 'r') as f:
            edit_data = json.load(f)
        generate_music21_midi(edit_data)
    elif type_data['mode'] == 'e':
        # JSONデータをロード
        with open('edit_music21_midi.json', 'r') as f:
            edit_data = json.load(f)
        edit_music21_midi(edit_data)
    elif type_data['mode'] == 'd':
        generate_music21_doremi()
    else:
        print("Invalid mode")

else:
    print("Invalid library in JSON")

今回は王道なJSONファイルを利用したが、他にも良い方法があったら教えてください。
今後の参考にします。

ここから先は

0字 / 1ファイル
この記事のみ ¥ 300

この記事が参加している募集

メンバーシップ加入で、全ての記事が閲覧できます。