brainblow's picture
Duplicate from dpe1/beat_manipulator
df5dab1
raw
history blame
9.44 kB
from . import main
import numpy as np
# L L L L L L L L L
def generate(song, difficulties = [0.2, 0.1, 0.05, 0.025, 0.01, 0.0075, 0.005, 0.0025], lib='madmom.MultiModelSelectionProcessor', caching=True, log = True, output = '', add_peaks = True):
# for i in difficulties:
# if i<0.005: print(f'Difficulties < 0.005 may result in broken beatmaps, found difficulty = {i}')
if lib.lower == 'stunlocked': add_peaks = False
if not isinstance(song, main.song): song = main.song(song)
if log is True: print(f'Using {lib}; ', end='')
filename = song.path.replace('\\', '/').split('/')[-1]
if ' - ' in filename and len(filename.split(' - '))>1:
artist = filename.split(' - ')[0]
title = ' - '.join(filename.split(' - ')[1:])
else:
artist = ''
title = filename
if caching is True:
audio_id=hex(len(song.audio[0]))
import os
if not os.path.exists('beat_manipulator/beatmaps'):
os.mkdir('beat_manipulator/beatmaps')
cacheDir="beat_manipulator/beatmaps/" + filename + "_"+lib+"_"+audio_id+'.txt'
try:
beatmap=np.loadtxt(cacheDir)
if log is True: print('loaded cached beatmap.')
except OSError:
if log is True:print("beatmap hasn't been generated yet. Generating...")
beatmap = None
if beatmap is None:
if 'madmom' in lib.lower():
from collections.abc import MutableMapping, MutableSequence
import madmom
assert len(song.audio[0])>song.sr*2, f'Audio file is too short, len={len(song.audio[0])} samples, or {len(song.audio[0])/song.sr} seconds. Minimum length is 2 seconds, audio below that breaks madmom processors.'
if lib=='madmom.RNNBeatProcessor':
proc = madmom.features.beats.RNNBeatProcessor()
beatmap = proc(madmom.audio.signal.Signal(song.audio.T, song.sr))
elif lib=='madmom.MultiModelSelectionProcessor':
proc = madmom.features.beats.RNNBeatProcessor(post_processor=None)
predictions = proc(madmom.audio.signal.Signal(song.audio.T, song.sr))
mm_proc = madmom.features.beats.MultiModelSelectionProcessor(num_ref_predictions=None)
beatmap= mm_proc(predictions)*song.sr
beatmap/= np.max(beatmap)
elif lib=='stunlocked':
spikes = np.abs(np.gradient(np.clip(song.audio[0], -1, 1)))[:int(len(song.audio[0]) - (len(song.audio[0])%int(song.sr/100)))]
spikes = spikes.reshape(-1, (int(song.sr/100)))
spikes = np.asarray(list(np.max(i) for i in spikes))
if len(beatmap) > len(spikes): beatmap = beatmap[:len(spikes)]
elif len(spikes) > len(beatmap): spikes = spikes[:len(beatmap)]
zeroing = 0
for i in range(len(spikes)):
if zeroing > 0:
if spikes[i] <= 0.1: zeroing -=1
spikes[i] = 0
elif spikes[i] >= 0.1:
spikes[i] = 1
zeroing = 7
if spikes[i] <= 0.1: spikes[i] = 0
beatmap = spikes
if caching is True: np.savetxt(cacheDir, beatmap)
if add_peaks is True:
spikes = np.abs(np.gradient(np.clip(song.audio[0], -1, 1)))[:int(len(song.audio[0]) - (len(song.audio[0])%int(song.sr/100)))]
spikes = spikes.reshape(-1, (int(song.sr/100)))
spikes = np.asarray(list(np.max(i) for i in spikes))
if len(beatmap) > len(spikes): beatmap = beatmap[:len(spikes)]
elif len(spikes) > len(beatmap): spikes = spikes[:len(beatmap)]
zeroing = 0
for i in range(len(spikes)):
if zeroing > 0:
if spikes[i] <= 0.1: zeroing -=1
spikes[i] = 0
elif spikes[i] >= 0.1:
spikes[i] = 1
zeroing = 7
if spikes[i] <= 0.1: spikes[i] = 0
else: spikes = None
def _process(song: main.song, beatmap, spikes, threshold):
'''ඞ'''
if add_peaks is True: beatmap += spikes
hitmap=[]
actual_samplerate=int(song.sr/100)
beat_middle=int(actual_samplerate/2)
for i in range(len(beatmap)):
if beatmap[i]>threshold: hitmap.append(i*actual_samplerate + beat_middle)
hitmap=np.asarray(hitmap)
clump=[]
for i in range(len(hitmap)-1):
#print(i, abs(song.beatmap[i]-song.beatmap[i+1]), clump)
if abs(hitmap[i] - hitmap[i+1]) < song.sr/16 and i != len(hitmap)-2: clump.append(i)
elif clump!=[]:
clump.append(i)
actual_time=hitmap[clump[0]]
hitmap[np.array(clump)]=0
#print(song.beatmap)
hitmap[clump[0]]=actual_time
clump=[]
hitmap=hitmap[hitmap!=0]
return hitmap
osufile=lambda title,artist,version: ("osu file format v14\n"
"\n"
"[General]\n"
f"AudioFilename: {song.path.split('/')[-1]}\n"
"AudioLeadIn: 0\n"
"PreviewTime: -1\n"
"Countdown: 0\n"
"SampleSet: Normal\n"
"StackLeniency: 0.5\n"
"Mode: 0\n"
"LetterboxInBreaks: 0\n"
"WidescreenStoryboard: 0\n"
"\n"
"[Editor]\n"
"DistanceSpacing: 1.1\n"
"BeatDivisor: 4\n"
"GridSize: 8\n"
"TimelineZoom: 1.6\n"
"\n"
"[Metadata]\n"
f"Title:{title}\n"
f"TitleUnicode:{title}\n"
f"Artist:{artist}\n"
f"ArtistUnicode:{artist}\n"
f'Creator:{lib} + BeatManipulator\n'
f'Version:{version} {lib}\n'
'Source:\n'
'Tags:BeatManipulator\n'
'BeatmapID:0\n'
'BeatmapSetID:-1\n'
'\n'
'[Difficulty]\n'
'HPDrainRate:4\n'
'CircleSize:4\n'
'OverallDifficulty:5\n'
'ApproachRate:10\n'
'SliderMultiplier:3.3\n'
'SliderTickRate:1\n'
'\n'
'[Events]\n'
'//Background and Video events\n'
'//Break Periods\n'
'//Storyboard Layer 0 (Background)\n'
'//Storyboard Layer 1 (Fail)\n'
'//Storyboard Layer 2 (Pass)\n'
'//Storyboard Layer 3 (Foreground)\n'
'//Storyboard Layer 4 (Overlay)\n'
'//Storyboard Sound Samples\n'
'\n'
'[TimingPoints]\n'
'0,140.0,4,1,0,100,1,0\n'
'\n'
'\n'
'[HitObjects]\n')
# remove the clumps
#print(self.beatmap)
#print(self.beatmap)
#print(len(osumap))
#input('banana')
import shutil, os
if os.path.exists('beat_manipulator/temp'): shutil.rmtree('beat_manipulator/temp')
os.mkdir('beat_manipulator/temp')
hitmap=[]
import random
for difficulty in difficulties:
for i in range(4):
#print(i)
this_difficulty=_process(song, beatmap, spikes, difficulty)
hitmap.append(this_difficulty)
for k in range(len(hitmap)):
osumap=np.vstack((hitmap[k],np.zeros(len(hitmap[k])),np.zeros(len(hitmap[k])))).T
difficulty= difficulties[k]
for i in range(len(osumap)-1):
if i==0:continue
dist=(osumap[i,0]-osumap[i-1,0])*(1-(difficulty**0.3))
if dist<1000: dist=0.005
elif dist<2000: dist=0.01
elif dist<3000: dist=0.015
elif dist<4000: dist=0.02
elif dist<5000: dist=0.25
elif dist<6000: dist=0.35
elif dist<7000: dist=0.45
elif dist<8000: dist=0.55
elif dist<9000: dist=0.65
elif dist<10000: dist=0.75
elif dist<12500: dist=0.85
elif dist<15000: dist=0.95
elif dist<20000: dist=1
#elif dist<30000: dist=0.8
prev_x=osumap[i-1,1]
prev_y=osumap[i-1,2]
if prev_x>0: prev_x=prev_x-dist*0.1
elif prev_x<0: prev_x=prev_x+dist*0.1
if prev_y>0: prev_y=prev_y-dist*0.1
elif prev_y<0: prev_y=prev_y+dist*0.1
dirx=random.uniform(-dist,dist)
diry=dist-abs(dirx)*random.choice([-1, 1])
if abs(prev_x+dirx)>1: dirx=-dirx
if abs(prev_y+diry)>1: diry=-diry
x=prev_x+dirx
y=prev_y+diry
#print(dirx,diry,x,y)
#print(x>1, x<1, y>1, y<1)
if x>1: x=0.8
if x<-1: x=-0.8
if y>1: y=0.8
if y<-1: y=-0.8
#print(dirx,diry,x,y)
osumap[i,1]=x
osumap[i,2]=y
osumap[:,1]*=300
osumap[:,1]+=300
osumap[:,2]*=180
osumap[:,2]+=220
file=osufile(artist, title, difficulty)
for j in osumap:
#print('285,70,'+str(int(int(i)*1000/self.samplerate))+',1,0')
file+=f'{int(j[1])},{int(j[2])},{str(int(int(j[0])*1000/song.sr))},1,0\n'
with open(f'beat_manipulator/temp/{artist} - {title} (BeatManipulator {difficulty} {lib}].osu', 'x', encoding="utf-8") as f:
f.write(file)
from . import io
import shutil, os
shutil.copyfile(song.path, 'beat_manipulator/temp/'+filename)
shutil.make_archive('beat_manipulator_osz', 'zip', 'beat_manipulator/temp')
outputname = io._outputfilename(path = output, filename = song.path, suffix = ' ('+lib + ')', ext = 'osz')
if not os.path.exists(outputname):
os.rename('beat_manipulator_osz.zip', outputname)
if log is True: print(f'Created `{outputname}`')
else: print(f'{outputname} already exists!')
shutil.rmtree('beat_manipulator/temp')
return outputname