diff options
Diffstat (limited to 'src/VaporSong.py')
-rw-r--r-- | src/VaporSong.py | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/src/VaporSong.py b/src/VaporSong.py new file mode 100644 index 0000000..bb75646 --- /dev/null +++ b/src/VaporSong.py @@ -0,0 +1,182 @@ +import os +import subprocess +import re +from random import randint + +import logzero +from logzero import logger +from logzero import setup_logger + +CONFIDENCE_THRESH = 0.02 + +class VaporSong: + + # Slows down Track + + def slow_down(src, rate, dest): + cmd = "sox -G -D " + src + " " + dest + " speed " + str(rate) + os.system(cmd) + return dest + + # Adds Reverb + + def reverbize(src, dest): + cmd = "sox -G -D " + src + " " + dest + " reverb 100 fade 5 -0 7" # idk what this does tbh, https://stackoverflow.com/a/57767238/8386344 + os.system(cmd) + return dest + + + # Crops "src" from "start" plus "start + dur" and return it in "dest" + def crop(src,dest,start,dur): + cmd = "sox " + src + " " + dest + " trim " + " " + str(start) + " " + str(dur) + os.system(cmd) + + + # Randomly crops a part of the song of at most max_sec_len. + def random_crop(src, max_sec_len, dest): + out = subprocess.check_output(["soxi","-D",src]).rstrip() + f_len = int(float(out)) + if (f_len <= max_sec_len): + os.system("cp " + src + " " + dest) + return + else: + start_region = f_len - max_sec_len + start = randint(0,start_region) + VaporSong.crop(src,dest,start,max_sec_len) + + + # Given a file, returns a list of [beats, confidence], executable based on audibo's test-beattracking.c + # TODO: Move away from executable and use aubio's Python module + def fetchbeats(src): + beat_matrix = [] + if os.name == 'posix': + beats = subprocess.check_output(["noah", "get-beats",src]).rstrip() + else: + beats = subprocess.check_output(["get-beats",src]).rstrip() + beats_ary = beats.splitlines() + for i in beats_ary: + record = i.split() + record[0] = float(record[0])/1000.0 + record[1] = float(record[1]) + beat_matrix.append(record) + return beat_matrix + + # Splits an audio file into beats according to beat_matrix list + + def split_beat(src,beat_matrix): + split_files = [] + for i in range(0,len(beat_matrix)-1): + + if(beat_matrix[i][1] > CONFIDENCE_THRESH): + dur = (beat_matrix[i+1][0] - beat_matrix[i][0]) + out = src.split(".")[0]+str(i)+".wav" + VaporSong.crop(src,out,beat_matrix[i][0],dur) + split_files.append(out) + return split_files + + # Combines a list of sections + + def combine(sections,dest): + tocomb = [] + tocomb.append("sox") + tocomb.append("-G") + for section in sections: + for sample in section: + tocomb.append(sample) + tocomb.append(dest) + tmpFileLimit = len(tocomb) + 256 # in case the program messes up, it does not actually frick up your system + n = str(tmpFileLimit) + #logger.info("Setting file limit to ", n) + os.system("ulimit -n " + n) + subprocess.check_output(tocomb) + return dest + + # Arbitrarily groups beats into lists of 4, 6, 8, or 9, perfect for looping. + + def generate_sections(ary): + sections = [] + beats = [4,6,8,9] + index = 0 + while(index != len(ary)): + current_beat = beats[randint(0,len(beats)-1)] + new_section = [] + while((current_beat != 0) and (index != len(ary))): + new_section.append(ary[index]) + current_beat -= 1 + index += 1 + sections.append(new_section) + return sections + + + # given a list of sections, selects some of them and duplicates them, perfect for that vaporwave looping effect + def dup_sections(sections): + new_section = [] + for section in sections: + new_section.append(section) + if(randint(0,1) == 0): + new_section.append(section) + return new_section + + # a passage is a list of sections. This takes some sections and groups them into passages. + + def make_passages(sections): + passages = [] + index = 0 + while(index != len(sections)): + passage_len = randint(1,4) + passage = [] + while(index != len(sections) and passage_len > 0): + passage.append(sections[index]) + index += 1 + passage_len -= 1 + passages.append(passage) + return passages + + # Given all of our passages, picks some of them and inserts them into a list some number of times. + + def reorder_passages(passages): + new_passages = [] + passage_count = randint(5,12) + while(passage_count != 0): + passage = passages[randint(0,len(passages)-1)] + passage_count -= 1 + dup = randint(1,4) + while(dup != 0): + dup -= 1 + new_passages.append(passage) + return new_passages + + # converts a list of passages to a list of sections. + + def flatten(passages): + sections = [] + for passage in passages: + for section in passage: + sections.append(section) + return sections + + # It's all coming together + + def vaporize_song(fname, title): + logger.info("Slowing down the music") + VaporSong.slow_down(fname, 0.7, "beats/out.wav") + #logger.info("Cropping") + #VaporSong.random_crop("beats/out.wav",150,"beats/outcrop.wav") + logger.info("Doing Beat Analysis") + bm = VaporSong.fetchbeats("beats/out.wav") + logger.info("Split into beats") + splitd = VaporSong.split_beat("beats/out.wav",bm) + #group beats to sections + logger.info("Divide into sections") + sections = VaporSong.generate_sections(splitd) + logger.info("Duping Sections") + sdup = VaporSong.dup_sections(sections) + # group sections into passages + paslist = VaporSong.make_passages(sdup) + # reorder packages + pasloop = VaporSong.reorder_passages(paslist) + sectionflat = VaporSong.flatten(pasloop) + logger.info("Mastering & Reverbing") + VaporSong.combine(sectionflat,"beats/out_norev.wav") + VaporSong.reverbize("beats/out_norev.wav","./" + (re.sub(r"\W+|_", " ", title)).replace(" ","_") + ".wav") + logger.info("Generated V A P O R W A V E") |