multitrack-midi-generator / string_to_notes.py
juancopi81's picture
Initial commit
3c6c416
from typing import Optional
from note_seq.protobuf.music_pb2 import NoteSequence
from note_seq.constants import STANDARD_PPQ
def token_sequence_to_note_sequence(
token_sequence: str,
qpm: float = 120.0,
use_program: bool = True,
use_drums: bool = True,
instrument_mapper: Optional[dict] = None,
only_piano: bool = False,
) -> NoteSequence:
"""
Converts a sequence of tokens into a sequence of notes.
Args:
token_sequence (str): The sequence of tokens to convert.
qpm (float, optional): The quarter notes per minute. Defaults to 120.0.
use_program (bool, optional): Whether to use program. Defaults to True.
use_drums (bool, optional): Whether to use drums. Defaults to True.
instrument_mapper (Optional[dict], optional): The instrument mapper. Defaults to None.
only_piano (bool, optional): Whether to only use piano. Defaults to False.
Returns:
NoteSequence: The resulting sequence of notes.
"""
if isinstance(token_sequence, str):
token_sequence = token_sequence.split()
note_sequence = empty_note_sequence(qpm)
# Compute note and bar lengths based on the provided QPM
note_length_16th = 0.25 * 60 / qpm
bar_length = 4.0 * 60 / qpm
# Render all notes.
current_program = 1
current_is_drum = False
current_instrument = 0
track_count = 0
for _, token in enumerate(token_sequence):
if token == "PIECE_START":
pass
elif token == "PIECE_END":
break
elif token == "TRACK_START":
current_bar_index = 0
track_count += 1
pass
elif token == "TRACK_END":
pass
elif token == "KEYS_START":
pass
elif token == "KEYS_END":
pass
elif token.startswith("KEY="):
pass
elif token.startswith("INST"):
instrument = token.split("=")[-1]
if instrument != "DRUMS" and use_program:
if instrument_mapper is not None:
if instrument in instrument_mapper:
instrument = instrument_mapper[instrument]
current_program = int(instrument)
current_instrument = track_count
current_is_drum = False
if instrument == "DRUMS" and use_drums:
current_instrument = 0
current_program = 0
current_is_drum = True
elif token == "BAR_START":
current_time = current_bar_index * bar_length
current_notes = {}
elif token == "BAR_END":
current_bar_index += 1
pass
elif token.startswith("NOTE_ON"):
pitch = int(token.split("=")[-1])
note = note_sequence.notes.add()
note.start_time = current_time
note.end_time = current_time + 4 * note_length_16th
note.pitch = pitch
note.instrument = current_instrument
note.program = current_program
note.velocity = 80
note.is_drum = current_is_drum
current_notes[pitch] = note
elif token.startswith("NOTE_OFF"):
pitch = int(token.split("=")[-1])
if pitch in current_notes:
note = current_notes[pitch]
note.end_time = current_time
elif token.startswith("TIME_DELTA"):
delta = float(token.split("=")[-1]) * note_length_16th
current_time += delta
elif token.startswith("DENSITY="):
pass
elif token == "[PAD]":
pass
else:
pass
# Make the instruments right.
instruments_drums = []
for note in note_sequence.notes:
pair = [note.program, note.is_drum]
if pair not in instruments_drums:
instruments_drums += [pair]
note.instrument = instruments_drums.index(pair)
if only_piano:
for note in note_sequence.notes:
if not note.is_drum:
note.instrument = 0
note.program = 0
return note_sequence
def empty_note_sequence(qpm: float = 120.0, total_time: float = 0.0) -> NoteSequence:
"""
Creates an empty note sequence.
Args:
qpm (float, optional): The quarter notes per minute. Defaults to 120.0.
total_time (float, optional): The total time. Defaults to 0.0.
Returns:
NoteSequence: The empty note sequence.
"""
note_sequence = NoteSequence()
note_sequence.tempos.add().qpm = qpm
note_sequence.ticks_per_quarter = STANDARD_PPQ
note_sequence.total_time = total_time
return note_sequence