import math import json import array import types import matplotlib.pyplot as plt import numpy as np import wt_util def plot_overlap( xV, bi, ei, wndN, smp_per_cycle, title ): fig, ax = plt.subplots(1,1) x0 = [ i for i in range(bi-wndN,bi+wndN) ] x1 = [ x-x0[0] for x in x0 ] ax.plot(x1,xV[x0]) x0 = [ i for i in range(ei-wndN,ei+wndN) ] x1 = [ x-x0[0] for x in x0 ] ax.plot(x1,xV[x0]) plt.title(title) plt.show() def sign(x): return x<0 def find_zero_crossing( xV, si, inc ): # find the next zero crossing before/after si while si > 0: if sign(xV[si-1])==False and sign(xV[si])==True: break; si += inc return si def meas_fit(xV,ei,bi,wndN): if bi-wndN < 0 or ei+wndN > len(xV): return None v0 = xV[ei-wndN:ei+wndN]/0x7fffffff v1 = xV[bi-wndN:bi+wndN]/0x7fffffff dv = (v1-v0) * (v1-v0) return np.mean(dv) def find_loop_points_1( xV, bsi, esi, smp_per_cycle, wndN, est_N ): # find the first zero crossing after the end of the search range ei = find_zero_crossing(xV,esi,-1) min_d = None min_bi = None bi = bsi # make est_N guesses for i in range(0,est_N): # find the next zero crossing after bi bi = find_zero_crossing(xV,bi,1) # measure the quality of the fit with the end of the loop d = meas_fit(xV,ei,bi,wndN) #print(i,bi,d) # store the best loop begin point if min_bi is None or d < min_d: min_d = d min_bi = bi # advance bi += int(wndN) #smp_per_cycle return min_bi, ei, min_d def find_loop_points_2(xV,bsi,esi, smp_per_cycle, wndN, est_N ): def _track_min( min_i, min_d, i, d ): if min_i is None or d rms_max: i_max = i rms_max = rms rmsV.append(float(rms)) return [ i_max, 0 if i_max==1 else 1 ] def process_all_samples( markL, smpM, srate, args ): wtL = [] #fund_hz = 13.75 * math.pow(2,(-9.0/12.0)) * math.pow(2.0,(args.midi_pitch / 12.0)) fund_hz = midi_pitch_to_hz(args.midi_pitch) smp_per_cycle = int(srate / fund_hz) end_offs_smp_idx = max(smp_per_cycle,int(args.end_offset_ms * srate / 1000)) loop_dur_smp = int(args.loop_dur_ms * srate / 1000) wndN = int(smp_per_cycle/6) print(f"Hz:{fund_hz} smp/cycle:{smp_per_cycle} loop_dur:{loop_dur_smp} cycles/loop:{loop_dur_smp/smp_per_cycle} wndN:{wndN}") # for each sampled note for beg_sec,end_sec,vel_label in markL: beg_smp_idx = int(beg_sec * srate) end_smp_idx = int(end_sec * srate) r = { "instr":"piano", "pitch":args.midi_pitch, "vel": int(vel_label), "beg_smp_idx":beg_smp_idx, "end_smp_idx":None, "chL": [] } # determine the loop search range from the end of the note sample eli = end_smp_idx - end_offs_smp_idx bli = eli - loop_dur_smp ch_map = determine_track_order( smpM, beg_smp_idx, end_smp_idx) #print(ch_map) esi = beg_smp_idx; for i in range(0,smpM.shape[1]): ch_idx = ch_map[i] xV = smpM[:,ch_idx] if True: #if i == 0: # s_per_c = srate / fund_hz # bi,ei,cost = find_loop_points(xV,bli,eli,s_per_c,wndN,args.guess_cnt) s_per_c = srate / fund_hz bi,ei,cost = find_loop_points_4(xV,bli,eli,s_per_c,wndN,args.guess_cnt) if False: bi,ei,cost = find_loop_points_2(xV,bli,eli,smp_per_cycle,wndN,args.guess_cnt) if False: if i == 0: bi,ei,cost = find_loop_points(xV,bli,eli,smp_per_cycle,wndN,args.guess_cnt) else: bi,ei,cost = find_best_zero_crossing(xV,bi,ei,wndN) if False: if i == 0: bi,ei,cost = find_loop_points(xV,bli,eli,smp_per_cycle,wndN,args.guess_cnt) else: pass #print(i,bi,ei) eli = ei # attempt to make the eli the second channel close to the first loop_dur_sec = (ei-bi)/srate cyc_per_loop = int(round((ei-bi)/smp_per_cycle)) plot_title = f"vel:{vel_label} cyc/loop:{cyc_per_loop} dur:{loop_dur_sec*1000:.1f} ms {ei-bi} smp ch:{ch_idx} cost:{0 if cost<= 0 else math.log(cost):.2f}" plot_overlap(xV,bi,ei,wndN,smp_per_cycle,plot_title) r["chL"].append({ "ch_idx":ch_idx, "segL":[] }) r["chL"][-1]["segL"].append({ "cost":0, "cyc_per_loop":1, "bsi":beg_smp_idx, "esi":bi }) r["chL"][-1]["segL"].append({ "cost":cost, "cyc_per_loop":cyc_per_loop, "bsi":bi, "esi":ei }) esi = max(esi,ei) r['end_smp_idx'] = esi r["chL"] = sorted(r["chL"],key=lambda x: x["ch_idx"]) wtL.append(r) return wtL def write_loop_label_file( fname, wtL, srate ): with open(fname,"w") as f: for r in wtL: for cr in r['chL']: for sr in cr['segL']: beg_sec = sr['bsi'] / srate end_sec = sr['esi'] / srate if sr['cost']!=0: cost = math.log(sr['cost']) label = f"ch:{cr['ch_idx']} {r['vel']} {cost:.2f}" f.write(f"{beg_sec}\t{end_sec}\t{label}\n") def write_wt_file( fname, audio_fname, wtL, srate ): r = { "audio_fname":audio_fname, #"srate":srate, "wt":wtL } with open(fname,"w") as f: json.dump(r,f); def gen_loop_positions( audio_fname, marker_tsv_fname, pitch, argsD, loop_marker_fname, wt_fname ): args = types.SimpleNamespace(**argsD) markL = wt_util.parse_marker_file(marker_tsv_fname) smpM,srate = wt_util.parse_audio_file(audio_fname) chN = smpM.shape[1] wtL = process_all_samples(markL,smpM,srate,args) write_loop_label_file(loop_marker_fname, wtL, srate) write_wt_file(wt_fname,audio_fname, wtL,srate) if __name__ == "__main__": audio_fname = "/home/kevin/temp/wt/wav/60_samples.wav" marker_tsv_fname = "/home/kevin/temp/wt/60_marker.txt" loop_marker_fname = "/home/kevin/temp/wt/60_loop_mark.txt" wt_fname = "/home/kevin/temp/wt/bank/60_wt.json" midi_pitch = 60 argsD = { 'end_offset_ms':100, 'loop_dur_ms':100, 'midi_pitch':midi_pitch, 'guess_cnt':40 } gen_loop_positions( audio_fname, marker_tsv_fname, midi_pitch, argsD, loop_marker_fname, wt_fname )