import gradio as gr, numpy as np from gradio.components import Audio, Textbox, Checkbox, Image import beat_manipulator as bm import cv2 def _safer_eval(string:str) -> float: if isinstance(string, str): string = eval(''.join([i for i in string if i.isdecimal() or i in '.+-*/'])) return string def BeatSwap(audiofile, pattern: str = 'test', scale:float = 1, shift:float = 0, caching:bool = True, variableBPM:bool = False): print() print(f'path = {audiofile}, pattern = "{pattern}", scale = {scale}, shift = {shift}, caching = {caching}, variable BPM = {variableBPM}', end=', ') if pattern == '' or pattern is None: pattern = 'test' try: scale=_safer_eval(scale) except: scale = 1 try: shift=_safer_eval(shift) except: shift = 0 if scale < 0.02: scale = 0.02 if audiofile is not None: try: song=bm.song(path=audiofile, filename=audiofile.split('.')[-2][:-8]+'.'+audiofile.split('.')[-1], caching=caching, log=False) except Exception as e: print(f'Failed to load audio, retrying: {e}') song=bm.song(path=audiofile, caching=caching, log=False) else: print(f'Audiofile is {audiofile}') return try: print(scale, shift, len(song.audio[0])/song.samplerate) if len(song.audio[0]) > (song.samplerate*1800): song.audio = np.asarray(song.audio) song.audio = song.audio[:,:song.samplerate*1800] except Exception as e: print(f'Reducing audio size failed, why? {e}') lib = 'madmom.BeatDetectionProcessor' if variableBPM is False else 'madmom.BeatTrackingProcessor' song.beatmap.generate(lib=lib, caching=caching) song.beatmap.shift(shift) song.beatmap.scale(scale) try: song.beat_image.generate() image = song.beat_image.combined y=min(len(image), len(image[0]), 2048) y=max(y, 2048) image = np.clip(cv2.resize(image, (y,y), interpolation=cv2.INTER_NEAREST).T/255, -1, 1) #print(image) except Exception as e: print(f'Image generation failed: {e}') image = np.asarray([[0.5,-0.5],[-0.5,0.5]]) song.quick_beatswap(output=None, pattern=pattern, scale=1, shift=0, lib=lib) song.audio = (np.clip(np.asarray(song.audio), -1, 1) * 32766).astype(np.int16).T #song.write_audio(output=bm.outputfilename('',song.filename, suffix=' (beatswap)')) print('___ SUCCESS ___') return ((song.samplerate, song.audio), image) audiofile=Audio(source='upload', type='filepath') patternbox = Textbox(label="Pattern, comma separated:", placeholder="1, 3, 2, 4!", value="1, 2!", lines=1) scalebox = Textbox(value=1, label="Beatmap scale, beatmap's beats per minute will be multiplied by this:", placeholder=1, lines=1) shiftbox = Textbox(value=0, label="Beatmap shift, in beats (applies before scaling):", placeholder=0, lines=1) cachebox = Checkbox(value=True, label="""Enable caching beatmaps. If enabled, a text file with the beatmap will be saved to the server (your PC if you are running locally), so that beatswapping for the second time doesn't have to generate the beatmap again. Text file will be named after your file, and will only contain a list of numbers with positions of each beat.""") beatdetectionbox = Checkbox(value=False, label='Enable support for variable BPM, however this makes beat detection slightly less accurate') gr.Interface (fn=BeatSwap,inputs=[audiofile,patternbox,scalebox,shiftbox, cachebox, beatdetectionbox],outputs=[Audio(type='numpy'), Image(type='numpy')],theme="default", title = "Stunlocked's Beat Manipulator" ,description = """Remix music using AI-powered beat detection and advanced beat swapping. Make \"every other beat is missing\" remixes, or completely change beat of the song. Github - https://github.com/stunlocked1/BeatManipulator. Colab version - https://colab.research.google.com/drive/1gEsZCCh2zMKqLmaGH5BPPLrImhEGVhv3?usp=sharing""" ,article="""#
# Basic usage Upload your audio, enter the beat swapping pattern, change scale and shift if needed, and run the app. It can be useful to test where each beat is by writing `test` into the `pattern` field, which will put cowbells on each beat. Beatmap can sometimes be shifted, for example 0.5 beats forward, so use scale and shift to adjust it. Feel free to use complex patterns and very low scales - most of the computation time is in detecting beats, not swapping them. # Pattern syntax Patterns are sequences of numbers or ranges, separated by `,`. Numbers and ranges can be followed by letters that apply effects to them. Spaces can be freely used for formatting as they will be ignored. Any other character that isnt used in the syntax can also be used for formatting but only between beats, not inside them. - `1, 3, 2, 4` - every 4 beats, swap 2nd and 3rd beat. This pattern loops every 4 beats, because 4 is the biggest number in it. - `!` after a number sets length of the pattern (beat isnt played). `1, 3, 4!` - every 4 beats, play first and third beats, i.e. skip every second beat (equivalent to `1, 2!`) - `1, 3, 4` - skip 2nd beat - `1, 2, 2, 4` - repeat 2nd beat - `1, 1:1.5, 4` - play a range of beats. `0:0.5` means first half of 1st beat. Keep that in mind, to play first half of 5th beat, you do `4:4.5`, not `5:5.5`. `1` is equivalent to `0:1`. `1.5` is equivalent to `0.5:1.5`. `1,2,3,4` is `0:4`. - `1, 0:1/3, 0:1/3, 2/3:1` - you can use expressions with `+`, `-`, `*`, `/`. - `?` after a beat makes that number not count for looping. `1, 2, 3, 4!, 8?` - every 4 beats, 4th beat is replaced with 8th beat. - `v` + number - controls volume of that beat. `1v2` means 200% volume, `1v1/3` means 33.33% volume, etc. - `r` after a beat reverses that beat. `1r, 2` - every two beats first beat will be reversed - another way to reverse - `4:0` is reversed `0:4`. - `s` + number - changes speed and pitch of that beat. 2 will be 2 times faster, 1/2 will be 2 times slower. Note: Only integers or 1/integer numbers are supported, everything else will be rounded. - `c` - swaps left and right channels of the beat. If followed by 0, mutes left channel instead, 1 - right channel. - `b` + number - bitcrush. The higher the number, the stronger the effect. Barely noticeable at values less then 1 - `d` + number - downsample (8-bit sound). The higher the number, the stronger the effect. Starts being noticeable at 3, good 8-bit sounding values are around 8+. - `t` + number - saturation - you can combine stuff like `0:1/3d8v2cr` - that line means 0:1/3 beat will be downsampled, 200% volume, swapped channels, and reversed there are certain commands you can write in pattern instead of the actual pattern: - `random` - each beat will be randomly selected from all beats, basically similar to shuffling all beats - `reverse` - reverses the order of all beats - `test` - test beat detection by putting cowbells on each beat. The highest pitched cowbell should be on the first beat; next cowbell should be on the snare. If it is not, use scale and shift. There are also some interesting patterns there: https://github.com/stunlocked1/BeatManipulator/blob/main/presets.json. Those are meant to be used with properly adjusted shift and scale, where 1st beat is 1st kick, 2nd beat is the snare after it, etc. Check my soundcloud https://soundcloud.com/stunlocked """ ).launch(share=False)