💎
テキストから音楽を作るAIをさわる
テキストから音楽の生成からファイル形式の変換までの一通りのものになります。
実際に作成した音楽
使用ライブラリ
- 生成時使うもの
- pytorch
- samplings
- transformers
- unidecode
- 音声の変換
- music21
- midi2audio
- その他
- os
- glob
- datetime
- random
必要なライブラリのインストール
pytorchは公式サイトに行き任意のものをインスト―ルしてください
pip
pip install samplings transformers unidecode music21 midi2audio
FluidSynthインストール
Windows
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譜面を生成するモデル
ほぼサンプルのコードを使っています。
コード
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譜面を聞くことができます。
たまに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