add midi api
This commit is contained in:
parent
34095a6c36
commit
29c5b1d804
BIN
assets/default_sound_font.sf2
Normal file
BIN
assets/default_sound_font.sf2
Normal file
Binary file not shown.
@ -12,7 +12,7 @@ from utils.rwkv import *
|
||||
from utils.torch import *
|
||||
from utils.ngrok import *
|
||||
from utils.log import log_middleware
|
||||
from routes import completion, config, state_cache
|
||||
from routes import completion, config, state_cache, midi
|
||||
import global_var
|
||||
|
||||
app = FastAPI(dependencies=[Depends(log_middleware)])
|
||||
@ -27,6 +27,7 @@ app.add_middleware(
|
||||
|
||||
app.include_router(completion.router)
|
||||
app.include_router(config.router)
|
||||
app.include_router(midi.router)
|
||||
app.include_router(state_cache.router)
|
||||
|
||||
|
||||
@ -55,20 +56,9 @@ def exit():
|
||||
parent.kill()
|
||||
|
||||
|
||||
def debug():
|
||||
model = RWKV(
|
||||
model="../models/RWKV-4-Raven-7B-v11-Eng49%-Chn49%-Jpn1%-Other1%-20230430-ctx8192.pth",
|
||||
strategy="cuda fp16",
|
||||
tokens_path="20B_tokenizer.json",
|
||||
)
|
||||
d = model.pipeline.decode([])
|
||||
print(d)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
port=8000 if len(sys.argv) < 2 else int(sys.argv[1]),
|
||||
host="127.0.0.1" if len(sys.argv) < 3 else sys.argv[2],
|
||||
)
|
||||
# debug()
|
||||
|
92
backend-python/routes/midi.py
Normal file
92
backend-python/routes/midi.py
Normal file
@ -0,0 +1,92 @@
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
from utils.midi import *
|
||||
from midi2audio import FluidSynth
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class TxtToMidiBody(BaseModel):
|
||||
txt_path: str
|
||||
midi_path: str
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"txt_path": "midi/sample.txt",
|
||||
"midi_path": "midi/sample.mid",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.post("/txt-to-midi")
|
||||
def txt_to_midi(body: TxtToMidiBody):
|
||||
if not body.midi_path.startswith("midi/"):
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, "bad output path")
|
||||
|
||||
vocab_config = "backend-python/utils/midi_vocab_config.json"
|
||||
cfg = VocabConfig.from_json(vocab_config)
|
||||
with open(body.txt_path, "r") as f:
|
||||
text = f.read()
|
||||
text = text.strip()
|
||||
mid = convert_str_to_midi(cfg, text)
|
||||
mid.save(body.midi_path)
|
||||
|
||||
|
||||
class MidiToWavBody(BaseModel):
|
||||
midi_path: str
|
||||
wav_path: str
|
||||
sound_font_path: str = "assets/default_sound_font.sf2"
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"midi_path": "midi/sample.mid",
|
||||
"wav_path": "midi/sample.wav",
|
||||
"sound_font_path": "assets/default_sound_font.sf2",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.post("/midi-to-wav")
|
||||
def midi_to_wav(body: MidiToWavBody):
|
||||
if not body.wav_path.startswith("midi/"):
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, "bad output path")
|
||||
|
||||
fs = FluidSynth(body.sound_font_path)
|
||||
fs.midi_to_audio(body.midi_path, body.wav_path)
|
||||
|
||||
|
||||
class TextToWavBody(BaseModel):
|
||||
text: str
|
||||
wav_name: str
|
||||
sound_font_path: str = "assets/default_sound_font.sf2"
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"text": "p:24:a p:2a:a p:31:a p:39:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:24:0 p:2a:0 p:31:0 p:39:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:26:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:2e:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2e:0 p:3b:0 p:45:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:2e:a p:3b:a p:45:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2e:0 p:3b:0 p:45:0 b:26:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:26:a p:2a:a p:3b:a p:45:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2a:0 p:3b:0 p:45:0 b:26:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:2d:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 b:2d:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2e:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2e:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:26:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:26:a p:2e:a p:31:a p:39:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:26:0 p:2e:0 p:31:0 p:39:0 p:3b:0 p:45:0 b:21:0 t2 p:26:a p:2e:a p:31:a p:39:a p:3b:a p:45:a b:21:a t14 p:26:0 p:2e:0 p:31:0 p:39:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2a:a p:31:a p:39:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:24:0 p:2a:0 p:31:0 p:39:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:2e:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2e:0 p:3b:0 p:45:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:2e:a p:3b:a p:45:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2e:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:26:a p:2a:a p:3b:a p:45:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2a:0 p:3b:0 p:45:0 b:1f:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:1f:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:24:a p:2e:a p:3b:a p:45:a b:26:a g:39:a g:39:a g:3e:a g:3e:a g:42:a g:42:a pi:39:a pi:3e:a pi:42:a t14 p:24:0 p:2e:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0",
|
||||
"wav_name": "sample",
|
||||
"sound_font_path": "assets/default_sound_font.sf2",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.post("/text-to-wav")
|
||||
def text_to_wav(body: TextToWavBody):
|
||||
text = body.text.strip()
|
||||
if not text.startswith("<start>"):
|
||||
text = "<start> " + text
|
||||
if not text.endswith("<end>"):
|
||||
text = text + " <end>"
|
||||
txt_path = f"midi/{body.wav_name}.txt"
|
||||
midi_path = f"midi/{body.wav_name}.mid"
|
||||
wav_path = f"midi/{body.wav_name}.wav"
|
||||
with open(txt_path, "w") as f:
|
||||
f.write(text)
|
||||
txt_to_midi(TxtToMidiBody(txt_path=txt_path, midi_path=midi_path))
|
||||
midi_to_wav(
|
||||
MidiToWavBody(
|
||||
midi_path=midi_path, wav_path=wav_path, sound_font_path=body.sound_font_path
|
||||
)
|
||||
)
|
673
backend-python/utils/midi.py
Normal file
673
backend-python/utils/midi.py
Normal file
@ -0,0 +1,673 @@
|
||||
import json
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from functools import lru_cache
|
||||
from math import ceil, floor, log
|
||||
from typing import Dict, Iterator, List, Optional, Tuple
|
||||
|
||||
import mido
|
||||
|
||||
|
||||
@dataclass
|
||||
class VocabConfig:
|
||||
# Number of note events. Should be 128.
|
||||
note_events: int
|
||||
# Number of wait events. Configurable, must evenly divide max_wait_time.
|
||||
wait_events: int
|
||||
# Max wait time in milliseconds to be represented by a single token.
|
||||
max_wait_time: int
|
||||
# Number of velocity events. Should be 128 (or 100? need to check midi standard)
|
||||
velocity_events: int
|
||||
# Number of bins to quantize velocity into. Should evenly divide velocity_events.
|
||||
velocity_bins: int
|
||||
# Exponential scaling factor for velocity bin sizes. 1.0 = linear scaling.
|
||||
velocity_exp: float
|
||||
# Whether to sort tokens by instrument, note. This should improve data reducibility.
|
||||
do_token_sorting: bool
|
||||
# Whether tokens should be represented as combined instrument/note/velocity tokens, or separate tokens for each.
|
||||
unrolled_tokens: bool
|
||||
# If non-zero, notes held for this many seconds will be automatically released during str->midi decoding.
|
||||
decode_end_held_note_delay: float
|
||||
# If true, repeated notes will be automatically released before playing again during str->midi decoding.
|
||||
decode_fix_repeated_notes: bool
|
||||
# List of instrument names to use for binning. Must have at most 16 values.
|
||||
bin_instrument_names: List[str]
|
||||
# Indicates which bin name represents percussion instruments on MIDI channel 10.
|
||||
ch10_instrument_bin_name: str
|
||||
# Mapping from instrument name to bin name.
|
||||
program_name_to_bin_name: Dict[str, str]
|
||||
# Mapping from bin name to program name.
|
||||
bin_name_to_program_name: Dict[str, str]
|
||||
# Mapping from program number to instrument name.
|
||||
instrument_names: Dict[str, str]
|
||||
|
||||
def __post_init__(self):
|
||||
self.validate()
|
||||
|
||||
self._instrument_names_str_to_int = {
|
||||
name: int(i) for i, name in self.instrument_names.items()
|
||||
}
|
||||
self._instrument_names_int_to_str = {
|
||||
int(i): name for i, name in self.instrument_names.items()
|
||||
}
|
||||
|
||||
self._bin_str_to_int = {
|
||||
name: int(i) for i, name in enumerate(self.bin_instrument_names)
|
||||
}
|
||||
|
||||
self._bin_int_to_instrument_int = [
|
||||
self._instrument_names_str_to_int[self.bin_name_to_program_name[name]]
|
||||
if name != self.ch10_instrument_bin_name
|
||||
else 0
|
||||
for name in self.bin_instrument_names
|
||||
]
|
||||
self._instrument_int_to_bin_int = [
|
||||
self._bin_str_to_int[self.program_name_to_bin_name[instr]]
|
||||
if self.program_name_to_bin_name[instr] != ""
|
||||
else -1
|
||||
for instr in self.program_name_to_bin_name.keys()
|
||||
]
|
||||
|
||||
self._ch10_bin_int = (
|
||||
self._bin_str_to_int[self.ch10_instrument_bin_name]
|
||||
if self.ch10_instrument_bin_name
|
||||
else -1
|
||||
)
|
||||
|
||||
self.short_instr_bin_names = []
|
||||
for instr in self.bin_instrument_names:
|
||||
i = min(1, len(instr))
|
||||
while instr[:i] in self.short_instr_bin_names:
|
||||
i += 1
|
||||
self.short_instr_bin_names.append(instr[:i])
|
||||
self._short_instrument_names_str_to_int = {
|
||||
name: int(i) for i, name in enumerate(self.short_instr_bin_names)
|
||||
}
|
||||
|
||||
range_excluding_ch10 = [
|
||||
(i if i < 9 else i + 1) for i in range(len(self.bin_instrument_names))
|
||||
]
|
||||
bins_excluding_ch10 = [
|
||||
n for n in self.bin_instrument_names if n != self.ch10_instrument_bin_name
|
||||
]
|
||||
self.bin_channel_map = {
|
||||
bin: channel
|
||||
for channel, bin in zip(range_excluding_ch10, bins_excluding_ch10)
|
||||
}
|
||||
if self.ch10_instrument_bin_name:
|
||||
self.bin_channel_map[self.ch10_instrument_bin_name] = 9
|
||||
|
||||
def validate(self):
|
||||
if self.max_wait_time % self.wait_events != 0:
|
||||
raise ValueError("max_wait_time must be exactly divisible by wait_events")
|
||||
if self.velocity_bins < 2:
|
||||
raise ValueError("velocity_bins must be at least 2")
|
||||
if len(self.bin_instrument_names) > 16:
|
||||
raise ValueError("bin_instruments must have at most 16 values")
|
||||
if (
|
||||
self.ch10_instrument_bin_name
|
||||
and self.ch10_instrument_bin_name not in self.bin_instrument_names
|
||||
):
|
||||
raise ValueError("ch10_instrument_bin_name must be in bin_instruments")
|
||||
if self.velocity_exp <= 0:
|
||||
raise ValueError("velocity_exp must be greater than 0")
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, path: str):
|
||||
with open(path, "r") as f:
|
||||
config = json.load(f)
|
||||
return cls(**config)
|
||||
|
||||
|
||||
class VocabUtils:
|
||||
def __init__(self, cfg: VocabConfig) -> None:
|
||||
self.cfg = cfg
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def format_wait_token(self, wait: int) -> str:
|
||||
return f"t{wait}"
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def format_note_token(
|
||||
self, instrument_bin: int, note: int, velocity_bin: int
|
||||
) -> str:
|
||||
return f"{self.cfg.short_instr_bin_names[instrument_bin]}:{note:x}:{velocity_bin:x}"
|
||||
|
||||
def format_unrolled_note(self, note: int) -> str:
|
||||
return f"n{note:x}"
|
||||
|
||||
def format_unrolled_velocity(self, velocity_bin: int) -> str:
|
||||
return f"v{velocity_bin:x}"
|
||||
|
||||
def format_unrolled_instrument_bin(self, instrument_bin: int) -> str:
|
||||
return f"i{self.cfg.short_instr_bin_names[instrument_bin]}"
|
||||
|
||||
def velocity_to_bin(self, velocity: float) -> int:
|
||||
velocity = max(0, min(velocity, self.cfg.velocity_events - 1))
|
||||
binsize = self.cfg.velocity_events / (self.cfg.velocity_bins - 1)
|
||||
if self.cfg.velocity_exp == 1.0:
|
||||
return ceil(velocity / binsize)
|
||||
else:
|
||||
return ceil(
|
||||
(
|
||||
self.cfg.velocity_events
|
||||
* (
|
||||
(
|
||||
self.cfg.velocity_exp
|
||||
** (velocity / self.cfg.velocity_events)
|
||||
- 1.0
|
||||
)
|
||||
/ (self.cfg.velocity_exp - 1.0)
|
||||
)
|
||||
)
|
||||
/ binsize
|
||||
)
|
||||
|
||||
def bin_to_velocity(self, bin: int) -> int:
|
||||
binsize = self.cfg.velocity_events / (self.cfg.velocity_bins - 1)
|
||||
if self.cfg.velocity_exp == 1.0:
|
||||
return max(0, ceil(bin * binsize - 1))
|
||||
else:
|
||||
return max(
|
||||
0,
|
||||
ceil(
|
||||
self.cfg.velocity_events
|
||||
* log(
|
||||
((self.cfg.velocity_exp - 1) * binsize * bin)
|
||||
/ self.cfg.velocity_events
|
||||
+ 1,
|
||||
self.cfg.velocity_exp,
|
||||
)
|
||||
- 1
|
||||
),
|
||||
)
|
||||
|
||||
def delta_to_wait_ids(self, delta_ms: float) -> Iterator[int]:
|
||||
def roundi(f: float):
|
||||
return ceil(f - 0.5)
|
||||
|
||||
max_wait_ms = self.cfg.max_wait_time
|
||||
div = max_wait_ms / self.cfg.wait_events
|
||||
|
||||
# if delta_ms // max_wait_ms > 512: # arbitrary limit to avoid excessive time_shifts
|
||||
# raise ValueError("delta_time is too large")
|
||||
if delta_ms > max_wait_ms * 10:
|
||||
delta_ms = max_wait_ms * 10 # truncate time
|
||||
|
||||
for _ in range(floor(delta_ms / max_wait_ms)):
|
||||
yield roundi(max_wait_ms / div)
|
||||
leftover_time_shift = roundi((delta_ms % max_wait_ms) / div)
|
||||
if leftover_time_shift > 0:
|
||||
yield leftover_time_shift
|
||||
|
||||
def prog_data_to_token_data(
|
||||
self, program: int, channel: int, note: int, velocity: float
|
||||
) -> Optional[Tuple[int, int, int]]:
|
||||
if channel == 9:
|
||||
if self.cfg._ch10_bin_int == -1:
|
||||
return None
|
||||
return self.cfg._ch10_bin_int, note, self.velocity_to_bin(velocity)
|
||||
|
||||
instrument_bin = self.cfg._instrument_int_to_bin_int[program]
|
||||
if instrument_bin != -1:
|
||||
return instrument_bin, note, self.velocity_to_bin(velocity)
|
||||
return None
|
||||
|
||||
def prog_data_list_to_token_data_list(
|
||||
self, data: List[Tuple[int, int, int, float]]
|
||||
) -> Iterator[Tuple[int, int, int]]:
|
||||
for d in data:
|
||||
token_data = self.prog_data_to_token_data(*d)
|
||||
if token_data is not None:
|
||||
yield token_data
|
||||
|
||||
def sort_token_data(
|
||||
self, data: List[Tuple[int, int, int]]
|
||||
) -> List[Tuple[int, int, int]]:
|
||||
# ensure order is preserved for tokens with the same instrument, note
|
||||
data = [(i, n, v, x) for x, (i, n, v) in enumerate(data)]
|
||||
data.sort(key=lambda x: (x[0] != self.cfg._ch10_bin_int, x[0], x[1], x[3]))
|
||||
return [(i, n, v) for i, n, v, _ in data]
|
||||
|
||||
def data_to_wait_tokens(self, delta_ms: float) -> List[str]:
|
||||
if delta_ms == 0.0:
|
||||
return []
|
||||
return [self.format_wait_token(i) for i in self.delta_to_wait_ids(delta_ms)]
|
||||
|
||||
def wait_token_to_delta(self, token: str) -> float:
|
||||
return self.cfg.max_wait_time / self.cfg.wait_events * int(token[1:])
|
||||
|
||||
def note_token_to_data(self, token: str) -> Tuple[int, int, int]:
|
||||
instr_str, note_str, velocity_str = token.strip().split(":")
|
||||
instr_bin = self.cfg._short_instrument_names_str_to_int[instr_str]
|
||||
note = int(note_str, base=16)
|
||||
velocity = self.bin_to_velocity(int(velocity_str, base=16))
|
||||
return instr_bin, note, velocity
|
||||
|
||||
|
||||
@dataclass
|
||||
class AugmentValues:
|
||||
instrument_bin_remap: Dict[int, int]
|
||||
velocity_mod_factor: float
|
||||
transpose_semitones: int
|
||||
time_stretch_factor: float
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> "AugmentValues":
|
||||
return cls(
|
||||
instrument_bin_remap={},
|
||||
velocity_mod_factor=1.0,
|
||||
transpose_semitones=0,
|
||||
time_stretch_factor=1.0,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AugmentConfig:
|
||||
# The number of times to augment each MIDI file. The dataset size will be multiplied by this number.
|
||||
augment_data_factor: int
|
||||
# A list of instrument names to randomly swap with each other.
|
||||
instrument_mixups: List[List[str]]
|
||||
# A list of percentages to change the note velocity by. 0.0 = no change. 0 is included by default.
|
||||
velocity_mod_pct: List[float]
|
||||
# A list of semitones to transpose by. 0 is included by default.
|
||||
transpose_semitones: List[int]
|
||||
# A list of percentages to stretch the tempo by. 0.0 = no stretch. 0 is included by default.
|
||||
time_stretch_pct: List[float]
|
||||
# Random seed to use for reproducibility.
|
||||
seed: int
|
||||
|
||||
cfg: VocabConfig
|
||||
|
||||
def __post_init__(self):
|
||||
self.validate()
|
||||
if len(self.velocity_mod_pct) == 0:
|
||||
self.velocity_mod_pct = [0.0]
|
||||
if len(self.transpose_semitones) == 0:
|
||||
self.transpose_semitones = [0]
|
||||
if len(self.time_stretch_pct) == 0:
|
||||
self.time_stretch_pct = [0.0]
|
||||
|
||||
self._instrument_mixups_int = [
|
||||
[self.cfg._bin_str_to_int[i] for i in l if i in self.cfg._bin_str_to_int]
|
||||
for l in self.instrument_mixups
|
||||
]
|
||||
self._instrument_mixups_int = [
|
||||
l for l in self._instrument_mixups_int if len(l) > 0
|
||||
] # remove empty lists
|
||||
self._instrument_pool_assignments = {}
|
||||
self._mixup_pools = []
|
||||
for pool_i, mixup_list in enumerate(self._instrument_mixups_int):
|
||||
pool = set()
|
||||
for i in mixup_list:
|
||||
pool.add(i)
|
||||
self._instrument_pool_assignments[i] = pool_i
|
||||
self._mixup_pools.append(pool)
|
||||
|
||||
def validate(self):
|
||||
if self.augment_data_factor < 1:
|
||||
raise ValueError("augment_data_factor must be at least 1")
|
||||
used_instruments = set()
|
||||
for mixup_list in self.instrument_mixups:
|
||||
for n in mixup_list:
|
||||
if n in used_instruments:
|
||||
raise ValueError(f"Duplicate instrument name: {n}")
|
||||
used_instruments.add(n)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, path: str, cfg: VocabConfig):
|
||||
with open(path, "r") as f:
|
||||
config = json.load(f)
|
||||
config["cfg"] = cfg
|
||||
if "seed" not in config:
|
||||
config["seed"] = random.randint(0, 2**32 - 1)
|
||||
return cls(**config)
|
||||
|
||||
def get_augment_values(self, filename: str) -> Iterator[AugmentValues]:
|
||||
# first yield default values
|
||||
yield AugmentValues.default()
|
||||
|
||||
rng = random.Random(self.seed + hash(filename))
|
||||
for _ in range(int(self.augment_data_factor - 1)):
|
||||
# randomize order for each pool
|
||||
randomized_pools = [list(pool) for pool in self._mixup_pools]
|
||||
for pool in randomized_pools:
|
||||
rng.shuffle(pool)
|
||||
# distribute reassignments
|
||||
instrument_bin_remap = {}
|
||||
for i, pool in enumerate(randomized_pools):
|
||||
for j, instrument in enumerate(pool):
|
||||
instrument_bin_remap[instrument] = randomized_pools[i - 1][j]
|
||||
yield AugmentValues(
|
||||
instrument_bin_remap=instrument_bin_remap,
|
||||
velocity_mod_factor=1.0 + rng.choice(self.velocity_mod_pct),
|
||||
transpose_semitones=rng.choice(self.transpose_semitones),
|
||||
time_stretch_factor=1.0 + rng.choice(self.time_stretch_pct),
|
||||
)
|
||||
|
||||
|
||||
def mix_volume(velocity: int, volume: int, expression: int) -> float:
|
||||
return velocity * (volume / 127.0) * (expression / 127.0)
|
||||
|
||||
|
||||
def convert_midi_to_str(
|
||||
cfg: VocabConfig, mid: mido.MidiFile, augment: AugmentValues = None
|
||||
) -> str:
|
||||
utils = VocabUtils(cfg)
|
||||
if augment is None:
|
||||
augment = AugmentValues.default()
|
||||
|
||||
# filter out unknown meta messages before merge (https://github.com/mido/mido/pull/286)
|
||||
for i in range(len(mid.tracks)):
|
||||
mid.tracks[i] = [msg for msg in mid.tracks[i] if msg.type != "unknown_meta"]
|
||||
|
||||
if len(mid.tracks) > 1:
|
||||
mid.tracks = [mido.merge_tracks(mid.tracks)]
|
||||
|
||||
delta_time_ms = 0.0
|
||||
tempo = 500000
|
||||
channel_program = {i: 0 for i in range(16)}
|
||||
channel_volume = {i: 127 for i in range(16)}
|
||||
channel_expression = {
|
||||
i: 127 for i in range(16)
|
||||
} # unlikely to be useful. expression usually modifies an already played note.
|
||||
channel_notes = {i: {} for i in range(16)}
|
||||
channel_pedal_on = {i: False for i in range(16)}
|
||||
channel_pedal_events = {
|
||||
i: {} for i in range(16)
|
||||
} # {channel: {(note, program) -> True}}
|
||||
started_flag = False
|
||||
|
||||
output = ["<start>"]
|
||||
token_data_buffer: List[
|
||||
Tuple[int, int, int, float]
|
||||
] = [] # need to sort notes between wait tokens
|
||||
|
||||
def flush_token_data_buffer():
|
||||
nonlocal token_data_buffer, output, cfg, utils, augment
|
||||
token_data = [
|
||||
x for x in utils.prog_data_list_to_token_data_list(token_data_buffer)
|
||||
]
|
||||
if augment.instrument_bin_remap or augment.transpose_semitones:
|
||||
# TODO put transpose in a real function
|
||||
raw_transpose = (
|
||||
lambda bin, n: n + augment.transpose_semitones
|
||||
if bin != cfg._ch10_bin_int
|
||||
else n
|
||||
)
|
||||
octave_shift_if_oob = (
|
||||
lambda n: n + 12 if n < 0 else n - 12 if n >= cfg.note_events else n
|
||||
)
|
||||
# TODO handle ranges beyond 12
|
||||
# octave_shift_if_oob = lambda n: 0 if n < 0 else (n - cfg.note_events) % 12 + cfg.note_events if n >= cfg.note_events else n
|
||||
transpose = lambda bin, n: octave_shift_if_oob(raw_transpose(bin, n))
|
||||
|
||||
token_data = [
|
||||
(augment.instrument_bin_remap.get(i, i), transpose(i, n), v)
|
||||
for i, n, v in token_data
|
||||
]
|
||||
if cfg.do_token_sorting:
|
||||
token_data = utils.sort_token_data(token_data)
|
||||
if cfg.unrolled_tokens:
|
||||
for t in token_data:
|
||||
output += [
|
||||
utils.format_unrolled_instrument_bin(t[0]),
|
||||
utils.format_unrolled_note(t[1]),
|
||||
utils.format_unrolled_velocity(t[2]),
|
||||
]
|
||||
else:
|
||||
output += [utils.format_note_token(*t) for t in token_data]
|
||||
token_data_buffer = []
|
||||
|
||||
def consume_note_program_data(prog: int, chan: int, note: int, vel: float):
|
||||
nonlocal output, started_flag, delta_time_ms, cfg, utils, token_data_buffer
|
||||
is_token_valid = (
|
||||
utils.prog_data_to_token_data(prog, chan, note, vel) is not None
|
||||
)
|
||||
if not is_token_valid:
|
||||
return
|
||||
if started_flag:
|
||||
wait_tokens = utils.data_to_wait_tokens(delta_time_ms)
|
||||
if len(wait_tokens) > 0:
|
||||
flush_token_data_buffer()
|
||||
output += wait_tokens
|
||||
delta_time_ms = 0.0
|
||||
token_data_buffer.append((prog, chan, note, vel * augment.velocity_mod_factor))
|
||||
started_flag = True
|
||||
|
||||
for msg in mid.tracks[0]:
|
||||
time_ms = mido.tick2second(msg.time, mid.ticks_per_beat, tempo) * 1000.0
|
||||
delta_time_ms += time_ms
|
||||
t = msg.type
|
||||
|
||||
if msg.is_meta:
|
||||
if t == "set_tempo":
|
||||
tempo = msg.tempo * augment.time_stretch_factor
|
||||
continue
|
||||
|
||||
def handle_note_off(ch, prog, n):
|
||||
if channel_pedal_on[ch]:
|
||||
channel_pedal_events[ch][(n, prog)] = True
|
||||
else:
|
||||
consume_note_program_data(prog, ch, n, 0)
|
||||
if n in channel_notes[ch]:
|
||||
del channel_notes[ch][n]
|
||||
|
||||
if t == "program_change":
|
||||
channel_program[msg.channel] = msg.program
|
||||
elif t == "note_on":
|
||||
if msg.velocity == 0:
|
||||
handle_note_off(msg.channel, channel_program[msg.channel], msg.note)
|
||||
else:
|
||||
if (msg.note, channel_program[msg.channel]) in channel_pedal_events[
|
||||
msg.channel
|
||||
]:
|
||||
del channel_pedal_events[msg.channel][
|
||||
(msg.note, channel_program[msg.channel])
|
||||
]
|
||||
consume_note_program_data(
|
||||
channel_program[msg.channel],
|
||||
msg.channel,
|
||||
msg.note,
|
||||
mix_volume(
|
||||
msg.velocity,
|
||||
channel_volume[msg.channel],
|
||||
channel_expression[msg.channel],
|
||||
),
|
||||
)
|
||||
channel_notes[msg.channel][msg.note] = True
|
||||
elif t == "note_off":
|
||||
handle_note_off(msg.channel, channel_program[msg.channel], msg.note)
|
||||
elif t == "control_change":
|
||||
if msg.control == 7 or msg.control == 39: # volume
|
||||
channel_volume[msg.channel] = msg.value
|
||||
elif msg.control == 11: # expression
|
||||
channel_expression[msg.channel] = msg.value
|
||||
elif msg.control == 64: # sustain pedal
|
||||
channel_pedal_on[msg.channel] = msg.value >= 64
|
||||
if not channel_pedal_on[msg.channel]:
|
||||
for note, program in channel_pedal_events[msg.channel]:
|
||||
handle_note_off(msg.channel, program, note)
|
||||
channel_pedal_events[msg.channel] = {}
|
||||
elif msg.control == 123: # all notes off
|
||||
for channel in channel_notes.keys():
|
||||
for note in list(channel_notes[channel]).copy():
|
||||
handle_note_off(channel, channel_program[channel], note)
|
||||
else:
|
||||
pass
|
||||
|
||||
flush_token_data_buffer()
|
||||
output.append("<end>")
|
||||
return " ".join(output)
|
||||
|
||||
|
||||
def generate_program_change_messages(cfg: VocabConfig):
|
||||
for bin_name, channel in cfg.bin_channel_map.items():
|
||||
if channel == 9:
|
||||
continue
|
||||
program = cfg._instrument_names_str_to_int[
|
||||
cfg.bin_name_to_program_name[bin_name]
|
||||
]
|
||||
yield mido.Message("program_change", program=program, time=0, channel=channel)
|
||||
yield mido.Message("program_change", program=0, time=0, channel=9)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DecodeState:
|
||||
total_time: float # milliseconds
|
||||
delta_accum: float # milliseconds
|
||||
current_bin: int
|
||||
current_note: int
|
||||
active_notes: Dict[Tuple[int, int], float] # { (channel, note): time started, ... }
|
||||
|
||||
|
||||
def token_to_midi_message(
|
||||
utils: VocabUtils, token: str, state: DecodeState, end_token_pause: float = 3.0
|
||||
) -> Iterator[Tuple[Optional[mido.Message], DecodeState]]:
|
||||
if state is None:
|
||||
state = DecodeState(
|
||||
total_time=0.0,
|
||||
delta_accum=0.0,
|
||||
current_bin=utils.cfg._short_instrument_names_str_to_int[
|
||||
utils.cfg.short_instr_bin_names[0]
|
||||
],
|
||||
current_note=0,
|
||||
active_notes={},
|
||||
)
|
||||
token = token.strip()
|
||||
if not token:
|
||||
yield None, state
|
||||
return
|
||||
if token == "<end>":
|
||||
d = end_token_pause * 1000.0
|
||||
state.delta_accum += d
|
||||
state.total_time += d
|
||||
if utils.cfg.decode_end_held_note_delay != 0.0:
|
||||
# end held notes
|
||||
for (channel, note), start_time in list(state.active_notes.items()).copy():
|
||||
ticks = int(mido.second2tick(state.delta_accum / 1000.0, 480, 500000))
|
||||
state.delta_accum = 0.0
|
||||
del state.active_notes[(channel, note)]
|
||||
yield mido.Message(
|
||||
"note_off", note=note, time=ticks, channel=channel
|
||||
), state
|
||||
yield None, state
|
||||
return
|
||||
if token.startswith("<"):
|
||||
yield None, state
|
||||
return
|
||||
|
||||
if utils.cfg.unrolled_tokens:
|
||||
if token[0] == "t":
|
||||
d = utils.wait_token_to_delta(token)
|
||||
state.delta_accum += d
|
||||
state.total_time += d
|
||||
elif token[0] == "n":
|
||||
state.current_note = int(token[1:], base=16)
|
||||
elif token[0] == "i":
|
||||
state.current_bin = utils.cfg._short_instrument_names_str_to_int[token[1:]]
|
||||
elif token[0] == "v":
|
||||
current_velocity = utils.bin_to_velocity(int(token[1:], base=16))
|
||||
channel = utils.cfg.bin_channel_map[
|
||||
utils.cfg.bin_instrument_names[state.current_bin]
|
||||
]
|
||||
ticks = int(mido.second2tick(state.delta_accum / 1000.0, 480, 500000))
|
||||
state.delta_accum = 0.0
|
||||
if current_velocity > 0:
|
||||
yield mido.Message(
|
||||
"note_on",
|
||||
note=state.current_note,
|
||||
velocity=current_velocity,
|
||||
time=ticks,
|
||||
channel=channel,
|
||||
), state
|
||||
else:
|
||||
yield mido.Message(
|
||||
"note_off",
|
||||
note=state.current_note,
|
||||
velocity=0,
|
||||
time=ticks,
|
||||
channel=channel,
|
||||
), state
|
||||
else:
|
||||
if token[0] == "t" and token[1].isdigit(): # wait token
|
||||
d = utils.wait_token_to_delta(token)
|
||||
state.delta_accum += d
|
||||
state.total_time += d
|
||||
if utils.cfg.decode_end_held_note_delay != 0.0:
|
||||
# remove notes that have been held for too long
|
||||
for (channel, note), start_time in list(
|
||||
state.active_notes.items()
|
||||
).copy():
|
||||
if (
|
||||
state.total_time - start_time
|
||||
> utils.cfg.decode_end_held_note_delay * 1000.0
|
||||
):
|
||||
ticks = int(
|
||||
mido.second2tick(state.delta_accum / 1000.0, 480, 500000)
|
||||
)
|
||||
state.delta_accum = 0.0
|
||||
del state.active_notes[(channel, note)]
|
||||
yield mido.Message(
|
||||
"note_off", note=note, time=ticks, channel=channel
|
||||
), state
|
||||
return
|
||||
else: # note token
|
||||
bin, note, velocity = utils.note_token_to_data(token)
|
||||
channel = utils.cfg.bin_channel_map[utils.cfg.bin_instrument_names[bin]]
|
||||
ticks = int(mido.second2tick(state.delta_accum / 1000.0, 480, 500000))
|
||||
state.delta_accum = 0.0
|
||||
if velocity > 0:
|
||||
if utils.cfg.decode_fix_repeated_notes:
|
||||
if (channel, note) in state.active_notes:
|
||||
del state.active_notes[(channel, note)]
|
||||
yield mido.Message(
|
||||
"note_off", note=note, time=ticks, channel=channel
|
||||
), state
|
||||
ticks = 0
|
||||
state.active_notes[(channel, note)] = state.total_time
|
||||
yield mido.Message(
|
||||
"note_on", note=note, velocity=velocity, time=ticks, channel=channel
|
||||
), state
|
||||
return
|
||||
else:
|
||||
if (channel, note) in state.active_notes:
|
||||
del state.active_notes[(channel, note)]
|
||||
yield mido.Message(
|
||||
"note_off", note=note, time=ticks, channel=channel
|
||||
), state
|
||||
return
|
||||
yield None, state
|
||||
|
||||
|
||||
def str_to_midi_messages(utils: VocabUtils, data: str) -> Iterator[mido.Message]:
|
||||
state = None
|
||||
for token in data.split(" "):
|
||||
for msg, new_state in token_to_midi_message(utils, token, state):
|
||||
state = new_state
|
||||
if msg is not None:
|
||||
yield msg
|
||||
|
||||
|
||||
def convert_str_to_midi(
|
||||
cfg: VocabConfig, data: str, meta_text: str = "Generated by MIDI-LLM-tokenizer"
|
||||
) -> mido.MidiFile:
|
||||
utils = VocabUtils(cfg)
|
||||
mid = mido.MidiFile()
|
||||
track = mido.MidiTrack()
|
||||
mid.tracks.append(track)
|
||||
|
||||
tempo = 500000
|
||||
if meta_text:
|
||||
track.append(mido.MetaMessage("text", text=meta_text, time=0))
|
||||
track.append(mido.MetaMessage("set_tempo", tempo=tempo, time=0))
|
||||
for msg in generate_program_change_messages(cfg):
|
||||
track.append(msg)
|
||||
|
||||
# data = data.replace("<start>", "").replace("<end>", "").replace("<pad>", "").strip()
|
||||
for msg in str_to_midi_messages(utils, data):
|
||||
track.append(msg)
|
||||
|
||||
track.append(mido.MetaMessage("end_of_track", time=0))
|
||||
|
||||
return mid
|
303
backend-python/utils/midi_vocab_config.json
Normal file
303
backend-python/utils/midi_vocab_config.json
Normal file
@ -0,0 +1,303 @@
|
||||
{
|
||||
"note_events": 128,
|
||||
"wait_events": 125,
|
||||
"max_wait_time": 1000,
|
||||
"velocity_events": 128,
|
||||
"velocity_bins": 12,
|
||||
"velocity_exp": 0.5,
|
||||
"do_token_sorting": true,
|
||||
"unrolled_tokens": false,
|
||||
"decode_end_held_note_delay": 5.0,
|
||||
"decode_fix_repeated_notes": true,
|
||||
"bin_instrument_names": [
|
||||
"percussion",
|
||||
"drum",
|
||||
"tuba",
|
||||
"marimba",
|
||||
"bass",
|
||||
"guitar",
|
||||
"violin",
|
||||
"trumpet",
|
||||
"piano",
|
||||
"sax",
|
||||
"flute",
|
||||
"lead",
|
||||
"pad"
|
||||
],
|
||||
"ch10_instrument_bin_name": "percussion",
|
||||
"program_name_to_bin_name": {
|
||||
"Acoustic Grand Piano": "piano",
|
||||
"Bright Acoustic Piano": "piano",
|
||||
"Electric Grand Piano": "piano",
|
||||
"Honky-tonk Piano": "piano",
|
||||
"Electric Piano 1 (Rhodes Piano)": "piano",
|
||||
"Electric Piano 2 (Chorused Piano)": "piano",
|
||||
"Harpsichord": "piano",
|
||||
"Clavinet": "piano",
|
||||
"Celesta": "marimba",
|
||||
"Glockenspiel": "marimba",
|
||||
"Music Box": "marimba",
|
||||
"Vibraphone": "marimba",
|
||||
"Marimba": "marimba",
|
||||
"Xylophone": "marimba",
|
||||
"Tubular Bells": "marimba",
|
||||
"Dulcimer (Santur)": "marimba",
|
||||
"Drawbar Organ (Hammond)": "marimba",
|
||||
"Percussive Organ": "piano",
|
||||
"Rock Organ": "piano",
|
||||
"Church Organ": "piano",
|
||||
"Reed Organ": "piano",
|
||||
"Accordion (French)": "piano",
|
||||
"Harmonica": "piano",
|
||||
"Tango Accordion (Band neon)": "piano",
|
||||
"Acoustic Guitar (nylon)": "guitar",
|
||||
"Acoustic Guitar (steel)": "guitar",
|
||||
"Electric Guitar (jazz)": "guitar",
|
||||
"Electric Guitar (clean)": "guitar",
|
||||
"Electric Guitar (muted)": "guitar",
|
||||
"Overdriven Guitar": "guitar",
|
||||
"Distortion Guitar": "guitar",
|
||||
"Guitar harmonics": "guitar",
|
||||
"Acoustic Bass": "bass",
|
||||
"Electric Bass (fingered)": "bass",
|
||||
"Electric Bass (picked)": "bass",
|
||||
"Fretless Bass": "bass",
|
||||
"Slap Bass 1": "bass",
|
||||
"Slap Bass 2": "bass",
|
||||
"Synth Bass 1": "bass",
|
||||
"Synth Bass 2": "bass",
|
||||
"Violin": "violin",
|
||||
"Viola": "violin",
|
||||
"Cello": "bass",
|
||||
"Contrabass": "bass",
|
||||
"Tremolo Strings": "violin",
|
||||
"Pizzicato Strings": "violin",
|
||||
"Orchestral Harp": "violin",
|
||||
"Timpani": "drum",
|
||||
"String Ensemble 1 (strings)": "violin",
|
||||
"String Ensemble 2 (slow strings)": "violin",
|
||||
"SynthStrings 1": "violin",
|
||||
"SynthStrings 2": "violin",
|
||||
"Choir Aahs": "violin",
|
||||
"Voice Oohs": "violin",
|
||||
"Synth Voice": "violin",
|
||||
"Orchestra Hit": "",
|
||||
"Trumpet": "trumpet",
|
||||
"Trombone": "tuba",
|
||||
"Tuba": "tuba",
|
||||
"Muted Trumpet": "trumpet",
|
||||
"French Horn": "trumpet",
|
||||
"Brass Section": "trumpet",
|
||||
"SynthBrass 1": "trumpet",
|
||||
"SynthBrass 2": "trumpet",
|
||||
"Soprano Sax": "sax",
|
||||
"Alto Sax": "sax",
|
||||
"Tenor Sax": "sax",
|
||||
"Baritone Sax": "sax",
|
||||
"Oboe": "sax",
|
||||
"English Horn": "trumpet",
|
||||
"Bassoon": "sax",
|
||||
"Clarinet": "sax",
|
||||
"Piccolo": "flute",
|
||||
"Flute": "flute",
|
||||
"Recorder": "flute",
|
||||
"Pan Flute": "flute",
|
||||
"Blown Bottle": "flute",
|
||||
"Shakuhachi": "flute",
|
||||
"Whistle": "flute",
|
||||
"Ocarina": "flute",
|
||||
"Lead 1 (square wave)": "lead",
|
||||
"Lead 2 (sawtooth wave)": "lead",
|
||||
"Lead 3 (calliope)": "lead",
|
||||
"Lead 4 (chiffer)": "lead",
|
||||
"Lead 5 (charang)": "lead",
|
||||
"Lead 6 (voice solo)": "violin",
|
||||
"Lead 7 (fifths)": "lead",
|
||||
"Lead 8 (bass + lead)": "lead",
|
||||
"Pad 1 (new age Fantasia)": "pad",
|
||||
"Pad 2 (warm)": "pad",
|
||||
"Pad 3 (polysynth)": "pad",
|
||||
"Pad 4 (choir space voice)": "violin",
|
||||
"Pad 5 (bowed glass)": "pad",
|
||||
"Pad 6 (metallic pro)": "pad",
|
||||
"Pad 7 (halo)": "pad",
|
||||
"Pad 8 (sweep)": "pad",
|
||||
"FX 1 (rain)": "",
|
||||
"FX 2 (soundtrack)": "",
|
||||
"FX 3 (crystal)": "",
|
||||
"FX 4 (atmosphere)": "",
|
||||
"FX 5 (brightness)": "",
|
||||
"FX 6 (goblins)": "",
|
||||
"FX 7 (echoes, drops)": "",
|
||||
"FX 8 (sci-fi, star theme)": "",
|
||||
"Sitar": "guitar",
|
||||
"Banjo": "guitar",
|
||||
"Shamisen": "guitar",
|
||||
"Koto": "guitar",
|
||||
"Kalimba": "guitar",
|
||||
"Bag pipe": "sax",
|
||||
"Fiddle": "violin",
|
||||
"Shanai": "sax",
|
||||
"Tinkle Bell": "marimba",
|
||||
"Agogo": "marimba",
|
||||
"Steel Drums": "marimba",
|
||||
"Woodblock": "marimba",
|
||||
"Taiko Drum": "drum",
|
||||
"Melodic Tom": "drum",
|
||||
"Synth Drum": "drum",
|
||||
"Reverse Cymbal": "",
|
||||
"Guitar Fret Noise": "",
|
||||
"Breath Noise": "",
|
||||
"Seashore": "",
|
||||
"Bird Tweet": "",
|
||||
"Telephone Ring": "",
|
||||
"Helicopter": "",
|
||||
"Applause": "",
|
||||
"Gunshot": ""
|
||||
},
|
||||
"bin_name_to_program_name": {
|
||||
"piano": "Acoustic Grand Piano",
|
||||
"marimba": "Marimba",
|
||||
"drum": "Synth Drum",
|
||||
"guitar": "Acoustic Guitar (steel)",
|
||||
"bass": "Acoustic Bass",
|
||||
"violin": "Violin",
|
||||
"percussion": "",
|
||||
"trumpet": "Trumpet",
|
||||
"tuba": "Tuba",
|
||||
"sax": "Tenor Sax",
|
||||
"flute": "Flute",
|
||||
"lead": "Lead 1 (square wave)",
|
||||
"pad": "Pad 1 (new age Fantasia)"
|
||||
},
|
||||
"instrument_names": {
|
||||
"0": "Acoustic Grand Piano",
|
||||
"1": "Bright Acoustic Piano",
|
||||
"2": "Electric Grand Piano",
|
||||
"3": "Honky-tonk Piano",
|
||||
"4": "Electric Piano 1 (Rhodes Piano)",
|
||||
"5": "Electric Piano 2 (Chorused Piano)",
|
||||
"6": "Harpsichord",
|
||||
"7": "Clavinet",
|
||||
"8": "Celesta",
|
||||
"9": "Glockenspiel",
|
||||
"10": "Music Box",
|
||||
"11": "Vibraphone",
|
||||
"12": "Marimba",
|
||||
"13": "Xylophone",
|
||||
"14": "Tubular Bells",
|
||||
"15": "Dulcimer (Santur)",
|
||||
"16": "Drawbar Organ (Hammond)",
|
||||
"17": "Percussive Organ",
|
||||
"18": "Rock Organ",
|
||||
"19": "Church Organ",
|
||||
"20": "Reed Organ",
|
||||
"21": "Accordion (French)",
|
||||
"22": "Harmonica",
|
||||
"23": "Tango Accordion (Band neon)",
|
||||
"24": "Acoustic Guitar (nylon)",
|
||||
"25": "Acoustic Guitar (steel)",
|
||||
"26": "Electric Guitar (jazz)",
|
||||
"27": "Electric Guitar (clean)",
|
||||
"28": "Electric Guitar (muted)",
|
||||
"29": "Overdriven Guitar",
|
||||
"30": "Distortion Guitar",
|
||||
"31": "Guitar harmonics",
|
||||
"32": "Acoustic Bass",
|
||||
"33": "Electric Bass (fingered)",
|
||||
"34": "Electric Bass (picked)",
|
||||
"35": "Fretless Bass",
|
||||
"36": "Slap Bass 1",
|
||||
"37": "Slap Bass 2",
|
||||
"38": "Synth Bass 1",
|
||||
"39": "Synth Bass 2",
|
||||
"40": "Violin",
|
||||
"41": "Viola",
|
||||
"42": "Cello",
|
||||
"43": "Contrabass",
|
||||
"44": "Tremolo Strings",
|
||||
"45": "Pizzicato Strings",
|
||||
"46": "Orchestral Harp",
|
||||
"47": "Timpani",
|
||||
"48": "String Ensemble 1 (strings)",
|
||||
"49": "String Ensemble 2 (slow strings)",
|
||||
"50": "SynthStrings 1",
|
||||
"51": "SynthStrings 2",
|
||||
"52": "Choir Aahs",
|
||||
"53": "Voice Oohs",
|
||||
"54": "Synth Voice",
|
||||
"55": "Orchestra Hit",
|
||||
"56": "Trumpet",
|
||||
"57": "Trombone",
|
||||
"58": "Tuba",
|
||||
"59": "Muted Trumpet",
|
||||
"60": "French Horn",
|
||||
"61": "Brass Section",
|
||||
"62": "SynthBrass 1",
|
||||
"63": "SynthBrass 2",
|
||||
"64": "Soprano Sax",
|
||||
"65": "Alto Sax",
|
||||
"66": "Tenor Sax",
|
||||
"67": "Baritone Sax",
|
||||
"68": "Oboe",
|
||||
"69": "English Horn",
|
||||
"70": "Bassoon",
|
||||
"71": "Clarinet",
|
||||
"72": "Piccolo",
|
||||
"73": "Flute",
|
||||
"74": "Recorder",
|
||||
"75": "Pan Flute",
|
||||
"76": "Blown Bottle",
|
||||
"77": "Shakuhachi",
|
||||
"78": "Whistle",
|
||||
"79": "Ocarina",
|
||||
"80": "Lead 1 (square wave)",
|
||||
"81": "Lead 2 (sawtooth wave)",
|
||||
"82": "Lead 3 (calliope)",
|
||||
"83": "Lead 4 (chiffer)",
|
||||
"84": "Lead 5 (charang)",
|
||||
"85": "Lead 6 (voice solo)",
|
||||
"86": "Lead 7 (fifths)",
|
||||
"87": "Lead 8 (bass + lead)",
|
||||
"88": "Pad 1 (new age Fantasia)",
|
||||
"89": "Pad 2 (warm)",
|
||||
"90": "Pad 3 (polysynth)",
|
||||
"91": "Pad 4 (choir space voice)",
|
||||
"92": "Pad 5 (bowed glass)",
|
||||
"93": "Pad 6 (metallic pro)",
|
||||
"94": "Pad 7 (halo)",
|
||||
"95": "Pad 8 (sweep)",
|
||||
"96": "FX 1 (rain)",
|
||||
"97": "FX 2 (soundtrack)",
|
||||
"98": "FX 3 (crystal)",
|
||||
"99": "FX 4 (atmosphere)",
|
||||
"100": "FX 5 (brightness)",
|
||||
"101": "FX 6 (goblins)",
|
||||
"102": "FX 7 (echoes, drops)",
|
||||
"103": "FX 8 (sci-fi, star theme)",
|
||||
"104": "Sitar",
|
||||
"105": "Banjo",
|
||||
"106": "Shamisen",
|
||||
"107": "Koto",
|
||||
"108": "Kalimba",
|
||||
"109": "Bag pipe",
|
||||
"110": "Fiddle",
|
||||
"111": "Shanai",
|
||||
"112": "Tinkle Bell",
|
||||
"113": "Agogo",
|
||||
"114": "Steel Drums",
|
||||
"115": "Woodblock",
|
||||
"116": "Taiko Drum",
|
||||
"117": "Melodic Tom",
|
||||
"118": "Synth Drum",
|
||||
"119": "Reverse Cymbal",
|
||||
"120": "Guitar Fret Noise",
|
||||
"121": "Breath Noise",
|
||||
"122": "Seashore",
|
||||
"123": "Bird Tweet",
|
||||
"124": "Telephone Ring",
|
||||
"125": "Helicopter",
|
||||
"126": "Applause",
|
||||
"127": "Gunshot"
|
||||
}
|
||||
}
|
1
midi/sample.txt
Normal file
1
midi/sample.txt
Normal file
@ -0,0 +1 @@
|
||||
<start> p:24:a p:2a:a p:31:a p:39:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:24:0 p:2a:0 p:31:0 p:39:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:26:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:2e:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2e:0 p:3b:0 p:45:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:2e:a p:3b:a p:45:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2e:0 p:3b:0 p:45:0 b:26:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:26:a p:2a:a p:3b:a p:45:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2a:0 p:3b:0 p:45:0 b:26:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:2d:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 b:2d:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2e:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2e:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:26:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:26:a p:2e:a p:31:a p:39:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:26:0 p:2e:0 p:31:0 p:39:0 p:3b:0 p:45:0 b:21:0 t2 p:26:a p:2e:a p:31:a p:39:a p:3b:a p:45:a b:21:a t14 p:26:0 p:2e:0 p:31:0 p:39:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2a:a p:31:a p:39:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:24:0 p:2a:0 p:31:0 p:39:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:2e:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2e:0 p:3b:0 p:45:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:2e:a p:3b:a p:45:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2e:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:26:a p:2a:a p:3b:a p:45:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2a:0 p:3b:0 p:45:0 b:1f:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:1f:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:24:a p:2e:a p:3b:a p:45:a b:26:a g:39:a g:39:a g:3e:a g:3e:a g:42:a g:42:a pi:39:a pi:3e:a pi:42:a t14 p:24:0 p:2e:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 <end>
|
Loading…
Reference in New Issue
Block a user