Skip to content

Commit beba9ff

Browse files
committed
Remove window-function dep
1 parent e6e62c2 commit beba9ff

68 files changed

Lines changed: 13452 additions & 126 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.work/research.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,3 +576,57 @@ prototype poles → analog transform (LP→HP/BP/BS) → bilinear → digital SO
576576
All three filter families (Butterworth, Chebyshev, Bessel) refactored to use this pipeline. Each module now only defines its prototype poles — the rest is shared.
577577

578578
**Bug found and fixed**: Butterworth damping coefficient used `cos` instead of `sin` — gave wrong -3dB frequency for all odd orders (3,5,7,9). Order 3 was 597 Hz instead of 1000 Hz.
579+
580+
581+
582+
## Questions
583+
584+
What would qualify as an offering?
585+
A library that saves engineers from reimplementing known math. 53 filters, each correct, tested against scipy, documented with plot + formula + code + "use when." Someone designing a hearing aid, building a synth, or filtering ECG data can start in minutes instead of days. That's service.
586+
587+
What secret analogy unites this with nature?
588+
A filter is a prism. White light in, spectrum separated. Every physical system is a filter — a room, an ear, a wire. This library is a box of prisms.
589+
590+
What paradox lives here?
591+
The simplest filters (onePole, movingAverage) are the most used. The most mathematically elegant (elliptic, Legendre) are rarely needed. The library must honor both.
592+
593+
What's the territory?
594+
Between scipy (Python, scientific) and Web Audio (browser, limited). For JavaScript engineers who need real DSP without switching languages.
595+
596+
What are all the ways this could be useful?
597+
Audio plugins, hearing aids, ECG monitors, guitar pedals, noise cancellation, radar, speech recognition, sensor fusion, music production, broadcast loudness, scientific measurement, education.
598+
599+
Who already solved for an adjacent pain?
600+
scipy.signal (Python), MATLAB DSP Toolbox, Julius Smith's online books, Web Audio API (browser-only, limited).
601+
602+
What would be ideal result — the timeless form?
603+
npm install digital-filter and you have every classical filter, correctly implemented, with a readme that teaches you which one to pick. No configuration, no build step, no dependencies.
604+
605+
What's the theoretically pure form?
606+
One function: filter(signal, spec) where spec is "I need X" and it returns the right answer. We're close — iirdesign does this for IIR.
607+
608+
What's the theoretical minimum?
609+
biquad + filter + freqz. Everything else is convenience. But convenience is the product.
610+
611+
What's the single-player value?
612+
An engineer alone at 2am debugging a feedback loop can npm install digital-filter, design a notch, apply it, plot the response, and verify it works — all in JavaScript, no Python, no MATLAB license.
613+
614+
Is this so clear a stranger would grasp it?
615+
The readme now starts with onePole(data, { fc: 100, fs: 44100 }) — one line, immediately useful. The intro explains filters in 6 questions. The ToC shows everything at a glance. Yes.
616+
617+
What's the boundary — where does this end?
618+
Design and apply filters. Not: FFT, spectral analysis, machine learning, audio I/O, real-time scheduling. Those are different packages. audio-filter handles the domain-specific stuff.
619+
620+
What's the soul — the spark, the secret, moat, x-factor?
621+
The plots. Every filter has a 4-panel SVG showing exactly what it does. No other JS filter library has this. You see the filter before you use it.
622+
623+
What's the spine everything hangs on?
624+
SOS format. Every IIR filter returns the same [{b0,b1,b2,a1,a2}, ...] array. filter() processes it. freqz() analyzes it. plotFilter() visualizes it. One format, entire ecosystem.
625+
626+
What's the price — and am I willing to pay it?
627+
Maintaining correctness as the library grows. Every new filter needs tests, plots, docs. The scipy cross-validation tests are the insurance policy.
628+
629+
What unlocks everything?
630+
The package split. digital-filter = pure DSP primitives (domain-agnostic). audio-filter = audio-specific (depends on digital-filter). Each is focused, testable, documentable. The plot library is reusable. The readme is one page.
631+
632+
Current state assessment: Ready to ship. 53 filters, 160 tests, 1206 assertions, scipy-validated, 55+ plots, one-page readme with intro + reference + choosing + recipes + FAQ + pitfalls + bibliography. The good-to-have items are polish, not blockers.

core/window.js

Lines changed: 0 additions & 48 deletions
This file was deleted.

fir/differentiator.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* @module digital-filter/differentiator
66
*/
77

8-
import * as windows from '../core/window.js'
8+
import { getWindow } from './util.js'
99

1010
/**
1111
* @param {number} N - Filter length (must be odd)
@@ -23,7 +23,7 @@ export default function differentiator (N, opts) {
2323
else h[i] = Math.cos(Math.PI * n) / n // (-1)^n / n
2424
}
2525

26-
let win = (opts.window ? windows[opts.window] : windows.hamming)(N)
26+
let win = getWindow(opts.window, N)
2727
for (let i = 0; i < N; i++) h[i] *= win[i]
2828

2929
if (opts.fs) for (let i = 0; i < N; i++) h[i] *= opts.fs

fir/firwin.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as windows from '../core/window.js'
1+
import { getWindow } from './util.js'
22

33
/**
44
* Design FIR filter using the window method.
@@ -80,9 +80,3 @@ export default function firwin (numtaps, cutoff, fs, opts) {
8080
return h
8181
}
8282

83-
function getWindow (win, N) {
84-
if (win instanceof Float64Array || Array.isArray(win)) return win
85-
if (typeof win === 'function') return win(N)
86-
if (typeof win === 'string' && windows[win]) return windows[win](N)
87-
return windows.hamming(N)
88-
}

fir/firwin2.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* @module digital-filter/firwin2
66
*/
77

8-
import * as windows from '../core/window.js'
8+
import { getWindow } from './util.js'
99

1010
let { cos, PI } = Math
1111

@@ -58,8 +58,7 @@ export default function firwin2 (numtaps, freq, gain, opts) {
5858
}
5959

6060
// Apply window
61-
let winName = opts.window || 'hamming'
62-
let win = typeof winName === 'string' ? (windows[winName] || windows.hamming)(numtaps) : winName
61+
let win = getWindow(opts.window, numtaps)
6362
for (let i = 0; i < numtaps; i++) out[i] *= win[i]
6463

6564
return out

fir/hilbert.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { hamming } from '../core/window.js'
1+
import { getWindow } from './util.js'
22

33
/**
44
* Generate Hilbert transform FIR coefficients.
@@ -24,8 +24,7 @@ export default function hilbert (N, opts) {
2424
}
2525

2626
// Apply window
27-
let win = opts.window || hamming(N)
28-
if (typeof win === 'function') win = win(N)
27+
let win = getWindow(opts.window, N)
2928
for (let i = 0; i < N; i++) h[i] *= win[i]
3029

3130
return h

fir/util.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
let { cos, PI } = Math
2+
3+
/**
4+
* Hamming window. Inline default — no external dependency.
5+
* @param {number} N - Window length
6+
* @returns {Float64Array}
7+
*/
8+
export function hamming (N) {
9+
let w = new Float64Array(N)
10+
for (let i = 0; i < N; i++) w[i] = 0.54 - 0.46 * cos(2 * PI * i / (N - 1))
11+
return w
12+
}
13+
14+
/**
15+
* Resolve a window argument to a Float64Array.
16+
* Accepts: Float64Array/Array (pass through), function(N)→array, or nothing (default hamming).
17+
* @param {Float64Array|Array|Function|undefined} win
18+
* @param {number} N
19+
* @returns {Float64Array|Array}
20+
*/
21+
export function getWindow (win, N) {
22+
if (win instanceof Float64Array || Array.isArray(win)) return win
23+
if (typeof win === 'function') return win(N)
24+
return hamming(N)
25+
}

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@
6363
"engines": {
6464
"node": ">=18"
6565
},
66-
"dependencies": {
67-
"window-function": "^3.0.0"
68-
},
66+
"dependencies": {},
6967
"devDependencies": {
7068
"tst": "^9.4.0"
7169
}

plot/bessel.svg

Lines changed: 202 additions & 1 deletion
Loading

0 commit comments

Comments
 (0)