brainblow's picture
Duplicate from dpe1/beat_manipulator
df5dab1
raw
history blame contribute delete
No virus
10.8 kB
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}`")