libcw/py/gen_wavetables/old/wt_study.py
2024-09-05 11:17:08 -04:00

324 lines
8.6 KiB
Python

import math
import json
import array
import types
import matplotlib.pyplot as plt
import numpy as np
import wt_util
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 table_read_2( tab, frac ):
i0 = math.floor(frac + 1)
i1 = i0 + 1
f = frac - int(frac)
return tab[i0] + (tab[i1] - tab[i0]) * f
def hann_read( x, N ):
while x > N:
x -= N
x = x - (N/2)
return (0.5 + 0.5 * math.cos(2*math.pi * x / N))
def sine_0():
srate = 48000.0
hz = wt_util.midi_pitch_to_hz(6)
fsmp_per_cyc = srate / hz # fractional samples per cycle
fsmp_per_wt = 2* fsmp_per_cyc # fractional samples per wavetable
smp_per_wt = int(math.floor(fsmp_per_wt)) # integer samples per wavetable
# Note that when wrapping from the last sample to the first there is less
# than one sample period and so the wavetable phase after the wrap will be fractional
# fill two wave tables with two identical cycles of a sine signal
wt0 = [0] + [ math.sin(2*math.pi*hz*i/srate) for i in range(smp_per_wt) ] + [0]
wt1 = [0] + [ math.sin(2*math.pi*hz*i/srate) for i in range(smp_per_wt) ] + [0]
xN = smp_per_wt * 4
phs0 = 0
phs1 = fsmp_per_wt/2
y0 = []
y1 = []
y2 = []
h0 = []
h1 = []
for i in range(xN):
# read the wave tables
s0 = table_read_2( wt0, phs0 )
s1 = table_read_2( wt1, phs1 )
y0.append( s0 )
y1.append( s1 )
# calc the envelopes
e0 = hann_read(phs0,fsmp_per_wt)
e1 = hann_read(phs1,fsmp_per_wt)
h0.append( e0 )
h1.append( e1 )
# sum the two signals
y2.append( e0*s0 + e1*s1 )
# advance the phases of the oscillators
phs0 += 1
if phs0 >= smp_per_wt:
phs0 -= smp_per_wt
phs1 += 1
if phs1 >= smp_per_wt:
phs1 -= smp_per_wt
fix,ax = plt.subplots(4,1)
ax[0].plot(y0)
ax[1].plot(y1)
ax[2].plot(h0)
ax[2].plot(h1)
ax[3].plot(y2)
plt.show()
def piano_0():
i_audio_fname = "/home/kevin/temp/wt1/wav/060_samples.wav"
o_audio_fname = "/home/kevin/temp/temp.wav"
marker_tsv_fname = "/home/kevin/temp/wt1/60_marker.txt"
midi_pitch = 60
offs_ms = 50
note_dur_secs = 3
inter_note_secs = 0.5
markL = wt_util.parse_marker_file(marker_tsv_fname)
aM,srate = wt_util.parse_audio_file(i_audio_fname)
hz = wt_util.midi_pitch_to_hz(midi_pitch)
fsmp_per_cyc = srate/hz
fsmp_per_wt = fsmp_per_cyc * 2
smp_per_wt = int(math.floor(fsmp_per_wt))
offs_smp = int(math.floor(offs_ms * srate / 1000))
ch_cnt = aM.shape[1]
note_dur_smp = int(round(note_dur_secs*srate))
inter_note_smp = int(round(inter_note_secs*srate))
yN = len(markL) * (note_dur_smp + inter_note_smp)
yM = np.zeros((yN,ch_cnt))
yi = 0
for beg_sec,end_sec,vel_label in markL:
bsi = int(round(beg_sec * srate))
esi = int(round(end_sec * srate))
for ch_idx in range(ch_cnt):
wtbi = find_zero_crossing(aM[:,ch_idx],esi-offs_smp,-1)
wtei = wtbi + smp_per_wt
wt = [0] + aM[wtbi:wtei,ch_idx].tolist() + [0]
wt[0] = aM[wtei-1,ch_idx]
wt[-1] = wt[1]
atkN = wtbi - bsi
yM[yi:yi+atkN,ch_idx] = aM[bsi:wtbi,ch_idx]
phs0 = 0
phs1 =fsmp_per_wt/2
for i in range(note_dur_smp-atkN):
s0 = table_read_2( wt, phs0 )
s1 = table_read_2( wt, phs1 )
e0 = hann_read(phs0,fsmp_per_wt)
e1 = hann_read(phs1,fsmp_per_wt)
# advance the phases of the oscillators
phs0 += 1
if phs0 >= smp_per_wt:
phs0 -= smp_per_wt
phs1 += 1
if phs1 >= smp_per_wt:
phs1 -= smp_per_wt
yM[yi+atkN+i,ch_idx] = e0*s0 + e1*s1
yi += note_dur_smp + inter_note_smp
wt_util.write_audio_file( yM, srate, o_audio_fname )
def select_wave_table( aV, si, smp_per_wt ):
wtbi = find_zero_crossing(aV,si,-1)
wtei = wtbi + smp_per_wt
wt = [0] + aV[wtbi:wtei].tolist() + [0]
wt[0] = aV[wtei-1]
wt[-1] = wt[1]
return wt,wtbi
def piano_1():
o_audio_fname = "/home/kevin/temp/temp.wav"
o_mark_tsv_fname = "/home/kevin/temp/temp_mark.txt"
if False:
i_audio_fname = "/home/kevin/temp/wt1/wav/060_samples.wav"
marker_tsv_fname = "/home/kevin/temp/wt1/60_marker.txt"
midi_pitch = 60
offs_0_ms = 100
offs_1_ms = 50
g_coeff = 0.9985
if True:
i_audio_fname = "/home/kevin/temp/wt3/wav/21_samples.wav"
marker_tsv_fname = "/home/kevin/temp/wt3/21_marker.txt"
midi_pitch = 21
offs_0_ms = 100
offs_1_ms = 80
g_coeff = 0.9992
note_dur_secs = 6
inter_note_secs = 0.5
markL = wt_util.parse_marker_file(marker_tsv_fname)
aM,srate = wt_util.parse_audio_file(i_audio_fname)
hz = wt_util.midi_pitch_to_hz(midi_pitch)
fsmp_per_cyc = srate/hz
fsmp_per_wt = fsmp_per_cyc * 2
smp_per_wt = int(math.floor(fsmp_per_wt))
offs_0_smp = int(math.floor(offs_0_ms * srate / 1000))
offs_1_smp = int(math.floor(offs_1_ms * srate / 1000))
ch_cnt = aM.shape[1]
note_dur_smp = int(round(note_dur_secs*srate))
inter_note_smp = int(round(inter_note_secs*srate))
yN = len(markL) * (note_dur_smp + inter_note_smp)
yM = np.zeros((yN,ch_cnt))
yi = 0
oMarkL = []
for beg_sec,end_sec,vel_label in markL:
bsi = int(round(beg_sec * srate))
esi = int(round(end_sec * srate))
for ch_idx in range(ch_cnt):
wt0,wtbi = select_wave_table( aM[:,ch_idx], esi-offs_0_smp, smp_per_wt )
wt1,_ = select_wave_table( aM[:,ch_idx], esi-offs_1_smp, smp_per_wt)
rms0 = np.pow( np.mean( np.pow(wt0,2) ),0.5)
rms1 = np.pow( np.mean( np.pow(wt1,2) ),0.5)
wt1 = [ w*rms0/rms1 for w in wt1 ]
# The attack abutts the wavetable at it's center point
# so we need to offset the end of the attack half way
# through the first wave table.
abi = int(wtbi + smp_per_wt/2)
atkN = abi - bsi
yM[yi:yi+atkN,ch_idx] = aM[bsi:abi,ch_idx]
oMarkL.append(((yi+atkN)/srate, (yi+atkN)/srate, f"{vel_label}-{ch_idx}"))
phs0 = 0
phs1 = fsmp_per_wt/2
phs2 = fsmp_per_wt/4
phs3 = fsmp_per_wt/4 + fsmp_per_wt/2
g = 1.0
g_phs = 0
for i in range(note_dur_smp-atkN):
s0 = table_read_2( wt0, phs0 )
s1 = table_read_2( wt0, phs1 )
s2 = table_read_2( wt1, phs2 )
s3 = table_read_2( wt1, phs3 )
e0 = hann_read(phs0,fsmp_per_wt)
e1 = hann_read(phs1,fsmp_per_wt)
e2 = hann_read(phs0,fsmp_per_wt)
e3 = hann_read(phs1,fsmp_per_wt)
# advance the phases of the oscillators
phs0 += 1
if phs0 >= smp_per_wt:
phs0 -= smp_per_wt
phs1 += 1
if phs1 >= smp_per_wt:
phs1 -= smp_per_wt
phs2 += 1
if phs2 >= smp_per_wt:
phs2 -= smp_per_wt
phs3 += 1
if phs3 >= smp_per_wt:
phs3 -= smp_per_wt
mix_g = math.cos(0.25*2*math.pi*i/srate)
#yM[yi+atkN+i,ch_idx] = g* (mix_g*(e0*s0 + e1*s1) + (1.0-mix_g)*(e2*s2 + e3*s3))
yM[yi+atkN+i,ch_idx] = g* (e0*s0 + e1*s1)
g_phs += 1
if g_phs >= 64:
g *= g_coeff
g_phs = 0
yi += note_dur_smp + inter_note_smp
wt_util.write_audio_file( yM, srate, o_audio_fname )
wt_util.write_mark_tsv_file(oMarkL, o_mark_tsv_fname)
if __name__ == "__main__":
#sine_0()
piano_1()