Skip to content

Interface Contracts

Purpose: Explicit contracts defining behavior across layer boundaries

Overview

Interfaces define WHAT components must do, not HOW they do it. These are pure virtual classes (abstract interfaces) that define contracts between AudioLab layers.

Core Interfaces

IAudioProcessor.hpp

THE fundamental contract - all audio processors implement this

Lifecycle: Uninitialized → Prepared → Active → Processing Thread Safety: Explicit (GUI thread vs RT thread) Key Methods: - prepare() - Configure for processing - process() - RT-safe audio processing - get_state() - Query current state

Use: Every audio effect, synth, analyzer

IParameter.hpp

Parameter automation contract

All parameters normalized [0.0, 1.0] Thread-Safe: Can be called from any thread Use: Automatable controls (gain, frequency, etc.)

IEventListener.hpp

Event subscription contract

Template-based: Type-safe event handling Use: Responding to parameter changes, MIDI events, etc.

Design Principles

1. Explicit Contracts

Interfaces make promises explicit:

class IAudioProcessor {
    // Promise: I will process audio in real-time
    virtual void process(/*...*/) = 0;
};

2. Dependency Inversion

High-level modules depend on interfaces, not concrete implementations:

class PluginHost {
    void add_processor(IAudioProcessor& processor);  // Interface, not concrete class
};

3. Testability

Interfaces enable mocking:

class MockProcessor : public IAudioProcessor {
    // Implement for testing
};

Usage Patterns

Implementing an Interface

class MyEffect : public audiolab::IAudioProcessor {
public:
    void prepare(const audiolab::BufferConfig& config) override {
        // Implementation
    }

    void process(const audiolab::Sample* const* inputs,
                 audiolab::Sample** outputs,
                 audiolab::BlockSize num_samples) override {
        // RT-safe implementation
    }

    // ... implement all pure virtual methods
};

Using an Interface

void use_processor(audiolab::IAudioProcessor& processor) {
    audiolab::BufferConfig config{48000, 512, 2};
    processor.prepare(config);
    processor.activate();

    // Process audio...
}

Best Practices

✅ Do

  • Keep interfaces focused (Single Responsibility)
  • Use pure virtual functions (= 0)
  • Document thread-safety requirements
  • Use noexcept where appropriate
  • Make interfaces non-copyable

❌ Don't

  • Add data members to interfaces
  • Use interfaces as namespaces
  • Make interfaces too large (Interface Segregation)
  • Forget to make destructor virtual

Future Interfaces

As AudioLab grows: - IMIDIProcessor - MIDI processing - IModulator - Modulation sources - IVisualization - Visual components - IPresetManager - Preset handling


Remember: Interfaces are contracts. Breaking them breaks everything that depends on them.