Spaces:
Runtime error
Runtime error
added > and < syntax
Browse files- .vscode/settings.json +0 -3
- app.py +21 -14
- beat_manipulator/beatmap.py +2 -0
- beat_manipulator/main.py +74 -13
.vscode/settings.json
DELETED
@@ -1,3 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"python.analysis.typeCheckingMode": "basic"
|
3 |
-
}
|
|
|
|
|
|
|
|
app.py
CHANGED
@@ -4,7 +4,9 @@ import beat_manipulator as bm
|
|
4 |
import cv2
|
5 |
def _safer_eval(string:str) -> float:
|
6 |
if isinstance(string, str):
|
7 |
-
|
|
|
|
|
8 |
return string
|
9 |
|
10 |
def BeatSwap(audiofile, pattern: str = 'test', scale:float = 1, shift:float = 0, caching:bool = True, variableBPM:bool = False):
|
@@ -17,6 +19,7 @@ def BeatSwap(audiofile, pattern: str = 'test', scale:float = 1, shift:float = 0,
|
|
17 |
try:
|
18 |
shift=_safer_eval(shift)
|
19 |
except: shift = 0
|
|
|
20 |
if scale < 0.02: scale = 0.02
|
21 |
if audiofile is not None:
|
22 |
try:
|
@@ -57,9 +60,7 @@ audiofile=Audio(source='upload', type='filepath')
|
|
57 |
patternbox = Textbox(label="Pattern, comma separated:", placeholder="1, 3, 2, 4!", value="1, 2!", lines=1)
|
58 |
scalebox = Textbox(value=1, label="Beatmap scale, beatmap's beats per minute will be multiplied by this:", placeholder=1, lines=1)
|
59 |
shiftbox = Textbox(value=0, label="Beatmap shift, in beats (applies before scaling):", placeholder=0, lines=1)
|
60 |
-
cachebox = Checkbox(value=True, label="
|
61 |
-
|
62 |
-
Text file will be named after your file, and will only contain a list of numbers with positions of each beat.""")
|
63 |
beatdetectionbox = Checkbox(value=False, label='Enable support for variable BPM, however this makes beat detection slightly less accurate')
|
64 |
|
65 |
gr.Interface (fn=BeatSwap,inputs=[audiofile,patternbox,scalebox,shiftbox, cachebox, beatdetectionbox],outputs=[Audio(type='numpy'), Image(type='numpy')],theme="default",
|
@@ -75,25 +76,31 @@ Colab version - https://colab.research.google.com/drive/1gEsZCCh2zMKqLmaGH5BPPLr
|
|
75 |
|
76 |
Upload your audio, enter the beat swapping pattern, change scale and shift if needed, and run the app.
|
77 |
|
78 |
-
It can be useful to test where each beat is by writing `test` into the `pattern` field, which will put cowbells on each beat.
|
|
|
|
|
79 |
|
80 |
Feel free to use complex patterns and very low scales - most of the computation time is in detecting beats, not swapping them.
|
81 |
|
82 |
# Pattern syntax
|
83 |
|
84 |
-
Patterns are sequences of
|
85 |
- `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.
|
86 |
-
-
|
87 |
-
- `1,
|
88 |
-
- `1, 2, 2, 4
|
89 |
-
- `
|
90 |
-
- `
|
91 |
-
-
|
|
|
|
|
|
|
|
|
92 |
- `v` + number - controls volume of that beat. `1v2` means 200% volume, `1v1/3` means 33.33% volume, etc.
|
93 |
-
- `r` after a beat reverses that beat. `1r, 2` - every two beats first beat will be reversed
|
94 |
- another way to reverse - `4:0` is reversed `0:4`.
|
95 |
- `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.
|
96 |
-
- `c` - swaps left and right channels of the beat. If followed by 0, mutes left channel
|
97 |
- `b` + number - bitcrush. The higher the number, the stronger the effect. Barely noticeable at values less then 1
|
98 |
- `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+.
|
99 |
- `t` + number - saturation
|
|
|
4 |
import cv2
|
5 |
def _safer_eval(string:str) -> float:
|
6 |
if isinstance(string, str):
|
7 |
+
try:
|
8 |
+
string = eval(''.join([i for i in string if i.isdecimal() or i in '.+-*/']))
|
9 |
+
except: string=1
|
10 |
return string
|
11 |
|
12 |
def BeatSwap(audiofile, pattern: str = 'test', scale:float = 1, shift:float = 0, caching:bool = True, variableBPM:bool = False):
|
|
|
19 |
try:
|
20 |
shift=_safer_eval(shift)
|
21 |
except: shift = 0
|
22 |
+
if scale <0: scale = -scale
|
23 |
if scale < 0.02: scale = 0.02
|
24 |
if audiofile is not None:
|
25 |
try:
|
|
|
60 |
patternbox = Textbox(label="Pattern, comma separated:", placeholder="1, 3, 2, 4!", value="1, 2!", lines=1)
|
61 |
scalebox = Textbox(value=1, label="Beatmap scale, beatmap's beats per minute will be multiplied by this:", placeholder=1, lines=1)
|
62 |
shiftbox = Textbox(value=0, label="Beatmap shift, in beats (applies before scaling):", placeholder=0, lines=1)
|
63 |
+
cachebox = Checkbox(value=True, label="Enable caching generated beatmaps for faster loading. Saves a file with beat positions and loads it when you open same audio again.")
|
|
|
|
|
64 |
beatdetectionbox = Checkbox(value=False, label='Enable support for variable BPM, however this makes beat detection slightly less accurate')
|
65 |
|
66 |
gr.Interface (fn=BeatSwap,inputs=[audiofile,patternbox,scalebox,shiftbox, cachebox, beatdetectionbox],outputs=[Audio(type='numpy'), Image(type='numpy')],theme="default",
|
|
|
76 |
|
77 |
Upload your audio, enter the beat swapping pattern, change scale and shift if needed, and run the app.
|
78 |
|
79 |
+
It can be useful to test where each beat is by writing `test` into the `pattern` field, which will put cowbells on each beat. Highest cowbell should be the on first beat.
|
80 |
+
|
81 |
+
Use scale and shift to adjust the beatmap, for example if it is shifted 0.5 beats forward, set shift to -0.5. If it is two times faster than you want, set scale to 0.5
|
82 |
|
83 |
Feel free to use complex patterns and very low scales - most of the computation time is in detecting beats, not swapping them.
|
84 |
|
85 |
# Pattern syntax
|
86 |
|
87 |
+
Patterns are sequences of expressions, separated by `,` - for example, `1>3/8, 1>3/8, 1>0.25, 2, 3>0.75s2, 3>3/8, 3>0.25, 4d9`. 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.
|
88 |
- `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.
|
89 |
+
- `1, 3, 4` - every 4 beats, skip 2nd beat.
|
90 |
+
- `1, 2, 2, 4` - every 4 beats, repeat 2nd beat.
|
91 |
+
- `1, 2!` - skip every second beat. `!` after a number sets length of the pattern (beat isnt played). `1, 2, 3, 4!` - skip every 4th beat.
|
92 |
+
- `2>0.5` - play only first half of the second beat. `>` after a beat allows you to take first `i` of that beat.
|
93 |
+
- `2<0.5` - play only second half of the second beat. `<` after a beat takes last `i` of that beat.
|
94 |
+
- `1.5:4.5` - play a range of beats from 1.5 to 4.5. `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`.
|
95 |
+
|
96 |
+
**Tip: instead of slicing beats, sometimes it is easier to make scale smaller, like 0.5 or 0.25.**
|
97 |
+
- `1, 1>1/3, 1>1/3, 1<1/3` - you can use math expressions with `+`, `-`, `*`, `/` in place of numbers.
|
98 |
+
- `1, 2, 3, 4!, 8?` - every 4 beats, 4th beat is replaced with 8th beat. `?` after a beat makes that number not count for looping.
|
99 |
- `v` + number - controls volume of that beat. `1v2` means 200% volume, `1v1/3` means 33.33% volume, etc.
|
100 |
+
- `r` after a beat reverses that beat. `1r, 2` - every two beats, first beat will be reversed
|
101 |
- another way to reverse - `4:0` is reversed `0:4`.
|
102 |
- `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.
|
103 |
+
- `c` - if not followed by a number, swaps left and right channels of the beat. If followed by 0, mutes left channel, 1 - right channel.
|
104 |
- `b` + number - bitcrush. The higher the number, the stronger the effect. Barely noticeable at values less then 1
|
105 |
- `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+.
|
106 |
- `t` + number - saturation
|
beat_manipulator/beatmap.py
CHANGED
@@ -69,6 +69,8 @@ class beatmap:
|
|
69 |
elif lib=='madmom.BeatDetectionProcessor':
|
70 |
proc = madmom.features.beats.BeatDetectionProcessor(fps=100)
|
71 |
act = madmom.features.beats.RNNBeatProcessor()(madmom.audio.signal.Signal(self.audio.T, self.samplerate))
|
|
|
|
|
72 |
self.beatmap= proc(act)*self.samplerate
|
73 |
elif lib=='madmom.BeatDetectionProcessor.consistent':
|
74 |
proc = madmom.features.beats.BeatDetectionProcessor(fps=100, look_aside=0)
|
|
|
69 |
elif lib=='madmom.BeatDetectionProcessor':
|
70 |
proc = madmom.features.beats.BeatDetectionProcessor(fps=100)
|
71 |
act = madmom.features.beats.RNNBeatProcessor()(madmom.audio.signal.Signal(self.audio.T, self.samplerate))
|
72 |
+
#print(proc, act)
|
73 |
+
assert len(act)>200, f'Audio file is too short, len={len(act)}'
|
74 |
self.beatmap= proc(act)*self.samplerate
|
75 |
elif lib=='madmom.BeatDetectionProcessor.consistent':
|
76 |
proc = madmom.features.beats.BeatDetectionProcessor(fps=100, look_aside=0)
|
beat_manipulator/main.py
CHANGED
@@ -253,6 +253,7 @@ class song:
|
|
253 |
n+=1
|
254 |
if type(self.audio) is tuple or list: self.audio = numpy.asarray(self.audio)
|
255 |
self.audio = numpy.asarray([self.audio[0,n:], self.audio[1,n:]])
|
|
|
256 |
if self.bm is not None:
|
257 |
self.beatmap.beatmap=numpy.absolute(self.beatmap.beatmap-n)
|
258 |
if self.hm is not None:
|
@@ -273,20 +274,56 @@ class song:
|
|
273 |
while ' ' in pattern: pattern = pattern.relace(' ', ' ')
|
274 |
pattern=pattern.split(sep)
|
275 |
self._printlog(f"beatswapping with {' '.join(pattern)}; ")
|
|
|
276 |
for j in pattern:
|
277 |
s=''
|
278 |
if '?' not in j:
|
279 |
for i in j:
|
280 |
-
|
|
|
|
|
|
|
281 |
elif i==':':
|
282 |
if s=='': s='0'
|
283 |
-
#print(s, _safer_eval(s))
|
284 |
size=max(math.ceil(float(_safer_eval(s))), size)
|
285 |
s=''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
286 |
elif s!='': break
|
|
|
287 |
if s=='': s='0'
|
288 |
if s=='': s='0'
|
289 |
size=max(math.ceil(float(_safer_eval(s))), size)
|
|
|
|
|
|
|
|
|
|
|
|
|
290 |
|
291 |
self._audio_tolist()
|
292 |
self.beatmap._toarray()
|
@@ -339,7 +376,7 @@ class song:
|
|
339 |
x=i.index(c)+1
|
340 |
z=''
|
341 |
try:
|
342 |
-
while i[x].
|
343 |
z+=i[x]
|
344 |
x+=1
|
345 |
return z
|
@@ -352,44 +389,68 @@ class song:
|
|
352 |
for j in range(iterations+1):
|
353 |
for i in pattern:
|
354 |
if '!' not in i:
|
355 |
-
n,s,st,reverse,z=0,'',None,False,None
|
356 |
for c in i:
|
357 |
n+=1
|
358 |
#print('c =', s, ', st =', st, ', s =', s, ', n =,',n)
|
359 |
|
360 |
# Get the character
|
361 |
-
if c.
|
362 |
s=str(s)+str(c)
|
363 |
|
364 |
# If character is : - get start
|
365 |
-
elif s!='' and c==':':
|
366 |
#print ('Beat start:',s,'=', _safer_eval(s),'=',int(_safer_eval(s)//1), '+',j,'*',size,' =',int(_safer_eval(s)//1)+j*size, ', mod=',_safer_eval(s)%1)
|
367 |
-
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
368 |
except IndexError: break
|
369 |
s=''
|
370 |
|
371 |
# create a beat
|
372 |
-
if s!='' and (n==len(i) or not(c.
|
373 |
|
374 |
-
# start already exists
|
375 |
if st is not None:
|
376 |
#print ('Beat end: ',s,'=', _safer_eval(s),'=',int(_safer_eval(s)//1), '+',j,'*',size,' =',int(_safer_eval(s)//1)+j*size, ', mod=',_safer_eval(s)%1)
|
377 |
try:
|
378 |
-
|
379 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
380 |
except IndexError: break
|
381 |
else:
|
382 |
# start doesn't exist
|
383 |
#print ('Beat start:',s,'=', _safer_eval(s),'=',int(_safer_eval(s)//1), '+',j,'*',size,'- 1 =',int(_safer_eval(s)//1)+j*size, ', mod=',_safer_eval(s)%1)
|
384 |
#print ('Beat end: ',s,'=', _safer_eval(s),'=',int(_safer_eval(s)//1), '+',j,'*',size,' =',int(_safer_eval(s)//1)+j*size+1, ', mod=',_safer_eval(s)%1)
|
385 |
try:
|
386 |
-
|
387 |
-
|
|
|
388 |
except IndexError: break
|
389 |
|
390 |
if st>s:
|
391 |
s, st=st, s
|
392 |
reverse=True
|
|
|
|
|
393 |
|
394 |
# create the beat
|
395 |
if len(self.audio)>1:
|
|
|
253 |
n+=1
|
254 |
if type(self.audio) is tuple or list: self.audio = numpy.asarray(self.audio)
|
255 |
self.audio = numpy.asarray([self.audio[0,n:], self.audio[1,n:]])
|
256 |
+
self.beatmap._toarray()
|
257 |
if self.bm is not None:
|
258 |
self.beatmap.beatmap=numpy.absolute(self.beatmap.beatmap-n)
|
259 |
if self.hm is not None:
|
|
|
274 |
while ' ' in pattern: pattern = pattern.relace(' ', ' ')
|
275 |
pattern=pattern.split(sep)
|
276 |
self._printlog(f"beatswapping with {' '.join(pattern)}; ")
|
277 |
+
prev,prevb = None,None
|
278 |
for j in pattern:
|
279 |
s=''
|
280 |
if '?' not in j:
|
281 |
for i in j:
|
282 |
+
#get the math expression
|
283 |
+
#print(f'j = {j}, s = {s}, i = {i}, size = {size}, prev = {prev}, prevb = {prevb}')
|
284 |
+
if i.isdecimal() or i=='.' or i=='-' or i=='/' or i=='+' or i=='%': s=str(s)+str(i)
|
285 |
+
#if got :, write it to size
|
286 |
elif i==':':
|
287 |
if s=='': s='0'
|
|
|
288 |
size=max(math.ceil(float(_safer_eval(s))), size)
|
289 |
s=''
|
290 |
+
#if got ;, save the number and then add it
|
291 |
+
elif i=='>':
|
292 |
+
if s=='': s='0'
|
293 |
+
size=max(math.ceil(float(_safer_eval(s))), size)
|
294 |
+
prev = _safer_eval(s)-1
|
295 |
+
s=''
|
296 |
+
elif i=='<':
|
297 |
+
if s=='': s='0'
|
298 |
+
size=max(math.ceil(float(_safer_eval(s))), size)
|
299 |
+
prevb = _safer_eval(s)
|
300 |
+
s=''
|
301 |
+
# if prev is defined, add it to s (a>b to a+b)
|
302 |
+
elif prev is not None:
|
303 |
+
if s=='': s='0'
|
304 |
+
#print(1, _safer_eval(s), prev, float(_safer_eval(s))+float(prev))
|
305 |
+
size=max(math.ceil(float(_safer_eval(s))+float(prev)), size)
|
306 |
+
prev=None
|
307 |
+
break
|
308 |
+
#prevb : a<b to a-b
|
309 |
+
elif prevb is not None:
|
310 |
+
if s=='': s='0'
|
311 |
+
#print(2, _safer_eval(s), prevb, float(_safer_eval(s))+float(prevb))
|
312 |
+
size=max(math.ceil(float(_safer_eval(s))-float(prevb)), size)
|
313 |
+
prevb=None
|
314 |
+
break
|
315 |
+
# i isn't digit or any of the symbols, so stop parsing
|
316 |
elif s!='': break
|
317 |
+
#print(f'end: j = {j}, s = {s}, i = {i}, size = {size}, prev = {prev}, prevb = {prevb}')
|
318 |
if s=='': s='0'
|
319 |
if s=='': s='0'
|
320 |
size=max(math.ceil(float(_safer_eval(s))), size)
|
321 |
+
if prev is not None:
|
322 |
+
size=max(math.ceil(float(_safer_eval(s))+float(prev)), size)
|
323 |
+
prev=None
|
324 |
+
if prevb is not None:
|
325 |
+
size=max(math.ceil(float(_safer_eval(s))-float(prevb)), size)
|
326 |
+
prev=None
|
327 |
|
328 |
self._audio_tolist()
|
329 |
self.beatmap._toarray()
|
|
|
376 |
x=i.index(c)+1
|
377 |
z=''
|
378 |
try:
|
379 |
+
while i[x].isdecimal() or i[x]=='.' or i[x]=='-' or i[x]=='/' or i[x]=='+' or i[x]=='%':
|
380 |
z+=i[x]
|
381 |
x+=1
|
382 |
return z
|
|
|
389 |
for j in range(iterations+1):
|
390 |
for i in pattern:
|
391 |
if '!' not in i:
|
392 |
+
n,s,st,reverse,z, is_c, is_cr=0,'',None,False,None,False,False
|
393 |
for c in i:
|
394 |
n+=1
|
395 |
#print('c =', s, ', st =', st, ', s =', s, ', n =,',n)
|
396 |
|
397 |
# Get the character
|
398 |
+
if c.isdecimal() or c=='.' or c=='-' or c=='/' or c=='+' or c=='%':
|
399 |
s=str(s)+str(c)
|
400 |
|
401 |
# If character is : - get start
|
402 |
+
elif s!='' and (c==':' or c=='>' or c=='<'):
|
403 |
#print ('Beat start:',s,'=', _safer_eval(s),'=',int(_safer_eval(s)//1), '+',j,'*',size,' =',int(_safer_eval(s)//1)+j*size, ', mod=',_safer_eval(s)%1)
|
404 |
+
try:
|
405 |
+
sti = _safer_eval(s)
|
406 |
+
if c=='>': sti-=1
|
407 |
+
st=self.beatmap[int(sti//1)+j*size ] + sti%1* (self.beatmap[int(sti//1)+j*size +1] - self.beatmap[int(sti//1)+j*size])
|
408 |
+
if c == '>': is_c = True
|
409 |
+
elif c=='<': is_cr = True
|
410 |
+
else:
|
411 |
+
is_c = False
|
412 |
+
is_cr = False
|
413 |
except IndexError: break
|
414 |
s=''
|
415 |
|
416 |
# create a beat
|
417 |
+
if s!='' and (n==len(i) or not(c.isdecimal() or c=='.' or c=='-' or c=='/' or c=='+' or c=='%')):
|
418 |
|
419 |
+
# start already exists, e.g. : or >
|
420 |
if st is not None:
|
421 |
#print ('Beat end: ',s,'=', _safer_eval(s),'=',int(_safer_eval(s)//1), '+',j,'*',size,' =',int(_safer_eval(s)//1)+j*size, ', mod=',_safer_eval(s)%1)
|
422 |
try:
|
423 |
+
#print(1, is_c, s, st)
|
424 |
+
if is_c is False and is_cr is False:
|
425 |
+
si = _safer_eval(s)
|
426 |
+
s=self.beatmap[int(si//1)+j*size ] + si%1* (self.beatmap[int(si//1)+j*size +1] - self.beatmap[int(si//1)+j*size])
|
427 |
+
elif is_c is True:
|
428 |
+
si=sti+_safer_eval(s)
|
429 |
+
s=(self.beatmap[int(si//1)+j*size ] + si%1* (self.beatmap[int(si//1)+j*size +1] - self.beatmap[int(si//1)+j*size]))
|
430 |
+
is_c = False
|
431 |
+
elif is_cr is True:
|
432 |
+
si=sti-_safer_eval(s)
|
433 |
+
s=(self.beatmap[int(si//1)+j*size ] + si%1* (self.beatmap[int(si//1)+j*size +1] - self.beatmap[int(si//1)+j*size]))
|
434 |
+
#print(si, sti, st, s)
|
435 |
+
st, s = s, st
|
436 |
+
is_cr = False
|
437 |
+
#print(2, is_c, s, st)
|
438 |
except IndexError: break
|
439 |
else:
|
440 |
# start doesn't exist
|
441 |
#print ('Beat start:',s,'=', _safer_eval(s),'=',int(_safer_eval(s)//1), '+',j,'*',size,'- 1 =',int(_safer_eval(s)//1)+j*size, ', mod=',_safer_eval(s)%1)
|
442 |
#print ('Beat end: ',s,'=', _safer_eval(s),'=',int(_safer_eval(s)//1), '+',j,'*',size,' =',int(_safer_eval(s)//1)+j*size+1, ', mod=',_safer_eval(s)%1)
|
443 |
try:
|
444 |
+
si = _safer_eval(s)
|
445 |
+
st=self.beatmap[int(si//1)+j*size-1 ] + si%1* (self.beatmap[int(si//1)+j*size +1] - self.beatmap[int(si//1)+j*size])
|
446 |
+
s=self.beatmap[int(si//1)+j*size ] + si%1* (self.beatmap[int(si//1)+j*size +1] - self.beatmap[int(si//1)+j*size])
|
447 |
except IndexError: break
|
448 |
|
449 |
if st>s:
|
450 |
s, st=st, s
|
451 |
reverse=True
|
452 |
+
if st<0: st=0
|
453 |
+
if s<0 or st==s: continue
|
454 |
|
455 |
# create the beat
|
456 |
if len(self.audio)>1:
|