libcw/py/sample_looper.py

283 lines
7.4 KiB
Python
Raw Normal View History

2024-07-07 17:36:42 +00:00
import math
import json
import wave as w
import array
import types
import matplotlib.pyplot as plt
import numpy as np
def parse_marker_file( marker_fname ):
markL = []
with open(marker_fname) as f:
for line in f:
tokL = line.split("\t");
assert( len(tokL) == 3 )
markL.append( ( float(tokL[0]), float(tokL[1]), tokL[2] ) )
return markL
def parse_audio_file( audio_fname ):
with w.open(audio_fname,"rb") as f:
print(f"ch:{f.getnchannels()} bits:{f.getsampwidth()} srate:{f.getframerate()} frms:{f.getnframes()}")
srate = f.getframerate()
frmN = f.getnframes()
data_bytes = f.readframes(frmN)
smpM = np.array(array.array('i',data_bytes))
smpM = np.reshape(smpM,(frmN,2))
if 0:
fig, ax = plt.subplots(1,1)
ax.plot(smpM[0:48000*10,1])
plt.show()
return smpM,srate
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 ):
while si > 0:
if sign(xV[si-1]) != sign(xV[si]):
break;
si += inc
return si
def meas_fit(xV,ei,bi,wndN):
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( xV, bsi, esi, smp_per_cycle, wndN, est_N ):
ei = find_zero_crossing(xV,esi,-1)
min_d = None
min_bi = None
bi = bsi
for i in range(0,est_N):
bi = find_zero_crossing(xV,bi,1)
d = meas_fit(xV,ei,bi,wndN)
#print(i,bi,d)
if min_bi is None or d < min_d:
min_d = d
min_bi = bi
bi += int(wndN/2) #smp_per_cycle
return min_bi, ei, min_d
def process_all_samples_0( 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))
end_offs_smp_idx = int(args.end_offset_ms * srate / 1000)
loop_dur_smp = int(args.loop_dur_ms * srate / 1000)
smp_per_cycle = int(srate / fund_hz)
print(f"Hz:{fund_hz} smp/cycle:{smp_per_cycle}")
for beg_sec,end_sec,vel_label in markL:
for ch_idx in range(0,smpM.shape[1]):
beg_smp_idx = int(beg_sec * srate)
end_smp_idx = int(end_sec * srate)
eli = end_smp_idx - end_offs_smp_idx
bli = eli - loop_dur_smp
#print(beg_smp_idx,bli,eli,end_smp_idx)
xV = smpM[:,ch_idx]
wndN = int(smp_per_cycle/3)
bi,ei,cost = find_loop_points(xV,bli,eli,smp_per_cycle,wndN,args.guess_cnt)
plot_title = f"vel:{vel_label} ch:{ch_idx} cost:{math.log(cost):.2f}"
#plot_overlap(xV,bi,ei,wndN,smp_per_cycle,plot_title)
wtL.append( {
"pitch":args.midi_pitch,
"vel": int(vel_label),
"cost":cost,
"ch_idx":ch_idx,
"beg_smp_idx":beg_smp_idx,
"end_smp_idx":end_smp_idx,
"beg_loop_idx":bi,
"end_loop_idx":ei })
return wtL
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))
end_offs_smp_idx = int(args.end_offset_ms * srate / 1000)
loop_dur_smp = int(args.loop_dur_ms * srate / 1000)
smp_per_cycle = int(srate / fund_hz)
print(f"Hz:{fund_hz} smp/cycle:{smp_per_cycle}")
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": []
}
eli = end_smp_idx - end_offs_smp_idx
bli = eli - loop_dur_smp
esi = beg_smp_idx;
for ch_idx in range(0,smpM.shape[1]):
xV = smpM[:,ch_idx]
wndN = int(smp_per_cycle/3)
bi,ei,cost = find_loop_points(xV,bli,eli,smp_per_cycle,wndN,args.guess_cnt)
plot_title = f"vel:{vel_label} ch:{ch_idx} cost:{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"][ch_idx]["segL"].append({
"cost":0,
"bsi":beg_smp_idx,
"esi":bi })
r["chL"][ch_idx]["segL"].append({
"cost":cost,
"bsi":bi,
"esi":ei })
esi = max(esi,ei)
r['end_smp_idx'] = esi
wtL.append(r)
return wtL
def write_loop_label_file_0( fname, wtL, srate ):
with open(fname,"w") as f:
for r in wtL:
beg_sec = r['beg_smp_idx'] / srate
end_sec = r['end_smp_idx'] / srate
# f.write(f"{beg_sec}\t{end_sec}\t{r['vel_label']}\n")
beg_sec = r['beg_loop_idx'] / srate
end_sec = r['end_loop_idx'] / srate
cost = math.log(r['cost'])
label = f"ch:{r['ch_idx']} {cost:.2f}"
f.write(f"{beg_sec}\t{end_sec}\t{label}\n")
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']} {cost:.2f}"
f.write(f"{r['vel']} {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_fname, pitch, argsD, loop_marker_fname, wt_fname ):
args = types.SimpleNamespace(**argsD)
markL = parse_marker_file(marker_fname)
smpM,srate = 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)
2024-07-07 17:36:42 +00:00
if __name__ == "__main__":
audio_fname = "/home/kevin/temp/wt/wav/60_samples.wav"
marker_fname = "/home/kevin/temp/wt/60_marker.txt"
2024-07-07 17:36:42 +00:00
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 = {
2024-07-07 17:36:42 +00:00
'end_offset_ms':100,
'loop_dur_ms':100,
'midi_pitch':midi_pitch,
'guess_cnt':40
}
gen_loop_positions( audio_fname, marker_fname, midi_pitch, argsD, loop_marker_fname, wt_fname )