loopgen / beat_this /utils.py
Vansh Chugh
initial deploy
83e59db
Raw
History Blame Contribute Delete
4.15 kB
from itertools import chain
from pathlib import Path
import numpy as np
def index_to_framewise(index, length):
"""Convert an index to a framewise sequence"""
sequence = np.zeros(length, dtype=bool)
sequence[index] = True
return sequence
def filename_to_augmentation(filename):
"""Convert a filename to an augmentation factor."""
parts = Path(filename).stem.split("_")
augmentations = {}
for part in parts[1:]:
if part.startswith("ps"):
augmentations["shift"] = int(part[2:])
elif part.startswith("ts"):
augmentations["stretch"] = int(part[2:])
return augmentations
def infer_beat_numbers(beats: np.ndarray, downbeats: np.ndarray) -> np.ndarray:
"""
From beat and downbeat times, infer a number for each beat such that each downbeat
is associated with a 1 and beats in between are counted upwards.
The function requires that all downbeats are also listed as beats.
Args:
beats (numpy.ndarray): Array of beat positions in seconds (including downbeats).
downbeats (numpy.ndarray): Array of downbeat positions in seconds.
Returns:
numbers (numpy.ndarray): Array of integer beat numbers.
"""
# check if all downbeats are beats
if not np.all(np.isin(downbeats, beats)):
raise ValueError("Not all downbeats are beats.")
# handle pickup measure, by considering the beat count of the first full measure
if len(downbeats) >= 2:
# find the number of beats between the first two downbeats
first_downbeat, second_downbeat = np.searchsorted(beats, downbeats[:2])
beats_in_first_measure = second_downbeat - first_downbeat
# find the number of beats before the first downbeat
pickup_beats = first_downbeat
# derive where to start counting
if pickup_beats < beats_in_first_measure:
start_counter = beats_in_first_measure - pickup_beats
else:
print(
"WARNING: There are more beats in the pickup measure than in the first measure. The beat count will start from 2 without trying to estimate the length of the pickup measure."
)
start_counter = 1
else:
print(
"WARNING: There are less than two downbeats in the predictions. Something may be wrong. The beat count will start from 2 without trying to estimate the length of the pickup measure."
)
start_counter = 1
# assemble the beat numbers
numbers = []
counter = start_counter
downbeats = chain(downbeats, [-1])
next_downbeat = next(downbeats)
for beat in beats:
if beat == next_downbeat:
counter = 1
next_downbeat = next(downbeats)
else:
counter += 1
numbers.append(counter)
return np.asarray(numbers)
def save_beat_tsv(beats: np.ndarray, downbeats: np.ndarray, outpath: str) -> None:
"""
Save beat information to a tab-separated file in the standard .beats format:
each line has a time in seconds, a tab, and a beat number (1 = downbeat).
The function requires that all downbeats are also listed as beats.
Args:
beats (numpy.ndarray): Array of beat positions in seconds (including downbeats).
downbeats (numpy.ndarray): Array of downbeat positions in seconds.
outpath (str): Path to the output TSV file.
Returns:
None
"""
# infer beat numbers
numbers = infer_beat_numbers(beats, downbeats)
# write the beat file
Path(outpath).parent.mkdir(parents=True, exist_ok=True)
try:
with open(outpath, "w") as f:
f.writelines(f"{beat}\t{number}\n" for beat, number in zip(beats, numbers))
except KeyboardInterrupt:
outpath.unlink() # avoid half-written files
def replace_state_dict_key(state_dict: dict, old: str, new: str):
"""Replaces `old` in all keys of `state_dict` with `new`."""
keys = list(state_dict.keys()) # take snapshot of the keys
for key in keys:
if old in key:
state_dict[key.replace(old, new)] = state_dict.pop(key)
return state_dict