Skip to content

Calibration System - Quick Start Guide

Installation

Using CMake

cd 3 - COMPONENTS/05_MODULES/05_12_CALIBRATION_SYSTEM
mkdir build && cd build
cmake .. -DBUILD_CALIBRATION_EXAMPLES=ON
cmake --build .

Using in Your Project

find_package(AudioLabCalibration REQUIRED)

target_link_libraries(your_target
    PRIVATE
        AudioLab::calibration_framework
        AudioLab::frequency_calibration
)

Basic Usage

Step 1: Make Your Component Calibratable

Implement the ICalibratableComponent interface:

#include <CalibrationTarget.hpp>

class MyFilter : public AudioLab::Calibration::ICalibratableComponent {
public:
    std::string getComponentId() const override {
        return "MyFilter";
    }

    void process(const float* input, float* output, size_t numSamples) override {
        // Your DSP processing here
    }

    void setParameter(const std::string& name, float value) override {
        if (name == "cutoff") {
            cutoff_ = value;
            updateCoefficients();
        }
    }

    float getParameter(const std::string& name) const override {
        if (name == "cutoff") return cutoff_;
        return 0.0f;
    }

    std::vector<std::string> getParameterNames() const override {
        return {"cutoff", "resonance"};
    }

    void getParameterRange(const std::string& name,
                          float& minVal, float& maxVal) const override {
        if (name == "cutoff") {
            minVal = 20.0f;
            maxVal = 20000.0f;
        } else if (name == "resonance") {
            minVal = 0.1f;
            maxVal = 10.0f;
        }
    }

    void reset() override {
        // Reset internal state
    }

    double getSampleRate() const override { return sampleRate_; }
    void setSampleRate(double sr) override { sampleRate_ = sr; }

private:
    double sampleRate_ = 48000.0;
    float cutoff_ = 1000.0f;
    float resonance_ = 0.707f;
};

Step 2: Create Calibration Target

using namespace AudioLab::Calibration;

// Create your component
auto filter = std::make_shared<MyFilter>();

// Create calibration target
CalibrationTarget target("MyFilter", filter);

Step 3: Define Specification

// Define what you want to calibrate
CalibrationSpecification spec(TargetType::FREQUENCY_RESPONSE);

// Measurement config
spec.measurement.sampleRate = 48000.0;
spec.measurement.blockSize = 512;
spec.measurement.signalLevel = -18.0f;  // dBFS

// Target behavior
spec.idealResponse = {
    // Your target frequency response
    // e.g., flat to 1kHz, then rolloff
};

spec.tolerances = {
    // Acceptable error per frequency
    // e.g., ±0.5dB
};

// Optimization settings
spec.errorThreshold = 0.1f;  // Stop when error < 0.1dB
spec.maxIterations = 100;

// Add to target
target.addSpecification(spec);

Step 4: Run Calibration

#include <FilterCalibrator.hpp>

// Create calibrator
FilterCalibrator calibrator;

// Optional: Set progress callback
calibrator.setProgressCallback(
    [](CalibrationStage stage, float progress, const std::string& message) {
        std::cout << "[" << (int)(progress * 100) << "%] " << message << "\n";
    }
);

// Run calibration
CalibrationResult result = calibrator.calibrate(target);

// Check results
if (result.success) {
    std::cout << "Calibration successful!\n";
    std::cout << "Final error: " << result.finalError << " dB\n";
    std::cout << "Iterations: " << result.iterationsUsed << "\n";

    // Parameters are already applied to component
    // Optionally save them:
    for (const auto& [name, value] : result.parameters) {
        std::cout << name << ": " << value << "\n";
    }
} else {
    std::cerr << "Calibration failed: " << result.notes << "\n";
}

Common Use Cases

Use Case 1: Calibrate Filter Cutoff

Goal: Set filter cutoff to exactly 1kHz (-3dB point)

auto filter = std::make_shared<MyFilter>();
CalibrationTarget target("Filter_1kHz", filter);

CalibrationSpecification spec(TargetType::FREQUENCY_RESPONSE);
spec.measurement.sampleRate = 48000.0;

// Target: -3dB at 1kHz (one-pole lowpass characteristic)
std::vector<float> testFreqs = {500, 750, 1000, 1500, 2000};
spec.idealResponse = {-0.5, -1.5, -3.0, -6.0, -9.0};  // Expected magnitudes
spec.tolerances = {0.5, 0.5, 0.3, 0.5, 0.5};  // Tighter at cutoff

target.addSpecification(spec);

FilterCalibrator calibrator;
auto result = calibrator.calibrate(target);

if (result.success) {
    std::cout << "Calibrated cutoff: "
             << result.parameters["cutoff"] << " Hz\n";
}

Use Case 2: Match Hardware EQ Curve

Goal: Match the frequency response of a hardware EQ

// 1. Measure reference hardware
auto referenceResponse = measureHardwareEQ();

// 2. Create software component
auto softwareEQ = std::make_shared<MySoftwareEQ>();
CalibrationTarget target("EQ_Clone", softwareEQ);

// 3. Set reference as target
CalibrationSpecification spec(TargetType::FREQUENCY_RESPONSE);
spec.idealResponse = referenceResponse.magnitudes;
spec.tolerances.resize(spec.idealResponse.size(), 0.5f);  // ±0.5dB

target.addSpecification(spec);

// 4. Calibrate
FilterCalibrator calibrator;
auto result = calibrator.calibrate(target);

// 5. Verify with null test
auto nullError = performNullTest(softwareEQ, referenceResponse);
std::cout << "Null test error: " << nullError << " dB\n";

Use Case 3: Multi-Objective Calibration

Goal: Optimize both frequency response AND phase response

CalibrationTarget target("LinearPhaseEQ", eq);

// Objective 1: Frequency response
CalibrationSpecification freqSpec(TargetType::FREQUENCY_RESPONSE);
freqSpec.idealResponse = flatResponse;
freqSpec.importance = 1.0f;  // High priority
target.addSpecification(freqSpec);

// Objective 2: Phase response
CalibrationSpecification phaseSpec(TargetType::PHASE_RESPONSE);
phaseSpec.idealResponse = linearPhase;
phaseSpec.importance = 0.7f;  // Medium priority
target.addSpecification(phaseSpec);

// Use multi-objective optimizer
MultiObjectiveOptimizer optimizer;
auto result = optimizer.optimize(target, evaluationFunction);

// Result is Pareto-optimal compromise

Use Case 4: Batch Calibration

Calibrate multiple components:

std::vector<CalibrationResult> results;

for (auto& component : components) {
    CalibrationTarget target(component->getId(), component);

    // Add specifications
    target.addSpecification(spec);

    // Calibrate
    FilterCalibrator calibrator;
    auto result = calibrator.calibrate(target);

    results.push_back(result);

    // Report progress
    std::cout << component->getId() << ": "
             << (result.success ? "SUCCESS" : "FAILED")
             << " (error: " << result.finalError << " dB)\n";
}

// Summary
int successCount = std::count_if(results.begin(), results.end(),
                                [](const auto& r) { return r.success; });
std::cout << "\nCalibrated " << successCount << " / " << results.size()
         << " components\n";

Configuration

Calibrator Configuration

FilterCalibrator::Config config;

// Frequency range
config.startFreq = 20.0f;
config.endFreq = 20000.0f;
config.numFrequencies = 100;

// Measurement method
config.useLogSweep = true;  // vs discrete tones
config.sweepDuration = 1.0f;  // seconds
config.averagingCount = 5;  // measurements to average

// Optimization
config.learningRate = 0.01f;
config.momentum = 0.9f;
config.useAdaptiveLearning = true;

calibrator.setConfig(config);

Measurement Configuration

MeasurementConfig measConfig;
measConfig.sampleRate = 48000.0;
measConfig.blockSize = 512;
measConfig.signalLevel = -18.0f;  // dBFS
measConfig.averagingCount = 10;
measConfig.useOversampling = false;

Troubleshooting

Calibration Not Converging

Problem: result.success = false, max iterations reached

Solutions: 1. Reduce errorThreshold (less strict) 2. Increase maxIterations 3. Adjust learning rate: try 0.001f to 0.1f 4. Check parameter ranges are reasonable 5. Verify component works correctly

// Debug: Monitor error over iterations
calibrator.setProgressCallback(
    [](CalibrationStage stage, float progress, const std::string& message) {
        if (stage == CalibrationStage::OPTIMIZATION) {
            // Log error progression
            logFile << progress << "," << message << "\n";
        }
    }
);

High Error Despite Convergence

Problem: result.success = true but error still high

Solutions: 1. Component may not be capable of achieving target 2. Target specification may be unrealistic 3. Measurement noise too high - increase averagingCount 4. Check if component is stable/reproducible

// Test reproducibility
auto r1 = measure(target);
auto r2 = measure(target);
float diff = calculateDifference(r1, r2);
if (diff > 0.5f) {
    std::cout << "Warning: Component is not reproducible\n";
}

Calibration Too Slow

Problem: Takes too long

Solutions: 1. Reduce numFrequencies 2. Reduce averagingCount 3. Use log sweep instead of discrete tones 4. Reduce maxIterations 5. Use faster optimizer (gradient descent vs GA)

// Fast configuration
FilterCalibrator::Config fastConfig;
fastConfig.numFrequencies = 50;  // vs 100
fastConfig.averagingCount = 3;   // vs 10
fastConfig.useLogSweep = true;   // Faster measurement

Examples

See examples/ directory for complete examples:

  • simple_filter_calibration.cpp - Basic filter calibration
  • eq_curve_matching.cpp - Match hardware EQ (TODO)
  • multi_objective_example.cpp - Multi-objective optimization (TODO)
  • batch_calibration.cpp - Calibrate multiple components (TODO)

Next Steps

  • Read ARCHITECTURE.md for detailed design
  • Explore calibration types in subsystem directories
  • Implement custom calibrators for your components
  • Contribute calibration profiles to the community

Support

For issues, questions, or contributions: - See main project README - Check documentation in 05_12_documentation/ - Review tests in 05_12_test_integration/


Happy Calibrating! 🎯