Mixer engine: output routing, full state restore, MIDI learn API, PipeWire multi-channel support
This commit is contained in:
@@ -411,6 +411,16 @@ namespace pipedal
|
||||
return this->sampleRate;
|
||||
}
|
||||
|
||||
virtual uint32_t GetDeviceCaptureChannels() const override
|
||||
{
|
||||
return (uint32_t)this->captureChannels;
|
||||
}
|
||||
|
||||
virtual uint32_t GetDevicePlaybackChannels() const override
|
||||
{
|
||||
return (uint32_t)this->playbackChannels;
|
||||
}
|
||||
|
||||
JackServerSettings jackServerSettings;
|
||||
|
||||
std::string alsa_device_name;
|
||||
|
||||
@@ -57,6 +57,12 @@ namespace pipedal {
|
||||
|
||||
virtual uint32_t GetSampleRate() = 0;
|
||||
|
||||
/// Get the number of capture (input) channels the device actually provides.
|
||||
virtual uint32_t GetDeviceCaptureChannels() const = 0;
|
||||
|
||||
/// Get the number of playback (output) channels the device actually provides.
|
||||
virtual uint32_t GetDevicePlaybackChannels() const = 0;
|
||||
|
||||
virtual size_t GetMidiInputEventCount() = 0;
|
||||
virtual MidiEvent*GetMidiEvents() = 0;
|
||||
|
||||
|
||||
@@ -1501,6 +1501,25 @@ public:
|
||||
{
|
||||
return this->sampleRate;
|
||||
}
|
||||
|
||||
virtual uint32_t GetDeviceCaptureChannels() override
|
||||
{
|
||||
if (this->audioDriver)
|
||||
{
|
||||
return this->audioDriver->GetDeviceCaptureChannels();
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
virtual uint32_t GetDevicePlaybackChannels() override
|
||||
{
|
||||
if (this->audioDriver)
|
||||
{
|
||||
return this->audioDriver->GetDevicePlaybackChannels();
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
void HandleAlsaSequencerDevicesChanged(
|
||||
AlsaSequencerDeviceMonitor::MonitorAction action, int client, const std::string &clientName)
|
||||
{
|
||||
|
||||
@@ -237,6 +237,12 @@ namespace pipedal
|
||||
virtual void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration) = 0;
|
||||
virtual uint32_t GetSampleRate() = 0;
|
||||
|
||||
/// Get the number of capture (input) channels the audio device provides.
|
||||
virtual uint32_t GetDeviceCaptureChannels() = 0;
|
||||
|
||||
/// Get the number of playback (output) channels the audio device provides.
|
||||
virtual uint32_t GetDevicePlaybackChannels() = 0;
|
||||
|
||||
virtual JackConfiguration GetServerConfiguration() = 0;
|
||||
|
||||
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0;
|
||||
|
||||
@@ -136,6 +136,16 @@ namespace pipedal
|
||||
return this->sampleRate;
|
||||
}
|
||||
|
||||
virtual uint32_t GetDeviceCaptureChannels() const override
|
||||
{
|
||||
return (uint32_t)deviceCaptureBuffers.size();
|
||||
}
|
||||
|
||||
virtual uint32_t GetDevicePlaybackChannels() const override
|
||||
{
|
||||
return (uint32_t)devicePlaybackBuffers.size();
|
||||
}
|
||||
|
||||
JackServerSettings jackServerSettings;
|
||||
AlsaSequencer::ptr alsaSequencer;
|
||||
|
||||
|
||||
@@ -330,6 +330,22 @@ std::string MixerApi::getStateJson() const
|
||||
}
|
||||
writer.write_raw("]");
|
||||
|
||||
// Output routing
|
||||
writer.write_member("outputRoutes", "");
|
||||
writer.write_raw("[");
|
||||
first = true;
|
||||
for (const auto& route : mixerEngine_->outputRoutes()) {
|
||||
if (!first) writer.write_raw(",");
|
||||
first = false;
|
||||
writer.start_object();
|
||||
writer.write_member("sourceBusId", route.sourceBusId);
|
||||
writer.write_member("sourceStartChannel", (int64_t)route.sourceStartChannel);
|
||||
writer.write_member("targetStartChannel", (int64_t)route.targetStartChannel);
|
||||
writer.write_member("channels", (int64_t)route.channels);
|
||||
writer.end_object();
|
||||
}
|
||||
writer.write_raw("]");
|
||||
|
||||
writer.end_object();
|
||||
return ss.str();
|
||||
}
|
||||
@@ -665,3 +681,64 @@ void MixerApi::loadMidiMappingsFromFile()
|
||||
if (!mixerEngine_) return;
|
||||
mixerEngine_->midiMapper().loadFromFile();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Output Routing
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::string MixerApi::getOutputRoutesJson() const
|
||||
{
|
||||
if (!mixerEngine_) return "[]";
|
||||
|
||||
const auto& routes = mixerEngine_->outputRoutes();
|
||||
|
||||
std::stringstream ss;
|
||||
json_writer writer(ss, false);
|
||||
writer.write_raw("[");
|
||||
bool first = true;
|
||||
for (const auto& route : routes) {
|
||||
if (!first) writer.write_raw(",");
|
||||
first = false;
|
||||
writer.start_object();
|
||||
writer.write_member("sourceBusId", route.sourceBusId);
|
||||
writer.write_member("sourceStartChannel", (int64_t)route.sourceStartChannel);
|
||||
writer.write_member("targetStartChannel", (int64_t)route.targetStartChannel);
|
||||
writer.write_member("channels", (int64_t)route.channels);
|
||||
writer.end_object();
|
||||
}
|
||||
writer.write_raw("]");
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void MixerApi::setOutputRoutesFromJson(const std::string& json)
|
||||
{
|
||||
if (!mixerEngine_) return;
|
||||
|
||||
std::vector<MixerEngine::MixerOutputRoute> routes;
|
||||
|
||||
std::istringstream ss(json);
|
||||
json_reader reader(ss);
|
||||
reader.consume('[');
|
||||
while (reader.peek() != ']')
|
||||
{
|
||||
MixerEngine::MixerOutputRoute route{};
|
||||
reader.start_object();
|
||||
while (reader.peek() != '}')
|
||||
{
|
||||
std::string key = reader.read_string();
|
||||
reader.consume(':');
|
||||
if (key == "sourceBusId") reader.read(&route.sourceBusId);
|
||||
else if (key == "sourceStartChannel") { int64_t v; reader.read(&v); route.sourceStartChannel = (int)v; }
|
||||
else if (key == "targetStartChannel") { int64_t v; reader.read(&v); route.targetStartChannel = (int)v; }
|
||||
else if (key == "channels") { int64_t v; reader.read(&v); route.channels = (int)v; }
|
||||
else reader.skip_property();
|
||||
if (reader.peek() == ',') reader.consume(',');
|
||||
}
|
||||
reader.end_object();
|
||||
routes.push_back(route);
|
||||
if (reader.peek() == ',') reader.consume(',');
|
||||
}
|
||||
reader.consume(']');
|
||||
|
||||
mixerEngine_->setOutputRoutes(routes);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,17 @@ public:
|
||||
/// Get the full mixer state as a JSON string
|
||||
std::string getStateJson() const;
|
||||
|
||||
/// --- Output Routing ---
|
||||
|
||||
/// Get output routes as a JSON array string
|
||||
std::string getOutputRoutesJson() const;
|
||||
|
||||
/// Set output routes from a JSON array string
|
||||
void setOutputRoutesFromJson(const std::string& json);
|
||||
|
||||
/// Apply a full mixer state from a JSON string
|
||||
void setFullState(const std::string& stateJson);
|
||||
|
||||
/// --- Scenes ---
|
||||
|
||||
/// Save current mixer state as a scene
|
||||
@@ -101,6 +112,52 @@ public:
|
||||
/// Delete a scene
|
||||
bool deleteScene(const std::string& sceneId);
|
||||
|
||||
/// --- MIDI Control Surface Mapping ---
|
||||
|
||||
/// Get all MIDI mappings as JSON
|
||||
std::string getMidiMappingsJson() const;
|
||||
|
||||
/// Set all MIDI mappings from JSON
|
||||
void setMidiMappingsFromJson(const std::string& json);
|
||||
|
||||
/// Add a single MIDI mapping
|
||||
void addMidiMapping(int midiChannel, int ccNumber,
|
||||
const std::string& targetType, int64_t targetId,
|
||||
float minValue, float maxValue);
|
||||
|
||||
/// Remove MIDI mapping by CC and channel
|
||||
void removeMidiMapping(int midiChannel, int ccNumber);
|
||||
|
||||
/// Remove MIDI mapping by index
|
||||
void removeMidiMappingByIndex(size_t index);
|
||||
|
||||
/// Clear all MIDI mappings
|
||||
void clearMidiMappings();
|
||||
|
||||
/// Toggle MIDI learn mode
|
||||
void setMidiLearnMode(bool enabled);
|
||||
bool getMidiLearnMode() const;
|
||||
|
||||
/// Set the pending learn target (which UI control was touched)
|
||||
void setMidiLearnTarget(const std::string& targetType, int64_t targetId);
|
||||
|
||||
/// Commit a learned mapping (captured CC + pending target)
|
||||
bool commitMidiLearnMapping();
|
||||
|
||||
/// Get last learned CC event info (for UI feedback)
|
||||
struct LearnedEventInfo {
|
||||
bool hasEvent = false;
|
||||
int midiChannel = -1;
|
||||
int ccNumber = -1;
|
||||
};
|
||||
LearnedEventInfo getLastLearnedMidiEvent() const;
|
||||
|
||||
/// Save MIDI mappings to config file
|
||||
void saveMidiMappingsToFile() const;
|
||||
|
||||
/// Load MIDI mappings from config file
|
||||
void loadMidiMappingsFromFile();
|
||||
|
||||
private:
|
||||
MixerEngine* mixerEngine_ = nullptr;
|
||||
};
|
||||
|
||||
+154
-19
@@ -7,6 +7,7 @@
|
||||
#include "MixerBus.hpp"
|
||||
#include "Lv2Pedalboard.hpp"
|
||||
#include "IHost.hpp"
|
||||
#include "MidiEvent.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
@@ -374,17 +375,31 @@ void MixerEngine::process(
|
||||
|
||||
// Step 2: Process each channel
|
||||
size_t numChannels = channels_.size();
|
||||
|
||||
// Determine channel pairing mode:
|
||||
// For 1-2 total input channels: stereo pairing per strip (backward compatible)
|
||||
// For 3+ input channels: each strip is mono (one input channel → stereo output with pan)
|
||||
bool stereoPairing = (inputChannels <= 2);
|
||||
|
||||
for (size_t ch = 0; ch < numChannels; ++ch) {
|
||||
auto* channel = channels_[ch].get();
|
||||
|
||||
// Build input buffer pointers for this channel
|
||||
// Channel ch reads from device input ch (if available)
|
||||
float* channelInputs[2] = { nullptr, nullptr };
|
||||
if (ch < inputChannels) {
|
||||
channelInputs[0] = deviceInputs[ch]; // mono input
|
||||
// For stereo, pair consecutive channels: (0,1), (2,3), etc.
|
||||
if (ch + 1 < inputChannels) {
|
||||
channelInputs[1] = deviceInputs[ch + 1];
|
||||
if (stereoPairing) {
|
||||
// Stereo pairing: channel 0 ← inputs[0,1], channel 1 ← inputs[2,3], etc.
|
||||
uint32_t baseInput = (uint32_t)(ch * 2);
|
||||
if (baseInput < inputChannels) {
|
||||
channelInputs[0] = deviceInputs[baseInput];
|
||||
if (baseInput + 1 < inputChannels) {
|
||||
channelInputs[1] = deviceInputs[baseInput + 1];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Mono per strip: each channel gets exactly one input
|
||||
if (ch < inputChannels) {
|
||||
channelInputs[0] = deviceInputs[ch];
|
||||
// Second input stays nullptr → mono processing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,10 +410,15 @@ void MixerEngine::process(
|
||||
channelOutputs[1] = channelOutputBuffers_[ch].data() + maxBufferSize_;
|
||||
}
|
||||
|
||||
// Determine the actual number of input channels for this strip
|
||||
size_t stripInputChannels = 0;
|
||||
if (channelInputs[0] != nullptr) stripInputChannels = 1;
|
||||
if (channelInputs[1] != nullptr) stripInputChannels = 2;
|
||||
|
||||
// Process the channel strip
|
||||
channel->process(
|
||||
(const float* const*)channelInputs,
|
||||
std::min((size_t)2, (size_t)inputChannels),
|
||||
stripInputChannels,
|
||||
channelOutputs,
|
||||
2,
|
||||
frames
|
||||
@@ -416,25 +436,52 @@ void MixerEngine::process(
|
||||
bus->process(frames);
|
||||
}
|
||||
|
||||
// Step 5: Write master bus to device outputs
|
||||
if (masterBus_) {
|
||||
for (uint32_t outCh = 0; outCh < outputChannels; ++outCh) {
|
||||
if (deviceOutputs[outCh] == nullptr) continue;
|
||||
// Step 5: Write buses to physical outputs according to output routing
|
||||
if (outputRoutes_.empty()) {
|
||||
// Legacy fallback: write master bus to device outputs 1:1
|
||||
if (masterBus_) {
|
||||
for (uint32_t outCh = 0; outCh < outputChannels; ++outCh) {
|
||||
if (deviceOutputs[outCh] == nullptr) continue;
|
||||
|
||||
const float* src = masterBus_->buffer(outCh);
|
||||
if (src) {
|
||||
std::copy(src, src + frames, deviceOutputs[outCh]);
|
||||
} else if (outCh == 1) {
|
||||
// Mono to stereo: copy L to R if R bus channel doesn't exist
|
||||
const float* srcL = masterBus_->buffer(0);
|
||||
if (srcL) {
|
||||
std::copy(srcL, srcL + frames, deviceOutputs[outCh]);
|
||||
const float* src = masterBus_->buffer(outCh);
|
||||
if (src) {
|
||||
std::copy(src, src + frames, deviceOutputs[outCh]);
|
||||
} else if (outCh == 1) {
|
||||
const float* srcL = masterBus_->buffer(0);
|
||||
if (srcL) {
|
||||
std::copy(srcL, srcL + frames, deviceOutputs[outCh]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use configured output routes
|
||||
for (const auto& route : outputRoutes_) {
|
||||
MixerBus* sourceBus = getBus(route.sourceBusId);
|
||||
if (!sourceBus) continue;
|
||||
|
||||
for (int ch = 0; ch < route.channels; ++ch) {
|
||||
uint32_t targetCh = (uint32_t)(route.targetStartChannel + ch);
|
||||
if (targetCh >= outputChannels) break;
|
||||
if (deviceOutputs[targetCh] == nullptr) continue;
|
||||
|
||||
const float* src = sourceBus->buffer(route.sourceStartChannel + ch);
|
||||
if (src) {
|
||||
std::copy(src, src + frames, deviceOutputs[targetCh]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- MIDI Control Surface Mapping ---
|
||||
|
||||
bool MixerEngine::processMidiEvent(const MidiEvent& event)
|
||||
{
|
||||
midiMapper_.setMixerEngine(this);
|
||||
return midiMapper_.processEvent(event);
|
||||
}
|
||||
|
||||
// --- State Serialization ---
|
||||
|
||||
MixerEngine::MixerSnapshot MixerEngine::captureSnapshot() const
|
||||
@@ -474,6 +521,94 @@ MixerEngine::MixerSnapshot MixerEngine::captureSnapshot() const
|
||||
return snap;
|
||||
}
|
||||
|
||||
// --- Output Routing ---
|
||||
|
||||
void MixerEngine::setOutputRoutes(const std::vector<MixerOutputRoute>& routes)
|
||||
{
|
||||
outputRoutes_ = routes;
|
||||
}
|
||||
|
||||
void MixerEngine::addOutputRoute(int64_t busId, int sourceStartChannel, int targetStartChannel, int channels)
|
||||
{
|
||||
// Remove any existing route that conflicts with the target
|
||||
outputRoutes_.erase(
|
||||
std::remove_if(outputRoutes_.begin(), outputRoutes_.end(),
|
||||
[busId, targetStartChannel](const MixerOutputRoute& r) {
|
||||
return r.sourceBusId == busId &&
|
||||
r.targetStartChannel == targetStartChannel;
|
||||
}),
|
||||
outputRoutes_.end()
|
||||
);
|
||||
|
||||
MixerOutputRoute route;
|
||||
route.sourceBusId = busId;
|
||||
route.sourceStartChannel = sourceStartChannel;
|
||||
route.targetStartChannel = targetStartChannel;
|
||||
route.channels = channels;
|
||||
outputRoutes_.push_back(route);
|
||||
}
|
||||
|
||||
void MixerEngine::removeOutputRoutes(int64_t busId)
|
||||
{
|
||||
outputRoutes_.erase(
|
||||
std::remove_if(outputRoutes_.begin(), outputRoutes_.end(),
|
||||
[busId](const MixerOutputRoute& r) { return r.sourceBusId == busId; }),
|
||||
outputRoutes_.end()
|
||||
);
|
||||
}
|
||||
|
||||
std::vector<MixerEngine::MixerOutputRoute> MixerEngine::findOutputRoutesForBus(int64_t busId) const
|
||||
{
|
||||
std::vector<MixerOutputRoute> result;
|
||||
for (const auto& route : outputRoutes_) {
|
||||
if (route.sourceBusId == busId) {
|
||||
result.push_back(route);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- Auto channel creation ---
|
||||
|
||||
void MixerEngine::autoCreateChannels(uint32_t inputChannelCount)
|
||||
{
|
||||
// Remove existing channels if any
|
||||
for (int i = (int)channels_.size() - 1; i >= 0; --i) {
|
||||
removeChannel(i);
|
||||
}
|
||||
|
||||
// Create one channel strip per input
|
||||
for (uint32_t i = 0; i < inputChannelCount; ++i) {
|
||||
addChannel((int)i);
|
||||
auto* ch = getChannel((int)i);
|
||||
if (ch) {
|
||||
char label[32];
|
||||
snprintf(label, sizeof(label), "Input %u", i + 1);
|
||||
ch->setLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
// Set default output routes:
|
||||
// For 1-2 channels: master bus → physical 1-2 (backward compatible)
|
||||
// For 3+ channels: master bus → physical 1-2, then pair remaining channels
|
||||
// in groups of 2 to physical 3-4, 5-6, etc.
|
||||
outputRoutes_.clear();
|
||||
|
||||
if (!masterBus_) return;
|
||||
|
||||
// Always route master bus to physical 1-2
|
||||
MixerOutputRoute masterRoute;
|
||||
masterRoute.sourceBusId = masterBus_->id();
|
||||
masterRoute.sourceStartChannel = 0;
|
||||
masterRoute.targetStartChannel = 0;
|
||||
masterRoute.channels = 2;
|
||||
outputRoutes_.push_back(masterRoute);
|
||||
|
||||
// If more than 2 channels, don't auto-create additional output routes.
|
||||
// The user should configure them via the output routing UI.
|
||||
// (Aux buses, subgroups, etc. are not auto-routed)
|
||||
}
|
||||
|
||||
void MixerEngine::applySnapshot(const MixerSnapshot& snapshot)
|
||||
{
|
||||
// Apply channel states
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <functional>
|
||||
#include "MixerChannelStrip.hpp"
|
||||
#include "MixerBus.hpp"
|
||||
#include "MidiMapper.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
@@ -70,6 +71,11 @@ public:
|
||||
/// 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);
|
||||
|
||||
/// Remove a channel by its channel index.
|
||||
void removeChannel(int channelIndex);
|
||||
|
||||
@@ -113,6 +119,32 @@ public:
|
||||
/// Clear all routes.
|
||||
void clearRoutes();
|
||||
|
||||
/// --- Output Routing ---
|
||||
|
||||
/// Describes a mapping from a mixer bus to physical output channels.
|
||||
/// Multiple routes can be active simultaneously (e.g. Master→1-2, Aux1→3-4).
|
||||
struct MixerOutputRoute {
|
||||
int64_t sourceBusId; // Bus to route from
|
||||
int sourceStartChannel; // Starting channel on the bus (0=L, 1=R)
|
||||
int targetStartChannel; // Starting physical output channel
|
||||
int channels; // Number of consecutive channels to route (1 or 2 typically)
|
||||
};
|
||||
|
||||
/// 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_; }
|
||||
|
||||
@@ -143,6 +175,16 @@ public:
|
||||
/// True if any channel has solo engaged.
|
||||
bool anySoloActive() const;
|
||||
|
||||
/// --- MIDI Control Surface Mapping ---
|
||||
|
||||
/// Access the MIDI mapper for CC control surface mapping.
|
||||
MidiMapper& midiMapper() { return midiMapper_; }
|
||||
const MidiMapper& midiMapper() const { return midiMapper_; }
|
||||
|
||||
/// Process a MIDI event (typically from the real-time audio thread).
|
||||
/// Routes CC messages to the midi mapper. Returns true if consumed.
|
||||
bool processMidiEvent(const struct MidiEvent& event);
|
||||
|
||||
/// --- State Serialization ---
|
||||
|
||||
struct MixerSnapshot {
|
||||
@@ -181,6 +223,9 @@ private:
|
||||
// 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;
|
||||
@@ -188,6 +233,9 @@ private:
|
||||
// IHost reference for FX preparation
|
||||
IHost* pHost_ = nullptr;
|
||||
|
||||
// MIDI CC control surface mapper
|
||||
MidiMapper midiMapper_;
|
||||
|
||||
// Next bus ID counter
|
||||
static std::atomic<int64_t> nextBusId_;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "CpuGovernor.hpp"
|
||||
#include "RegDb.hpp"
|
||||
#include "RingBufferReader.hpp"
|
||||
#include "MixerEngine.hpp"
|
||||
#include "PiPedalUI.hpp"
|
||||
#include "atom_object.hpp"
|
||||
#include "Lv2PluginChangeMonitor.hpp"
|
||||
@@ -3088,6 +3089,24 @@ void PiPedalModel::SetMixerEngine(const std::shared_ptr<MixerEngine>& engine)
|
||||
{
|
||||
this->audioHost->SetMixerEngine(engine);
|
||||
}
|
||||
|
||||
if (engine && this->audioHost && this->audioHost->IsOpen())
|
||||
{
|
||||
// Auto-create channels based on detected input channel count
|
||||
uint32_t inputChannels = this->audioHost->GetDeviceCaptureChannels();
|
||||
if (inputChannels == 0) inputChannels = 2; // default to stereo
|
||||
|
||||
engine->autoCreateChannels(inputChannels);
|
||||
|
||||
Lv2Log::info(SS("Mixer engine initialized with "
|
||||
<< inputChannels << " channels, "
|
||||
<< engine->channelCount() << " strips created."));
|
||||
}
|
||||
|
||||
// Load saved MIDI control surface mappings
|
||||
if (engine) {
|
||||
engine->midiMapper().loadFromFile();
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::SetSelectedPedalboardPlugin(uint64_t clientId, uint64_t pedalboardId)
|
||||
|
||||
@@ -1395,6 +1395,25 @@ public:
|
||||
}
|
||||
REGISTER_MESSAGE_HANDLER(mixerGetScenes)
|
||||
|
||||
/************************************************************************/
|
||||
/* Mixer Output Routing Messages */
|
||||
/************************************************************************/
|
||||
|
||||
void handle_mixerGetOutputRoutes(int replyTo, json_reader *pReader)
|
||||
{
|
||||
std::string json = this->mixerApi.getOutputRoutesJson();
|
||||
this->JsonReply(replyTo, "mixerOutputRoutes", json.c_str());
|
||||
}
|
||||
REGISTER_MESSAGE_HANDLER(mixerGetOutputRoutes)
|
||||
|
||||
void handle_mixerSetOutputRoutes(int replyTo, json_reader *pReader)
|
||||
{
|
||||
std::string json;
|
||||
pReader->read(&json);
|
||||
this->mixerApi.setOutputRoutesFromJson(json);
|
||||
}
|
||||
REGISTER_MESSAGE_HANDLER(mixerSetOutputRoutes)
|
||||
|
||||
/************************************************************************/
|
||||
/* MIDI Control Surface Mapping Messages */
|
||||
/************************************************************************/
|
||||
|
||||
@@ -157,6 +157,16 @@ namespace pipedal
|
||||
return this->sampleRate;
|
||||
}
|
||||
|
||||
virtual uint32_t GetDeviceCaptureChannels() const override
|
||||
{
|
||||
return this->captureChannels;
|
||||
}
|
||||
|
||||
virtual uint32_t GetDevicePlaybackChannels() const override
|
||||
{
|
||||
return this->playbackChannels;
|
||||
}
|
||||
|
||||
virtual size_t GetMidiInputEventCount() override
|
||||
{
|
||||
return midiEventCount;
|
||||
|
||||
Reference in New Issue
Block a user