Home │ Audio
Home Page |
(1) |
H(s) = |
(s – z1)
(s – p1)(s – p2) |
(2) |
ω = 2πf0 |
(3) |
z = esT, where T = | 1
fs |
the inverse of the sampling
frequency. |
(4) |
p(z) = ep(s)T, where p(s) is in natural frequency ω = 2πf0 |
(5) |
p(z) = e |
2πf0
fs |
(6) |
H(z) = |
z
– z1
(z – p1)(z – p2) |
(7) |
H(z) = |
z
– z1
z2 + (– p1– p2)z + p1p2 |
(8) |
H(z) = |
b0 + b1z-1 1 + a1z-1 + a2z-2 |
(9) |
ejωT = cos(ωT) + sin(ωT)j |
(10) |
ωz = ωT = | ω
fs |
(11) |
fz = fT = | f
fs |
Figure
1:
riaaiir.c
program
listing |
#include <stdio.h> #include <string.h> #include <math.h> typedef struct {int nn, nd; double n[3], d[3];} biquad; #define TP1 3180e-6 #define TP2 75e-6 #define TZ1 318e-6 #define TZ2 3.18e-6 #define PZ(T) exp(-1.0/(fs*(T))) biquad computeriaaiir(double fs, double dcgain, int extrazero) { static biquad bq; double p1, p2, z1, z2, gain; int i; p1 = PZ(TP1); p2 = PZ(TP2); z1 = PZ(TZ1); z2 = PZ(TZ2); bq.nd = 3; bq.d[0] = 1.0; bq.d[1] = -p1-p2; bq.d[2] = p1*p2; if(extrazero) { bq.nn = 3; bq.n[0] = 1.0; bq.n[1] = -z1-z2; bq.n[2] = z1*z2; } else { bq.nn = 2; bq.n[0] = 1.0; bq.n[1] = -z1; bq.n[2] = 0.0; } gain = (bq.n[0]+bq.n[1]+bq.n[2])/(1.0+bq.d[1]+bq.d[2]); for(i=0; i<3; i++) bq.n[i] *= (dcgain/gain); return bq; } void printbiquadcoeff(biquad bq) { int i; printf("Coefficients for H(z):\n\n"); printf("num"); for(i=0; i<bq.nn; i++) { printf("[%1d] = %e",i,bq.n[i]); if(i<bq.nn-1) printf(", "); } printf("\n--------------------------------"\ "--------------------------------\n"); printf("den"); for(i=0; i<bq.nd; i++) { printf("[%1d] = %e",i,bq.d[i]); if(i<bq.nd-1) printf(", "); } printf("\n\n"); printf("Audacity Nyquist biquad equations (LISP format)\n"); // a's are denominators printf("(biquad s b0 b1 b2 a0 a1 a2)\n"); printf("(biquad s"); for(i=0; i<3; i++) printf(" %e",bq.n[i]); printf(" 1.0"); for(i=1; i<3; i++) printf(" %e",-bq.d[i]); printf(")\n"); printf("(biquad-m s b0 b1 b2 a0 a1 a2)\n"); printf("(biquad-m s"); for(i=0; i<3; i++) printf(" %e",bq.n[i]); for(i=0; i<3; i++) printf(" %e",bq.d[i]); printf(")\n\n"); } int main() { biquad riaa; double fs, gain; int extrazero; do printf("\nEnter sampling frequency, dc gain, and 0/1 for extra zero: "); while(3 < scanf("%lf%lf%d",&fs,&gain,&extrazero)); printf("\n"); riaa = computeriaaiir(fs,gain,extrazero); printbiquadcoeff(riaa); printf("\n"); return 0; } |
Figure
2:
Plot
of
H(z)
for
coefficients
calculated
for
44.1kHz. |
Figure 3: Plot of H(z) for coefficients calculated for 96kHz. |
Figure 4: Plot of H(z) for coefficients calculated for 192kHz. |
Figure
5:
Plot
of
H(z)
for
coefficients
calculated
for
44.1kHz
with
inverse
RIAA. |
Figure 6: Plot of H(z) for coefficients calculated for 96kHz with inverse RIAA. |
Figure 7: Plot of H(z) for coefficients calculated for 192kHz with inverse RIAA. |
Figure
8:
Plot
of
H(z)
for
coefficients
calculated
for
44.1kHz
with
practical
inverse
RIAA. |
Figure 9: Plot of H(z) for coefficients calculated for 96kHz with practical inverse RIAA. |
Figure 10: Plot of H(z) for coefficients calculated for 192kHz with practical inverse RIAA. |
Figure
11:
FFT
shows
that
upsampling
to
176.4kHz
before
equalization
to
return to 44.1kHz leaves no apparent anomaly. |
(12) |
fLR
= |
R 2πL |
Figure
12:
Representative
linear
transimpedance
amplifiers
suitable
for
current
input
preamps. |
Figure 13: riaaiir.c program listing adding option of current input zero. |
#include <stdio.h> #include <string.h> #include <math.h> typedef struct {int nn, nd; double n[3], d[3];} biquad; #define TP1 3180e-6 #define TP2 75e-6 #define TZ1 318e-6 #define TZ2 3.18e-6 #define PZ(T) exp(-1.0/(fs*(T))) biquad computeriaaiir(double fs, double dcgain, double extrazero) { static biquad bq; double p1, p2, z1, z2, gain; int i; p1 = PZ(TP1); p2 = PZ(TP2); z1 = PZ(TZ1); if(extrazero > 1.0) z2 = PZ(1.0/(2*acos(-1.0)*extrazero)); else z2 = PZ(TZ2); bq.nd = 3; bq.d[0] = 1.0; bq.d[1] = -p1-p2; bq.d[2] = p1*p2; if(extrazero) { bq.nn = 3; bq.n[0] = 1.0; bq.n[1] = -z1-z2; bq.n[2] = z1*z2; } else { bq.nn = 2; bq.n[0] = 1.0; bq.n[1] = -z1; bq.n[2] = 0.0; } gain = (bq.n[0]+bq.n[1]+bq.n[2])/(1.0+bq.d[1]+bq.d[2]); for(i=0; i<3; i++) bq.n[i] *= (dcgain/gain); return bq; } void printbiquadcoeff(biquad bq) { int i; for(i=0; i<bq.nn; i++) printf("\t%e",bq.n[i]); printf("\n------------------------------------------------\n"); for(i=0; i<bq.nd; i++) printf("\t%e",bq.d[i]); printf("\n"); printf("Audacity biquad equations\n"); // believe a's are denominators printf("(biquad s b0 b1 b2 a0 a1 a2)\n"); printf("(biquad s"); for(i=0; i<3; i++) printf(" %e",bq.n[i]); printf(" 1.0"); for(i=1; i<3; i++) printf(" %e",-bq.d[i]); printf(")\n"); printf("(biquad-m s b0 b1 b2 a0 a1 a2)\n"); printf("(biquad-m s"); for(i=0; i<3; i++) printf(" %e",bq.n[i]); for(i=0; i<3; i++) printf(" %e",bq.d[i]); printf(")\n\n"); } int main() { biquad riaa; double fs, gain; double extrazero; do { printf("\nExtra zero options:\n0: No extra zero\n1: Extra zero at 50kHz" \ "\n>1: Frequency of extra zero to compensate for lowpass rolloff of current input preamp"); printf("\n\nEnter sampling frequency, dc gain, and 0/1/f(current input LR freq) for extra zero: "); } while(3 < scanf("%lf%lf%lf",&fs,&gain,&extrazero)); printf("\n"); riaa = computeriaaiir(fs,gain,extrazero); printbiquadcoeff(riaa); printf("\n"); return 0; } |
Figure 14: Plot of H(z) for coefficients calculated for fs = 96kHz and fLR = 212.2Hz. |
Figure 15: Plot of H(z) for coefficients calculated for fs = 96kHz and fLR = 212.2Hz with inverse RIAA. |
Figure 16: Equalized with fLR = 212.2Hz but actually fLR = 150Hz. Adjust fLR down. |
Figure 17: Equalized with fLR = 212.2Hz but actually fLR = 300Hz. Adjust fLR up. |
Figure
18:
Tascam
DR-05
Digital
recorder
setup
to
record
directly
from
turntable. |
|
The idea for this article was
actually based on recording directly to a high-resolution field
recorder that I already possessed: a Tascam DR-05 purchased from
Amazon.com for $89. Before recording, I calculated the frequency response in PhonoLCR using the recorder's specified 25kΩ input impedance without changing the estimated capacitance to get near Butterworth response in order to estimate what changes in sound I might expect from the different cartridge loading. I connected my turntable directly to the MIC/EXT IN input and recorded according to the recorder's instructions at 24bit/96kHz. The recording level set to a level somewhere in the middle of the range, suggesting a good fit to the output of the MM cartridge that I was using. Monitoring on the headphones, pops and crackles were very loud but I expected later equalization to greatly lower it. When finished, I equalized the recording in Audacity as I have detailed above. The first gain setting looked clipped so I recalculated the filter coefficients and tried again for correct results and returned the file to the recorder. I only intended to verify that the equalization sounded correct, which it did. The recorder's noise floor also seemed well below that of the album as well. Many years ago I had recorded the same album with a CD recorder. Those results, though they seemed accurate, seemed flat compared to listening to the vinyl while the recording proceeded. The vibrant colors of the vinyl seem muted somehow. In contrast, this high resolution recording has the vivid colors like the album, perhaps better because the treble sounded more neutral, something I attributed to cartridge loading. A test of current-input equalization await another time. |
Figure
19:
Version 1.2.0 riaaiir.c
program
listing
adds an inverse RIAA digital filter
function. |
#include <stdio.h> #include <string.h> #include <math.h> typedef struct {int nn, nd; double n[3], d[3];} biquad; #define TP1 3180e-6 #define TP2 75e-6 #define TZ1 318e-6 #define TZ2 3.18e-6 #define PZ(T) exp(-1.0/(fs*(T))) biquad computeriaaiir(double fs, double dcgain, double extrazero, int inverse) { static biquad bq; double p1, p2, z1, z2, gain, tmp; int i, itmp; p1 = PZ(TP1); p2 = PZ(TP2); z1 = PZ(TZ1); if(extrazero > 1.0) z2 = PZ(1.0/(2*acos(-1.0)*extrazero)); else z2 = PZ(TZ2); bq.nd = 3; bq.d[0] = 1.0; bq.d[1] = -p1-p2; bq.d[2] = p1*p2; if((int)extrazero) { bq.nn = 3; bq.n[0] = 1.0; bq.n[1] = -z1-z2; bq.n[2] = z1*z2; } else { bq.nn = 2; bq.n[0] = 1.0; bq.n[1] = -z1; bq.n[2] = 0.0; } if(inverse) { /* swap numerator and denominator */ for(i=0; i<3; i++) { tmp = bq.d[i]; bq.d[i] = bq.n[i]; bq.n[i] = tmp; } itmp = bq.nd; bq.nd = bq.nn; bq.nn = itmp; } gain = (bq.n[0]+bq.n[1]+bq.n[2])/(1.0+bq.d[1]+bq.d[2]); for(i=0; i<3; i++) bq.n[i] *= (dcgain/gain); return bq; } void printbiquadcoeff(biquad bq) { int i; for(i=0; i<bq.nn; i++) printf("\t%e",bq.n[i]); printf("\n------------------------------------------------\n"); for(i=0; i<bq.nd; i++) printf("\t%e",bq.d[i]); printf("\n"); printf("Audacity biquad equations\n"); // believe a's are denominators printf("(biquad s b0 b1 b2 a0 a1 a2)\n"); printf("(biquad s"); for(i=0; i<3; i++) printf(" %e",bq.n[i]); printf(" 1.0"); for(i=1; i<3; i++) printf(" %e",-bq.d[i]); printf(")\n"); printf("(biquad-m s b0 b1 b2 a0 a1 a2)\n"); printf("(biquad-m s"); for(i=0; i<3; i++) printf(" %e",bq.n[i]); for(i=0; i<3; i++) printf(" %e",bq.d[i]); printf(")\n\n"); } int main() { biquad riaa; double fs, gain; double extrazero; int inverse; do { printf("\nExtra zero options:\n0: No extra zero\n1: Extra zero at 50kHz" \ "\n>1: Frequency of extra zero to compensate for lowpass rolloff of current input preamp" \ "\nInverse options: (0) RIAA eq, (1) Inverse RIAA eq"); printf("\n\nEnter sampling frequency,\ndc gain,\n(0/1/f(current input LR freq)) for extra zero" \ "\n(0/1) to select inverse:\n\n: "); } while(4 < scanf("%lf%lf%lf%d",&fs,&gain,&extrazero,&inverse)); printf("\n"); riaa = computeriaaiir(fs,gain,extrazero,inverse); printbiquadcoeff(riaa); printf("\n"); return 0; } |
Figure
20:
Entry
dialog
for version 1.2.0 of the Windows program |
Figure 21: Plot of H(z) for coefficients calculated for 192kHz inverse filter with extra pole. |
|
Document History
June 6, 2015 Created.
June 11. 2015 Corrected a misspelling and added an extra step to
each of the compilation of riaaiir and the Audacity filtering process.
September 1, 2015 Added capability to digitally equalize a
current-input preamplifier.
September 7, 2015 Added Results of an actual test recording.
May 3, 2016 Added inverse RIAA functionality to software.