💎

テキストから音楽を作るAIをさわる

2023/04/09に公開

テキストから音楽の生成からファイル形式の変換までの一通りのものになります。

実際に作成した音楽
https://drive.google.com/drive/folders/1L9_Y27YUhDPiIu7MoA9vnViNcsMyg0ql?usp=sharing

使用ライブラリ

  • 生成時使うもの
    • pytorch
    • samplings
    • transformers
    • unidecode
  • 音声の変換
    • music21
    • midi2audio
  • その他
    • os
    • glob
    • datetime
    • random

必要なライブラリのインストール

pytorchは公式サイトに行き任意のものをインスト―ルしてください
https://pytorch.org/

pip
pip install samplings transformers unidecode music21 midi2audio


FluidSynthインストール

Windows

https://github.com/FluidSynth/fluidsynth/releases

macOS

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Linux

sudo apt-get install fluidsynth


ファイル構造

ABC、MIDI、WAVは自動作成できるようにしてあります。

ファイル構造
text2music
├─ABC abc譜面フォルダ
├─fluidsynth MIDI関連のもの
│  ├─bin
│  ├─include
│  │  └─fluidsynth
│  └─lib
│      ├─cmake
│      │  └─fluidsynth
│      └─pkgconfig
├─MIDI
├─sf
└─WAV


音楽の生成

ABC譜面を生成するモデル

https://huggingface.co/sander-wood/text-to-music
https://github.com/sander-wood/text-to-music

ほぼサンプルのコードを使っています。

コード
import torch
from samplings import top_p_sampling, temperature_sampling
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
import datetime, os, random
from unidecode import unidecode

device = "cuda" if torch.cuda.is_available() else "cpu"

tokenizer = AutoTokenizer.from_pretrained('sander-wood/text-to-music')
model = AutoModelForSeq2SeqLM.from_pretrained('sander-wood/text-to-music')
model = model.to(device)

# 曲数
num_tunes = 3
max_length = 1024
top_p = 0.9
temperature = 1.0

text = "This is a traditional Irish dance music."
input_ids = tokenizer(text, 
                      return_tensors='pt', 
                      truncation=True, 
                      max_length=max_length)['input_ids'].to(device)

decoder_start_token_id = model.config.decoder_start_token_id
eos_token_id = model.config.eos_token_id


dt_now = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
path = os.getcwd()
dir_abc = os.path.join(path, 'ABC')

for i in range(num_tunes):
    
    tune = f"X:{i + 1}\n"
    decoder_input_ids = torch.tensor([[decoder_start_token_id]])    
    
    for t_idx in range(max_length):
    
        rand_seed = random.randint(0, 1000000)
        random.seed(rand_seed)
	
        outputs = model(
            input_ids=input_ids, 
            decoder_input_ids=decoder_input_ids.to(device))
        probs = outputs.logits[0][-1]
        probs = torch.nn.Softmax(dim=-1)(probs).cpu().detach().numpy()
        sampled_id = temperature_sampling(probs=top_p_sampling(probs, 
                                                            top_p=top_p,
                                                            seed=rand_seed,
                                                            return_probs=True),
                                        seed=rand_seed,
                                        temperature=temperature)
        decoder_input_ids = torch.cat((decoder_input_ids, torch.tensor([[sampled_id]])), 1)
        if sampled_id!=eos_token_id:
            sampled_token = tokenizer.decode([sampled_id])
            tune += sampled_token
            # continue
        else:
            tune += '\n'
            break

    with open(f'{dir_abc}\{dt_now}.abc', 'a') as f:
        f.write(f'{unidecode(tune)}')
    
else:
    print(
        f'{dir_abc}\{dt_now}.abc に譜面が保存されました。')


ABC譜面の例
X:1
L:1/8
M:4/4
K:Bb
"Bb" D2 FD"Bb7" F2 ^FD |"Eb" _G2 FE"Bb7" F2 z F |"Eb" FGFD"F7" _A2 =AF |"Bb" B6"Bb7" z2 |
"Eb" FGFD"F7" _A2 =AF |"Bb" B6"F7" z F |"Bb" BcBF"F7" _A2 cA |"Bb" B6"F7" z2 |]

abc譜面は楽譜を記述するためのフォーマットの一つです。

以下のサイトを使えばABC譜面を聞くことができます。
https://ldzhangyx.github.io/abc/


たまにABC記譜法では注釈で使われるキャレット(^)が記載されていることがあります。
注釈(^)を削除してから変換する必要があります。

以下の譜面だと「Swing」「Couplet」を削除するとMIDIに変換できます。

変更前
X:1
L:1/8
Q:1/4=90
M:4/4
K:C   
"^Swing" A3/2A3/2G A3/2A3/2G | A2 G A2 e2 d/ | cA3/2A3/2G A2 B2 | A2 G A2 g/e/ d/e/c- | c8 |:
"^Couplet" F2 A2 G E2 F | GFEE z2 EF | GFG G2 F G2 | ABA A2 B c2 :| F3/2F3/2A G E2 F |
GFE E2 E/F/ G2 | F2 A2 G E2 F | GEFE z2 EF | GFE E2 Bc c- | cccc- c z A2- | AAAd ddcd- | dc c4 A2- |
A A2 A2 A2 c- | c6 z2 |: F2 A2 G E2 F | GFEE z2 EF | GFG G2 F G2 | ABA A2 B c2 :|
変更後
X:1
L:1/8
Q:1/4=90
M:4/4
K:C   
 A3/2A3/2G A3/2A3/2G | A2 G A2 e2 d/ | cA3/2A3/2G A2 B2 | A2 G A2 g/e/ d/e/c- | c8 |:
 F2 A2 G E2 F | GFEE z2 EF | GFG G2 F G2 | ABA A2 B c2 :| F3/2F3/2A G E2 F |
GFE E2 E/F/ G2 | F2 A2 G E2 F | GEFE z2 EF | GFE E2 Bc c- | cccc- c z A2- | AAAd ddcd- | dc c4 A2- |
A A2 A2 A2 c- | c6 z2 |: F2 A2 G E2 F | GFEE z2 EF | GFG G2 F G2 | ABA A2 B c2 :|


音声変換

abcファイルをwavにするには、一度MIDIファイルに変換する必要があります。
そこからMIDIファイルをwavに変換することができます。

ライブラリのインポート

import os, glob, datetime
from midi2audio import FluidSynth
import music21

ABCファイルをMIDIファイルに変換を定義

# ABCファイルをMIDIファイルに変換する関数
def abc_to_midi(abc_file_path, midi_file_path):
    
    # ABCファイル読み込み
    abc_text = open(abc_file_path).read()
    
    # ABC譜面の解析
    score = music21.converter.parseData(abc_text)
    
    dt = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
    # MIDIファイルへの変換
    midi_file_path = f"{midi_file_path}\{dt}.mid"
    score.write("midi", midi_file_path)

MIDIファイルをWAVファイル変換の関数を定義

# MIDIファイルをWAVファイルに変換する関数
def midi_to_wav(midi_file, wav_file):
    FluidSynth(sound_font='sf/font.sf2').midi_to_audio(midi_file, wav_file)

環境変数設定

fluidsynthを使うときにシステムの環境変数に設定してもいいのですが、プロジェクトファイルをひとまとめにしたかったので、標準ライブラリのosを使って一時的に環境変数を設定します。

PCのosの環境変数には影響は出ません。

getcwd = os.getcwd()

# 環境変数の設定
path = os.path.join(getcwd, r'fluidsynth\bin')
os.environ['PATH'] = path

読み込み保存するフォルダの指定

# ファイルのパスを指定する
dir_abc = os.path.join(getcwd, 'ABC')
dir_midi = os.path.join(getcwd, 'MIDI')
dir_wav = os.path.join(getcwd, 'WAV')

# MIDIフォルダがなければ作成
if not os.path.isdir(dir_midi):
    os.mkdir(dir_midi)
    
# WAVフォルダがなければ作成
if not os.path.isdir(dir_wav):
    os.mkdir(dir_wav)

ABCファイルをMIDIファイルに変換

# abcファイルの最後のファイルパスの取得
abc_file = glob.glob(f'{dir_abc}\*')[-1]

# ABCファイルをMIDIファイルに変換する
abc_to_midi(abc_file, dir_midi)

MIDIファイルをWAVファイルに変換

# 変換されたMIDIファイルの読み込み
midi_file = glob.glob(f'{dir_midi}\*')[-1]

# 保存する
dt = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
wav_file = f"{dir_wav}\output-{dt}.wav"

# MIDIファイルをWAVファイルに変換する
midi_to_wav(f'MIDI\{os.path.basename(midi_file)}', wav_file)

全体のコード

コード
import os, glob, datetime
from midi2audio import FluidSynth
import music21

# ABCファイルをMIDIファイルに変換する関数
def abc_to_midi(abc_file_path, midi_file_path):
    
    # ABCファイル読み込み
    abc_text = open(abc_file_path).read()
    
    # ABC譜面の解析
    score = music21.converter.parseData(abc_text)
    
    dt = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
    # MIDIファイルへの変換
    midi_file_path = f"{midi_file_path}\{dt}.mid"
    score.write("midi", midi_file_path)
    
# MIDIファイルをWAVファイルに変換する関数
def midi_to_wav(midi_file, wav_file):
    FluidSynth(sound_font='sf/font.sf2').midi_to_audio(midi_file, wav_file)
    
getcwd = os.getcwd()

# 環境変数の設定
path = os.path.join(getcwd, r'fluidsynth\bin')
os.environ['PATH'] = path

# ファイルのパスを指定する
dir_abc = os.path.join(getcwd, 'ABC')
dir_midi = os.path.join(getcwd, 'MIDI')
dir_wav = os.path.join(getcwd, 'WAV')

# MIDIフォルダがなければ作成
if not os.path.isdir(dir_midi):
    os.mkdir(dir_midi)
    
# WAVフォルダがなければ作成
if not os.path.isdir(dir_wav):
    os.mkdir(dir_wav)

# abcファイルの最後のファイルパスの取得
abc_file = glob.glob(f'{dir_abc}\*')[-1]

# ABCファイルをMIDIファイルに変換する
abc_to_midi(abc_file, dir_midi)

midi_file = glob.glob(f'{dir_midi}\*')[-1]

dt = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
wav_file = f"{dir_wav}\output-{dt}.wav"

# MIDIファイルをWAVファイルに変換する
midi_to_wav(f'MIDI\{os.path.basename(midi_file)}', wav_file)

Discussion