Compare commits

..

2 Commits

Author SHA1 Message Date
Thibaud fc719fac68 Adapt files other than sound_process to accomodate changes 2024-06-01 17:17:57 +02:00
Thibaud d499dee0de Experimental Changes 2024-06-01 16:21:04 +02:00
7 changed files with 298 additions and 577 deletions

View File

@ -8,10 +8,9 @@ def compare_plot():
beatmap = sl.Beatmap.from_path(filename) beatmap = sl.Beatmap.from_path(filename)
timing = beatmap.timing_points[0] timing = beatmap.timing_points[0]
bpm = timing.bpm bpm = timing.bpm
offset = timing.offset.total_seconds() * 1000 offset = timing.offset
data = sound_process.process_song(beatmap.audio_filename, bpm, offset0=offset, n_iter_2=48, divisor=4) timings, amplitudes = sound_process.process_song(beatmap.audio_filename, bpm, offset=offset, n_iter_2=-1)
timings = [x.total_seconds() for x in timings]
timings, amplitudes, freqs = [x[0].total_seconds() for x in data], [x[1] for x in data], [x[2] for x in data]
original_times = [x.time.total_seconds() for x in beatmap.hit_objects(spinners=False) if x.time.total_seconds() <= timings[len(timings) - 1]] original_times = [x.time.total_seconds() for x in beatmap.hit_objects(spinners=False) if x.time.total_seconds() <= timings[len(timings) - 1]]

279
debug.py
View File

@ -1,279 +0,0 @@
from math import *
import numpy as np
from scipy.io import wavfile
from scipy import signal
import matplotlib.pyplot as plt
import subprocess
import wave as wv
import struct
import librosa
import heapq
import scipy
import os
import random
from pathlib import Path
from time import sleep
from datetime import timedelta
def adjust_timings(raw_data, snapped_data, indexes, thr=100):
"""
adjusts weirdly snapped notes
"""
current = 0
while(current < len(indexes)):
if(current < len(indexes) - 3 and current % 2 == 1): # on a 1/4 beat
if(snapped_data[indexes[current]] > thr and snapped_data[indexes[current+1]] > thr and snapped_data[indexes[current+2]] > thr and snapped_data[indexes[current+3]] <= thr):
# -XXX_
snapped_data[indexes[current+3]] = snapped_data[indexes[current+2]]
snapped_data[indexes[current+2]] = 0
if(current > 0 and current < len(indexes) - 1 and current % 2 == 1):
if(snapped_data[indexes[current]] > thr and (snapped_data[indexes[current+1]] < thr or snapped_data[indexes[current-1]] < thr)):
#_X_
'''if(snapped_data[indexes[current-1]] < thr and raw_data[indexes[current-1]] > raw_data[indexes[current+1]]):
snapped_data[indexes[current-1]] = snapped_data[indexes[current]]
else:
snapped_data[indexes[current+1]] = snapped_data[indexes[current]]'''
snapped_data[indexes[current]] = 0
current += 1
print("Resnap done")
return snapped_data
def snap(data, sample_rate, bpm, divisor, show=False):
# adjust time amplitudes to match the given BPM
new = [0 for x in range(int(1000*len(data)/sample_rate))] # 1pt per millisecond
print("old =", len(data))
print("len =", 1000*len(data)/sample_rate)
k = 0
t = 0
percent = 0
for i in range(len(data)):
while(t < i/sample_rate):
t = k/(bpm*divisor)
k += 60
'''
if(np.abs(i/sample_rate - k/(bpm*divisor)) > np.abs(i/sample_rate - (k-60)/(bpm*divisor))):
k -= 60
t = k/(bpm*divisor)'''
if(i%(len(data)//100) == 0):
print(percent, "%")
percent += 1
if(int(t*1000) < len(new)):
new[int(t*1000)] = max(data[i], new[int(t*1000)])
else:
new[len(new)-1] = max(data[i], new[len(new)-1])
if(show):
t = [j/1000 for j in range(len(new))]
plt.plot(t, new)
plt.xlabel("Time (e)")
plt.ylabel("Amplitude")
plt.grid()
plt.show()
return new
def snap2(data, sample_rate, bpm, first_offset=0, div=4, show=False, adjust=False):
"""
data : list(int)
sample_rate : int
bpm = float
"""
song_len = int(len(data)/sample_rate)
indexes = []
app = True
reduced = [0 for i in range(song_len*1000)]
new = [0 for i in range(song_len*1000)]
# build the reduced version
for i in range(len(data)):
x = int(i*1000/sample_rate)
if(x < len(reduced)):
reduced[x] = max(reduced[x], data[i])
print("Build done")
# snap
k = 0
current_t = first_offset
while(current_t < 0):
k += 1
current_t = first_offset + k*60/(bpm*div)
for j in range(len(new)):
if(j/1000 > current_t):
k += 1
current_t = first_offset + k*60/(bpm*div)
app = True
y = int(current_t*1000)
if(y < len(new)):
new[y] = max(new[y], reduced[j])
if(app):
indexes.append(y)
app = False
print("Snap done")
if(adjust):
print("Len :", len(indexes))
new = adjust_timings(reduced, new, indexes)
if(show):
new2 = [0.9 if new[i] != 0 else 0 for i in range(len(new))]
t = [j/1000+first_offset for j in range(len(new))]
beats_1 = [0 for j in range(len(new))]
beats_2 = [0 for k in range(len(new))]
beats_4 = [0 for l in range(len(new))]
k = 0
current_t = first_offset
while(current_t < 0):
k += 1
current_t = first_offset + k*60/(bpm*div)
while(1000*current_t < len(new)):
beats_4[int(1000*current_t)] = 0.9
if(k % 2 == 0):
beats_2[int(1000*current_t)] = 0.92
if(k % 4 == 0):
beats_1[int(1000*current_t)] = 0.94
k += 1
current_t = first_offset + k*60/(bpm*div)
plt.plot(t, new2, "bo")
plt.plot(t, beats_4, "r-")
plt.plot(t, beats_2, "y-")
plt.plot(t, beats_1, "g-")
plt.xlabel("Time (s)")
plt.ylabel("Amplitude")
plt.grid()
plt.show()
return new
def filter_peaks(data, sample_rate=44100, thr=1000):
tdata = []
times = []
for i in range(len(data)):
if data[i] > thr:
tdata.append(data[i])
times.append(i/sample_rate)
return (tdata, times)
'''
times is in seconds
'''
def get_spacing(data, sample_rate=44100, show=False, retrieve=False):
tdata, times = filter_peaks(data, sample_rate=sample_rate)
absc = [i for i in range(len(times))]
dt = [0]
for i in range(1, len(times)):
dt.append(1000*(times[i]-times[i-1]))
if(show):
plt.plot(absc, dt)
plt.xlabel("x")
plt.ylabel("T(peak x) - T(peak x-1) (ms)")
plt.grid()
plt.show()
if(retrieve):
return dt
'''
post-condition :
- dt[i] = time(peak number i) - time(peak number i-1)
- dt is in ms
'''
def avg(data, i, j):
res = 0
for e in range(i, min(len(data), j)):
res += data[e]
return (res/(min(len(data), j) - i))
def snap3(data, sample_rate=44100, mintime=10, initial_plot=False, after_plot=False):
'''
explaination :
1) get the time differences (cf get_spacing)
2) for eack peak : 2 cases
- if it's farther than mintime (in ms) :
> calculate the weighted mean if all elements in temp_list
> place a note at that mean
> empty temp_list
> push the current peak to temp_list
- else :
> push the current peak to temp_list
'''
data_peaks, peak_times = filter_peaks(data, sample_rate=sample_rate)
time_diff = get_spacing(data, show=initial_plot, retrieve=True)
res_peaks = []
res_times = []
segments = []
seglen = []
current_left = 0
for i in range(len(peak_times)):
if(time_diff[i] > mintime):
segments.append([current_left, i])
seglen.append(peak_times[i]-peak_times[current_left])
res_peaks.append(avg(data_peaks, current_left, i))
res_times.append(peak_times[i])
current_left = i
for i in range(len(segments)):
print(segments[i], ":", seglen[i])
if(after_plot):
peakplot = []
diffplot = []
for x in range(len(peak_times)):
peakplot.append(peak_times[x]-peak_times[x]/1000)
peakplot.append(peak_times[x])
peakplot.append(peak_times[x]+peak_times[x]/1000)
diffplot.append(0)
diffplot.append(time_diff[x])
diffplot.append(0)
plt.plot(res_times, res_peaks, "ro", label="placed beats")
plt.plot(peakplot, diffplot, label="derivatine of time")
plt.xlabel("t (s)")
plt.ylabel(".")
plt.legend(loc="upper left")
plt.grid()
plt.show()
return (res_peaks, res_times)
""" res_times is in seconds """

View File

@ -3,21 +3,18 @@ import slider as sl
from datetime import timedelta from datetime import timedelta
import place import place
import sound_process import sound_process
import os
import numpy as np import numpy as np
def main(): def main():
filename = fd.askopenfilename() filename = fd.askopenfilename()
os.chdir(os.path.dirname(filename))
beatmap = sl.Beatmap.from_path(filename) beatmap = sl.Beatmap.from_path(filename)
timing = beatmap.timing_points[0] timing = beatmap.timing_points[0]
bpm = timing.bpm bpm = timing.bpm
offset = timing.offset.total_seconds() * 1000 offset = timing.offset
print(beatmap.audio_filename) print(beatmap.audio_filename)
amplitudes, timings, frequencies = sound_process.process_song(beatmap.audio_filename, bpm, offset0=offset, n_iter_2=-1) timings, amplitudes = sound_process.process_song(beatmap.audio_filename, bpm, offset=offset, n_iter_2=-1)
# NOTE : remove n_iter_2 to map the whole music # NOTE : remove n_iter_2 to map the whole music
timings = [timedelta(seconds=x) for x in timings]
beatmap._hit_objects = place.greedy(bpm, offset, timings, amplitudes) beatmap._hit_objects = place.greedy(bpm, offset, timings, amplitudes)
#beatmap._hit_objects = [sl.Slider(sl.Position(0, 0), timedelta(milliseconds=3), timedelta(milliseconds=130), 0, sl.curve.Linear([sl.Position(0, 0), sl.Position(100, 100)], 100), 100, 2, 1, 1, 1, timing.ms_per_beat, [], [],)] #beatmap._hit_objects = [sl.Slider(sl.Position(0, 0), timedelta(milliseconds=3), timedelta(milliseconds=130), 0, sl.curve.Linear([sl.Position(0, 0), sl.Position(100, 100)], 100), 100, 2, 1, 1, 1, timing.ms_per_beat, [], [],)]

View File

@ -12,36 +12,23 @@ def beatify(bpm:float, offset:int, time:timedelta) -> float:
def debeatify(bpm:float, offset:int, beat:int) -> timedelta: def debeatify(bpm:float, offset:int, beat:int) -> timedelta:
return timedelta(milliseconds=(beat*60000/bpm) + offset) return timedelta(milliseconds=(beat*60000/bpm) + offset)
def f(intensity): return np.pi/2 - np.arctan(2*intensity) + np.pi/5 def f(intensity): return np.pi/2 - np.arctan(2*intensity - 5)
#def f(intensity): return np.pi/2
def greedy(bpm:int, offset:int, timings:list, amplitudes:list): def greedy(bpm:int, offset:int, timings:list, amplitudes:list):
""" """
input: takes Alexandre's note selection / intensity data input: takes Alexandre's note selection / intensity data
output: list of object type / position output: list of object type / position
""" """
new_combo = True flow = 1
flow = 0 notes = [sl.HitObject(0, timedelta(milliseconds=0), 0)] * len(timings)
notes = [sl.HitObject(sl.Position(260, 188), timedelta(milliseconds=0), 0)] * len(timings) beats = [beatify(bpm, offset, timing) for timing in timings]
beats = [int(beatify(bpm, offset, timing)*4) for timing in timings]
last_position = (260, 188)
max_x, max_y, min_x, min_y = -np.inf, -np.inf, np.inf, np.inf
for (delta, note_beat, intensity, i) in zip(timings, beats, amplitudes, range(len(timings))): for (delta, note_beat, intensity, i) in zip(timings, beats, amplitudes, range(len(timings))):
try: try:
if int(note_beat/32) % 2: #test si la mesure est impaire ou non duration = note_beat - beats[i + 1]
new_combo = not (flow == 1)
flow = 1
else:
new_combo = (flow == 1)
flow = -1
duration = abs(note_beat - beats[i + 1])
print(duration)
"""
if duration in (QUARTER, HALF): if duration in (QUARTER, HALF):
notes[i] = sl.Circle(sl.Position(0, 0), delta, 0) notes[i] = sl.Circle(sl.Position(0, 0), delta, 0)
notes[i] = sl.Circle(sl.Position(0, 0), delta, 0) notes[i] = sl.Circle(sl.Position(0, 0), delta, 0)
""" """
"""
elif duration % 2 == 0: elif duration % 2 == 0:
rhythms.insert(0, f"slider {duration}") rhythms.insert(0, f"slider {duration}")
else: else:
@ -49,37 +36,18 @@ def greedy(bpm:int, offset:int, timings:list, amplitudes:list):
""" """
except IndexError: except IndexError:
notes[i] = sl.Circle(sl.Position(0, 0), delta, 0) notes[i] = sl.Circle(sl.Position(0, 0), delta, 0)
# TODO mettre à jour flow
"""
if len(notes) > 2: if len(notes) > 2:
if duration == QUARTER: angle = flow * f(rhythm.intensite)
x2, y2 = int(notes[i-1].position.x), int(notes[i-1].position.y) x1, y1 = notes[i-2].position
notes[i] = sl.Circle(sl.Position(x2, y2), delta, 0) x2, y2 = notes[i-1].position
old_angle = np.arctan2((y1, y2), (x1, x2))
else: x3 = x2 + (intensity * np.cos(angle + old_angle))
angle = flow * f(intensity/100) y3 = y2 + (intensity * np.sin(angle + old_angle))
x1, y1 = int(notes[i-2].position.x), int(notes[i-2].position.y)
x2, y2 = int(notes[i-1].position.x), int(notes[i-1].position.y)
old_angle = np.arctan2([y2-y1], [x2-x1])
x3 = x2 + ((duration + new_combo*4)* np.cos(angle + old_angle))
y3 = y2 + ((duration + new_combo*4) * np.sin(angle + old_angle))
notes[i] = sl.Circle(sl.Position(int(x3[0]), int(y3[0])), delta, 0)
else: else:
notes[i] = sl.Circle(sl.Position(260, 188), delta, 0) pass
if notes[i].position.x > max_x: """
max_x = notes[i].position.x
elif notes[i].position.x < min_x:
min_x = notes[i].position.x
if notes[i].position.y > max_y:
max_y = notes[i].position.y
elif notes[i].position.y < min_y:
min_y = notes[i].position.y
notes[i].new_combo = new_combo
factor_x = 1/(max_x - min_x)
factor_y = 1/(max_y - min_y)
for note in notes:
note.position = sl.Position((note.position.x-min_x)*factor_x*512, (note.position.y-min_y)*factor_y*384)
return notes return notes

View File

@ -1,4 +1,3 @@
slider
audioread==3.0.1 audioread==3.0.1
certifi==2024.2.2 certifi==2024.2.2
cffi==1.16.0 cffi==1.16.0

View File

@ -1,145 +0,0 @@
import numpy as np
import scipy as scp
import heapq
def retrieve_dominant_freqs(song_name, offset, songlen, segsize):
# returns a list with peak frequencies alongside the sample rate
# /!\ song_name is specified to be a list, NOT a list of couples (aka song is mono)
# segsize is in seconds
# remove high_pitched/low-pitched frequencies
minfreq = 110
maxfreq = 440*8
# cutting the song to only keep the one we're interested in
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(songlen+offset), "-i", song_name, "crop.wav"], shell=False)
# extracting data from cropped song
sample_rate, song_data = wavfile.read("crop.wav")
blit = int(sample_rate*segsize) # Te
# remove the copy of the song
subprocess.run(["rm", "crop.wav"], shell=False)
# calculate the frequencies associated to the FFTs
pfreq = scipy.fft.rfftfreq(blit, 1/sample_rate)
# left boundary of segment to crop
current_time = offset
# list of FFTs
fft_list = []
# number of samples
k = 0
while(current_time <= songlen+offset):
# index corresponding to left boundary
left_id = int(current_time*sample_rate)
# index corresponding to right boundary
right_id = int((current_time+segsize)*sample_rate)
# calculate the fft, append it to fft_list
pff = scp.fft.rfft(global_data[left:right])
fft_list.append(pff)
# just to avoid what causes 0.1 + 0.1 == 0.2 to be False
k += 1
current_time = offset + k*segsize
# spacing between samples (time)
fe = segsize/sample_rate
# list that will contain the maximum frequencies/amplitudes for all FFTs
maxlist = []
maxamps = []
# find all maximums
for i in range(len(fft_list)):
current_max = -1
current_fmax = 0
for j in range(len(fft_list[i])):
if(pfreq[j] < maxfreq & pfreq[j] >= minfreq & np.abs(fft_list[i][j]) > current_max):
current_max = np.abs(fft_list[i][j])
current_fmax = pfreq[j]
maxlist.append(current_fmax)
maxamps.append(current_max)
# gg
# maxlist[i] corresponds to time (offset + i*segsize)
return (maxlist, maxamps, segsize)
def retrieve_dominant_amps(song_name, offset, songlen, segsize, percent):
# returns a list with the percent% peak amplitudes alongside the sample rate
# /!\ song_name is specified to be a list, NOT a list of couples (aka song is mono)
# segsize is in seconds
# cutting the song to only keep the one we're interested in
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(songlen+offset), "-i", song_name, "crop.wav"], shell=False)
# extracting data from cropped song
sample_rate, song_data = wavfile.read("crop.wav")
blit = int(sample_rate*segsize) # Te
# remove the copy of the song
subprocess.run(["rm", "crop.wav"], shell=False)
# which notes will be voided
is_locked = [False for i in range(len(song_data))]
x = int((len(song_data)*threshold)//100)
print("Retreiving the", int(x), "/", len(song_data), "highest values")
elements = heapq.nlargest(int(x), enumerate(song_data), key=lambda x: x[1])
#returns a list of couples [id, value]
for idx in range(len(elements)):
is_locked[elements[idx][0]] = True
for r in range(len(song_data)):
if(is_locked[r] == False):
song_data[r] = 0
# now we need to reduce song_data so that it matches the length of the previous function's return
res = []
k = 0
current_time = offset
while(current_time <= songlen+offset):
# index corresponding to left boundary
left_id = int(current_time*sample_rate)
# index corresponding to right boundary
right_id = int((current_time+segsize)*sample_rate)
# merge the segment into one value
cmax = 0
for i in range(left_id, right_id):
if(i < len(song_data) & cmax < song_data[i]):
cmax = song_data[i]
res.append(cmax)
k += 1
current_time = current_time + k*segsize
# gg
# res[i] corresponds to time (offset + i*segsize)
return res
print("done")

View File

@ -15,7 +15,7 @@ from pathlib import Path
from time import sleep from time import sleep
from datetime import timedelta from datetime import timedelta
import debug WORKING_SAMPLE_RATE = 1000
print("Starting...\n") print("Starting...\n")
@ -30,12 +30,14 @@ def filter_n_percent_serial(song_name, offset, n_iter, step, threshold):
filter data associated with song_name to keep only the highest threshold% values filter data associated with song_name to keep only the highest threshold% values
""" """
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(offset+step*n_iter), "-i", song_name, "crop.wav"], shell=False) offset = offset.total_seconds()
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(offset+step*n_iter), "-i", song_name, "crop.wav"])
sample_rate, global_data = wavfile.read('crop.wav') sample_rate, global_data = wavfile.read('crop.wav')
subprocess.run(["clear"], shell=False) subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"], shell=False) subprocess.run(["rm", "crop.wav"])
for i in range(n_iter): for i in range(n_iter):
print(i, "/", n_iter) print(i, "/", n_iter)
@ -92,38 +94,46 @@ def round_t(id, sample_rate, bpm, div, offset, k0):
def compress(Zxx): def compress(Zxx):
res = [] res = []
def get_freq(song_name, times, width=1000, display=False): def get_freq(song_name, offset, step, songlen, data, display=False):
""" """
for a given list of times (in seconds), returns the corresponding peak frequencies for a given list of amplitudes, returns the corresponding peak frequencies
""" """
offset = offset.total_seconds()
fft_list = []
times = []
current_time = offset
k = 0
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(offset+songlen),"-i",song_name, "crop.wav"])
subprocess.run(["ffmpeg", "-ss", str(0), "-t", str(max(np.array(times))), "-i", song_name, "crop.wav"], shell=False) sample_rate, global_data = wavfile.read("crop.wav")
#blit = int(len(global_data) / len(data))
blit = int(sample_rate*step)
sample_rate, global_data = wavfile.read(song_name) subprocess.run(["clear"])
#blit = int(sample_rate*step) subprocess.run(["rm", "crop.wav"])
subprocess.run(["clear"], shell=False) pfreq = scipy.fft.rfftfreq(blit, 1/sample_rate)
subprocess.run(["rm", "crop.wav"], shell=False)
pfreq = scipy.fft.rfftfreq(2*width, 1/sample_rate) print("len : ", len(global_data))
print("len : ", len(data))
frequencies = [0 for s in range(len(times))] frequencies = [0 for s in range(len(data))]
print(len(pfreq)) print(len(pfreq))
for s in range(len(times)): for s in range(len(data)):
left = max(0, int(times[s]*44100)-width) if(data[s] != 0):
right = min(len(global_data), int(times[s]*44100)+width) pff = scipy.fft.rfft(global_data[int(s*len(global_data)/len(data)):int(WORKING_SAMPLE_RATE*step+int(s*len(global_data)/len(data)))])
pff = scipy.fft.rfft(global_data[left:right])
#print(len(pff), len(pfreq)) mx = max(np.abs(pff))
for id in range(len(pff)):
if frequencies[s] == 0 and np.abs(pff[id]) == mx:
frequencies[s] = pfreq[id]
mx = max(np.abs(pff)) elif s != 0:
for id in range(len(pff)): frequencies[s] = 0
if frequencies[s] == 0 and np.abs(pff[id]) == mx:
frequencies[s] = pfreq[id]
if(display): if(display):
plt.plot(times, frequencies) plt.plot([offset+t/1000 for t in range(len(data))], frequencies)
plt.grid() plt.grid()
plt.xlabel("Time (s)") plt.xlabel("Time (s)")
plt.ylabel("Dominant frequency (Hz)") plt.ylabel("Dominant frequency (Hz)")
@ -161,20 +171,22 @@ def void_freq(song_name, offset, songlen, increment, minfreq, maxfreq, upperthr,
write : bool (should be set to True) write : bool (should be set to True)
output_file : technical output_file : technical
""" """
offset = offset.total_seconds()
fft_list = [] fft_list = []
times = [] times = []
current_time = offset current_time = offset
k = 0 k = 0
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(songlen+offset), "-i", song_name, "crop.wav"], shell=False) subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(songlen+offset), "-i", song_name, "crop.wav"])
sample_rate, raw_global_data = wavfile.read("crop.wav") sample_rate, raw_global_data = wavfile.read("crop.wav")
blit = int(sample_rate*increment) blit = int(sample_rate*increment)
global_data = [0 for i in range(len(raw_global_data))] global_data = [0 for i in range(len(raw_global_data))]
#subprocess.run(["clear"]) subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"], shell=False) subprocess.run(["rm", "crop.wav"])
a = 0 a = 0
@ -251,7 +263,7 @@ def void_freq(song_name, offset, songlen, increment, minfreq, maxfreq, upperthr,
res[i] = np.int16(32767*res[i]/mx) res[i] = np.int16(32767*res[i]/mx)
res = np.array(res) res = np.array(res)
wavfile.write(output_file, 44100, res) wavfile.write(output_file, WORKING_SAMPLE_RATE, res)
#plt.plot(np.abs(pfreq[:len(fft_list[0])]), np.abs(fft_list[0])) #plt.plot(np.abs(pfreq[:len(fft_list[0])]), np.abs(fft_list[0]))
#plt.grid() #plt.grid()
@ -259,11 +271,29 @@ def void_freq(song_name, offset, songlen, increment, minfreq, maxfreq, upperthr,
print("Done") print("Done")
def convert_tuple(data, times): def get_tpts(data, sample_rate, thr):
res = []
for i in range(len(data)):
if(data[i] > thr):
res.append(i/sample_rate)
for i in res:
print(i)
return res
def test_sample(timelist):
for i in range(1,len(timelist)):
#os.system('play -n synth %s sin %s' % (0.05, 440))
for k in range(random.randint(1, 10)):
print("E", end="")
print("F")
sleep(timelist[i]-timelist[i-1])
def convert_tuple(datares, freq):
""" """
Takes data and converts it to a list of tuples (amplitude, datetimes) Takes datares and converts it to a list of tuples (amplitude, datetimes)
""" """
return [(times[i], data[i]) for i in range(len(data))] return [(timedelta(milliseconds=i), datares[i], freq[i]) for i in range(len(datares)) if datares[i] > 0]
def get_songlen(filename): def get_songlen(filename):
""" """
@ -274,21 +304,201 @@ def get_songlen(filename):
return (len(global_data)/sample_rate) return (len(global_data)/sample_rate)
def convert_to_wav(song_name:str, output_file="audio.wav") -> str: def snap(data, sample_rate, bpm, divisor, show=False):
# adjust time amplitudes to match the given BPM
new = [0 for x in range(int(1000*len(data)/sample_rate))] # 1pt per millisecond
print("old =", len(data))
print("len =", 1000*len(data)/sample_rate)
k = 0
t = 0
percent = 0
for i in range(len(data)):
while(t < i/sample_rate):
t = k/(bpm*divisor)
k += 60
'''
if(np.abs(i/sample_rate - k/(bpm*divisor)) > np.abs(i/sample_rate - (k-60)/(bpm*divisor))):
k -= 60
t = k/(bpm*divisor)'''
if(i%(len(data)//100) == 0):
print(percent, "%")
percent += 1
if(int(t*1000) < len(new)):
new[int(t*1000)] = max(data[i], new[int(t*1000)])
else:
new[len(new)-1] = max(data[i], new[len(new)-1])
if(show):
t = [j/1000 for j in range(len(new))]
plt.plot(t, new)
plt.xlabel("Time (e)")
plt.ylabel("Amplitude")
plt.grid()
plt.show()
return new
def adjust_timings(raw_data, snapped_data, indexes, thr=100):
""" """
Converts the song to .wav, only if it's not already in wave format. adjusts weirdly snapped notes
Currently relies on file extension. """
current = 0
while(current < len(indexes)):
if(current < len(indexes) - 3 and current % 2 == 1): # on a 1/4 beat
if(snapped_data[indexes[current]] > thr and snapped_data[indexes[current+1]] > thr and snapped_data[indexes[current+2]] > thr and snapped_data[indexes[current+3]] <= thr):
# -XXX_
snapped_data[indexes[current+3]] = snapped_data[indexes[current+2]]
snapped_data[indexes[current+2]] = 0
if(current > 0 and current < len(indexes) - 1 and current % 2 == 1):
if(snapped_data[indexes[current]] > thr and (snapped_data[indexes[current+1]] < thr or snapped_data[indexes[current-1]] < thr)):
#_X_
'''if(snapped_data[indexes[current-1]] < thr and raw_data[indexes[current-1]] > raw_data[indexes[current+1]]):
snapped_data[indexes[current-1]] = snapped_data[indexes[current]]
else:
snapped_data[indexes[current+1]] = snapped_data[indexes[current]]'''
snapped_data[indexes[current]] = 0
current += 1
print("Resnap done")
return snapped_data
def snap2(data, sample_rate, bpm, first_offset=0, div=4, show=False, adjust=False):
"""
data : list(int)
sample_rate : int
bpm = float
"""
song_len = int(len(data)/sample_rate)
indexes = []
app = True
reduced = [0 for i in range(song_len*1000)]
new = [0 for i in range(song_len*1000)]
# build the reduced version
for i in range(len(data)):
x = int(i*1000/sample_rate)
if(x < len(reduced)):
reduced[x] = max(reduced[x], data[i])
print("Build done")
# snap
k = 0
current_t = first_offset
while(current_t < 0):
k += 1
current_t = first_offset + k*60/(bpm*div)
for j in range(len(new)):
if(j/1000 > current_t):
k += 1
current_t = first_offset + k*60/(bpm*div)
app = True
y = int(current_t*1000)
if(y < len(new)):
new[y] = max(new[y], reduced[j])
if(app):
indexes.append(y)
app = False
print("Snap done")
if(adjust):
print("Len :", len(indexes))
new = adjust_timings(reduced, new, indexes)
if(show):
t = [j/1000+first_offset for j in range(len(new))]
scatter_t = [t[i] for i in range(len(new)) if new[i] != 0]
scatter_chosen_rhythm = [0.9 for i in range(len(new)) if new[i] != 0 ]
beats_1 = [0 for j in range(len(new))]
beats_2 = [0 for k in range(len(new))]
beats_4 = [0 for l in range(len(new))]
k = 0
current_t = first_offset
while(current_t < 0):
k += 1
current_t = first_offset + k*60/(bpm*div)
while(1000*current_t < len(new)):
beats_4[int(1000*current_t)] = 0.9
if(k % 2 == 0):
beats_2[int(1000*current_t)] = 0.902
if(k % 4 == 0):
beats_1[int(1000*current_t)] = 0.91
if(k % 16 == 0):
beats_1[int(1000*current_t)] = 0.915
k += 1
current_t = first_offset + k*60/(bpm*div)
points = plt.scatter(scatter_t, scatter_chosen_rhythm, marker="o", label="Detected Rhythm")
div1_plot, = plt.plot(t, beats_4, "b-", label="1/4")
div2_plot, = plt.plot(t, beats_2, "r-", label="1/2")
div3_plot, = plt.plot(t, beats_1, "black", label="1/1")
plt.xlabel("Time (s)")
#plt.ylabel("Amplitude")
plt.legend(handles=[points, div1_plot, div2_plot, div3_plot])
plt.grid()
plt.show()
return new
def convert_song(song_name:str, output_file="audio.wav") -> str:
"""
Converts the song to .wav AND lower its sample rate to 1000.
Returns: the song_name that should be used afterwards. Returns: the song_name that should be used afterwards.
""" """
extension = Path(song_name).suffix subprocess.run(["ffmpeg", "-y", "-i", song_name, "-ar", "1000", output_file])
match extension: return output_file
case ".mp3" | ".ogg":
print("Converting to .wav...")
subprocess.run(["ffmpeg", "-y", "-i", song_name, output_file], shell=False)
return output_file
return song_name
def process_song(filename, bpm, offset0=0, div_len_factor=1, n_iter_2=-1, threshold=0.5, divisor=4): def quantify(time: timedelta, bpm, offset, snapping):
"""
Input: timedelta, bpm, offset, and snapping divisor (2 for 1/2, etc...)
Returns a timedelta that is properly timed to the map.
"""
offset_ms = offset.total_seconds() / 1000
time_ms = time.total_seconds() * 1000
time_spacing = (60000/bpm)/snapping
beats_away = round((time_ms - offset_ms)/time_spacing)
new_time = timedelta(milliseconds=time_spacing*beats_away + offset_ms)
return new_time
def quantify_all(amplitudes_ugly, bpm, offset_ms, divisor):
n = len(amplitudes_ugly)
covered = [False] * n
times = []
amplitudes = []
for i in range(n):
if amplitudes_ugly[i] != 0 and not covered[i]:
times.append(quantify(timedelta(milliseconds=i), bpm, offset_ms, divisor))
amplitudes.append(amplitudes_ugly[i])
covered[i] = True
return times, amplitudes
def process_song(filename, bpm, offset=timedelta(milliseconds=0), div_len_factor=1, n_iter_2=-1, threshold=0.5, divisor=4):
""" """
filename : string (name of the song) filename : string (name of the song)
offset : int [+] (song mapping will start from this time in seconds, default is 0) offset : int [+] (song mapping will start from this time in seconds, default is 0)
@ -299,9 +509,7 @@ def process_song(filename, bpm, offset0=0, div_len_factor=1, n_iter_2=-1, thresh
divisor : int [+] (beat divisor used to snap the notes, default is 4) divisor : int [+] (beat divisor used to snap the notes, default is 4)
""" """
filename = convert_to_wav(filename) filename = convert_song(filename)
offset = offset0/1000
div_len = div_len_factor*60/bpm-0.01 div_len = div_len_factor*60/bpm-0.01
@ -309,33 +517,22 @@ def process_song(filename, bpm, offset0=0, div_len_factor=1, n_iter_2=-1, thresh
song_len = get_songlen(filename) song_len = get_songlen(filename)
if(n_iter == -1): if(n_iter == -1):
n_iter = int((song_len-offset/1000)/div_len)-1 n_iter = floor((song_len-offset.total_seconds())/div_len)-1
filtered_name = f"{filename}_trimmed.wav" filtered_name = f"{filename}_trimmed.wav"
void_freq(filename, offset, min(song_len, offset+div_len*(n_iter+1)+0.01), 4*60/bpm, minfreq=0, maxfreq=220, upperthr=5000, ampthr=60, ampfreq = 1200, ampval = 5.0, leniency = 0.005, write=True, linear=False, output_file=filtered_name) void_freq(filename, offset, min(song_len, offset.total_seconds()+div_len*(n_iter+1)+0.01), 4*60/bpm, minfreq=0, maxfreq=220, upperthr=5000, ampthr=60, ampfreq = 1200, ampval = 5.0, leniency = 0.005, write=True, linear=False, output_file=filtered_name)
#void_freq(filename, offset, offset+div_len*(n_iter+1)+0.01, 4*60/bpm, minfreq=0, maxfreq=330, upperthr=2500, ampthr=60, ampfreq = 1200, ampval = 1/2000, leniency = 0.0, write=True, linear=True, output_file=filtered_name)
datares = filter_n_percent_serial(filtered_name, offset, n_iter, div_len, threshold) amplitudes_ugly = filter_n_percent_serial(filtered_name, offset, n_iter, div_len, threshold)
#datares = snap(datares, WORKING_SAMPLE_RATE, bpm, 4, True)
#snapped_data = amplitude times, amplitudes = quantify_all(amplitudes_ugly, bpm, offset, divisor)
#times in ms #frequencies = get_freq(filtered_name, offset, div_len, div_len*n_iter, datares, True)
(snapped_data, times) = debug.snap3(datares, mintime=50, initial_plot=True, after_plot=True)
#frequencies=get_freq(filtered_name, offset, div_len, div_len*n_iter, snapped_data, True)
frequencies = get_freq(filtered_name, times, display=True)
Path(f"{filename}_trimmed.wav").unlink() Path(f"{filename}_trimmed.wav").unlink()
return snapped_data, times, frequencies #return convert_tuple(datares, frequencies)
return times, amplitudes
'''
datares = debug.snap2(datares, 44100, bpm, first_offset=offset, div=divisor, show=True, adjust=True)
frequencies = get_freq(filtered_name, offset, div_len, div_len*n_iter, datares, True)
Path(f"{filename}_trimmed.wav").unlink()
return convert_tuple(datares, frequencies)
'''
def main(): def main():
aa, bb, cc = process_song("tetris_4.wav", 160, n_iter_2=48) data = process_song("tetris_4.wav", 160, n_iter_2=48, threshold=100)
#print(data) #print(data)
print("Program finished with return 0") print("Program finished with return 0")
@ -390,7 +587,7 @@ if(False):
#t, f, Zxx = fct("deltamax.wav", 9.992, 0.032, 20, 3000, False) #t, f, Zxx = fct("deltamax.wav", 9.992, 0.032, 20, 3000, False)
#t, f, Zxx = fct("da^9.wav", 8.463, 0.032, 20, 5000, False) #t, f, Zxx = fct("da^9.wav", 8.463, 0.032, 20, 5000, False)
t, f, Zxx = fct("13. Cosmic Mind.wav", 0, 0.032, 20, 5000, False) t, f, Zxx = fct("13. Cosmic Mind.wav", 0, 0.032, 20, 5000, False)
#t, f, Zxx = fct("Furioso Melodia 44100.wav", 4, 0.032, 8, 3000, False) #t, f, Zxx = fct("Furioso Melodia WORKING_SAMPLE_RATE.wav", 4, 0.032, 8, 3000, False)
#t, f, Zxx = fct("changing.wav", 0, 0.05, 3.9, 5000, False) #t, f, Zxx = fct("changing.wav", 0, 0.05, 3.9, 5000, False)
#fct("worlds_end_3.wav", 75, (60/178)/4, 75+2, 2500) #fct("worlds_end_3.wav", 75, (60/178)/4, 75+2, 2500)
@ -401,7 +598,7 @@ if(False):
(t, data) = peaks("worlds_end_3.wav", 74.582, 6, False, 0.9) (t, data) = peaks("worlds_end_3.wav", 74.582, 6, False, 0.9)
#(t, data) = peaks("da^9.wav", 8.463, 301.924 - 8.463, False, 0.95) #(t, data) = peaks("da^9.wav", 8.463, 301.924 - 8.463, False, 0.95)
#(t, data) = peaks("deltamax.wav", 8.463, 30101.924 - 8.463, False, 0.92) #(t, data) = peaks("deltamax.wav", 8.463, 30101.924 - 8.463, False, 0.92)
da = find_bpm(t, 44100, data, 100, 200, 1, 10) da = find_bpm(t, WORKING_SAMPLE_RATE, data, 100, 200, 1, 10)
print("BPM data is", da)''' print("BPM data is", da)'''
#data = [-1 for i in range(int(x))] #data = [-1 for i in range(int(x))]
@ -454,13 +651,13 @@ def fct(song_name, offset, increment, songlen, maxfreq, display):
current_time = offset current_time = offset
k = 0 k = 0
while(current_time <= songlen): while(current_time <= songlen):
subprocess.run(["ffmpeg", "-ss", str(current_time), "-t", str(increment), "-i", song_name, "crop.wav"], shell=False) subprocess.run(["ffmpeg", "-ss", str(current_time), "-t", str(increment), "-i", song_name, "crop.wav"])
sample_rate, audio_data = wavfile.read('crop.wav') sample_rate, audio_data = wavfile.read('crop.wav')
size = audio_data.size size = audio_data.size
#subprocess.run(["clear"]) #subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"], shell=False) subprocess.run(["rm", "crop.wav"])
# do stuff here # do stuff here
#f, t, Zxx = signal.stft(audio_data, sample_rate, nperseg=1000) #f, t, Zxx = signal.stft(audio_data, sample_rate, nperseg=1000)
@ -603,15 +800,16 @@ def extract_peaks_v2(song_data, sample_rate, offset, display, threshold, seglen)
return (t, song_data) return (t, song_data)
def peaks(song_name, offset, length, display, thr): def peaks(song_name, offset, length, display, thr):
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(length), "-i", song_name, "crop.wav"], shell=False) offset = offset.total_seconds()
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(length), "-i", song_name, "crop.wav"])
sample_rate, audio_data = wavfile.read('crop.wav') sample_rate, audio_data = wavfile.read('crop.wav')
#subprocess.run(["clear"]) subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"], shell=False) subprocess.run(["rm", "crop.wav"])
#return extract_peaks(audio_data, sample_rate, offset, display, thr) #return extract_peaks(audio_data, sample_rate, offset, display, thr)
return extract_peaks_v2(audio_data, sample_rate, offset, display, thr, 44100*2) return extract_peaks_v2(audio_data, sample_rate, offset, display, thr, WORKING_SAMPLE_RATE*2)
def find_bpm(sample_rate, data, minbpm, maxbpm, step, width): def find_bpm(sample_rate, data, minbpm, maxbpm, step, width):
optimal = minbpm optimal = minbpm
@ -813,15 +1011,17 @@ def filter_n_percent(song_name, offset, length, threshold, reduce, show):
# threshold is in ]0, 100] # threshold is in ]0, 100]
# filter data associated with song_name to keep only the highest threshold% values # filter data associated with song_name to keep only the highest threshold% values
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(length), "-i", song_name, "crop.wav"], shell=False) offset = offset.total_seconds()
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(length), "-i", song_name, "crop.wav"])
sample_rate, song_data = wavfile.read('crop.wav') sample_rate, song_data = wavfile.read('crop.wav')
subprocess.run(["clear"], shell=False) subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"], shell=False) subprocess.run(["rm", "crop.wav"])
if(reduce): if(reduce):
(song_data,e) = to_ms(song_data, 44100, 1) (song_data,e) = to_ms(song_data, WORKING_SAMPLE_RATE, 1)
sample_rate = 1000 sample_rate = 1000
mx = max(song_data) mx = max(song_data)
@ -851,22 +1051,4 @@ def filter_n_percent(song_name, offset, length, threshold, reduce, show):
plt.show() plt.show()
return song_data return song_data
def get_tpts(data, sample_rate, thr):
res = []
for i in range(len(data)):
if(data[i] > thr):
res.append(i/sample_rate)
for i in res:
print(i)
return res
def test_sample(timelist):
for i in range(1,len(timelist)):
#os.system('play -n synth %s sin %s' % (0.05, 440))
for k in range(random.randint(1, 10)):
print("E", end="")
print("F")
sleep(timelist[i]-timelist[i-1])
''' '''