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