3v324v23 commited on
Commit
867bb6e
1 Parent(s): fe380fd

added > and < syntax

Browse files
.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
- string = eval(''.join([i for i in string if i.isdecimal() or i in '.+-*/']))
 
 
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="""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.
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. Beatmap can sometimes be shifted, for example 0.5 beats forward, so use scale and shift to adjust it.
 
 
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 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.
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
- - `!` 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!`)
87
- - `1, 3, 4` - skip 2nd beat
88
- - `1, 2, 2, 4` - repeat 2nd beat
89
- - `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`.
90
- - `1, 0:1/3, 0:1/3, 2/3:1` - you can use expressions with `+`, `-`, `*`, `/`.
91
- - `?` 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.
 
 
 
 
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 instead, 1 - right 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
- if i.isdigit() or i=='.' or i=='-' or i=='/' or i=='+' or i=='%': s=str(s)+str(i)
 
 
 
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].isdigit() or i[x]=='.' or i[x]=='-' or i[x]=='/' or i[x]=='+' or 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.isdigit() or c=='.' or c=='-' or c=='/' or c=='+' or 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: st=self.beatmap[int(_safer_eval(s)//1)+j*size ] + _safer_eval(s)%1* (self.beatmap[int(_safer_eval(s)//1)+j*size +1] - self.beatmap[int(_safer_eval(s)//1)+j*size])
 
 
 
 
 
 
 
 
368
  except IndexError: break
369
  s=''
370
 
371
  # create a beat
372
- if s!='' and (n==len(i) or not(c.isdigit() or c=='.' or c=='-' or c=='/' or c=='+' or 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
- s=self.beatmap[int(_safer_eval(s)//1)+j*size ] + _safer_eval(s)%1* (self.beatmap[int(_safer_eval(s)//1)+j*size +1] - self.beatmap[int(_safer_eval(s)//1)+j*size])
379
- #print(s)
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- st=self.beatmap[int(_safer_eval(s)//1)+j*size-1 ] + _safer_eval(s)%1* (self.beatmap[int(_safer_eval(s)//1)+j*size +1] - self.beatmap[int(_safer_eval(s)//1)+j*size])
387
- s=self.beatmap[int(_safer_eval(s)//1)+j*size ] + _safer_eval(s)%1* (self.beatmap[int(_safer_eval(s)//1)+j*size +1] - self.beatmap[int(_safer_eval(s)//1)+j*size])
 
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: