Skip to content

Building Plugins with CORE

Tutorial paso a paso para crear tu primer plugin usando 04_CORE


🎯 Lo Que Vas a Construir

Un simple gain plugin funcional que demuestra: - Herencia de AudioProcessor - Uso de Parameter para controls - Processing RT-safe - Lifecycle completo

Tiempo estimado: 30 minutos


📋 Prerequisitos

# 1. CORE debe estar compilado
cd C:/AudioDev/audio-lab/build
cmake --build . --target audiolab_core

# 2. Familiarízate con:
- C++17 básico
- Conceptos de audio (sample rate, buffer size)

🚀 Paso 1: Crear Estructura del Proyecto

# Crear directorio
mkdir -p 05_MODULES/MY_PLUGINS/simple_gain
cd 05_MODULES/MY_PLUGINS/simple_gain

# Archivos a crear:
# - gain_plugin.hpp     # Plugin class
# - CMakeLists.txt      # Build config
# - README.md           # Documentation

📝 Paso 2: Implementar el Plugin

gain_plugin.hpp

#pragma once

#include "../../../04_CORE/04_10_audio_processor/audio_processor.hpp"
#include "../../../04_CORE/04_08_parameter_system/parameter.hpp"

namespace audiolab {
namespace myplugins {

class SimpleGainPlugin : public core::AudioProcessor {
public:
    SimpleGainPlugin()
        : AudioProcessor(core::ProcessorInfo{
            "Simple Gain",
            "My Plugins",
            "com.myplugins.gain",
            "1.0.0"
          })
        , gainDb_("Gain dB", -60.0f, 20.0f, 0.0f)
    {}

protected:
    // Called once when DAW prepares
    void on_prepare(const core::ProcessorConfig& config) override {
        // Nothing to allocate for simple gain
        sampleRate_ = config.sample_rate;
    }

    // RT-SAFE: Main DSP (called every buffer)
    void on_process(
        const float* const* inputs,
        float** outputs,
        uint32_t num_samples
    ) noexcept override {
        // Get gain in dB, convert to linear
        const float gainDb = gainDb_.get();
        const float gain = std::pow(10.0f, gainDb / 20.0f);

        // Process each channel
        for (uint32_t ch = 0; ch < config().num_output_channels; ++ch) {
            for (uint32_t i = 0; i < num_samples; ++i) {
                outputs[ch][i] = inputs[ch][i] * gain;
            }
        }
    }

    void on_release() override {
        // Nothing to release
    }

public:
    core::Parameter<float>& getGainParameter() {
        return gainDb_;
    }

private:
    core::Parameter<float> gainDb_;
    double sampleRate_ = 48000.0;
};

} // namespace myplugins
} // namespace audiolab

🔧 Paso 3: Configurar Build

CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(simple_gain VERSION 1.0.0)

set(CMAKE_CXX_STANDARD 17)

# Create library (or executable for testing)
add_library(simple_gain STATIC
    gain_plugin.hpp
)

# Link with CORE
target_link_libraries(simple_gain
    PRIVATE
        # audiolab_core  # Uncomment when CORE builds library
)

# Include paths
target_include_directories(simple_gain
    PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/../../../
)

🧪 Paso 4: Testing

test_gain.cpp (opcional)

#include "gain_plugin.hpp"
#include <iostream>
#include <vector>

int main() {
    using namespace audiolab::myplugins;

    SimpleGainPlugin plugin;

    // Prepare
    plugin.prepareToPlay(48000.0, 512, 2);
    plugin.activate();

    // Set gain to +6dB
    plugin.getGainParameter().set(6.0f);

    // Create test buffer
    std::vector<float> input(512, 1.0f);   // 0 dB
    std::vector<float> output(512);

    const float* inputs[] = {input.data()};
    float* outputs[] = {output.data()};

    // Process
    plugin.processBlock(inputs, outputs, 512);

    // Check output
    float expectedGain = std::pow(10.0f, 6.0f / 20.0f);  // ~2.0
    std::cout << "Input: 1.0, Gain: +6dB, Output: "
              << output[0] << " (expected ~" << expectedGain << ")\n";

    plugin.deactivate();
    plugin.releaseResources();

    return 0;
}

🏗️ Paso 5: Build y Run

# Build
cd build
cmake ..
cmake --build .

# Run test
./simple_gain_test

# Expected output:
# Input: 1.0, Gain: +6dB, Output: 1.995 (expected ~1.995)

🎨 Paso 6: Añadir Más Features

Parámetros Adicionales

private:
    core::Parameter<float> gainDb_;
    core::Parameter<bool> mute_;       // NEW: Mute switch
    core::Parameter<float> panPos_;    // NEW: Pan position

public:
    SimpleGainPlugin()
        : gainDb_("Gain", -60, 20, 0)
        , mute_("Mute", false)          // NEW
        , panPos_("Pan", -1, 1, 0)      // NEW: -1=L, 0=C, 1=R
    {}

protected:
    void on_process(...) noexcept override {
        if (mute_.get()) {
            // Output silence
            for (uint32_t ch = 0; ch < num_channels; ++ch) {
                std::memset(outputs[ch], 0, num_samples * sizeof(float));
            }
            return;
        }

        const float gain = db_to_linear(gainDb_.get());
        const float pan = panPos_.get();

        // Stereo pan law
        const float gainL = gain * std::cos(M_PI * (pan + 1.0f) / 4.0f);
        const float gainR = gain * std::sin(M_PI * (pan + 1.0f) / 4.0f);

        for (uint32_t i = 0; i < num_samples; ++i) {
            outputs[0][i] = inputs[0][i] * gainL;  // Left
            outputs[1][i] = inputs[1][i] * gainR;  // Right
        }
    }

🔄 Patrones Comunes

1. Pre-calcular en prepare()

void on_prepare(const ProcessorConfig& config) override {
    sampleRate_ = config.sample_rate;

    // Pre-calculate lookup table
    for (int i = 0; i < 1024; ++i) {
        float x = (float)i / 1024.0f;
        lookupTable_[i] = std::sin(2.0f * M_PI * x);
    }
}

2. Usar RingBuffer para Delay

#include "04_CORE/04_03_memory_management/ring_buffer.hpp"

private:
    core::containers::RingBuffer<float> delayLine_;

void on_prepare(const ProcessorConfig& config) override {
    delayLine_.resize(config.sample_rate * 0.5);  // 500ms max
}

void on_process(...) noexcept override {
    for (uint32_t i = 0; i < num_samples; ++i) {
        float input = inputs[0][i];
        float delayed = delayLine_.read(delaySamples);

        outputs[0][i] = input + 0.5f * delayed;
        delayLine_.write(input);
    }
}

3. GUI↔Audio Events

#include "04_CORE/04_07_event_dispatcher/event_dispatcher.hpp"

// In GUI thread
void onButtonClick() {
    MyEvent event;
    core::EventDispatcher::instance().enqueue_deferred(event);
}

// In audio thread (process)
core::EventDispatcher::instance().process_deferred_events();

✅ Checklist

Antes de considerar tu plugin completo:

  • Hereda de AudioProcessor
  • Implementa on_prepare() (allocate resources)
  • Implementa on_process() noexcept (DSP)
  • Implementa on_release() (free resources)
  • Parámetros expuestos via getXXXParameter()
  • CMakeLists.txt enlaza con audiolab_core
  • README.md documenta uso
  • Tests verifican funcionalidad básica

🐛 Troubleshooting

Error: "audiolab_core not found"

# Solución: Asegurar que CORE está en scope
# Opción 1: Build como parte del proyecto principal
add_subdirectory(04_CORE)
add_subdirectory(05_MODULES/MY_PLUGINS/simple_gain)

# Opción 2: Usar find_package
find_package(AudioLabCore REQUIRED)

Error: "Cannot allocate in on_process"

// ❌ WRONG
void on_process(...) noexcept override {
    std::vector<float> temp(num_samples);  // ALLOCATION!
}

// ✅ CORRECT
void on_prepare(...) override {
    tempBuffer_.resize(config.max_block_size);
}

void on_process(...) noexcept override {
    // tempBuffer_ already allocated
}

🔗 Recursos

Ejemplos para Estudiar

Documentación de CORE


🚀 Próximos Pasos

  1. Añade complejidad gradualmente:
  2. Empieza simple (gain)
  3. Añade delay
  4. Añade modulación (LFO)
  5. Añade filtros

  6. Estudia plugins existentes:

  7. 05_MODULES/05_15_REFERENCE_IMPLEMENTATIONS/
  8. 05_MODULES/05_07_ATOMS_L1/

  9. Integra con frameworks:

  10. JUCE (AudioProcessor → juce::AudioProcessor wrapper)
  11. VST3 (vst::IAudioProcessor)
  12. AAX (AAX_CEffectParameters)

¡Ahora sabes cómo construir plugins con CORE! 🎵

Siguiente: Advanced Patterns (coming soon)