Files
shawn a5423b7f55 Initial extraction: C++ MixerEngine standalone daemon
Extracted from op-pedal mixer-engine branch. Core components:

- MixerEngine / MixerChannelStrip / MixerBus — cleaned of LV2/Pedalboard/IHost/MidiMapper deps
- MixerControlServer — Unix domain socket JSON control interface
- main.cpp — JACK audio I/O client with headless fallback
- CMake build — auto-fetches nlohmann/json, links JACK
- 9 core tests passing

Architecture: JACK ports → MixerEngine::process() → JACK ports,
controlled via Unix socket JSON commands.
2026-06-23 12:17:45 -04:00

211 lines
6.7 KiB
C++

// Copyright (c) 2026 Ourpad Network
// See LICENSE file in the project root for full license text.
#pragma once
#include <memory>
#include <vector>
#include <map>
#include <atomic>
#include <cstdint>
#include <functional>
#include "mixer/MixerChannelStrip.hpp"
#include "mixer/MixerBus.hpp"
#include "mixer/Types.hpp"
namespace mixer {
class MixerChannelStrip;
class MixerBus;
/// The MixerEngine is the heart of the band-in-a-box digital mixer.
///
/// It owns and manages:
/// - N channel strips (MixerChannelStrip), one per physical/logical input
/// - M buses (MixerBus), including master, subgroups, aux sends
/// - A routing graph connecting channels to buses and buses to buses
///
/// Processing order per audio cycle:
/// 1. Clear all bus buffers
/// 2. For each channel: apply volume/pan → accumulate to routed buses
/// 3. For each aux send: calculate send level, accumulate to aux buses
/// 4. Route buses to buses according to routing matrix
/// 5. Process each bus (apply volume, compute VU)
/// 6. Master bus outputs are the final mix
///
/// All control methods are thread-safe for use from the non-RT thread.
/// The process() method runs in the RT audio thread.
class MixerEngine {
public:
MixerEngine();
~MixerEngine();
// Disable copy
MixerEngine(const MixerEngine&) = delete;
MixerEngine& operator=(const MixerEngine&) = delete;
/// --- Configuration ---
/// Set sample rate and max buffer size before preparation
void setSampleRate(uint32_t sampleRate);
void setMaxBufferSize(size_t frames);
/// --- Channel Management ---
/// Add a new channel strip for the given physical input index.
/// Returns a pointer to the new channel (valid until removed).
MixerChannelStrip* addChannel(int physicalInputIndex);
/// Auto-create channels based on the number of detected input channels.
/// Removes existing channels and creates one strip per input.
/// Also sets up default output routes.
void autoCreateChannels(uint32_t inputChannelCount, uint32_t outputChannelCount = 2);
/// Remove a channel by its channel index.
void removeChannel(int channelIndex);
/// Get a channel by index. Returns nullptr if not found.
MixerChannelStrip* getChannel(int channelIndex);
const MixerChannelStrip* getChannel(int channelIndex) const;
/// Number of channels currently in the mixer.
size_t channelCount() const { return channels_.size(); }
/// --- Bus Management ---
/// Add a new bus and return its ID.
int64_t addBus(MixerBusType type, const std::string& name, int channels = 2);
/// Remove a bus by ID.
void removeBus(int64_t busId);
/// Get a bus by ID. Returns nullptr if not found.
MixerBus* getBus(int64_t busId);
const MixerBus* getBus(int64_t busId) const;
/// Access the master bus (always present).
MixerBus* masterBus() { return masterBus_; }
const MixerBus* masterBus() const { return masterBus_; }
/// Get all bus IDs (for iteration).
std::vector<int64_t> busIds() const;
/// --- Routing ---
/// Route a channel to a bus with a given level in dB.
void routeChannelToBus(int channelIndex, int64_t busId, float levelDb = 0.0f);
/// Route a bus to another bus (e.g., subgroup to master).
void routeBusToBus(int64_t sourceBusId, int64_t targetBusId, float levelDb = 0.0f);
/// Remove a route.
void removeRoute(int64_t sourceId, int64_t targetBusId);
/// Clear all routes.
void clearRoutes();
/// --- Output Routing ---
/// Get the current output routing table (bus → physical output channel mapping).
const std::vector<MixerOutputRoute>& outputRoutes() const { return outputRoutes_; }
/// Set the entire output routing table.
void setOutputRoutes(const std::vector<MixerOutputRoute>& routes);
/// Add a single output route.
void addOutputRoute(int64_t busId, int sourceStartChannel, int targetStartChannel, int channels);
/// Remove all output routes for a given bus.
void removeOutputRoutes(int64_t busId);
/// Get all routes for a given bus.
std::vector<MixerOutputRoute> findOutputRoutesForBus(int64_t busId) const;
/// Get all current routes.
const std::vector<MixerRouteEntry>& routes() const { return routes_; }
/// --- Physical I/O Info ---
/// Number of physical input channels detected on the audio device.
uint32_t physicalInputCount() const { return physicalInputCount_; }
/// Number of physical output channels detected on the audio device.
uint32_t physicalOutputCount() const { return physicalOutputCount_; }
/// Set the physical I/O channel counts.
void setPhysicalChannelCounts(uint32_t inputs, uint32_t outputs) {
physicalInputCount_ = inputs;
physicalOutputCount_ = outputs;
}
/// --- Audio Processing (real-time thread) ---
/// Prepare all channels and allocate buffers.
void Prepare();
/// Activate all channels.
void Activate();
/// Deactivate all channels.
void Deactivate();
/// Process one full mixer cycle.
/// deviceInputs/outputs are the raw audio interface buffers.
void process(
float** deviceInputs,
uint32_t inputChannels,
float** deviceOutputs,
uint32_t outputChannels,
uint32_t frames
);
/// --- Solo Management ---
/// True if any channel has solo engaged.
bool anySoloActive() const;
/// --- State Serialization ---
MixerSnapshot captureSnapshot() const;
void applySnapshot(const MixerSnapshot& snapshot);
private:
std::vector<std::unique_ptr<MixerChannelStrip>> channels_;
std::map<int64_t, std::unique_ptr<MixerBus>> buses_;
MixerBus* masterBus_ = nullptr;
// Routing entries
std::vector<MixerRouteEntry> routes_;
// Output routing entries (bus → physical output channel mapping)
std::vector<MixerOutputRoute> outputRoutes_;
// Audio configuration
uint32_t sampleRate_ = 48000;
size_t maxBufferSize_ = 512;
// Next bus ID counter
static std::atomic<int64_t> nextBusId_;
// Temporary per-channel output buffers for routing
std::vector<std::vector<float>> channelOutputBuffers_;
// Physical I/O channel counts
uint32_t physicalInputCount_ = 0;
uint32_t physicalOutputCount_ = 0;
// Internal helper: accumulate a channel's output to all its routed buses
void routeChannelOutput(
MixerChannelStrip* channel,
float** channelOutput,
uint32_t frames
);
// Internal helper: process all bus-to-bus routing
void processBusRouting(uint32_t frames);
// Build a list of all routes from a given source
std::vector<MixerRouteEntry*> findRoutesForSource(int64_t sourceId);
};
} // namespace mixer