| name | eqapo-format-expert |
| description | Expert in EqualizerAPO configuration file format, parsing, and generation. Use when implementing EAPO config import/export, adding support for new filter types, or debugging config file issues. |
EqualizerAPO Format Expert
Specialized agent for working with EqualizerAPO configuration files, ensuring correct parsing and generation of .txt config files.
EqualizerAPO Overview
EqualizerAPO is a parametric equalizer for Windows that processes system audio via APO (Audio Processing Objects) framework.
- Config Location:
C:\Program Files\EqualizerAPO\config\config.txt - Format: Plain text with line-based commands
- Real-time: Changes apply immediately when file is saved
- Channels: Supports per-channel processing (L, R, C, SUB, etc.)
Config File Format
Basic Structure
# Comment lines start with #
Preamp: -5.0 dB
Filter: ON PK Fc 1000 Hz Gain 3.0 dB Q 1.41
Filter: ON LS Fc 80 Hz Gain -2.5 dB Q 0.71
Filter: ON HS Fc 10000 Hz Gain 4.0 dB Q 0.71
Filter Types Supported by EQAPO GUI
| Type | EAPO Code | Description | Parameters |
|---|---|---|---|
| Peaking | PK |
Bell curve EQ | Fc, Gain, Q |
| Low Shelf | LS |
Bass adjustment | Fc, Gain, Q |
| High Shelf | HS |
Treble adjustment | Fc, Gain, Q |
Additional Filter Types (Not Yet Implemented)
| Type | EAPO Code | Description |
|---|---|---|
| Low Pass | LP |
Cuts high frequencies |
| High Pass | HP |
Cuts low frequencies |
| Band Pass | BP |
Passes only a frequency range |
| Notch | NO |
Cuts a specific frequency |
| All Pass | AP |
Affects phase, not magnitude |
Preamp Syntax
Preamp: <value> dB
Examples:
Preamp: -5.0 dB
Preamp: 3 dB
Preamp: 0 dB
Rules:
- Must be on its own line
- Value can be integer or float
- Unit "dB" required
- Negative values reduce volume (prevent clipping)
- Typical range: -20 to +20 dB
Filter Syntax
Filter: <ON|OFF> <TYPE> Fc <frequency> Hz Gain <gain> dB Q <q_factor>
Examples:
Filter: ON PK Fc 1000 Hz Gain 3.0 dB Q 1.41
Filter: ON LS Fc 80 Hz Gain -2.5 dB Q 0.71
Filter: OFF HS Fc 10000 Hz Gain 4.0 dB Q 0.71
Rules:
ONorOFFstate (case-insensitive)- Filter type:
PK,LS,HS, etc. - Frequency:
Fc <value> Hz(20-20000 typical) - Gain:
Gain <value> dB(can be negative) - Q factor:
Q <value>(0.1-30 typical)
Channel-Specific Filters
Channel: L
Filter: ON PK Fc 1000 Hz Gain 3.0 dB Q 1.41
Channel: R
Filter: ON PK Fc 1000 Hz Gain 2.0 dB Q 1.41
Channel: ALL
Not yet supported in EQAPO GUI - applies filters to all channels globally.
Parsing EqualizerAPO Files
Parser Implementation (TypeScript)
export interface ParsedEapoConfig {
preamp: number;
bands: ParametricBand[];
}
export function parseEapoConfig(content: string): ParsedEapoConfig {
const lines = content.split('\n').map((line) => line.trim());
let preamp = 0;
const bands: ParametricBand[] = [];
for (const line of lines) {
// Skip comments and empty lines
if (line.startsWith('#') || line === '') continue;
// Parse preamp
if (line.startsWith('Preamp:')) {
const match = line.match(/Preamp:\s*([-+]?\d+\.?\d*)\s*dB/i);
if (match) {
preamp = parseFloat(match[1]);
}
continue;
}
// Parse filter
if (line.startsWith('Filter:')) {
const band = parseFilterLine(line);
if (band) {
bands.push(band);
}
}
}
return { preamp, bands };
}
function parseFilterLine(line: string): ParametricBand | null {
// Extract state (ON/OFF)
const stateMatch = line.match(/Filter:\s*(ON|OFF)/i);
if (!stateMatch || stateMatch[1].toUpperCase() === 'OFF') {
return null; // Skip disabled filters
}
// Extract filter type
const typeMatch = line.match(/(PK|LS|HS|LP|HP|BP|NO|AP)/i);
if (!typeMatch) return null;
const filterType = normalizeFilterType(typeMatch[1].toUpperCase());
// Extract frequency
const freqMatch = line.match(/Fc\s+([\d.]+)\s*Hz/i);
if (!freqMatch) return null;
const frequency = parseFloat(freqMatch[1]);
// Extract gain
const gainMatch = line.match(/Gain\s+([-+]?[\d.]+)\s*dB/i);
if (!gainMatch) return null;
const gain = parseFloat(gainMatch[1]);
// Extract Q factor
const qMatch = line.match(/Q\s+([\d.]+)/i);
if (!qMatch) return null;
const qFactor = parseFloat(qMatch[1]);
// Validate
if (!isValidBand(frequency, gain, qFactor)) {
return null;
}
return { filterType, frequency, gain, qFactor };
}
function normalizeFilterType(type: string): FilterType {
switch (type) {
case 'PK':
case 'PEAKING':
return 'Peaking';
case 'LS':
case 'LOWSHELF':
return 'LowShelf';
case 'HS':
case 'HIGHSHELF':
return 'HighShelf';
default:
throw new Error(`Unsupported filter type: ${type}`);
}
}
function isValidBand(frequency: number, gain: number, qFactor: number): boolean {
return (
frequency >= 20 &&
frequency <= 20000 &&
gain >= -30 &&
gain <= 30 &&
qFactor >= 0.01 &&
qFactor <= 100
);
}
Edge Cases in Parsing
Case Insensitivity
filter: on pk fc 1000 hz gain 3.0 db q 1.41 ✓ FILTER: ON PK FC 1000 HZ GAIN 3.0 DB Q 1.41 ✓ Filter: ON PK Fc 1000 Hz Gain 3.0 dB Q 1.41 ✓Extra Whitespace
Filter: ON PK Fc 1000 Hz Gain 3.0 dB Q 1.41 ✓Integer vs Float
Filter: ON PK Fc 1000 Hz Gain 3 dB Q 1.41 ✓ Filter: ON PK Fc 1000.0 Hz Gain 3.0 dB Q 1.41 ✓Negative Gains
Filter: ON PK Fc 1000 Hz Gain -3.0 dB Q 1.41 ✓ Filter: ON LS Fc 80 Hz Gain -6 dB Q 0.71 ✓Comments Mid-Line (Not standard, but seen in wild)
Filter: ON PK Fc 1000 Hz Gain 3.0 dB Q 1.41 # Boost presence
Generating EqualizerAPO Files
Writer Implementation (Rust)
pub fn write_eapo_config(
path: &Path,
bands: &[ParametricBand],
preamp: f32,
) -> Result<(), std::io::Error> {
let mut content = String::new();
// Header comment
content.push_str("# Generated by EQAPO GUI\n");
content.push_str(&format!("# Generated: {}\n\n", chrono::Local::now().format("%Y-%m-%d %H:%M:%S")));
// Preamp
content.push_str(&format!("Preamp: {:.1} dB\n", preamp));
// Filters
for band in bands {
let filter_line = format!(
"Filter: ON {} Fc {} Hz Gain {:.1} dB Q {:.2}\n",
filter_type_to_code(&band.filter_type),
band.frequency as u32,
band.gain,
band.q_factor
);
content.push_str(&filter_line);
}
// Write to file
std::fs::write(path, content)?;
Ok(())
}
fn filter_type_to_code(filter_type: &FilterType) -> &'static str {
match filter_type {
FilterType::Peaking => "PK",
FilterType::LowShelf => "LS",
FilterType::HighShelf => "HS",
}
}
Output Format Example
# Generated by EQAPO GUI
# Generated: 2026-01-04 15:30:00
Preamp: -3.5 dB
Filter: ON PK Fc 1000 Hz Gain 3.0 dB Q 1.41
Filter: ON LS Fc 80 Hz Gain -2.5 dB Q 0.71
Filter: ON HS Fc 10000 Hz Gain 4.0 dB Q 0.71
Formatting Rules:
- Frequency: Integer (no decimal places)
- Gain: 1 decimal place
- Q factor: 2 decimal places
- Consistent spacing
- One filter per line
Advanced EqualizerAPO Features
Include Directive
Include: subwoofer_eq.txt
Include: headphone_compensation.txt
Loads another config file at that position.
Copy Filter
Copy: L=L+0.5*R R=R+0.5*L
Mixes channels (crossfeed for headphones).
Delay
Delay: L=0 R=2.5
Delays right channel by 2.5ms (speaker positioning).
Convolution (Impulse Response)
Convolution: room_correction.wav
Applies FIR filter from WAV file (room correction).
None of these are supported in EQAPO GUI yet - future features.
File Permissions
Windows UAC Challenges
EqualizerAPO's default config location requires admin rights:
C:\Program Files\EqualizerAPO\config\config.txt
Solutions:
Live Config (Recommended)
C:\Users\<Username>\Documents\EQAPO GUI\live_config.txtThen use EqualizerAPO's
Include:directive in main config:Include: C:\Users\<Username>\Documents\EQAPO GUI\live_config.txtRun as Admin (Not recommended)
- Tauri app requests elevation
- Security risk
- UAC prompt every time
Permission Check (Rust)
pub fn can_write_to_eapo_config(path: &Path) -> bool {
// Try to open file for writing
std::fs::OpenOptions::new()
.write(true)
.create(false)
.open(path)
.is_ok()
}
pub fn get_recommended_config_path() -> PathBuf {
if can_write_to_eapo_config(&get_default_config_path()) {
get_default_config_path()
} else {
get_live_config_path()
}
}
Validation
Pre-Write Validation
function validateEapoConfig(bands: ParametricBand[], preamp: number): string[] {
const errors: string[] = [];
// Check preamp
if (preamp < -20 || preamp > 20) {
errors.push(`Preamp ${preamp} dB exceeds typical range (-20 to +20 dB)`);
}
// Check band count
if (bands.length > 32) {
errors.push(`Too many bands (${bands.length}). EqualizerAPO supports max 32.`);
}
// Check each band
bands.forEach((band, i) => {
if (band.frequency < 20 || band.frequency > 20000) {
errors.push(`Band ${i + 1}: Frequency ${band.frequency} Hz out of range (20-20000 Hz)`);
}
if (band.gain < -30 || band.gain > 30) {
errors.push(`Band ${i + 1}: Gain ${band.gain} dB exceeds typical range (-30 to +30 dB)`);
}
if (band.qFactor < 0.1 || band.qFactor > 30) {
errors.push(`Band ${i + 1}: Q factor ${band.qFactor} out of range (0.1-30)`);
}
});
return errors;
}
Testing
Parser Test Cases
describe('EAPO Parser', () => {
it('should parse basic config', () => {
const config = `
Preamp: -5.0 dB
Filter: ON PK Fc 1000 Hz Gain 3.0 dB Q 1.41
`;
const result = parseEapoConfig(config);
expect(result.preamp).toBe(-5.0);
expect(result.bands).toHaveLength(1);
expect(result.bands[0].frequency).toBe(1000);
});
it('should skip disabled filters', () => {
const config = `
Filter: ON PK Fc 1000 Hz Gain 3.0 dB Q 1.41
Filter: OFF PK Fc 2000 Hz Gain 6.0 dB Q 1.41
`;
const result = parseEapoConfig(config);
expect(result.bands).toHaveLength(1);
});
it('should handle case insensitivity', () => {
const config = `FILTER: on pk fc 1000 hz gain 3.0 db q 1.41`;
const result = parseEapoConfig(config);
expect(result.bands[0].filterType).toBe('Peaking');
});
});
Common Issues
❌ Missing "dB" Unit
Preamp: -5 # WRONG: Missing unit Preamp: -5 dB # CORRECT❌ Wrong Filter Code
Filter: ON Peaking Fc 1000 Hz ... # WRONG: Use "PK" Filter: ON PK Fc 1000 Hz ... # CORRECT❌ Missing Fc Keyword
Filter: ON PK 1000 Hz ... # WRONG Filter: ON PK Fc 1000 Hz ... # CORRECT❌ Comma as Decimal Separator
Filter: ON PK Fc 1000 Hz Gain 3,0 dB Q 1,41 # WRONG (European notation) Filter: ON PK Fc 1000 Hz Gain 3.0 dB Q 1.41 # CORRECT
Reference Materials
references/eapo_spec.md- Full EqualizerAPO specificationreferences/filter_types.md- All supported filter typesreferences/examples.md- Real-world config file examples