Spaces:
Runtime error
Runtime error
import numpy as np | |
from . import utils | |
def scale(beatmap:np.ndarray, scale:float, log = True, integer = True) -> np.ndarray: | |
if isinstance(scale, str): scale = utils._safer_eval(scale) | |
assert scale>0, f"scale should be > 0, your scale is {scale}" | |
if scale == 1: return beatmap | |
else: | |
import math | |
if log is True: print(f'scale={scale}; ') | |
a = 0 | |
b = np.array([], dtype=int) | |
if scale%1==0: | |
while a < len(beatmap): | |
b = np.append(b, beatmap[int(a)]) | |
a += scale | |
else: | |
if integer is True: | |
while a + 1 < len(beatmap): | |
b = np.append(b, int((1 - (a % 1)) * beatmap[math.floor(a)] + (a % 1) * beatmap[math.ceil(a)])) | |
a += scale | |
else: | |
while a + 1 < len(beatmap): | |
b = np.append(b, (1 - (a % 1)) * beatmap[math.floor(a)] + (a % 1) * beatmap[math.ceil(a)]) | |
a += scale | |
return b | |
def shift(beatmap:np.ndarray, shift:float, log = True, mode = 1) -> np.ndarray: | |
if isinstance(shift, str): shift = utils._safer_eval(shift) | |
if shift == 0: return beatmap | |
# positive shift | |
elif shift > 0: | |
# full value of beats is removed from the beginning | |
if shift >= 1: beatmap = beatmap[int(shift//1):] | |
# shift beatmap by the decimal value | |
if shift%1 != 0: | |
shift = shift%1 | |
for i in range(len(beatmap) - int(shift) - 1): | |
beatmap[i] = int(beatmap[i] + shift * (beatmap[i + 1] - beatmap[i])) | |
# negative shift | |
else: | |
shift = -shift | |
# full values are inserted in between first beats | |
if shift >= 1: | |
if mode == 1: | |
step = int((beatmap[1] - beatmap[0]) / (int(shift//1) + 1)) | |
beatmap = np.insert(arr = beatmap, obj = 1, values = np.linspace(start = beatmap[0] + step - 1, stop = 1 + beatmap[1] - step, num = int(shift//1))) | |
elif mode == 2: | |
for i in range(int(shift//1)): | |
beatmap = np.insert(arr = beatmap, obj = (i*2)+1, values = int((beatmap[i*2] + beatmap[(i*2)+1])/2)) | |
# shift beatmap by the decimal value | |
if shift%1 != 0: | |
shift = shift%1 | |
for i in reversed(range(len(beatmap))): | |
if i==0: continue | |
beatmap[i] = int(beatmap[i] - shift * (beatmap[i] - beatmap[i-1])) | |
return beatmap | |
def generate(audio: np.ndarray, sr: int, lib='madmom.BeatDetectionProcessor', caching=True, filename: str = None, log = True, load_settings = True, split=None): | |
"""Creates beatmap attribute with a list of positions of beats in samples.""" | |
if log is True: print(f'Analyzing beats using {lib}; ', end='') | |
# load a beatmap if it is cached: | |
if caching is True and filename is not None: | |
audio_id=hex(len(audio[0])) | |
import os | |
if not os.path.exists('beat_manipulator/beatmaps'): | |
os.mkdir('beat_manipulator/beatmaps') | |
cacheDir="beat_manipulator/beatmaps/" + ''.join(filename.replace('\\', '/').split('/')[-1]) + "_"+lib+"_"+audio_id+'.txt' | |
try: | |
beatmap=np.loadtxt(cacheDir, dtype=int) | |
if log is True: print('loaded cached beatmap.') | |
except OSError: | |
if log is True:print("beatmap hasn't been generated yet. Generating...") | |
beatmap = None | |
#generate the beatmap | |
if beatmap is None: | |
if 'madmom' in lib.lower(): | |
from collections.abc import MutableMapping, MutableSequence | |
import madmom | |
assert len(audio[0])>sr*2, f'Audio file is too short, len={len(audio[0])} samples, or {len(audio[0])/sr} seconds. Minimum length is 2 seconds, audio below that breaks madmom processors.' | |
if lib=='madmom.BeatTrackingProcessor': | |
proc = madmom.features.beats.BeatTrackingProcessor(fps=100) | |
act = madmom.features.beats.RNNBeatProcessor()(madmom.audio.signal.Signal(audio.T, sr)) | |
beatmap= proc(act)*sr | |
elif lib=='madmom.BeatTrackingProcessor.constant': | |
proc = madmom.features.beats.BeatTrackingProcessor(fps=100, look_ahead=None) | |
act = madmom.features.beats.RNNBeatProcessor()(madmom.audio.signal.Signal(audio.T, sr)) | |
beatmap= proc(act)*sr | |
elif lib=='madmom.BeatTrackingProcessor.consistent': | |
proc = madmom.features.beats.BeatTrackingProcessor(fps=100, look_ahead=None, look_aside=0) | |
act = madmom.features.beats.RNNBeatProcessor()(madmom.audio.signal.Signal(audio.T, sr)) | |
beatmap= proc(act)*sr | |
elif lib=='madmom.BeatDetectionProcessor': | |
proc = madmom.features.beats.BeatDetectionProcessor(fps=100) | |
act = madmom.features.beats.RNNBeatProcessor()(madmom.audio.signal.Signal(audio.T, sr)) | |
beatmap= proc(act)*sr | |
elif lib=='madmom.BeatDetectionProcessor.consistent': | |
proc = madmom.features.beats.BeatDetectionProcessor(fps=100, look_aside=0) | |
act = madmom.features.beats.RNNBeatProcessor()(madmom.audio.signal.Signal(audio.T, sr)) | |
beatmap= proc(act)*sr | |
elif lib=='madmom.CRFBeatDetectionProcessor': | |
proc = madmom.features.beats.CRFBeatDetectionProcessor(fps=100) | |
act = madmom.features.beats.RNNBeatProcessor()(madmom.audio.signal.Signal(audio.T, sr)) | |
beatmap= proc(act)*sr | |
elif lib=='madmom.CRFBeatDetectionProcessor.constant': | |
proc = madmom.features.beats.CRFBeatDetectionProcessor(fps=100, use_factors=True, factors=[0.5, 1, 2]) | |
act = madmom.features.beats.RNNBeatProcessor()(madmom.audio.signal.Signal(audio.T, sr)) | |
beatmap= proc(act)*sr | |
elif lib=='madmom.DBNBeatTrackingProcessor': | |
proc = madmom.features.beats.DBNBeatTrackingProcessor(fps=100) | |
act = madmom.features.beats.RNNBeatProcessor()(madmom.audio.signal.Signal(audio.T, sr)) | |
beatmap= proc(act)*sr | |
elif lib=='madmom.DBNBeatTrackingProcessor.1000': | |
proc = madmom.features.beats.DBNBeatTrackingProcessor(fps=100, transition_lambda=1000) | |
act = madmom.features.beats.RNNBeatProcessor()(madmom.audio.signal.Signal(audio.T, sr)) | |
beatmap= proc(act)*sr | |
elif lib=='madmom.DBNDownBeatTrackingProcessor': | |
proc = madmom.features.downbeats.DBNDownBeatTrackingProcessor(beats_per_bar=[4], fps=100) | |
act = madmom.features.downbeats.RNNDownBeatProcessor()(madmom.audio.signal.Signal(audio.T, sr)) | |
beatmap= proc(act)*sr | |
beatmap=beatmap[:,0] | |
elif lib=='madmom.PatternTrackingProcessor': #broken | |
from madmom.models import PATTERNS_BALLROOM | |
proc = madmom.features.downbeats.PatternTrackingProcessor(PATTERNS_BALLROOM, fps=50) | |
from madmom.audio.spectrogram import LogarithmicSpectrogramProcessor, SpectrogramDifferenceProcessor, MultiBandSpectrogramProcessor | |
from madmom.processors import SequentialProcessor | |
log = LogarithmicSpectrogramProcessor() | |
diff = SpectrogramDifferenceProcessor(positive_diffs=True) | |
mb = MultiBandSpectrogramProcessor(crossover_frequencies=[270]) | |
pre_proc = SequentialProcessor([log, diff, mb]) | |
act = pre_proc(madmom.audio.signal.Signal(audio.T, sr)) | |
beatmap= proc(act)*sr | |
beatmap=beatmap[:,0] | |
elif lib=='madmom.DBNBarTrackingProcessor': #broken | |
beats = generate(audio=audio, sr=sr, filename=filename, lib='madmom.DBNBeatTrackingProcessor', caching = caching) | |
proc = madmom.features.downbeats.DBNBarTrackingProcessor(beats_per_bar=[4], fps=100) | |
act = madmom.features.downbeats.RNNBarProcessor()(((madmom.audio.signal.Signal(audio.T, sr)), beats)) | |
beatmap= proc(act)*sr | |
elif lib=='librosa': #broken in 3.9, works in 3.8 | |
import librosa | |
beat_frames = librosa.beat.beat_track(y=audio[0], sr=sr, hop_length=512) | |
beatmap = librosa.frames_to_samples(beat_frames[1]) | |
# save the beatmap and return | |
if caching is True: np.savetxt(cacheDir, beatmap.astype(int), fmt='%d') | |
if not isinstance(beatmap, np.ndarray): beatmap=np.asarray(beatmap, dtype=int) | |
else: beatmap=beatmap.astype(int) | |
if load_settings is True: | |
settingsDir="beat_manipulator/beatmaps/" + ''.join(filename.split('/')[-1]) + "_"+lib+"_"+audio_id+'_settings.txt' | |
if os.path.exists(settingsDir): | |
with open(settingsDir, 'r') as f: | |
settings = f.read().split(',') | |
if settings[0] != 'None': beatmap = scale(beatmap, settings[0], log = False) | |
if settings[1] != 'None': beatmap = shift(beatmap, settings[1], log = False) | |
if settings[2] != 'None': beatmap = np.sort(np.absolute(beatmap - int(settings[2]))) | |
return beatmap | |
def save_settings(audio: np.ndarray, filename: str = None, lib: str = 'madmom.BeatDetectionProcessor', scale: float = None, shift: float = None, adjust: int = None, normalized: str = None, log = True, overwrite = 'ask'): | |
if isinstance(overwrite, str): overwrite = overwrite.lower() | |
audio_id=hex(len(audio[0])) | |
cacheDir="beat_manipulator/beatmaps/" + ''.join(filename.split('/')[-1]) + "_"+lib+"_"+audio_id+'.txt' | |
import os | |
assert os.path.exists(cacheDir), f"Beatmap `{cacheDir}` doesn't exist" | |
settingsDir="beat_manipulator/beatmaps/" + ''.join(filename.split('/')[-1]) + "_"+lib+"_"+audio_id+'_settings.txt' | |
try: | |
a = utils._safer_eval_strict(scale) | |
if a == 1: scale = None | |
except Exception as e: assert scale is None, f'scale = `{scale}` - Not a valid scale, should be either a number, a math expression, or None: {e}' | |
try: | |
a = utils._safer_eval_strict(shift) | |
if a == 0: shift = None | |
except Exception as e: assert shift is None, f'shift = `{shift}` - Not a valid shift: {e}' | |
assert isinstance(adjust, int) or adjust is None, f'adjust = `{adjust}` should be int, but it is `{type(adjust)}`' | |
if adjust == 0: adjust = None | |
if os.path.exists(settingsDir): | |
if overwrite == 'ask' or overwrite =='a': | |
what = input(f'`{settingsDir}` already exists. Overwrite (y/n)?: ') | |
if not (what.lower() == 'y' or what.lower() == 'yes'): return | |
elif not (overwrite == 'true' or overwrite =='y' or overwrite =='yes' or overwrite is True): return | |
with open(settingsDir, 'w') as f: | |
f.write(f'{scale},{shift},{adjust},{normalized}') | |
if log is True: print(f"Saved scale = `{scale}`, shift = `{shift}`, adjust = `{adjust}` to `{settingsDir}`") | |