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.txt -
Run 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