Generating FIR filters to balance tones at low volumes using ISO 226 equal-loudness contours.
A modern Python CLI tool that generates Finite Impulse Response (FIR) filters to maintain consistent tonal balance across different volume levels. Uses ISO 226:2003/2023 equal-loudness contours to create EQ adjustments that compensate for human hearing characteristics at lower volumes. Now refactored with clean architecture and full type safety.
Perfect for use with Equalizer APO, APO-loudness, Easy Convolver, or other convolution-based EQ systems.
- Multiple Standards: Choose between ISO 226:2003/2023 or Fletcher-Munson equal-loudness contours
- Flexible Range Generation: Generate filters for any phon level range
- Multiple Formats: WAV output in float32 or int16 PCM formats
- Stereo Support: Mono or stereo channel output
- Advanced Tuning: Configurable smoothing, DC gain, and Nyquist response
- Export Options: Filter response CSV export available
- Performance Benchmarking: Built-in latency analysis for real-time applications
- Production Ready: Comprehensive input validation and security
# Clone this repository
git clone https://github.com/ryzen3100/FIR-Filter-Maker-for-Equal-Loudness.git
cd FIR-Filter-Maker-for-Equal-Loudness
# Create virtual environment (recommended)
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install dependencies
pip install numpy scipy
# Optional: Install development tools
pip install pyright mypy flake8 # For type checking and linting
# Generate single filter (40→50 phon transition)
python fir_loudness_cli.py --start-phon 40 --end-phon 50
# Use Fletcher-Munson curves instead of ISO
python fir_loudness_cli.py --start-phon 40 --end-phon 50 --fletcher
# Custom sampling rate and filter length
python fir_loudness_cli.py --start-phon 60 --end-phon 80 --fs 44100 --taps 8192
# Stereo output with fine resolution
python fir_loudness_cli.py --start-phon 35 --end-phon 85 --step-phon 0.5 --channels 2
# Batch generation with CSV export
python fir_loudness_cli.py --start-phon 50 --end-phon 90 --step-phon 2.5 --export-csv
# Enable logging for debugging
python fir_loudness_cli.py --start-phon 40 --end-phon 50 --log --log-level DEBUG
Option | Description | Default |
---|---|---|
--start-phon |
Source phon level (0-100) | Required |
--end-phon |
Target phon level (0-100) | Required |
--step-phon |
Step size for start phon (0, 10] | 0.1 |
--fs |
Sampling rate (Hz) | 48000 |
--taps |
Filter length (4-262144) | 65536 |
--channels |
Output channels (1=mono, 2=stereo) | 1 |
--format |
Sample format (float32/pcm16) | float32 |
--iso |
ISO standard (2003/2023) | 2023 |
--fletcher |
Use Fletcher-Munson contours (mutually exclusive with --iso) | False |
--out-dir |
Output directory | output |
--log |
Enable logging to logs/ directory | False |
--log-level |
Set logging level (DEBUG/INFO/WARNING/ERROR/CRITICAL) | INFO |
--smooth-db
: Enable smoothing across ISO points--smooth-window
: Smoothing window size (odd integer)--dc-gain-mode
: DC gain control (first_iso/unity)--nyq-gain-db
: Nyquist gain control (dB) - requires odd tap count for non-zero values--grid-points
: Frequency grid resolution--export-csv
: Export response data as CSV--export-fir-resp
: Export actual FIR response data
--log
: Enable logging to logs/ directory--log-level
: Set logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
--benchmark
: Run latency benchmark to determine optimal tap sizes for your system
- Filter Generation: ~3ms per filter (tested 101 filters in 318ms total)
- Memory Usage: Scales with tap count (64k taps ≈ 500KB RAM)
- Real-time Latency: See
--benchmark
for system-specific recommendations - Batch Processing: Efficiently handles hundreds of filters in sequence
Generate a filter to compensate when listening at 70dB while the content was mastered at 85dB:
python fir_loudness_cli.py --start-phon 70 --end-phon 85
Create filters for every 2.5 phon step from 45 to 85:
python fir_loudness_cli.py --start-phon 45 --end-phon 85 --step-phon 2.5
Determine optimal tap sizes for your system:
python fir_loudness_cli.py --benchmark
Generate with specific technical requirements:
python fir_loudness_cli.py \
--start-phon 60 \
--end-phon 75 \
--fs 44100 \
--taps 16384 \
--channels 2 \
--format pcm16 \
--out-dir my_filters
pip install numpy scipy
# For development (optional)
pip install pyright mypy flake8 # Type checking and linting
Filter files are named with metadata:
ISO2023_fs48000_t65536_60.0-75.0_filter.wav
FLETCHER_fs48000_t1024_40.0-60.0_filter.wav
Where:
ISO2023
/FLETCHER
: Curve standard usedfs48000
: Sampling rate (48000 Hz)t65536
: Filter length (65536 taps)60.0-75.0
: Source → Target phon levels
All parameters are validated with:
- File path sanitization preventing directory traversal
- Numeric bounds checking (phon 0-100, sampling rate 8k-192kHz)
- Input type and range validation
- Zero division protection and NaN handling
- Type safety with mypy and pyright compatibility
- Copy generated WAV files to Equalizer APO config directory
- Reference filters by phon level differences
- Use with smart gain switching or manual control
Directly compatible with APO-Loudness convolution setup.
# Type checking - All modules now mypy compliant
mypy src/ --ignore-missing-imports # ✅ 0 errors
pyright src/ # ✅ 0 errors
# Architecture verification
python -c "import src.domain.equal_loudness_curves, src.application_service"
# Module validation
python -c "import src.cli, src.config, src.config_validator, src.application_service"
- Clean Architecture: Domain → Application → Infrastructure layers
- Type Safety: Full mypy and pyright compliance achieved
- Security: Input validation separated from DTOs
- Pure DTOs: Configuration objects have zero dependencies
- Domain Models: Isolated business logic without infrastructure coupling
src/
├── domain/ # ✅ Pure business logic and domain models
│ ├── equal_loudness_curves.py # Domain model for curves
│ ├── phon_transition.py # Phon transition value objects
│ └── fir_design_spec.py # Filter specification domain
├── cli.py # CLI interface layer
├── config.py # ✅ Pure DTO configuration objects
├── config_validator.py # Configuration validation service
├── application_service.py # ✅ Clean service for orchestration
├── services.py # Legacy compatibility layer
├── phon_service.py # ✅ Phon level operations service
├── repositories.py # Data access layer
├── design.py # FIR filter design algorithms
├── interpolation.py # Curve interpolation utilities
├── io_utils.py # File I/O with pathlib
├── iso_data.py # ISO contour data
├── fletcher_data.py # Fletcher-Munson data
├── benchmark.py # Performance analysis
└── validation.py # Input validation & security
logs/ # Log files (when logging enabled)
output/ # Generated filter files
- Clean Domain Layer: Business logic isolated from infrastructure
- Pure DTOs: Configuration objects have no validation dependencies
- Service Separation: Application orchestration vs legacy compatibility maintained
- Type Safety: Full mypy and pyright compliance
- Zero Breaking Changes: All existing commands work identically
The original deprecated scripts (FIR_LOUDNESS.py
, FIR_LOUDNESS_2023.py
) have been replaced with the modern fir_loudness_cli.py
interface. All functionality is preserved but with improved CLI, security, and performance.
This project was originally created by grisys83 and is based on the original repository at:
https://github.com/grisys83/FIR-Filter-Maker-for-Equal-Loudness
This repository contains refactored and enhanced versions of the original code, maintaining the same core functionality while adding security, performance, and architecture improvements.
GNU General Public License version 3 (GPLv3)
- ISO 226:2003/2023 equal-loudness contours
- Fletcher-Munson equal-loudness contours (classic 1933 data)
- Missing 20000 Hz and 16000 Hz frequencies in 2023 standard preserved from 2003
"Validation error: Sampling rate X is out of valid range"
- Ensure sampling rate is between 8000-192000 Hz
- Common rates: 44100, 48000, 96000
"A Type II filter must have zero gain at the Nyquist frequency"
- Use odd tap count when specifying
--nyq-gain-db
- Or remove
--nyq-gain-db
for default behavior
Memory Issues with Large Tap Counts
- Reduce
--taps
or use--benchmark
to find optimal size - Consider 8192 taps for music, 2048 for video applications
"ModuleNotFoundError: No module named 'src'"
- Ensure you're running from the project root directory
- Use
python fir_loudness_cli.py
notpython src/cli.py
- Fork the repository
- Create a feature branch
- Ensure all tests pass:
mypy src/
andpyright src/
- Run linting:
flake8 src/
- Submit a pull request
Please maintain the existing code style and type safety standards.
Current version includes comprehensive code quality refactoring with:
- Full type safety (mypy/pyright compliant)
- Improved Flake8 compliance (minor formatting issues remain)
- Modular architecture
- Security-focused validation
- Performance benchmarking
For questions about the original project: 136304138+grisys83@users.noreply.github.com