Skip to content

Commit 7dd9bca

Browse files
filter types
1 parent 8e65932 commit 7dd9bca

6 files changed

Lines changed: 162 additions & 29 deletions

File tree

all-panels/index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
<h1>Digital Audio Workbench (Sampling and Quantization)</h1>
2525

2626
<h2>By Arden Butterfield, Josh Rohs, Travis J. West & Marcelo M. Wanderley
27-
with contributions by Eduardo Meneses, Christian Frisson, Erivan Duarte, Laurent Tarabout, and Maxwell Gentili-Morin</h2>
28-
<h3>Copyright IDMIL/McGill University, 2025</h3>
27+
with contributions by Laurent Tarabout</h2>
28+
<h3>Copyright CIRMMT/McGill University, 2025, based on IDMIL's <h ref="https://idmil.github.io/DigitalAudioWorkbench/">Digital Audio Workbench</h></h3>
2929

3030
<div class="section" id="input-section">
3131
<div class="titlebar">
@@ -66,6 +66,7 @@ <h2>Filter</h2>
6666
</div>
6767
<div class="row sliders">
6868
<div class="slider" id="antialiasing-filter-order-slider"></div>
69+
<div class="slider" id="filter-type-slider"></div>
6970
</div>
7071
<button class="play-button" id="play-filter-kernel">Play kernel</button>
7172
<button class="play-button" id="play-filtered-input">Play filtered input</button>

ideas

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
play quantization error separately
2+
filter types (butterworth, cheb)
3+
contriubtions: only Laurent Tarabout
4+
pinned floating panel for input signal?
5+
change name --CIRMMT not IDMIL (based on...)
6+
7+
graph pos + neg bits in ds
8+
9+
noise shaping - push outside of audible band, especially w/ ds

panel.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// Canned documentation blurbs
2-
//Panel class. should be extended with a drawPanel method
31
const log10 = Math.log(10);
42
function linToDB(a, a_0 = 1)
53
{

slider.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ class AudioInputTypeSlider extends Slider{
104104
this.slopeSel.option("1/x2");
105105
this.slopeSel.option("lin");
106106
this.slopeSel.option("flat");
107+
this.slopeSel.option("log");
108+
this.slopeSel.option("flute");
109+
this.slopeSel.option("clarinet");
110+
this.slopeSel.option("french horn");
111+
this.slopeSel.option("violin");
107112
this.slopeSel.option("vowel a");
108113
this.slopeSel.option("vowel e");
109114
this.slopeSel.option("vowel i");
@@ -127,6 +132,24 @@ class AudioInputTypeSlider extends Slider{
127132
}
128133
}
129134

135+
class FilterTypeSlider extends Slider {
136+
setup(p, settings) {
137+
this.settings = settings;
138+
this.name = "Filter Type";
139+
this.propName = "filterType";
140+
this.inputSelect = p.createSelect();
141+
this.inputSelect.option("FIR");
142+
this.inputSelect.option("Butterworth");
143+
this.inputSelect.option("Chebyshev");
144+
this.inputSelect.changed(() => this.settings.filterType = this.inputSelect.value());
145+
}
146+
147+
resize(x, y, w, p) {
148+
this.inputSelect.width = w;
149+
this.inputSelect.position(x, y);
150+
}
151+
}
152+
130153
class FreqSlider extends RangedSlider{
131154
setup(p,settings){
132155
this.settings = settings;

waves.js

Lines changed: 125 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,61 @@ function calculateHarmonics(settings) {
120120
let invert = 1;
121121
let harmInc = (settings.harmType === "Odd" || settings.harmType === "Even") ? 2 : 1;
122122

123+
124+
// data from SHARC dataset: https://web.archive.org/web/20090226034059/http://www.timbre.ws/sharc/
125+
const clarinetHarmonics = [
126+
1.0, 0.020330578512396693, 0.5368506493506493, 0.045386658795749706,
127+
0.39042207792207795, 0.13839728453364816, 0.49614521841794573, 0.038146399055489964,
128+
0.10071428571428573, 0.05957201889020071, 0.0363370720188902, 0.08095926800472256,
129+
0.03358028335301062, 0.046177685950413216, 0.008293978748524203, 0.026933293978748524,
130+
0.011124557260920896, 0.008400236127508854, 0.0048524203069657615, 0.011481700118063754,
131+
0.008500590318772138, 0.008288075560802834, 0.0031316410861865407, 0.0030991735537190084,
132+
0.0025974025974025974, 0.004126328217237308, 0.000655253837072019, 0.00017709563164108617,
133+
0.00012101534828807555, 0.0004309327036599764, 0.000678866587957497, 0.0006434474616292798,
134+
0.0004929161747343566, 0.0006463990554899646, 0.00035419126328217233, 0.00037190082644628097,
135+
0.0001180637544273908, 0.0005814639905548997
136+
]
137+
138+
const frenchHornHarmonics = [
139+
0.23043554773427188, 0.6242410910690718, 1.0, 0.8554861416630004,
140+
0.6396172459304883, 0.5116344038715354, 0.4244610646722393, 0.29865816102067755,
141+
0.19071271447426308, 0.1496480422349318, 0.12175758908930928, 0.07521997360316762,
142+
0.04647162340519138, 0.03197096348438187, 0.015767707875054996, 0.01661020677518698,
143+
0.009472063352397713, 0.00906291245050594, 0.009069511658600968, 0.005954685437747471,
144+
0.004100307963044435, 0.004392872855257369, 0.004025516937967444, 0.003570171579410471,
145+
0.00291684997800264, 0.0012736471623405192, 0.0020875494940607127, 0.0014672239331280246,
146+
0.0008205015398152222, 0.00031896172459304885, 0.00042894852617685877, 0.00042454905411350635,
147+
0.0002001759788825341, 0.00022657281126264847, 0.00020897492300923887, 0.00010558732952045754,
148+
7.259128904531456e-05, 0.0002067751869775627, 0.00014078310602727673, 6.379234491860976e-05,
149+
5.9392872855257375e-05, 0.00010118785745710515, 2.419709634843819e-05, 0.00011218653761548615,
150+
8.798944126704795e-05, 9.458864936207654e-05, 0.00012758468983721952, 4.6194456665200174e-05,
151+
9.238891333040035e-05, 8.139023317201936e-05, 0.0001847778266608007, 0.00015618125824901012,
152+
0.0001913770347558293, 0.00015618125824901012, 0.00017817861856577212, 0.0002441706995160581,
153+
3.7395512538495386e-05, 2.8596568411790586e-05, 0.00014738231412230534, 0.00011658600967883854,
154+
0.00022657281126264847, 0.00012098548174219095, 0.0001253849538055433, 0.00011218653761548615,
155+
5.719313682358117e-05, 5.0593928728552574e-05, 8.798944126704795e-05, 0.0001539815222173339,
156+
9.678838539375275e-05, 0.00016058073031236252, 0.00014518257809062912, 5.279366476022877e-05
157+
];
158+
159+
fluteHarmonics = [
160+
1.0, 0.7543711967545639, 0.1576450304259635, 0.1966977687626775,
161+
0.1143265720081136, 0.0762657200811359, 0.012525354969574036, 0.009004056795131846,
162+
0.0065091277890466535, 0.003862068965517241, 0.002328600405679513, 0.002464503042596349,
163+
0.002342799188640974, 0.001308316430020284, 0.0006450304259634889, 0.0001338742393509128,
164+
0.0010304259634888438, 0.0005436105476673428, 0.00010750507099391482, 0.0009574036511156186,
165+
0.000744421906693712, 0.0005578093306288032, 0.0006754563894523326, 0.00043002028397565926,
166+
0.0003387423935091278
167+
];
168+
169+
violinHarmonics = [
170+
0.46135830072666295, 1.0, 0.8625675423886714, 0.29511645239426126,
171+
0.9508216880939073, 0.2635252468790758, 0.06555617663499161, 0.03791503633314701,
172+
0.0403204769890069, 0.06457611328488913, 0.006828768399478293, 0.006172908514999069,
173+
0.0010508664058133034, 0.0038755356810136017, 0.00038382709148500094, 0.0020029811812930873,
174+
0.004158747903856903, 0.002397987702627166, 0.0010527296441214832, 0.0009782001117942985,
175+
0.0007080305571082541, 0.0008999441028507547, 0.000456493385504006, 0.0008347307620644682
176+
]
177+
123178
for (let i = 0; i < settings.numHarm; i++) {
124179

125180
// the amplitude of each harmonic depends on the harmonic slope setting
@@ -129,6 +184,14 @@ function calculateHarmonics(settings) {
129184
else if (settings.harmSlope === "flat") harmonic_amplitude = 1;
130185
else if (settings.harmSlope === "log") {
131186
harmonic_amplitude = Math.exp(-0.1 * (harmonic_number - 1));
187+
} else if (settings.harmSlope === "clarinet") {
188+
harmonic_amplitude = i < clarinetHarmonics.length ? clarinetHarmonics[i] : 0;
189+
} else if (settings.harmSlope === "french horn") {
190+
harmonic_amplitude = i < frenchHornHarmonics.length ? frenchHornHarmonics[i] : 0;
191+
} else if (settings.harmSlope === "flute") {
192+
harmonic_amplitude = i < fluteHarmonics.length ? fluteHarmonics[i] : 0;
193+
} else if (settings.harmSlope === "violin") {
194+
harmonic_amplitude = i < violinHarmonics.length ? violinHarmonics[i] : 0;
132195
} else if (settings.harmSlope === "vowel a") {
133196
harmonic_amplitude = formantFrequencyStrength(harmonic_number * settings.fundFreq,
134197
850, 1610, 0.2);
@@ -219,38 +282,75 @@ function normalize(arr, targetAmplitude) {
219282
arr.forEach((x, n, y) => y[n] = targetAmplitude * x / amp);
220283
}
221284

222-
function filterSignal(signal, frequency, order) {
285+
function filterSignal(signal, frequency, order, mode, filterKernel) {
223286
// specify the filter parameters; Fs = sampling rate, Fc = cutoff frequency
224287

225288
// The cutoff for the antialiasing filter is set to the Nyquist frequency
226289
// of the simulated sampling process. The sampling rate of the "sampled"
227290
// signal is WEBAUDIO_MAX_SAMPLERATE / the downsampling factor. This is
228291
// divided by 2 to get the Nyquist frequency.
229-
let firCalculator = new Fili.FirCoeffs();
230292

231-
let filterCoeffs = firCalculator.lowpass(
232-
{
233-
order: order
234-
, Fs: WEBAUDIO_MAX_SAMPLERATE
235-
, Fc: frequency
293+
if (mode === "FIR") {
294+
let firCalculator = new Fili.FirCoeffs();
295+
296+
let filterCoeffs = firCalculator.lowpass(
297+
{
298+
order: order
299+
, Fs: WEBAUDIO_MAX_SAMPLERATE
300+
, Fc: frequency
301+
});
302+
303+
// generate the filter
304+
let filter = new Fili.FirFilter(filterCoeffs);
305+
306+
// apply the filter
307+
// filter.multiStep(signal);
308+
signal.forEach((x, n, y) => y[n] = filter.singleStep(x));
309+
310+
// time shift the signal by half the filter order to compensate for the
311+
// delay introduced by the FIR filter
312+
const shift = order / 2;
313+
for (let i = 0; i < signal.length - shift; i++) {
314+
signal[i] = signal[i + shift];
315+
}
316+
for (let i = signal.length - shift; i < signal.length; i++) {
317+
signal[i] = 0;
318+
}
319+
320+
if (filterKernel) {
321+
for (let i = 0; i < filterCoeffs.length; i++) {
322+
filterKernel[i] = filterCoeffs[i];
323+
}
324+
}
325+
} else if (mode === "Butterworth" || mode === "Chebyshev") {
326+
let iirCalculator = new Fili.CalcCascades();
327+
328+
let characteristic = mode === "Butterworth" ? "butterworth" : "tschebyscheff05";
329+
330+
order = mode === "Butterworth" ? Math.min(order, 12) : Math.min(order, 4);
331+
332+
let filterCoeffs = iirCalculator.lowpass({
333+
order: order, // cascade 3 biquad filters (max: 12)
334+
characteristic: characteristic,
335+
transform: characteristic === "tschebyscheff05" ? 'matchedZ' : undefined,
336+
Fs: WEBAUDIO_MAX_SAMPLERATE, // sampling frequency
337+
Fc: frequency, // cutoff frequency / center frequency for bandpass, bandstop, peak
338+
preGain: false // adds one constant multiplication for highpass and lowpass
339+
// k = (1 + cos(omega)) * 0.5 / k = 1 with preGain == false
236340
});
237341

238-
// generate the filter
239-
let filter = new Fili.FirFilter(filterCoeffs);
342+
let filter = new Fili.IirFilter(filterCoeffs);
240343

241-
// apply the filter
242-
signal.forEach((x, n, y) => y[n] = filter.singleStep(x));
344+
signal.forEach((x, n, y) => y[n] = filter.singleStep(x));
243345

244-
// time shift the signal by half the filter order to compensate for the
245-
// delay introduced by the FIR filter
246-
const shift = order / 2;
247-
for (let i = 0; i < signal.length - shift; i++) {
248-
signal[i] = signal[i + shift];
249-
}
250-
for (let i = signal.length - shift; i < signal.length; i++) {
251-
signal[i] = 0;
346+
if (filterKernel) {
347+
filterKernel[0] = 1;
348+
let filter = new Fili.IirFilter(filterCoeffs);
349+
filterKernel.forEach((x, n, y) => y[n] = filter.singleStep(x));
350+
}
252351
}
253-
return filterCoeffs;
352+
353+
// return filterCoeffs;
254354
}
255355

256356
function getDither(ditherType) {
@@ -396,11 +496,11 @@ function applyAntialiasingFilter(settings, fft, playback) {
396496
, Fc: cutoff
397497
});
398498

399-
for (let i = 0; i < filterCoeffs.length; i++) {
400-
filterKernel[i] = filterCoeffs[i];
401-
}
402499

403-
filterSignal(original, cutoff, order);
500+
501+
filterSignal(original, cutoff, order, settings.filterType, filterKernel);
502+
} else {
503+
filterKernel[0] = 1;
404504
}
405505
}
406506

@@ -489,7 +589,7 @@ function antiImagingFilter(settings, fft, playback) {
489589
? settings.reconstructionFilterFrequency
490590
: (WEBAUDIO_MAX_SAMPLERATE / settings.downsamplingFactor) / 2;
491591

492-
filterSignal(reconstructed, freq, settings.reconstructionFilterOrder); // TODO: slider for order, start at 200
592+
filterSignal(reconstructed, freq, settings.reconstructionFilterOrder, 'FIR'); // TODO: slider for order, start at 200
493593
}
494594

495595
function renderWavesImpl(

widget.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ function getDefaultSettings() {
5757
, quantType: "midRise" // type of quantization
5858
, dither: 0.0 // amplitude of white noise added to signal before quantization
5959
, antialiasing: 0 // antialiasing filter order
60+
, filterType: "FIR"
6061
, reconstructionFilterOrder: 200
6162
, deltaSigmaStep: 0.1
6263
, downsampled: new Float32Array(1) // this gets re-inited when rendering waves
@@ -103,6 +104,7 @@ let sliderIdLookups = {
103104
'frequency-slider' : FreqSlider,
104105
'num-harmonics-slider' : NumHarmSlider,
105106
'antialiasing-filter-order-slider': AntialiasingSlider,
107+
'filter-type-slider' : FilterTypeSlider,
106108
'reconstruction-filter-order-slider': ReconstructionOrderSlider,
107109
'reconstruction-filter-freq-slider' : ReconstructionFilterFreqSlider,
108110
'sample-rate-slider' : SampleRateSlider,

0 commit comments

Comments
 (0)