3v324v23 commited on
Commit
fe806b7
1 Parent(s): f8dacfb

it would seem that a new update has been uploaded, and that would, in fact, be true, should the truth of that be evaluated. Best regards, Remington (big ounce passed away)

Browse files
Files changed (4) hide show
  1. BeatManipulator.py +209 -26
  2. app.py +3 -3
  3. requirements.txt +1 -1
  4. wrapper.py +116 -0
BeatManipulator.py CHANGED
@@ -1,4 +1,6 @@
1
  import numpy
 
 
2
  def open_audio(filename=None, lib='auto'):
3
  if filename is None:
4
  from tkinter.filedialog import askopenfilename
@@ -38,12 +40,15 @@ def open_audio(filename=None, lib='auto'):
38
  return audio,samplerate
39
 
40
 
41
- def generate_sidechain(samplerate=44100, len=0.5, curve=2, vol0=0, vol1=1, smoothing=40):
42
- x=numpy.concaterate((numpy.linspace(1,0,smoothing),numpy.linspace(vol0,vol1,int(len*samplerate))**curve))
43
  return(x,x)
44
 
45
  def outputfilename(output, filename, suffix='_beatswap'):
46
- return output+''.join(''.join(filename.split('/')[-1]).split('.')[:-1])+suffix+'.mp3'
 
 
 
47
 
48
  def generate_sine(len, freq, samplerate, volume=1):
49
  return numpy.sin(numpy.linspace(0, freq*3.1415926*2*len, int(len*samplerate)))*volume
@@ -55,7 +60,8 @@ def generate_square(len, freq, samplerate, volume=1):
55
  return ((numpy.linspace(0, freq*2*len, int(len*samplerate)))//1%2 * 2 - 1)*volume
56
 
57
  class song:
58
- def __init__(self, filename=None, audio=None, samplerate=None, beatmap=None):
 
59
  if filename is None:
60
  from tkinter.filedialog import askopenfilename
61
  self.filename = askopenfilename(title='select song', filetypes=[("mp3", ".mp3"),("wav", ".wav"),("flac", ".flac"),("ogg", ".ogg"),("wma", ".wma")])
@@ -64,11 +70,13 @@ class song:
64
  self.filename=filename
65
  if audio is None or samplerate is None:
66
  self.audio, self.samplerate=open_audio(self.filename)
67
- self.beatmap=beatmap
 
68
  self.filename=self.filename.replace('\\', '/')
69
  self.samplerate=int(self.samplerate)
70
 
71
- def write_audio(self, output:str, lib='auto'):
 
72
  if lib=='pedalboard.io':
73
  if not isinstance(self.audio,numpy.ndarray): self.audio=numpy.asarray(self.audio)
74
  #print(audio)
@@ -82,7 +90,7 @@ class song:
82
  soundfile.write(output, audio, self.samplerate)
83
  del audio
84
  elif lib=='auto':
85
- for i in ('soundfile', 'pedalboard.io'):
86
  try:
87
  song.write_audio(self, output, i)
88
  break
@@ -106,23 +114,20 @@ class song:
106
  while a <len( self.beatmap[:-math.ceil(scale)]):
107
  b=numpy.append(b, (1-(a%1))*self.beatmap[math.floor(a)]+(a%1)*self.beatmap[math.ceil(a)])
108
  a+=scale
109
- self.beatmap=b
110
 
111
  def analyze_beats(self, lib='madmom.BeatDetectionProcessor', caching=True, split=None):
112
  #if audio is None and filename is None: (audio, samplerate) = open_audio()
113
  if caching is True:
114
- import hashlib
115
- with open(self.filename, "rb") as f:
116
- file_hash = hashlib.blake2b()
117
- while chunk := f.read(8192):
118
- file_hash.update(chunk)
119
  import os
120
  if not os.path.exists('SavedBeatmaps'):
121
  os.mkdir('SavedBeatmaps')
122
- cacheDir="SavedBeatmaps/" + ''.join(self.filename.split('/')[-1]) + lib+"_"+file_hash.hexdigest()[:5]+'.txt'
123
  try:
124
  self.beatmap=numpy.loadtxt(cacheDir, dtype=int)
125
- return numpy.loadtxt(cacheDir, dtype=int)
 
126
  except OSError: pass
127
 
128
  if lib.split('.')[0]=='madmom':
@@ -211,7 +216,9 @@ class song:
211
  if lib.split('.')[0]=='madmom':
212
  self.beatmap=numpy.absolute(self.beatmap-500)
213
 
214
- if caching is True: numpy.savetxt(cacheDir, self.beatmap.astype(int))
 
 
215
 
216
  def audio_autotrim(self):
217
  n=0
@@ -238,8 +245,10 @@ class song:
238
 
239
  def beatmap_autoinsert(self):
240
  diff=(self.beatmap[1]-self.beatmap[0])
241
- while diff<self.beatmap[0]:
 
242
  self.beatmap=numpy.insert(self.beatmap, 0, self.beatmap[0]-diff)
 
243
 
244
  def beatmap_shift(self, shift: float):
245
  if shift>0:
@@ -268,12 +277,13 @@ class song:
268
  if i.isdigit() or i=='.' or i=='-' or i=='/' or i=='+' or i=='%': s=str(s)+str(i)
269
  elif i==':':
270
  if s=='': s='0'
 
271
  size=max(math.ceil(float(eval(s))), size)
272
  s=''
273
  elif s!='': break
274
  if s=='': s='0'
275
  if s=='': s='0'
276
- size=max(size, eval(s))
277
 
278
  if isinstance(self.audio,numpy.ndarray): self.audio=numpy.ndarray.tolist(self.audio)
279
  if self.beatmap.dtype!='int32': self.beatmap=self.beatmap.astype(int)
@@ -294,11 +304,39 @@ class song:
294
 
295
  # size, iterations are integers
296
  size=int(max(size//1, 1))
297
- iterations=int(len(self.beatmap)//size)
298
 
299
  # add beat to the end
300
- self.beatmap=numpy.append(self.beatmap, len(self.audio[0]))
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  def beatswap_getnum(i: str, c: str):
303
  if c in i:
304
  try:
@@ -313,9 +351,9 @@ class song:
313
  return z
314
  except ValueError: return None
315
 
316
- #print(size, iterations)
317
  # processing
318
- for j in range(iterations):
319
  for i in pattern:
320
  if '!' not in i:
321
  n,s,st,reverse,z=0,'',None,False,None
@@ -451,8 +489,30 @@ class song:
451
  try: self.audio[:,int(self.beatmap[i])-smoothing + int(float(shift) * (int(self.beatmap[i+1])-int(self.beatmap[i]))) : int(self.beatmap[i])-smoothing+int(float(shift) * (int(self.beatmap[i+1])-int(self.beatmap[i])))+int(l)]*=audio2
452
  except (IndexError, ValueError): break
453
 
454
- def quick_beatswap(self, output='', pattern=None, scale=1, shift=0, start=0, end=None, autotrim=True, autoscale=False, autoinsert=False, suffix='_BeatSwap', lib='madmom.BeatDetectionProcessor'):
 
 
 
455
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  if self.beatmap is None: song.analyze_beats(self,lib=lib)
457
  if autotrim is True: song.audio_autotrim(self)
458
  save=self.beatmap
@@ -472,8 +532,32 @@ class song:
472
  self.beatmap=save
473
 
474
 
475
- def quick_sidechain(self, output='', audio2=None, scale=1, shift=0, start=0, end=None, autotrim=True, autoscale=False, autoinsert=False, filename2=None, suffix='_Sidechain', lib='madmom.BeatDetectionProcessor'):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
 
 
477
  if filename2 is None and audio2 is None:
478
  audio2=generate_sidechain()
479
 
@@ -498,7 +582,30 @@ class song:
498
 
499
  self.beatmap=save
500
 
501
- def quick_beatsample(self, output='', filename2=None, scale=1, shift=0, start=0, end=None, autotrim=True, autoscale=False, autoinsert=False, audio2=None, suffix='_BeatSample', lib='madmom.BeatDetectionProcessor'):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  if filename2 is None and audio2 is None:
503
  from tkinter.filedialog import askopenfilename
504
  filename2 = askopenfilename(title='select sidechain impulse', filetypes=[("mp3", ".mp3"),("wav", ".wav"),("flac", ".flac"),("ogg", ".ogg"),("wma", ".wma")])
@@ -522,4 +629,80 @@ class song:
522
  output=output+''.join(''.join(self.filename.split('/')[-1]).split('.')[:-1])+suffix+'.mp3'
523
  song.write_audio(self,output)
524
  self.beatmap=save
525
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import numpy
2
+ numpy.set_printoptions(suppress=True)
3
+
4
  def open_audio(filename=None, lib='auto'):
5
  if filename is None:
6
  from tkinter.filedialog import askopenfilename
 
40
  return audio,samplerate
41
 
42
 
43
+ def generate_sidechain(samplerate=44100, length=0.5, curve=2, vol0=0, vol1=1, smoothing=40) ->numpy.array:
44
+ x=numpy.concatenate((numpy.linspace(1,0,smoothing),numpy.linspace(vol0,vol1,int(length*samplerate))**curve))
45
  return(x,x)
46
 
47
  def outputfilename(output, filename, suffix='_beatswap'):
48
+ if not (output.lower().endswith('.mp3') or output.lower().endswith('.wav') or output.lower().endswith('.flac') or output.lower().endswith('.ogg') or
49
+ output.lower().endswith('.aac') or output.lower().endswith('.ac3') or output.lower().endswith('.aiff') or output.lower().endswith('.wma')):
50
+ return output+''.join(''.join(filename.split('/')[-1]).split('.')[:-1])+suffix+'.mp3'
51
+
52
 
53
  def generate_sine(len, freq, samplerate, volume=1):
54
  return numpy.sin(numpy.linspace(0, freq*3.1415926*2*len, int(len*samplerate)))*volume
 
60
  return ((numpy.linspace(0, freq*2*len, int(len*samplerate)))//1%2 * 2 - 1)*volume
61
 
62
  class song:
63
+ def __init__(self, filename:str=None, audio:numpy.array=None, samplerate:int=None, beatmap:list=None):
64
+ """song can be loaded from path to an audio file, or from a list/numpy array and samplerate. Audio array should have values from -1 to 1, multiple channels should be stacked vertically. Optionally you can provide your own beat map."""
65
  if filename is None:
66
  from tkinter.filedialog import askopenfilename
67
  self.filename = askopenfilename(title='select song', filetypes=[("mp3", ".mp3"),("wav", ".wav"),("flac", ".flac"),("ogg", ".ogg"),("wma", ".wma")])
 
70
  self.filename=filename
71
  if audio is None or samplerate is None:
72
  self.audio, self.samplerate=open_audio(self.filename)
73
+ else: self.audio, self.samplerate = audio, samplerate
74
+ self.beatmap=beatmap
75
  self.filename=self.filename.replace('\\', '/')
76
  self.samplerate=int(self.samplerate)
77
 
78
+ def write_audio(self, output:str, lib:str='auto'):
79
+ """"writes audio"""
80
  if lib=='pedalboard.io':
81
  if not isinstance(self.audio,numpy.ndarray): self.audio=numpy.asarray(self.audio)
82
  #print(audio)
 
90
  soundfile.write(output, audio, self.samplerate)
91
  del audio
92
  elif lib=='auto':
93
+ for i in ('pedalboard.io', 'soundfile'):
94
  try:
95
  song.write_audio(self, output, i)
96
  break
 
114
  while a <len( self.beatmap[:-math.ceil(scale)]):
115
  b=numpy.append(b, (1-(a%1))*self.beatmap[math.floor(a)]+(a%1)*self.beatmap[math.ceil(a)])
116
  a+=scale
117
+ self.beatmap=b
118
 
119
  def analyze_beats(self, lib='madmom.BeatDetectionProcessor', caching=True, split=None):
120
  #if audio is None and filename is None: (audio, samplerate) = open_audio()
121
  if caching is True:
122
+ id=hex(len(self.audio[0]))
 
 
 
 
123
  import os
124
  if not os.path.exists('SavedBeatmaps'):
125
  os.mkdir('SavedBeatmaps')
126
+ cacheDir="SavedBeatmaps/" + ''.join(self.filename.split('/')[-1]) + "_"+lib+"_"+id+'.txt'
127
  try:
128
  self.beatmap=numpy.loadtxt(cacheDir, dtype=int)
129
+ self.bpm=numpy.average(self.beatmap)/self.samplerate
130
+ return
131
  except OSError: pass
132
 
133
  if lib.split('.')[0]=='madmom':
 
216
  if lib.split('.')[0]=='madmom':
217
  self.beatmap=numpy.absolute(self.beatmap-500)
218
 
219
+ if caching is True: numpy.savetxt(cacheDir, self.beatmap.astype(int), fmt='%d')
220
+ self.bpm=numpy.average(self.beatmap)/self.samplerate
221
+ self.beatmap=self.beatmap.astype(int)
222
 
223
  def audio_autotrim(self):
224
  n=0
 
245
 
246
  def beatmap_autoinsert(self):
247
  diff=(self.beatmap[1]-self.beatmap[0])
248
+ a=0
249
+ while diff<self.beatmap[0] and a<100:
250
  self.beatmap=numpy.insert(self.beatmap, 0, self.beatmap[0]-diff)
251
+ a+=1
252
 
253
  def beatmap_shift(self, shift: float):
254
  if shift>0:
 
277
  if i.isdigit() or i=='.' or i=='-' or i=='/' or i=='+' or i=='%': s=str(s)+str(i)
278
  elif i==':':
279
  if s=='': s='0'
280
+ #print(s, eval(s))
281
  size=max(math.ceil(float(eval(s))), size)
282
  s=''
283
  elif s!='': break
284
  if s=='': s='0'
285
  if s=='': s='0'
286
+ size=max(math.ceil(float(eval(s))), size)
287
 
288
  if isinstance(self.audio,numpy.ndarray): self.audio=numpy.ndarray.tolist(self.audio)
289
  if self.beatmap.dtype!='int32': self.beatmap=self.beatmap.astype(int)
 
304
 
305
  # size, iterations are integers
306
  size=int(max(size//1, 1))
307
+
308
 
309
  # add beat to the end
310
+ self.beatmap=numpy.unique(numpy.abs(numpy.append(self.beatmap, len(self.audio[0]))))
311
+
312
+ iterations=int(len(self.beatmap)//size)
313
+
314
+ if 'random' in pattern[0].lower():
315
+ import random
316
+ for i in range(len(self.beatmap)):
317
+ choice=random.randint(1,len(self.beatmap)-1)
318
+ for a in range(len(self.audio)):
319
+ beat=self.audio[a][self.beatmap[choice-1]:self.beatmap[choice]-smoothing]
320
+ if smoothing>0: result[a].extend(numpy.linspace(result[a][-1],beat[0],smoothing))
321
+ result[a].extend(beat)
322
+ self.audio = result
323
+ return
324
 
325
+ if 'reverse' in pattern[0].lower():
326
+ for a in range(len(self.audio)):
327
+ for i in list(reversed(range(len(self.beatmap))))[:-1]:
328
+ beat=self.audio[a][self.beatmap[i-1]:self.beatmap[i]-smoothing]
329
+ #print(self.beatmap[i-1],self.beatmap[i])
330
+ #print(result[a][-1], beat[0])
331
+ if smoothing>0: result[a].extend(numpy.linspace(result[a][-1],beat[0],smoothing))
332
+ result[a].extend(beat)
333
+
334
+ self.audio = result
335
+ return
336
+
337
+ #print(len(result[0]))
338
+
339
+
340
  def beatswap_getnum(i: str, c: str):
341
  if c in i:
342
  try:
 
351
  return z
352
  except ValueError: return None
353
 
354
+ #print(len(self.beatmap), size, iterations)
355
  # processing
356
+ for j in range(iterations+1):
357
  for i in pattern:
358
  if '!' not in i:
359
  n,s,st,reverse,z=0,'',None,False,None
 
489
  try: self.audio[:,int(self.beatmap[i])-smoothing + int(float(shift) * (int(self.beatmap[i+1])-int(self.beatmap[i]))) : int(self.beatmap[i])-smoothing+int(float(shift) * (int(self.beatmap[i+1])-int(self.beatmap[i])))+int(l)]*=audio2
490
  except (IndexError, ValueError): break
491
 
492
+ def quick_beatswap(self, output:str='', pattern:str=None, scale:float=1, shift:float=0, start:float=0, end:float=None, autotrim:bool=True, autoscale:bool=False, autoinsert:bool=False, suffix:str='_BeatSwap', lib:str='madmom.BeatDetectionProcessor'):
493
+ """Generates beatmap if it isn't generated, applies beatswapping to the song and writes the processed song it next to the .py file. If you don't want to write the file, set output=None
494
+
495
+ output: can be a relative or an absolute path to a folder or to a file. Filename will be created from the original filename + a suffix to avoid overwriting. If path already contains a filename which ends with audio file extension, such as .mp3, that filename will be used.
496
 
497
+ pattern: the beatswapping pattern.
498
+
499
+ scale: scales the beatmap, for example if generated beatmap is two times faster than the song you can slow it down by putting 0.5.
500
+
501
+ shift: shifts the beatmap by this amount of unscaled beats
502
+
503
+ start: position in seconds, beats before the position will not be manipulated
504
+
505
+ end: position in seconds, same. Set to None by default.
506
+
507
+ autotrim: trims silence in the beginning for better beat detection, True by default
508
+
509
+ autoscale: scales beats so that they are between 10000 and 20000 samples long. Useful when you are processing a lot of files with similar BPMs, False by default.
510
+
511
+ autoinsert: uses distance between beats and inserts beats at the beginning at that distance if possible. Set to False by default, sometimes it can fix shifted beatmaps and sometimes can add unwanted shift.
512
+
513
+ suffix: suffix that will be appended to the filename
514
+
515
+ lib: beat detection library"""
516
  if self.beatmap is None: song.analyze_beats(self,lib=lib)
517
  if autotrim is True: song.audio_autotrim(self)
518
  save=self.beatmap
 
532
  self.beatmap=save
533
 
534
 
535
+ def quick_sidechain(self, output:str='', audio2:numpy.array=None, scale:float=1, shift:float=0, start:float=0, end:float=None, autotrim:bool=True, autoscale:bool=False, autoinsert:bool=False, filename2:str=None, suffix:str='_Sidechain', lib:str='madmom.BeatDetectionProcessor'):
536
+ """Generates beatmap if it isn't generated, applies fake sidechain on each beat to the song and writes the processed song it next to the .py file. If you don't want to write the file, set output=None
537
+
538
+ output: can be a relative or an absolute path to a folder or to a file. Filename will be created from the original filename + a suffix to avoid overwriting. If path already contains a filename which ends with audio file extension, such as .mp3, that filename will be used.
539
+
540
+ audio2: sidechain impulse, basically a curve that the volume will be multiplied by. By default one will be generated with generate_sidechain()
541
+
542
+ scale: scales the beatmap, for example if generated beatmap is two times faster than the song you can slow it down by putting 0.5.
543
+
544
+ shift: shifts the beatmap by this amount of unscaled beats
545
+
546
+ start: position in seconds, beats before the position will not be manipulated
547
+
548
+ end: position in seconds, same. Set to None by default.
549
+
550
+ autotrim: trims silence in the beginning for better beat detection, True by default
551
+
552
+ autoscale: scales beats so that they are between 10000 and 20000 samples long. Useful when you are processing a lot of files with similar BPMs, False by default.
553
+
554
+ autoinsert: uses distance between beats and inserts beats at the beginning at that distance if possible. Set to False by default, sometimes it can fix shifted beatmaps and sometimes can add unwanted shift.
555
+
556
+ filename2: loads sidechain impulse from the file if audio2 if not specified
557
+
558
+ suffix: suffix that will be appended to the filename
559
 
560
+ lib: beat detection library"""
561
  if filename2 is None and audio2 is None:
562
  audio2=generate_sidechain()
563
 
 
582
 
583
  self.beatmap=save
584
 
585
+ def quick_beatsample(self, output:str='', filename2:str=None, scale:float=1, shift:float=0, start:float=0, end:float=None, autotrim:bool=True, autoscale:bool=False, autoinsert:bool=False, audio2:numpy.array=None, suffix:str='_BeatSample', lib:str='madmom.BeatDetectionProcessor'):
586
+ """Generates beatmap if it isn't generated, adds chosen sample to each beat of the song and writes the processed song it next to the .py file. If you don't want to write the file, set output=None
587
+
588
+ output: can be a relative or an absolute path to a folder or to a file. Filename will be created from the original filename + a suffix to avoid overwriting. If path already contains a filename which ends with audio file extension, such as .mp3, that filename will be used.
589
+
590
+ filename2: path to the sample.
591
+
592
+ scale: scales the beatmap, for example if generated beatmap is two times faster than the song you can slow it down by putting 0.5.
593
+
594
+ shift: shifts the beatmap by this amount of unscaled beats
595
+
596
+ start: position in seconds, beats before the position will not be manipulated
597
+
598
+ end: position in seconds, same. Set to None by default.
599
+
600
+ autotrim: trims silence in the beginning for better beat detection, True by default
601
+
602
+ autoscale: scales beats so that they are between 10000 and 20000 samples long. Useful when you are processing a lot of files with similar BPMs, False by default.
603
+
604
+ autoinsert: uses distance between beats and inserts beats at the beginning at that distance if possible. Set to False by default, sometimes it can fix shifted beatmaps and sometimes can add unwanted shift.
605
+
606
+ suffix: suffix that will be appended to the filename
607
+
608
+ lib: beat detection library"""
609
  if filename2 is None and audio2 is None:
610
  from tkinter.filedialog import askopenfilename
611
  filename2 = askopenfilename(title='select sidechain impulse', filetypes=[("mp3", ".mp3"),("wav", ".wav"),("flac", ".flac"),("ogg", ".ogg"),("wma", ".wma")])
 
629
  output=output+''.join(''.join(self.filename.split('/')[-1]).split('.')[:-1])+suffix+'.mp3'
630
  song.write_audio(self,output)
631
  self.beatmap=save
632
+
633
+ def audio_spectogram(self, hop_length:int=512):
634
+ self.hop_length=hop_length
635
+ import librosa
636
+ self.spectogram=librosa.feature.melspectrogram(y=self.audio, sr=self.samplerate, hop_length=hop_length)
637
+
638
+ def spectogram_audio(self):
639
+ import librosa
640
+ self.audio=librosa.feature.inverse.mel_to_audio(M=numpy.swapaxes(numpy.swapaxes(numpy.dstack(( self.spectogram[0,:,:], self.spectogram[1,:,:])), 0, 2), 1,2), sr=self.samplerate, hop_length=self.hop_length)
641
+
642
+ def write_image(self):
643
+ """Turns song into an image based on beat positions. Currently semi-broken"""
644
+ import cv2
645
+ audio=self.audio[0].tolist()
646
+ height=len(audio)/len(self.beatmap)
647
+ width=len(self.beatmap)
648
+ height*=3
649
+ if height>width:
650
+ increase_length=int(height/width)
651
+ reduce_width=1
652
+ else:
653
+ reduce_width=int(width/height)
654
+ increase_length=1
655
+ increase_length/=10
656
+ reduce_width*=10
657
+ image=[audio[0:self.beatmap[0]]]
658
+ maximum=len(image)
659
+ for i in range(len(self.beatmap)-1):
660
+ image.append(audio[self.beatmap[i]:self.beatmap[i+1]])
661
+ maximum=max(maximum,len(image[i]))
662
+ for i in range(len(image)):
663
+ image[i].extend((maximum-len(image[i]))*[0])
664
+ image[i]=image[i][::reduce_width]
665
+
666
+ audio=self.audio[1].tolist()
667
+ image2=[audio[0:self.beatmap[0]]]
668
+ for i in range(len(self.beatmap)-1):
669
+ image2.append(audio[self.beatmap[i]:self.beatmap[i+1]])
670
+ for i in range(len(image2)):
671
+ image2[i].extend((maximum-len(image2[i]))*[0])
672
+ image2[i]=image2[i][::reduce_width]
673
+ print(len(image[i]), len(image2[i]))
674
+
675
+ image=numpy.asarray(image)*255
676
+ image2=numpy.asarray(image2)*255
677
+ image3=numpy.add(image, image2)/2
678
+ image,image2,image3=numpy.repeat(image,increase_length,axis=0),numpy.repeat(image2,increase_length,axis=0),numpy.repeat(image3,increase_length,axis=0)
679
+ image=cv2.merge([image.T,image2.T, image3.T])
680
+
681
+ #image=image.astype('uint8')
682
+ #image=cv2.resize(image, (0,0), fx=len(image))
683
+ cv2.imwrite('cv2_output.png', image)
684
+
685
+ def fix_beatmap(filename, lib='madmom.BeatDetectionProcessor', scale=1, shift=0):
686
+ track=song(filename)
687
+ track.analyze_beats(lib=lib)
688
+ track.beatmap_shift(shift)
689
+ track.beatmap_scale(scale)
690
+ id=hex(len(track.audio[0]))
691
+ import os
692
+ if not os.path.exists('SavedBeatmaps'):
693
+ os.mkdir('SavedBeatmaps')
694
+ cacheDir="SavedBeatmaps/" + ''.join(track.filename.split('/')[-1]) + "_"+lib+"_"+id+'.txt'
695
+ a=input(f'Are you sure you want to overwrite {cacheDir} using scale = {scale}; shift = {shift}? ("n" to cancel): ')
696
+ if 'n' in a.lower(): return
697
+ else: numpy.savetxt(cacheDir, track.beatmap.astype(int), fmt='%d')
698
+
699
+ def delete_beatmap(filename, lib='madmom.BeatDetectionProcessor'):
700
+ track=open_audio(filename)[0]
701
+ id=hex(len(track.audio[0]))
702
+ import os
703
+ if not os.path.exists('SavedBeatmaps'):
704
+ os.mkdir('SavedBeatmaps')
705
+ cacheDir="SavedBeatmaps/" + ''.join(track.filename.split('/')[-1]) + "_"+lib+"_"+id+'.txt'
706
+ a=input(f'Are you sure you want to delete {cacheDir}? ("n" to cancel): ')
707
+ if 'n' in a.lower(): return
708
+ else: os.remove(cacheDir)
app.py CHANGED
@@ -1,9 +1,9 @@
1
  import gradio as gr
2
  import BeatManipulator as bm
3
  def BeatSwap(pattern: str):
4
- audio=bm.song()
5
- audio.quick_beatswap(output=None, pattern=pattern)
6
- return audio.audio
7
 
8
  ui=gr.Interface (fn=BeatSwap,inputs="audio",outputs="audio" )
9
  ui.launch
 
1
  import gradio as gr
2
  import BeatManipulator as bm
3
  def BeatSwap(pattern: str):
4
+ song=bm.song()
5
+ song.quick_beatswap(output=None, pattern=pattern)
6
+ return song.audio
7
 
8
  ui=gr.Interface (fn=BeatSwap,inputs="audio",outputs="audio" )
9
  ui.launch
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
  cython
2
  numpy
3
  soundfile
4
- madmom
 
1
  cython
2
  numpy
3
  soundfile
4
+ git+https://github.com/CPJKU/madmom
wrapper.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import BeatManipulator as bm, json
2
+ with open("presets.json", "r") as f:
3
+ presets=f.read()
4
+
5
+ presets=json.loads(presets)
6
+
7
+ def lib_test(filename,output='', samplerate=44100, lib='madmom.BeatDetectionProcessor', scale=1, shift=0):
8
+ '''basically a way to quickly test scale and offset'''
9
+ if type(filename)==str :
10
+ song=bm.song(filename)
11
+ samplerate=song.samplerate
12
+ else:
13
+ song=filename
14
+ song.quick_beatsample(output=None, lib=lib, audio2=bm.generate_sine(0.1, 2000, samplerate), scale=8*scale, shift=0+shift)
15
+ song.quick_beatsample(output=None, lib=lib, audio2=bm.generate_sine(0.05, 1000, samplerate), scale=8*scale, shift=1*scale+shift)
16
+ song.quick_beatsample(output=None, lib=lib, audio2=bm.generate_saw(0.05, 500, samplerate), scale=8*scale, shift=2*scale+shift)
17
+ song.quick_beatsample(output=None, lib=lib, audio2=bm.generate_saw(0.05, 250, samplerate), scale=8*scale, shift=3*scale+shift)
18
+ song.quick_beatsample(output=None, lib=lib, audio2=bm.generate_saw(0.05, 125, samplerate), scale=8*scale, shift=4*scale+shift)
19
+ song.quick_beatsample(output=None, lib=lib, audio2=bm.generate_saw(0.05, 250, samplerate), scale=8*scale, shift=5*scale+shift)
20
+ song.quick_beatsample(output=None, lib=lib, audio2=bm.generate_saw(0.05, 500, samplerate), scale=8*scale, shift=6*scale+shift)
21
+ song.quick_beatsample(output=output, suffix=' ('+lib+')',lib=lib, audio2=bm.generate_saw(0.05, 1000, samplerate), scale=8*scale, shift=7*scale+shift)
22
+ del song
23
+
24
+ def lib_test_full(filename,samplerate):
25
+ '''A way to test all beat detection modules to see which one performs better.'''
26
+ print(filename)
27
+ lib_test(filename, samplerate,'madmom.BeatDetectionProcessor')
28
+ lib_test(filename, samplerate,'madmom.BeatDetectionProcessor.consistent')
29
+ #lib_test(filename, samplerate,'madmom.BeatTrackingProcessor') # better for live performances with variable BPM
30
+ #lib_test(filename, samplerate,'madmom.BeatTrackingProcessor.constant') # results identical to madmom.BeatDetectionProcessor
31
+ lib_test(filename, samplerate,'madmom.BeatTrackingProcessor.consistent')
32
+ lib_test(filename, samplerate,'madmom.CRFBeatDetectionProcessor')
33
+ lib_test(filename, samplerate,'madmom.CRFBeatDetectionProcessor.constant')
34
+ #lib_test(filename, samplerate,'madmom.DBNBeatTrackingProcessor') # better for live performances with variable BPM
35
+ lib_test(filename, samplerate,'madmom.DBNBeatTrackingProcessor.1000')
36
+ lib_test(filename, samplerate,'madmom.DBNDownBeatTrackingProcessor')
37
+ import gc
38
+ gc.collect()
39
+
40
+ def process(song:bm.song, preset: str, scale:float, shift:float)->bm.song:
41
+ #print(preset)
42
+ if 'pattern' in preset:
43
+ scale=scale*(preset['scale'] if 'scale' in preset else 1)
44
+ shift=shift+(preset['shift'] if 'shift' in preset else 0)
45
+ song.quick_beatswap(output=None, pattern=preset['pattern'], scale=scale, shift=shift)
46
+ elif preset['type'] =='sidechain':
47
+ length=preset['sc length'] if 'sc length' in preset else 0.5
48
+ curve=preset['sc curve'] if 'sc curve' in preset else 2
49
+ vol0=preset['sc vol0'] if 'sc vol0' in preset else 0
50
+ vol1=preset['sc vol1'] if 'sc vol1' in preset else 1
51
+ sidechain=bm.open_audio(preset['sc impulse'])[0] if 'sc impulse' in preset else bm.generate_sidechain(samplerate=song.samplerate, length=length, curve=curve, vol0=vol0, vol1=vol1, smoothing=40)
52
+ scale=scale*(preset['scale'] if 'scale' in preset else 1)
53
+ shift=shift+(preset['shift'] if 'shift' in preset else 0)
54
+ song.quick_sidechain(output=None, audio2=sidechain, scale=scale, shift=shift)
55
+ elif preset['type'] =='beatsample':
56
+ sample=preset['filename']
57
+ scale=scale*(preset['scale'] if 'scale' in preset else 1)
58
+ shift=shift+(preset['shift'] if 'shift' in preset else 0)
59
+ song.quick_beatsample(output=None, filename2=sample, scale=scale, shift=shift)
60
+ return song
61
+
62
+
63
+ def use_preset(output:str,filename: str, preset: str, presets=presets, scale=1, shift=0, beat:str='normal', test=False):
64
+ song=bm.song(filename)
65
+ if beat=='shifted': song.quick_beatswap(output=None, pattern='1,2,3,4,5,7,6,8', scale=0.5)
66
+ #print(song.samplerate)
67
+ if preset is None:
68
+ weights=[]
69
+ for i in presets.items():
70
+ weights.append(i[1]['weight'])
71
+ import random
72
+ preset = random.choices(population=list(presets), weights=weights, k=1)[0]
73
+ name=preset
74
+ preset=presets[preset]
75
+ if test is True:
76
+ testsong=bm.song(filename=filename, audio=song.audio, samplerate=song.samplerate)
77
+ lib_test(testsong, output, samplerate=testsong.samplerate)
78
+ del testsong
79
+ #print(name, preset)
80
+ if '1' in preset:
81
+ for i in preset:
82
+ if type(preset[i])==dict:song=process(song, preset[i], scale=scale, shift=shift)
83
+ else: song=process(song, preset,scale=scale,shift=shift)
84
+ song.write_audio(output=bm.outputfilename(output, filename, suffix=' ('+name+')'))
85
+
86
+ def all(output:str,filename: str, presets:dict=presets, scale=1, shift=0, test=True):
87
+ if test is True:
88
+ testsong=bm.song(filename=filename)
89
+ lib_test(testsong, output, samplerate=testsong.samplerate)
90
+ del testsong
91
+ for i in presets:
92
+ use_preset(output, filename, preset=i, presets=presets, scale=scale, shift=shift, test=False)
93
+
94
+
95
+
96
+ # ___ my stuff ___
97
+
98
+ import random, os
99
+ filename='F:/Stuff/Music/Tracks/Ivlril - 2.00.mp3'
100
+ #filename = 'F:/Stuff/Music/Tracks/'+random.choice(os.listdir("F:\Stuff\Music\Tracks"))
101
+
102
+ scale=1
103
+ shift=0
104
+ test=False
105
+
106
+ #bm.fix_beatmap(filename, scale=0.25, shift=-0.25)
107
+ #lib_test(filename, scale=0.25, shift=-0.25)
108
+
109
+ #use_preset ('', filename, 'jungle B', scale=scale, shift=shift, beat='normal', test=test)
110
+ #use_preset ('', filename, None, scale=scale, shift=shift, test=False)
111
+ all('',filename, scale=1, shift=0, test=test)
112
+
113
+ #song=bm.song(filename)
114
+ #song.analyze_beats()
115
+ #song.write_image()
116
+ #song.quick_beatswap('', 'random', 0.125, 0, autoinsert=False)