Inside the project
Prepared with the project’s development agent from source and retained evidence. This is a project-owner assessment, not an independent customer review. Dated checks were not rerun for this website.
FIRFilters is the piece missing from both sides of Apple-platform signal processing: Accelerate convolves fast but will not design a filter, while scipy and MATLAB design well and leave you to build the real-time engine elsewhere. This package does design, numerical analysis and render-callback-safe convolution in one place, entirely in double precision. The strongest part is not a feature but a disposition — it is built to make DSP fail loudly. Equiripple design used to swallow solver errors and hand back a unity passthrough; it now throws, with the four previously-bypassing specs pinned as regression cases. Its agreement with a direct-convolution reference has been checked by an external tool at 3.55e-15, about sixteen machine epsilons. It is pre-release, unlicensed and has no public distribution, so today it is engineering substance rather than a product.
What stands out
- Closes the design-to-render loop in one language: a filter can be specified, verified and convolved without leaving Swift.
- Four interchangeable convolution engines behind one protocol, including uniformly-partitioned overlap-save whose latency is the block size regardless of filter length — the right choice for reverb, HRTF and room correction.
- Numerical agreement with a direct-convolution reference externally checked at maxAbsoluteError 3.552713678800501e-15 (float64, 2026-09-01).
- Designed for real-time: pointer processBlock overloads are allocation-free, and the allocating array overloads say so in their documentation.
- Refuses silent substitution. A design that cannot be met throws instead of returning a different filter.
- Breadth beyond filtering: NLMS adaptive modelling with ERLE, polyphase and Farrow multirate, QMF and multilevel Daubechies wavelets, STFT/WOLA, Savitzky–Golay and Hampel smoothing, true-peak metering.
Current limits
- No demo material exists — no UI, no executable target, no sample audio. Nothing can be shown or played without new authoring.
- No LICENSE file, so publishing even code excerpts is an owner decision.
- No git remote: no public URL, release, tag or download. The CI workflow triggers on pushes to a remote that does not exist and would fail if run (wrong dependency name, missing test dependency, runner below the declared platform floor).
- Deployment floor is macOS 26 / iOS 26, forced by the RemezEngineFixed dependency — not a choice, and the README still advertises macOS 12 / iOS 15.
- Depends on a local fork, ../RemezEngineFixed, which the manifest asks to be folded back into ../RemezEngine. Still unresolved today.
- Test outcomes unverified in this session; no stored test report exists in the repository.
- Exntensions.swift is misspelled and roughly 3,200 lines, mixing design, analysis and multirate concerns.
- DocC omits six shipped modules (SavitzkyGolaySmoother, HampelIdentifier, TruePeakMeter, STFT, WaveletTransform, HalfbandFilter).
Features & evidence
Filter design
component-testedSpecify a filter by intent — windowed-sinc lowpass/highpass/bandpass/bandstop, one-call Kaiser from passband/stopband/attenuation, least-squares (firls), arbitrary magnitude (firwin2), frequency-sampling, multiband, Hilbert, differentiator, raised-cosine and root-raised-cosine, half-band.
Recorded evidence
Declared cases incl. testKaiserLowpassMeetsStopband, testLowpassUnityDC, testHighpassNyquistAndDC, testBandpassCentreAndBandstop, testLeastSquaresLowpass, testFirwin2ArbitraryResponse, testEvenTapsAreExactlyZero. Static inventory 2026-10-18; pass/fail not verified.
Equiripple design (Parks–McClellan)
component-testedExact equiripple via RemezEngine; a failed design throws rather than substituting another filter.
Recorded evidence
EquirippleWrapperTests + EquirippleSpecComplianceTests, 5 declared cases, incl. aFailedDesignNeverReturnsAPassthrough and aDesignedLowpassMeetsItsStopband. Dependency health corroborated second-hand by remezengine-collected.md (49 tests green, commit da7087a, 17 Oct 2026) — not verified by me.
Analysis
component-testedInspect what was actually designed: frequency response, frequency-dependent group delay valid for asymmetric filters, Type I–IV symmetry classification, per-band ripple statistics.
Recorded evidence
testGroupDelayConstantForLinearPhase, testMinimumPhaseHasLowerGroupDelay, testImpulseResponseEqualsTaps, testParseval. Static inventory 2026-10-18.
Convolution engines
component-testedRender audio through the designed filter: direct form with symmetry optimisation, two single-block overlap-save FFT variants, uniformly-partitioned overlap-save at fixed latency, equal-power crossfade for clickless live tap swaps, and an auto-selecting hybrid — all behind the FIRProcessing protocol.
Recorded evidence
testBlockConvolverMatchesDirect, testBlockConvolverRFMatchesDirect, testTwoConvolversAgree, testPartitionedConvolverMatchesDirect (+ short final block, non-power-of-two rejection), testProtocolInterchangeability, testCrossfadeConverges. Externally cross-checked at 3.55e-15, 2026-09-01.
NLMS adaptive filtering
installed-tested (indirect)Model an echo or room path from reference/desired signals, read ERLE to confirm convergence, then freeze the model into a static filter for rendering.
Recorded evidence
testNLMSConvergesAndReportsERLE and testQuietInputDoesNotFalselyReadConverged. Integrated consumer use is documented in Package.swift:50-56, which records a measured symptom in 'the voice memo chain's NLMS denoise stage' — evidence of real app integration, though the figure quoted there describes a pathological -Onone Debug build and is not a performance number.
Multirate
component-testedChange sample rate or split into bands: polyphase decimator/interpolator, rational L/M converter, Farrow fractional resampling, QMF bank, multilevel Daubechies wavelet transform (D1–D4).
Recorded evidence
testSampleRateConverterMatchesNaive, testSampleRateConverterStreamingMatchesSingleBlock, testFarrowConstantInputIsConstant, testQMFHaarPerfectReconstruction, testPerfectReconstructionAcrossWaveletsAndLevels, testEnergyAndCoefficientCountArePreserved, testHaarAgreesWithTheExistingQMFBank.
Spectral analysis/resynthesis
component-testedSTFT with WOLA overlap-add resynthesis, including an inspectable COLA envelope.
Recorded evidence
testRoundTripReconstructsSignal, testRoundTripAcrossWindowsAndHops, testToneLandsInTheExpectedBin, testDCAndNyquistAreHandled, testCOLAEnvelopeIsFlatForHannAtThreeQuarterOverlap.
Robust smoothing and metering
component-testedSavitzky–Golay smoothing and derivatives, Hampel median/MAD spike rejection, BS.1770-style true-peak metering by FIR oversampling.
Recorded evidence
SavitzkyGolayTests (9), RobustFilterTests (15). Savitzky–Golay is additionally oracle-checked against an independently coded SemanticCompute family implementation — SavitzkyGolaySemanticComputeOracleTests, 3 cases, centre weights and interior outputs, with edge-policy divergence documented deliberately.
FIRFiltersCpp
sourceStandalone C++17 port covering core filter, design, block/FFT convolvers, adaptive and filter banks; no Accelerate or RemezEngine dependency. CMake and Makefile builds plus a smoke-test example.
Recorded evidence
FIRFiltersCpp/README.md. No build, run or test evidence found; maturity unknown. Does not cover the newer spectral/wavelet/robust modules.
Documentation (DocC)
sourceLanding page, Getting Started, Choosing a Convolver.
Recorded evidence
Three .md files, no resource bundle. Buildable locally, not hosted. Omits six shipped modules.
Continuous integration
plannedmacOS build-and-test workflow.
Recorded evidence
.github/workflows/ci.yml present but cannot have run — no git remote exists; also stale on dependency name, missing test dependency, and runner below the declared platform floor.
Review updates & corrections
- NEW BUILD EVIDENCE: the package now has recorded arm64 iOS device build output. .build/ios/Build/Products/Debug-iphoneos/ contains FIRFilters.o and FIRFilters.swiftmodule/arm64-apple-ios.swiftmodule, plus the same for RemezEngine. Yesterday's biography reported macOS debug and release only, so iOS-device compilation is a genuine addition — observed 2026-10-18.
- NEWLY OBSERVED BUT NOT A CHANGE, AND NOT EVIDENCE: a Sentinal.xcworkspace sits in the package root. Its Package.resolved pins only unrelated remote packages (NIO, swift-crypto, WasmKit, SwiftCBOR and similar) with no FIRFilters or RemezEngine entry. Do NOT infer that FIRFilters ships inside an app called Sentinal — path dependencies never appear in Package.resolved, so this file is silent either way. I did not look for this file yesterday, so I cannot say it is new.
- CROSS-PACKET CONSISTENCY: remezengine-collected.md (17 Oct) reports 49 tests green at commit da7087a and alternation-theorem optimality verified (|E|/delta = 1.000000 at all 24 extremals). That is consistent with my framing that the repaired engine is the correct dependency. Second-hand; not verified by me.
- SUPERSEDED VERSION PIN: the same packet cites SemanticCompute v1.20.0, while my parity record pins v1.18.0 (buildCommit ab9c07d). My figure remains correct for its stated date, 2026-09-01 — it is a dated record, not a current claim. If you want a current number it needs re-running.
- UNCHANGED since the biography: still no media assets; still no LICENSE; still no git remote; dependency still ../RemezEngineFixed with reintegration outstanding; floor still macOS 26 / iOS 26; 96 declared test cases across 9 files.
Workflow walkthrough
A guide to the native workflow. These steps describe the app; only the recordings and images embedded on this page are available as website demonstrations.
- PROPOSED CAPTURE — none of this exists as an asset today, and it needs a build I am not permitted to run.
- 1. Design from intent, not guesswork: FIRFilter.designLowpassKaiser(passbandEdge: 0.18, stopbandEdge: 0.25, stopbandAttenuationDB: 60) — tap count and Kaiser beta are chosen for you.
- 2. Verify the specification was actually met: frequencyResponse(numFreqs: 1024), and show the stopband sitting at or below -60 dB. This is the step that distinguishes the package from pasting a tap array.
- 3. Cut the latency without changing the sound: minimumPhase() gives the same magnitude with far less delay; groupDelayResponse shows the delay falling.
- 4. Render it: makePartitionedConvolver(blockSize: 256) and processBlock — fixed 256-sample latency however long the filter.
- 5. Show the guard rail: request an equiripple design that cannot be met and show it throw, rather than quietly returning a different filter.
- Suggested capture: a two-panel still (magnitude response with the stopband target drawn in; group delay before/after minimum phase) plus the five code lines. Audio would be more persuasive but needs source material and a licence decision.
Planned captures · not yet available
- Magnitude response of the Kaiser lowpass from step 1 with the -60 dB stopband target marked.
- Group delay before and after minimumPhase(), same magnitude response.
- Non-executing, syntax-highlighted code block of the five-step journey, taken verbatim from README.md:38-51.
- Test report converting the 96 declared cases into a dated pass/fail record.
- Before/after of a filter or the NLMS denoise path.
Development biography · earlier archive
Development profile compiled from project source and retained records in October 2026. Dates and test results describe those records; they do not imply a new build, live acceptance test or public release.
Biography (approx. 780 words)
The problem. Anyone doing serious filtering on Apple platforms meets the same wall twice. Accelerate's vDSP gives you extremely fast convolution and FFT primitives, but it will not design a filter: you cannot hand it "60 dB stopband from 0.25 normalised" and receive taps. Conversely scipy and MATLAB design filters beautifully and then leave you to implement the real-time engine yourself, in another language, on another machine. The usual outcome is a split workflow — design offline in Python, paste a tap array into Swift — which breaks the moment the filter needs to change at runtime, and which makes the numerical behaviour of the shipping code untestable against the design intent that produced it.
FIRFilters closes that loop in one package: design → analyse → convolve, all in Double, all on vDSP.
Audience. Developers building audio or signal-processing software for Apple platforms who need more than a biquad: room correction and reverb convolution, HRTF rendering, echo cancellation, sample-rate conversion, pulse shaping for communications, and measurement. It is a library for people who care what the stopband actually is, which is why every design path has an analysis path next to it.
Origin, as documented. The repository's own files record an unusual history: the project's current shape was driven by a DSP code review whose findings are listed in REVIEW_DSP.md, with the resulting fixes catalogued in CHANGELOG.md and the subsequent feature work in IMPROVEMENTS.md. That review found real defects, not style issues. Non-linear-phase filters were being forced through a symmetric fast path and "silently produced wrong output" . The Hilbert transformer used a sampled continuous kernel instead of the ideal discrete one. A vDSP_zvmul call passed an undefined conjugate flag of 0. Frequency-sampling design produced a non-causal impulse. Convergence was reported in a way that a near-silent input could read as converged.
This origin shows in the architecture. The package is organised around the premise that DSP code fails quietly — a filter 24 dB off spec still produces plausible audio — so correctness has to be pinned by test rather than inspected by eye.
The distinctive idea: failure must be loud. The clearest example is equiripple design. FIRFilter.designEquiripple previously caught every error from the Remez solver and substituted a different filter without telling the caller — "a Hamming windowed-sinc, or a bare FIRFilter(taps: [1.0]) — a unity passthrough" . A caller asking for a three-band bandpass could receive a wire. That bypass was reachable on ordinary inputs. It now throws, and four previously-bypassing specs are pinned as regression cases.
The same instinct reaches the dependency. Package.swift:31-36 records that the original RemezEngine had an extremal search that "drove the wrong function — it reported delta = 9.4e-11 on a filter measured 24 dB off spec". A solver claiming near-perfect convergence on a filter that misses its specification by 24 dB is the exact failure mode the package is built to refuse, so the dependency was repointed at ../RemezEngineFixed and the reason written into the manifest rather than a commit message.
What it does. Design spans windowed-sinc lowpass/highpass/bandpass/bandstop, a one-call Kaiser design that chooses tap count and β from a specification, least-squares (firls) and arbitrary-magnitude (firwin2) design, exact Parks–McClellan through the dependency, frequency-sampling and multiband design, Hilbert transformers, differentiators, raised-cosine and root-raised-cosine pulse shaping, half-band filters, and cepstral minimum-phase conversion. Analysis covers frequency response, frequency-dependent group delay valid for asymmetric filters, tap-symmetry classification, and per-band ripple statistics. Convolution offers four engines — direct form with a symmetry optimisation, two single-block overlap-save FFT variants, and uniformly-partitioned overlap-save whose latency is fixed by block size regardless of filter length — unified behind one FIRProcessing protocol, plus an auto-selecting hybrid and an equal-power crossfade engine for clickless live tap swaps. Beyond filtering: NLMS adaptive filtering with leakage and ERLE reporting, polyphase multirate, Farrow fractional resampling, QMF and multilevel Daubechies wavelet banks, STFT with WOLA resynthesis, Savitzky–Golay and Hampel smoothing, and a true-peak meter.
A concrete journey. A developer correcting a room measures the impulse response with AdaptiveFIRFilter, reads erleDB to confirm the model converged, freezes it with asFIRFilter(sampleRate:), converts it with minimumPhase() to cut latency while preserving magnitude, checks the result with groupDelayResponse, then renders through makePartitionedConvolver(blockSize: 256) — fixed 256-sample latency however long the filter — calling the allocation-free pointer overload inside the render callback. Design, verification and rendering never leave Swift, and every step has a test behind it.
Modules and features
What a visitor can see, hear or try: nothing directly. No UI, no demo, no audio. What can honestly be shown on a website is code — the README.md and DocC snippets — and prose about verified numerical behaviour.
Dependencies, not authored here
RemezEngine(from../RemezEngineFixed) — exact Parks–McClellan.SemanticCompute— test target only;Package.swift:38-40states "the library stays free of SemanticCompute".- Accelerate/vDSP, Foundation — Apple.
4b. FIRFiltersCpp — related module, cover separately
Standalone C++17 port at FIRFiltersCpp/, "intentionally independent of the Swift package… does not depend on Accelerate or RemezEngine" , including native C++ replacements for the Remez-style public types. Ships CMake and plain-Makefile builds and examples/smoke_test.cpp. Maturity unknown — no build or run was performed for this profile, and no test report was established. Covers a subset: core filter, design, block/FFT convolvers, adaptive, filter banks. Not the newer spectral/wavelet/robust modules.
Technical foundations
- Double precision throughout (
vDSP_..._D,[Double]taps) — deliberate, and the reason the SemanticCompute parity work mattered. - Real-time discipline — pointer
processBlock(input:output:count:)overloads are allocation-free; array-returning overloads allocate and are documented as such . - Swift 6 concurrency —
@unchecked Sendableon single-stream processors. - Optimisation flag with a measured reason —
Package.swift:50-56forces-Oeven in Debug because a consumer's Debug build compiled the package at-Ononeand "the voice memo chain's NLMS denoise stage took 7.4 s for 2 s of audio in Debug". Note:-Okeeps IEEE semantics, so results are unchanged. - Documentation auditing —
Scripts/audit-doc-citations.pyverifies the mapping document'sfile:linecitations against source and its named families against a live catalogue, exiting non-zero on failure.
Maturity by feature, with evidence
Verified: source implemented + tests declared. 96 test cases across 9 files (static count; 91 correctness/property, 5 measure benchmarks). Property-level coverage includes: asymmetric-filter correctness vs direct convolution; both block convolvers vs direct; Hilbert even-tap zeros and antisymmetry; frequency-sampling centring; sample-rate conversion vs naive reference and streaming-vs-single-block; NLMS ERLE convergence and the quiet-input false-positive guard; UPOLS vs direct incl. short final block; Kaiser stopband compliance; minimum-phase magnitude match and energy front-loading; QMF Haar perfect reconstruction; wavelet perfect reconstruction, energy preservation and vanishing moments; STFT round-trip across windows and hops, DC/Nyquist handling, COLA flatness; half-band exact-zero taps; Hampel breakdown-point and step-edge preservation; true-peak inter-sample overshoot; equiripple no-passthrough and stopband compliance.
Verified: built locally in both configurations. .build/arm64-apple-macosx/debug/ and /release/ both contain FIRFiltersPackageTests.derived/runner.swift.
Unverified in the retained evidence.
- Test pass/fail result. No new test run was performed for this profile. Treat "96 tests declared", not "96 tests passing", as the supported claim.
- Dates and versions. No git access. The only dated evidence in-repo is in
SEMANTICCOMPUTE_UPGRADES.md: "Revised 2026-08-27… re-verified 2026-09-01 against SemanticCompute v1.18.0 (buildCommit ab9c07d, 163 families)".CHANGELOG.md:3says "Dates approximate". No release dates exist. - CI status. Config present; no remote; cannot have run. Additionally stale: it checks out
RemezEngine(manifest needsRemezEngineFixed), never checks out SemanticCompute, and targetsmacos-14/Xcode 16 against a.macOS(.v26)floor — it would fail if run. - FIRFiltersCpp — unknown at every level beyond source present.
One externally-verified numerical result, dated 2026-09-01, from SEMANTICCOMPUTE_UPGRADES.md:23-31: 128 samples of FIRBlockConvolver compared against a direct-convolution reference through sc_check_parity precision:"float64" gave maxAbsoluteError 3.552713678800501e-15 — about 16 machine epsilons, i.e. agreement at double-precision rounding. This is the strongest publishable numerical claim in the project, and it is third-party-tool-checked rather than self-asserted.
Released/deployed: no. No release, tag, licence or distribution.
Limitations, roadmap, FAQs
Known limitations (from source)
- Deployment floor is
v26, not what the README says.README.md:76claims "macOS 12+ / iOS 15+";Package.swift:14-17declares.v26. The manifest is correct and the floor is forced byRemezEngineFixed, which itself declares.v26(verified at the project archive). SwiftPM requires a package's floor ≥ its dependencies'. Lowering it means lowering RemezEngine first. - A dependency is a local fork pending reintegration.
Package.swift:35-36asks forRemezEngineFixedto be folded back intoRemezEngine"once the uncommitted work in that checkout has been resolved". Exntensions.swiftis misspelled and ~3,200 lines, carrying design, analysis and multirate concerns together.IMPROVEMENTS.md:71-75defers the split and rename as "risky to do blind".- Documentation lags source. DocC lists none of the newer modules (
SavitzkyGolaySmoother,HampelIdentifier,TruePeakMeter,STFT,WaveletTransform,HalfbandFilter);CHANGELOG.mdomits them too and still describes the superseded../RemezEnginepath and lowered floor. - No licence.
Roadmap Full vDSP.FFT Swift-overlay migration; vectorising the UPOLS per-partition multiply-accumulate with vDSP_zvmaD; splitting FIRFilter into an immutable design value plus a stateful processor (source-breaking); splitting and renaming Exntensions.swift; folding RemezEngineFixed back into RemezEngine. Not scheduled, not dated.
FAQs
- Why not just use vDSP? vDSP convolves and transforms but does not design. It has no equivalent of "give me 60 dB of stopband attenuation from 0.25" returning taps, and no order estimator, response analyser or minimum-phase conversion.
- Why double precision when audio is usually float? Because design and analysis are where precision matters — a −90 dB stopband is below float32 resolution in the intermediate arithmetic. The externally-checked agreement figure of 3.55e-15 is only meaningful in double.
- Which convolver should I use? Short filters: direct form. Long filters where latency matters — reverb, HRTF, room correction:
FIRPartitionedConvolver, whose latency is the block size regardless of filter length.FIRRealtimeConvolverRFchooses for you. There is a DocC article,ChoosingAConvolver. - Is it safe in an audio render callback? Use the pointer
processBlock(input:output:count:)overloads — allocation-free by design. The array-returning overloads allocate and say so. - Can I use it today? Not as a dependency: it is unreleased, unlicensed, has no remote, and requires macOS/iOS 26 plus two local sibling checkouts.