Compare commits

..

11 Commits

7 changed files with 577 additions and 298 deletions

View File

@ -8,9 +8,10 @@ def compare_plot():
beatmap = sl.Beatmap.from_path(filename)
timing = beatmap.timing_points[0]
bpm = timing.bpm
offset = timing.offset
timings, amplitudes = sound_process.process_song(beatmap.audio_filename, bpm, offset=offset, n_iter_2=-1)
timings = [x.total_seconds() for x in timings]
offset = timing.offset.total_seconds() * 1000
data = sound_process.process_song(beatmap.audio_filename, bpm, offset0=offset, n_iter_2=48, divisor=4)
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]]

279
debug.py Normal file
View File

@ -0,0 +1,279 @@
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,18 +3,21 @@ import slider as sl
from datetime import timedelta
import place
import sound_process
import os
import numpy as np
def main():
filename = fd.askopenfilename()
os.chdir(os.path.dirname(filename))
beatmap = sl.Beatmap.from_path(filename)
timing = beatmap.timing_points[0]
bpm = timing.bpm
offset = timing.offset
offset = timing.offset.total_seconds() * 1000
print(beatmap.audio_filename)
timings, amplitudes = sound_process.process_song(beatmap.audio_filename, bpm, offset=offset, n_iter_2=-1)
amplitudes, timings, frequencies = sound_process.process_song(beatmap.audio_filename, bpm, offset0=offset, n_iter_2=-1)
# 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 = [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,23 +12,36 @@ def beatify(bpm:float, offset:int, time:timedelta) -> float:
def debeatify(bpm:float, offset:int, beat:int) -> timedelta:
return timedelta(milliseconds=(beat*60000/bpm) + offset)
def f(intensity): return np.pi/2 - np.arctan(2*intensity - 5)
def f(intensity): return np.pi/2 - np.arctan(2*intensity) + np.pi/5
#def f(intensity): return np.pi/2
def greedy(bpm:int, offset:int, timings:list, amplitudes:list):
"""
input: takes Alexandre's note selection / intensity data
output: list of object type / position
"""
flow = 1
notes = [sl.HitObject(0, timedelta(milliseconds=0), 0)] * len(timings)
beats = [beatify(bpm, offset, timing) for timing in timings]
new_combo = True
flow = 0
notes = [sl.HitObject(sl.Position(260, 188), timedelta(milliseconds=0), 0)] * len(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))):
try:
duration = note_beat - beats[i + 1]
if int(note_beat/32) % 2: #test si la mesure est impaire ou non
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):
notes[i] = sl.Circle(sl.Position(0, 0), delta, 0)
notes[i] = sl.Circle(sl.Position(0, 0), delta, 0)
"""
"""
elif duration % 2 == 0:
rhythms.insert(0, f"slider {duration}")
else:
@ -36,18 +49,37 @@ def greedy(bpm:int, offset:int, timings:list, amplitudes:list):
"""
except IndexError:
notes[i] = sl.Circle(sl.Position(0, 0), delta, 0)
# TODO mettre à jour flow
"""
if len(notes) > 2:
angle = flow * f(rhythm.intensite)
x1, y1 = notes[i-2].position
x2, y2 = notes[i-1].position
old_angle = np.arctan2((y1, y2), (x1, x2))
x3 = x2 + (intensity * np.cos(angle + old_angle))
y3 = y2 + (intensity * np.sin(angle + old_angle))
if duration == QUARTER:
x2, y2 = int(notes[i-1].position.x), int(notes[i-1].position.y)
notes[i] = sl.Circle(sl.Position(x2, y2), delta, 0)
else:
pass
"""
angle = flow * f(intensity/100)
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:
notes[i] = sl.Circle(sl.Position(260, 188), delta, 0)
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

View File

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

145
script (1).py Normal file
View File

@ -0,0 +1,145 @@
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 datetime import timedelta
WORKING_SAMPLE_RATE = 1000
import debug
print("Starting...\n")
@ -30,14 +30,12 @@ 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
"""
offset = offset.total_seconds()
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(offset+step*n_iter), "-i", song_name, "crop.wav"])
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(offset+step*n_iter), "-i", song_name, "crop.wav"], shell=False)
sample_rate, global_data = wavfile.read('crop.wav')
subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"])
subprocess.run(["clear"], shell=False)
subprocess.run(["rm", "crop.wav"], shell=False)
for i in range(n_iter):
print(i, "/", n_iter)
@ -94,46 +92,38 @@ def round_t(id, sample_rate, bpm, div, offset, k0):
def compress(Zxx):
res = []
def get_freq(song_name, offset, step, songlen, data, display=False):
def get_freq(song_name, times, width=1000, display=False):
"""
for a given list of amplitudes, returns the corresponding peak frequencies
for a given list of times (in seconds), 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"])
sample_rate, global_data = wavfile.read("crop.wav")
#blit = int(len(global_data) / len(data))
blit = int(sample_rate*step)
subprocess.run(["ffmpeg", "-ss", str(0), "-t", str(max(np.array(times))), "-i", song_name, "crop.wav"], shell=False)
subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"])
sample_rate, global_data = wavfile.read(song_name)
#blit = int(sample_rate*step)
pfreq = scipy.fft.rfftfreq(blit, 1/sample_rate)
subprocess.run(["clear"], shell=False)
subprocess.run(["rm", "crop.wav"], shell=False)
print("len : ", len(global_data))
print("len : ", len(data))
pfreq = scipy.fft.rfftfreq(2*width, 1/sample_rate)
frequencies = [0 for s in range(len(data))]
frequencies = [0 for s in range(len(times))]
print(len(pfreq))
for s in range(len(data)):
if(data[s] != 0):
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)))])
for s in range(len(times)):
left = max(0, int(times[s]*44100)-width)
right = min(len(global_data), int(times[s]*44100)+width)
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]
elif s != 0:
frequencies[s] = 0
if(display):
plt.plot([offset+t/1000 for t in range(len(data))], frequencies)
plt.plot(times, frequencies)
plt.grid()
plt.xlabel("Time (s)")
plt.ylabel("Dominant frequency (Hz)")
@ -171,22 +161,20 @@ def void_freq(song_name, offset, songlen, increment, minfreq, maxfreq, upperthr,
write : bool (should be set to True)
output_file : technical
"""
offset = offset.total_seconds()
fft_list = []
times = []
current_time = offset
k = 0
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(songlen+offset), "-i", song_name, "crop.wav"])
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(songlen+offset), "-i", song_name, "crop.wav"], shell=False)
sample_rate, raw_global_data = wavfile.read("crop.wav")
blit = int(sample_rate*increment)
global_data = [0 for i in range(len(raw_global_data))]
subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"])
#subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"], shell=False)
a = 0
@ -263,7 +251,7 @@ def void_freq(song_name, offset, songlen, increment, minfreq, maxfreq, upperthr,
res[i] = np.int16(32767*res[i]/mx)
res = np.array(res)
wavfile.write(output_file, WORKING_SAMPLE_RATE, res)
wavfile.write(output_file, 44100, res)
#plt.plot(np.abs(pfreq[:len(fft_list[0])]), np.abs(fft_list[0]))
#plt.grid()
@ -271,29 +259,11 @@ def void_freq(song_name, offset, songlen, increment, minfreq, maxfreq, upperthr,
print("Done")
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):
def convert_tuple(data, times):
"""
Takes datares and converts it to a list of tuples (amplitude, datetimes)
Takes data and converts it to a list of tuples (amplitude, datetimes)
"""
return [(timedelta(milliseconds=i), datares[i], freq[i]) for i in range(len(datares)) if datares[i] > 0]
return [(times[i], data[i]) for i in range(len(data))]
def get_songlen(filename):
"""
@ -304,201 +274,21 @@ def get_songlen(filename):
return (len(global_data)/sample_rate)
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):
def convert_to_wav(song_name:str, output_file="audio.wav") -> str:
"""
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 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.
Converts the song to .wav, only if it's not already in wave format.
Currently relies on file extension.
Returns: the song_name that should be used afterwards.
"""
subprocess.run(["ffmpeg", "-y", "-i", song_name, "-ar", "1000", output_file])
extension = Path(song_name).suffix
match extension:
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 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):
def process_song(filename, bpm, offset0=0, div_len_factor=1, n_iter_2=-1, threshold=0.5, divisor=4):
"""
filename : string (name of the song)
offset : int [+] (song mapping will start from this time in seconds, default is 0)
@ -509,7 +299,9 @@ def process_song(filename, bpm, offset=timedelta(milliseconds=0), div_len_factor
divisor : int [+] (beat divisor used to snap the notes, default is 4)
"""
filename = convert_song(filename)
filename = convert_to_wav(filename)
offset = offset0/1000
div_len = div_len_factor*60/bpm-0.01
@ -517,22 +309,33 @@ def process_song(filename, bpm, offset=timedelta(milliseconds=0), div_len_factor
song_len = get_songlen(filename)
if(n_iter == -1):
n_iter = floor((song_len-offset.total_seconds())/div_len)-1
n_iter = int((song_len-offset/1000)/div_len)-1
filtered_name = f"{filename}_trimmed.wav"
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)
amplitudes_ugly = filter_n_percent_serial(filtered_name, offset, n_iter, div_len, threshold)
#datares = snap(datares, WORKING_SAMPLE_RATE, bpm, 4, True)
times, amplitudes = quantify_all(amplitudes_ugly, bpm, offset, divisor)
#frequencies = get_freq(filtered_name, offset, div_len, div_len*n_iter, datares, True)
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)
datares = filter_n_percent_serial(filtered_name, offset, n_iter, div_len, threshold)
#snapped_data = amplitude
#times in ms
(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()
#return convert_tuple(datares, frequencies)
return times, amplitudes
return snapped_data, times, frequencies
'''
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():
data = process_song("tetris_4.wav", 160, n_iter_2=48, threshold=100)
aa, bb, cc = process_song("tetris_4.wav", 160, n_iter_2=48)
#print(data)
print("Program finished with return 0")
@ -587,7 +390,7 @@ if(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("13. Cosmic Mind.wav", 0, 0.032, 20, 5000, False)
#t, f, Zxx = fct("Furioso Melodia WORKING_SAMPLE_RATE.wav", 4, 0.032, 8, 3000, False)
#t, f, Zxx = fct("Furioso Melodia 44100.wav", 4, 0.032, 8, 3000, 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)
@ -598,7 +401,7 @@ if(False):
(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("deltamax.wav", 8.463, 30101.924 - 8.463, False, 0.92)
da = find_bpm(t, WORKING_SAMPLE_RATE, data, 100, 200, 1, 10)
da = find_bpm(t, 44100, data, 100, 200, 1, 10)
print("BPM data is", da)'''
#data = [-1 for i in range(int(x))]
@ -651,13 +454,13 @@ def fct(song_name, offset, increment, songlen, maxfreq, display):
current_time = offset
k = 0
while(current_time <= songlen):
subprocess.run(["ffmpeg", "-ss", str(current_time), "-t", str(increment), "-i", song_name, "crop.wav"])
subprocess.run(["ffmpeg", "-ss", str(current_time), "-t", str(increment), "-i", song_name, "crop.wav"], shell=False)
sample_rate, audio_data = wavfile.read('crop.wav')
size = audio_data.size
#subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"])
subprocess.run(["rm", "crop.wav"], shell=False)
# do stuff here
#f, t, Zxx = signal.stft(audio_data, sample_rate, nperseg=1000)
@ -800,16 +603,15 @@ def extract_peaks_v2(song_data, sample_rate, offset, display, threshold, seglen)
return (t, song_data)
def peaks(song_name, offset, length, display, thr):
offset = offset.total_seconds()
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(length), "-i", song_name, "crop.wav"])
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(length), "-i", song_name, "crop.wav"], shell=False)
sample_rate, audio_data = wavfile.read('crop.wav')
subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"])
#subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"], shell=False)
#return extract_peaks(audio_data, sample_rate, offset, display, thr)
return extract_peaks_v2(audio_data, sample_rate, offset, display, thr, WORKING_SAMPLE_RATE*2)
return extract_peaks_v2(audio_data, sample_rate, offset, display, thr, 44100*2)
def find_bpm(sample_rate, data, minbpm, maxbpm, step, width):
optimal = minbpm
@ -1011,17 +813,15 @@ def filter_n_percent(song_name, offset, length, threshold, reduce, show):
# threshold is in ]0, 100]
# filter data associated with song_name to keep only the highest threshold% values
offset = offset.total_seconds()
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(length), "-i", song_name, "crop.wav"])
subprocess.run(["ffmpeg", "-ss", str(offset), "-t", str(length), "-i", song_name, "crop.wav"], shell=False)
sample_rate, song_data = wavfile.read('crop.wav')
subprocess.run(["clear"])
subprocess.run(["rm", "crop.wav"])
subprocess.run(["clear"], shell=False)
subprocess.run(["rm", "crop.wav"], shell=False)
if(reduce):
(song_data,e) = to_ms(song_data, WORKING_SAMPLE_RATE, 1)
(song_data,e) = to_ms(song_data, 44100, 1)
sample_rate = 1000
mx = max(song_data)
@ -1051,4 +851,22 @@ def filter_n_percent(song_name, offset, length, threshold, reduce, show):
plt.show()
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])
'''