247 lines
7.1 KiB
Python
247 lines
7.1 KiB
Python
import numpy as np
|
|
#from IPython.display import display
|
|
from matplotlib.pylab import show
|
|
|
|
from .util import *
|
|
from .data import *
|
|
gen_filters = lambda cascade, srate: [
|
|
s2z(*f[1], fc=f[0], srate=srate, gain=10**(f[2]/20)) for f in cascade
|
|
]
|
|
from .bq import *
|
|
from .butterworth import *
|
|
from .sweeps import *
|
|
from .smoothfft import *
|
|
from .plot import *
|
|
from .wav import *
|
|
from .planes import *
|
|
from .fft import *
|
|
from .bs import *
|
|
|
|
def analog(b, a):
|
|
import sympy as sym
|
|
w,s = sym.symbols('w s')
|
|
filt_expr = sym.Poly(b, s)/sym.Poly(a, s)
|
|
mag_expr = abs(filt_expr.subs({s: w*sym.I}))**2
|
|
return sym.lambdify(w, mag_expr, 'numpy')
|
|
|
|
def makemag(w0, ba, gain=0):
|
|
f = analog(*ba)
|
|
def magf(w):
|
|
a = f(w/w0)
|
|
a[0] = 1e-35
|
|
a = np.log10(a)*10 + gain
|
|
a[0] = a[1] # safety measure
|
|
return a
|
|
return magf
|
|
|
|
def test_filter_raw(ba, fc=1000, gain=0, precision=4096):
|
|
fig, ax = new_response(ymin=-24, ymax=24)
|
|
xs = xsp(precision)
|
|
ax.semilogx(xs, makemag(fc, ba, gain)(xs))
|
|
show(fig)
|
|
|
|
def test_filter(ff, A=toA(12), Q=toQ(1), **kwargs):
|
|
test_filter_raw(ff(A, Q), **kwargs)
|
|
|
|
npc = [makemag(*f) for f in cascades['raw']]
|
|
def neonpink(xs):
|
|
print("neonpink(): DEPRECATED")
|
|
combined = np.zeros(len(xs))
|
|
for f in npc:
|
|
combined += f(xs)
|
|
return combined
|
|
|
|
def c_render(cascade, precision=4096):
|
|
xs = xsp(precision)
|
|
ys = np.zeros_like(xs)
|
|
c = [makemag(*f) for f in cascade]
|
|
for f in c:
|
|
ys += f(xs)
|
|
return xs, ys
|
|
|
|
def firize(xs, ys, n=4096, srate=44100, plot=None):
|
|
import scipy.signal as sig
|
|
if plot:
|
|
plot.semilogx(xs, ys, label='desired')
|
|
xf = xs/srate*2
|
|
yg = 10**(ys/20)
|
|
|
|
xf = np.hstack((0, xf, 1))
|
|
yg = np.hstack((0, yg, yg[-1]))
|
|
|
|
b = sig.firwin2(n, xf, yg, antisymmetric=True)
|
|
|
|
if plot:
|
|
_, ys = sig.freqz(b, worN=xs/srate*tau)
|
|
ys = 20*np.log10(np.abs(ys))
|
|
plot.semilogx(xs, ys, label='FIR ({} taps)'.format(n))
|
|
plot.legend(loc=8)
|
|
|
|
return b
|
|
|
|
def normalize_test(s, srate):
|
|
# FIXME: where is fir3 defined?
|
|
#rms_naive = np.sqrt(np.mean(s**2))
|
|
filters3 = gen_filters(cascades['raw3'], srate)
|
|
db_standard = BS1770_3(s, srate)
|
|
db = BS1770_3(s, srate, filters=filters3)
|
|
print('raw3 would be\t{:+6.2f} dB louder/quieter than RG2'.format(db_standard - db))
|
|
db = BS1770_3(s, srate, filters=[fir3])
|
|
print('fir3 would be\t{:+6.2f} dB louder/quieter than RG2'.format(db_standard - db))
|
|
rms = 10**(db/20)
|
|
return s/rms, rms
|
|
|
|
def tilter(xs, ys, tilt):
|
|
"""tilts a magnitude plot by some decibels, or by equalizer curve."""
|
|
# should really just do this instead:
|
|
# ys -= tilt(xs, 3)
|
|
print("tilter(): DEPRECATED")
|
|
if tilt == 'neon':
|
|
noise = neonpink(xs)
|
|
elif type(tilt) is str:
|
|
noise = np.zeros(len(xs))
|
|
c = [makemag(*f) for f in cascades[tilt]]
|
|
for f in c:
|
|
noise += f(xs)
|
|
elif isinstance(tilt, int) or isinstance(tilt, float):
|
|
noise = tilt*(np.log2(1000) - np.log2(xs))
|
|
else:
|
|
noise = np.zeros(xs.shape)
|
|
return xs, ys - noise
|
|
|
|
def tilter2(xs, tilt):
|
|
if type(tilt) is str:
|
|
noise = np.zeros(len(xs))
|
|
c = [makemag(*f) for f in cascades[tilt]]
|
|
for f in c:
|
|
noise += f(xs)
|
|
elif isinstance(tilt, int) or isinstance(tilt, float):
|
|
noise = tilt*(np.log2(1000) - np.log2(xs + 1e-35))
|
|
else:
|
|
noise = np.zeros(xs.shape)
|
|
return noise
|
|
|
|
def plotwavsmooth(fn, ax, tilt=None, bw=1, size=8192, raw=False, fix=False, smoother=smoothfft2, **kwargs):
|
|
s, srate = wav_read(fn)
|
|
|
|
s, rms = normalize(s, srate)
|
|
sm = monoize(s)
|
|
ss = monoize(s*np.array((1, -1)))
|
|
|
|
xs_raw = np.arange(0, srate/2, srate/2/size)
|
|
ys_raw = averfft(sm, size=size)
|
|
|
|
# tilting beforehand is negligible besides lowest frequencies, but eh
|
|
if tilt is not None:
|
|
ys_raw -= tilter2(xs_raw, tilt)
|
|
|
|
xs, ys = smoother(xs_raw, ys_raw, bw=bw)
|
|
|
|
if not 'label' in kwargs:
|
|
kwargs['label'] = fn
|
|
|
|
if raw:
|
|
ax.semilogx(xs_raw, ys_raw, **kwargs)
|
|
ax.semilogx(xs, ys, **kwargs)
|
|
|
|
if not fix: return
|
|
|
|
fno = fn[:-4]+"-proc.wav"
|
|
fir = firize(xs, -ys, srate=srate)
|
|
sf = convolve_each(s/8, fir, mode='same')
|
|
|
|
import ewave
|
|
with ewave.open(fno, 'w', sampling_rate=srate, nchannels=count_channels(sf)) as f:
|
|
f.write(sf)
|
|
print('wrote '+fno)
|
|
|
|
def plotfftsmooth(s, srate, ax, bw=1, tilt=None, size=8192, window=0, raw=False, **kwargs):
|
|
sm = monoize(s)
|
|
|
|
xs_raw = np.arange(0, srate/2, srate/2/size)
|
|
ys_raw = averfft(sm, size=size, mode=window)
|
|
|
|
ys_raw -= tilter2(xs_raw, tilt)
|
|
|
|
xs, ys = smoothfft(xs_raw, ys_raw, bw=bw)
|
|
|
|
if raw: ax.semilogx(xs_raw, ys_raw, **kwargs)
|
|
ax.semilogx(xs, ys, **kwargs)
|
|
|
|
return xs, ys
|
|
|
|
def plotwav2(fn, ax, bw=1, size=8192, raw=False, fix=False,
|
|
smoother=smoothfft2, side_compensate=9, **kwargs):
|
|
s, srate = wav_read(fn)
|
|
|
|
s, rms = normalize(s, srate)
|
|
sm = monoize(s)
|
|
ss = monoize(s*np.array((1, -1)))
|
|
|
|
xs_raw = np.arange(0, srate/2, srate/2/size)
|
|
ys_raw = averfft(sm, size=size)
|
|
ys_raw_side = averfft(ss, size=size)
|
|
|
|
# tilting beforehand is negligible besides lowest frequencies, but eh
|
|
ys_raw -= tilter2(xs_raw, 'np2')
|
|
ys_raw_side -= tilter2(xs_raw, 'np2s')
|
|
|
|
xs, ys = smoother(xs_raw, ys_raw, bw=bw)
|
|
xs, ys_side = smoother(xs_raw, ys_raw_side, bw=bw)
|
|
|
|
if not 'label' in kwargs:
|
|
kwargs['label'] = fn
|
|
|
|
if raw:
|
|
ax.semilogx(xs_raw, ys_raw, **kwargs)
|
|
ax.semilogx(xs_raw, ys_raw_side + side_compensate, **kwargs)
|
|
ax.semilogx(xs, ys, **kwargs)
|
|
ax.semilogx(xs, ys_side + side_compensate, **kwargs)
|
|
|
|
side_gain = np.average(ys_raw_side) - np.average(ys_raw)
|
|
print("side gain:", side_gain)
|
|
|
|
if not fix: return
|
|
fno = fn[:-4]+"-proc.wav"
|
|
|
|
fir = firize(xs, -ys, srate=srate)
|
|
smf = convolve_each(sm/8, fir, mode='same')
|
|
fir = firize(xs, -ys_side, srate=srate)
|
|
ssf = convolve_each(ss/8, fir, mode='same')
|
|
ssf *= 10**(side_gain/20)
|
|
sf = np.array((smf + ssf, smf - ssf)).T
|
|
|
|
import ewave
|
|
with ewave.open(fno, 'w', sampling_rate=srate, nchannels=count_channels(sf)) as f:
|
|
f.write(sf)
|
|
print('wrote '+fno)
|
|
|
|
def pw(fn, ax, **kwargs):
|
|
plotwavsmooth(fn, ax, tilt='np2', bw=1/6, **kwargs)
|
|
|
|
def pwc(fn, **kwargs):
|
|
fig, ax = new_response(-18, 18)
|
|
ax.set_title('averaged magnitudes of normalized songs with tilt and smoothing')
|
|
|
|
pw(fn, ax, fix=True, **kwargs)
|
|
fno = fn[:-4]+"-proc.wav"
|
|
pw(fno, ax, fix=False, **kwargs)
|
|
|
|
ax.legend(loc=8)
|
|
show(fig)
|
|
|
|
def pw2(fn, **kwargs):
|
|
fig, ax = new_response(-18, 18)
|
|
ax.set_title('averaged magnitudes of normalized songs with tilt and smoothing')
|
|
|
|
plotwav2(fn, ax, fix=True, bw=1/6, **kwargs)
|
|
fno = fn[:-4]+"-proc.wav"
|
|
plotwav2(fno, ax, fix=False, bw=1/6, **kwargs)
|
|
|
|
ax.legend(loc=8)
|
|
show(fig)
|
|
|
|
# this is similar to default behaviour of having no __all__ variable at all,
|
|
# but ours ignores modules as well. this allows for `import sys` and such
|
|
# without clobbering `from our_module import *`.
|
|
__all__ = [o for o in locals() if type(o) != 'module' and not o.startswith('_')]
|