The sawtest.py script generates audio samples for various filter implementations (including matrix exponential and TPT) with various input signals and settings. It should be useful for comparing the quality of the various implementations, even though it's pretty slow.master
parent
cc93548845
commit
75902125d5
@ -0,0 +1,383 @@ |
||||
import argparse |
||||
import struct |
||||
import numpy as np |
||||
from math import * |
||||
|
||||
samplerate = 44100 * 1 |
||||
|
||||
# generate a saw matching the parameters of "fun with sigmoids" |
||||
def sawperiod(): |
||||
result = [] |
||||
f0 = 63. * 2 * pi / samplerate |
||||
for i in range(samplerate / 63): |
||||
y = 0 |
||||
x0 = i * f0 |
||||
for partial in range(1, 351): |
||||
gain = 1.0 / partial |
||||
if partial > 300: |
||||
gain *= (351 - partial) * (1.0 / (351 - 300)) |
||||
y += gain * sin(partial * x0) |
||||
result.append(y) |
||||
return result |
||||
|
||||
def saw(n): |
||||
return sawperiod() * (n / (samplerate / 63)) |
||||
|
||||
def sinsweep(n): |
||||
fmax = 22000 * pi * 2 / samplerate |
||||
scale = fmax * .5 / n |
||||
return [sin(i * i * scale) for i in range(n)] |
||||
|
||||
def sweep(n): |
||||
n2 = n / 2 |
||||
lamin = log(20 * 2 * pi / samplerate) |
||||
lamax = log(14000 * 2 * pi / samplerate) |
||||
result = [] |
||||
slope = (lamax - lamin) / n2 |
||||
for i in range(n2): |
||||
a = exp(lamin + slope * i) |
||||
result.append(a) |
||||
return result + result[-1::-1] |
||||
|
||||
# Based on code by mystran (Teemu Voipio) |
||||
# See http://www.kvraudio.com/forum/viewtopic.php?t=349859 |
||||
|
||||
def tanhXdx(x): |
||||
a = x * x |
||||
return ((a + 105.0)* a + 945.0) / ((15.0 * a + 420.0) * a + 945.0) |
||||
|
||||
def tpt_nl(xs, aas, k): |
||||
z1 = 0 |
||||
s = [0] * 4 |
||||
r = k |
||||
result = [] |
||||
for i in range(len(xs)): |
||||
xin = xs[i] |
||||
a = aas[i] |
||||
f = tan(a * .5) |
||||
ih = .5 * (xin + z1) |
||||
z1 = xin |
||||
|
||||
t0 = tanhXdx(ih - r * s[3]) |
||||
t1 = tanhXdx(s[0]) |
||||
t2 = tanhXdx(s[1]) |
||||
t3 = tanhXdx(s[2]) |
||||
t4 = tanhXdx(s[3]) |
||||
g0 = 1 / (1 + f * t1) |
||||
g1 = 1 / (1 + f * t2) |
||||
g2 = 1 / (1 + f * t3) |
||||
g3 = 1 / (1 + f * t4) |
||||
f3 = f * t3 * g3 |
||||
f2 = f * t2 * g2 * f3 |
||||
f1 = f * t1 * g1 * f2 |
||||
f0 = f * t0 * g0 * f1 |
||||
y3 = (g3*s[3] + f3*g2*s[2] + f2*g1*s[1] + f1*g0*s[0] + f0*xin)/(1 + r * f0) |
||||
xx = t0 * (xin - r * y3) |
||||
y0 = t1 * g0 * (s[0] + f * xx) |
||||
y1 = t2 * g1 * (s[1] + f * y0) |
||||
y2 = t3 * g2 * (s[2] + f * y1) |
||||
s[0] += 2 * f * (xx - y0) |
||||
s[1] += 2 * f * (y0 - y1) |
||||
s[2] += 2 * f * (y1 - y2) |
||||
s[3] += 2 * f * (y2 - t4 * y3) |
||||
result.append(y3) |
||||
return result |
||||
|
||||
def antti_nl(xs, aas, k, tanhfunc = tanh): |
||||
y0 = 0 |
||||
y1 = 0 |
||||
y2 = 0 |
||||
y3 = 0 |
||||
ty0 = 0 |
||||
ty1 = 0 |
||||
ty2 = 0 |
||||
ty3 = 0 |
||||
yy = 0 |
||||
result = [] |
||||
for i in range(len(xs)): |
||||
xin = xs[i] |
||||
a = aas[i] |
||||
tx = tanhfunc(xin - k * (y3 + yy) * .5) |
||||
y0 += a * (tx - ty0) |
||||
yy = y3 |
||||
ty0 = tanhfunc(y0) |
||||
y1 += a * (ty0 - ty1) |
||||
ty1 = tanhfunc(y1) |
||||
y2 += a * (ty1 - ty2) |
||||
ty2 = tanhfunc(y2) |
||||
y3 += a * (ty2 - ty3) |
||||
ty3 = tanhfunc(y3) |
||||
x = 0 |
||||
result.append(y3) |
||||
return result |
||||
|
||||
def expm_series(A, n = 16): |
||||
B = np.identity(len(A)) |
||||
C = np.identity(len(A)) |
||||
for i in range(1, n): |
||||
C = A * C / i |
||||
B = B + C |
||||
return B |
||||
|
||||
def expm_hyb(A, n1 = 8, n2 = 8): |
||||
A = expm_series(A / (1 << n2), n1) |
||||
for i in range(n2): |
||||
A = A * A |
||||
return A |
||||
|
||||
def mkjacobian(a, k): |
||||
return np.matrix([[0, 0, 0, 0, 0], |
||||
[a, -a, 0, 0, -k * a], |
||||
[0, a, -a, 0, 0], |
||||
[0, 0, a, -a, 0], |
||||
[0, 0, 0, a, -a]]) |
||||
|
||||
def expm_nl(xs, aas, k, tanhfunc = tanh, every = 64): |
||||
kk = k |
||||
y = np.zeros([4, 1]) |
||||
ty = y |
||||
result = [] |
||||
for i in range(len(xs)): |
||||
if i % every == 0: |
||||
a = aas[i] |
||||
A = expm_hyb(mkjacobian(a, k)) |
||||
|
||||
B = A[1:, 0] |
||||
A = A[1:, 1:] |
||||
AM = A - np.identity(4) |
||||
|
||||
for j in range(4): |
||||
AM[j, 3] += B[j, 0] * kk |
||||
|
||||
x = xs[i] |
||||
tx = tanhfunc(x - kk * y[3, 0]) |
||||
y += B * tx + AM * ty |
||||
ty = np.matrix([tanhfunc(x[0]) for x in y]).T |
||||
|
||||
result.append(y[3, 0]) |
||||
return result |
||||
|
||||
def invsqrt(x): |
||||
return x / sqrt(1 + x ** 2) |
||||
|
||||
def clip(x): |
||||
return min(1, max(-1, x)) |
||||
|
||||
fir = map(float, '''-0.00000152158394097619 |
||||
-0.00000932875737718674 |
||||
-0.00001008290833020705 |
||||
0.00000728628960094510 |
||||
0.00002272556429291851 |
||||
-0.00000017886444625648 |
||||
-0.00004038828866646377 |
||||
-0.00002127235603321839 |
||||
0.00005623314602326363 |
||||
0.00006233931357689778 |
||||
-0.00005884569707596701 |
||||
-0.00012410666372671377 |
||||
0.00003231781361429016 |
||||
0.00019973378481126099 |
||||
0.00004097428652472217 |
||||
-0.00027125558280978066 |
||||
-0.00017513777576291543 |
||||
0.00030833229435342778 |
||||
0.00037363561338823163 |
||||
-0.00027028256701905416 |
||||
-0.00062153272838533420 |
||||
0.00011242294792251808 |
||||
0.00087909017443692499 |
||||
0.00020309303892565388 |
||||
-0.00107928469499717137 |
||||
-0.00069305675670084180 |
||||
0.00113148992142813524 |
||||
0.00133794323705832747 |
||||
-0.00093286682198106608 |
||||
-0.00206892788224323455 |
||||
0.00038777937772768273 |
||||
0.00276141532345176117 |
||||
0.00056670689248167661 |
||||
-0.00323852878331680298 |
||||
-0.00193259906597622396 |
||||
0.00328731491441583683 |
||||
0.00362542589345449945 |
||||
-0.00268785554870503091 |
||||
-0.00545702740033716938 |
||||
0.00125317559909794512 |
||||
0.00713205061850966104 |
||||
0.00112492149537589880 |
||||
-0.00826148096867550946 |
||||
-0.00443085208574918715 |
||||
0.00839336065985300112 |
||||
0.00848839486761648020 |
||||
-0.00705662837862476577 |
||||
-0.01293781936476604867 |
||||
0.00380913247729417143 |
||||
0.01722862947876550518 |
||||
0.00172513205576642695 |
||||
-0.02062091118580804822 |
||||
-0.00985374287540894019 |
||||
0.02217056516274141381 |
||||
0.02089533076058044253 |
||||
-0.02062760756367006815 |
||||
-0.03546365160613443313 |
||||
0.01401630243169032369 |
||||
0.05541063386809867708 |
||||
0.00212045427486363437 |
||||
-0.08794052708207862612 |
||||
-0.04526389505656687462 |
||||
0.18221762668014460096 |
||||
0.41075960732889965632 |
||||
0.41075960732889965632 |
||||
0.18221762668014460096 |
||||
-0.04526389505656687462 |
||||
-0.08794052708207862612 |
||||
0.00212045427486363437 |
||||
0.05541063386809867708 |
||||
0.01401630243169032369 |
||||
-0.03546365160613443313 |
||||
-0.02062760756367006815 |
||||
0.02089533076058044253 |
||||
0.02217056516274141381 |
||||
-0.00985374287540894019 |
||||
-0.02062091118580804822 |
||||
0.00172513205576642695 |
||||
0.01722862947876550518 |
||||
0.00380913247729417143 |
||||
-0.01293781936476604867 |
||||
-0.00705662837862476577 |
||||
0.00848839486761648020 |
||||
0.00839336065985300112 |
||||
-0.00443085208574918715 |
||||
-0.00826148096867550946 |
||||
0.00112492149537589880 |
||||
0.00713205061850966104 |
||||
0.00125317559909794512 |
||||
-0.00545702740033716938 |
||||
-0.00268785554870503091 |
||||
0.00362542589345449945 |
||||
0.00328731491441583683 |
||||
-0.00193259906597622396 |
||||
-0.00323852878331680298 |
||||
0.00056670689248167661 |
||||
0.00276141532345176117 |
||||
0.00038777937772768273 |
||||
-0.00206892788224323455 |
||||
-0.00093286682198106608 |
||||
0.00133794323705832747 |
||||
0.00113148992142813524 |
||||
-0.00069305675670084180 |
||||
-0.00107928469499717137 |
||||
0.00020309303892565388 |
||||
0.00087909017443692499 |
||||
0.00011242294792251808 |
||||
-0.00062153272838533420 |
||||
-0.00027028256701905416 |
||||
0.00037363561338823163 |
||||
0.00030833229435342778 |
||||
-0.00017513777576291543 |
||||
-0.00027125558280978066 |
||||
0.00004097428652472217 |
||||
0.00019973378481126099 |
||||
0.00003231781361429016 |
||||
-0.00012410666372671377 |
||||
-0.00005884569707596701 |
||||
0.00006233931357689778 |
||||
0.00005623314602326363 |
||||
-0.00002127235603321839 |
||||
-0.00004038828866646377 |
||||
-0.00000017886444625648 |
||||
0.00002272556429291851 |
||||
0.00000728628960094510 |
||||
-0.00001008290833020705 |
||||
-0.00000932875737718674 |
||||
-0.00000152158394097619 |
||||
'''.split()) |
||||
|
||||
fir_n = len(fir) |
||||
|
||||
# would probably be faster to use numpy.convolve and slice, but oh well |
||||
def downsample(data): |
||||
result = [] |
||||
buf = [0] * fir_n |
||||
i = 0 |
||||
for x in data: |
||||
buf[i] = x |
||||
i = (i + 1) & (fir_n - 1) |
||||
if (i & 1) == 0: |
||||
y = 0 |
||||
for j in range(len(fir)): |
||||
y += fir[j] * buf[(i + j) & (fir_n - 1)] |
||||
result.append(y) |
||||
return result |
||||
|
||||
def wavwrite(seq, fn, sr = 44100): |
||||
f = file(fn, 'wb') |
||||
n_samples = len(seq) |
||||
f.write(struct.pack('<4sI4s4sIHHIIHH4sI', |
||||
'RIFF', |
||||
36 + 2 * n_samples, |
||||
'WAVE', |
||||
'fmt ', |
||||
16, |
||||
1, 1, |
||||
sr, |
||||
2 * sr, |
||||
2, 16, |
||||
'data', |
||||
2 * n_samples)) |
||||
for x in seq: |
||||
f.write(struct.pack('<h', min(32767, max(-32767, int(16384 * x))))) |
||||
|
||||
def main(): |
||||
parser = argparse.ArgumentParser() |
||||
parser.add_argument("--oversample", help="oversample factor") |
||||
parser.add_argument("--signal", help="saw or sinsweep") |
||||
parser.add_argument("--k", help="resonance, 4 = oscillate") |
||||
parser.add_argument("--filter", help="tpt, antti, or expm") |
||||
parser.add_argument("--tanhfunc", help="tanh or invsqrt") |
||||
parser.add_argument("--cutoff") |
||||
parser.add_argument("--gain", help="gain in dB") |
||||
parser.add_argument("--ogain", help="outputgain in dB") |
||||
parser.add_argument("--out", help="output wav file") |
||||
args = parser.parse_args() |
||||
oversample = 1 |
||||
if args.oversample: |
||||
oversample = int(args.oversample) |
||||
k = 0 |
||||
if args.k: |
||||
k = float(args.k) |
||||
signal = 'saw' |
||||
if args.signal: |
||||
signal = args.signal |
||||
global samplerate |
||||
samplerate *= oversample |
||||
n = 6 * samplerate |
||||
if signal == 'saw': |
||||
input = saw(n) |
||||
aas = sweep(n) |
||||
elif signal == 'sinsweep': |
||||
input = sinsweep(n) |
||||
aas = [1000./samplerate * 2 * pi] * n |
||||
gain = 1 |
||||
if args.gain: |
||||
gain = 10 ** (float(args.gain)/20) |
||||
input = [y * gain for y in input] |
||||
tanhfunc = tanh |
||||
if args.tanhfunc == 'invsqrt': |
||||
tanhfunc = invsqrt |
||||
if args.filter == 'tpt': |
||||
result = tpt_nl(input, aas, k) |
||||
elif args.filter == 'antti': |
||||
result = antti_nl(input, aas, k, tanhfunc = tanhfunc) |
||||
else: |
||||
result = expm_nl(input, aas, k, tanhfunc = tanhfunc) |
||||
while oversample > 1: |
||||
result = downsample(result) |
||||
oversample /= 2 |
||||
ogain = 1 |
||||
if args.ogain: |
||||
ogain = 10 ** (float(args.ogain)/20) |
||||
if args.out: |
||||
wavwrite(result, args.out) |
||||
|
||||
main() |
Loading…
Reference in new issue