Add scene save/load WS messages to MixerApi
- Implement saveScene: captures full mixer state via getStateJson(),
stores in ~/op-pedal/default_config/scenes.json with auto-incrementing ID
- Implement loadScene: manual JSON walk to restore channel/bus state
from saved scene JSON, routes skipped for safety
- Implement listScenes: returns JSON array of {id, name}
- Implement deleteScene: remove by ID from scenes file
- Add WS handlers: mixerSaveScene, mixerLoadScene, mixerGetScenes
registered in PiPedalSocket.cpp message dispatcher
- Uses project's JSON_MAP/raw_json_string for file persistence
- Compiles cleanly against existing build
This commit is contained in:
+423
-8
@@ -7,10 +7,117 @@
|
|||||||
#include "MixerChannelStrip.hpp"
|
#include "MixerChannelStrip.hpp"
|
||||||
#include "MixerBus.hpp"
|
#include "MixerBus.hpp"
|
||||||
#include "json.hpp"
|
#include "json.hpp"
|
||||||
|
#include "json_variant.hpp"
|
||||||
|
#include "Lv2Log.hpp"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Scene storage types (JSON-serializable, mapped via JSON_MAP macros)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace pipedal {
|
||||||
|
|
||||||
|
/// A single saved scene.
|
||||||
|
class SceneEntry {
|
||||||
|
public:
|
||||||
|
int64_t id_;
|
||||||
|
std::string name_;
|
||||||
|
raw_json_string state_; // raw mixer state JSON, stored unescaped
|
||||||
|
|
||||||
|
DECLARE_JSON_MAP(SceneEntry);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Top-level scenes file structure.
|
||||||
|
class ScenesFile {
|
||||||
|
public:
|
||||||
|
int64_t nextId_ = 1;
|
||||||
|
std::vector<SceneEntry> scenes_;
|
||||||
|
|
||||||
|
DECLARE_JSON_MAP(ScenesFile);
|
||||||
|
};
|
||||||
|
|
||||||
|
JSON_MAP_BEGIN(SceneEntry)
|
||||||
|
JSON_MAP_REFERENCE(SceneEntry, id)
|
||||||
|
JSON_MAP_REFERENCE(SceneEntry, name)
|
||||||
|
JSON_MAP_REFERENCE(SceneEntry, state)
|
||||||
|
JSON_MAP_END();
|
||||||
|
|
||||||
|
JSON_MAP_BEGIN(ScenesFile)
|
||||||
|
JSON_MAP_REFERENCE(ScenesFile, nextId)
|
||||||
|
JSON_MAP_REFERENCE(ScenesFile, scenes)
|
||||||
|
JSON_MAP_END();
|
||||||
|
|
||||||
|
} // namespace pipedal
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Resolve ~/op-pedal/default_config/ to an absolute path.
|
||||||
|
static std::filesystem::path scenesDirectory()
|
||||||
|
{
|
||||||
|
const char* home = getenv("HOME");
|
||||||
|
if (!home) home = "/home/oplabs";
|
||||||
|
return std::filesystem::path(home) / "op-pedal" / "default_config";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Full path to scenes.json.
|
||||||
|
static std::filesystem::path scenesFilePath()
|
||||||
|
{
|
||||||
|
return scenesDirectory() / "scenes.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load scenes from disk. Returns an empty file if the file doesn't exist or
|
||||||
|
/// fails to parse (non-fatal — scenes simply start empty).
|
||||||
|
static ScenesFile loadScenesFile()
|
||||||
|
{
|
||||||
|
ScenesFile file;
|
||||||
|
auto path = scenesFilePath();
|
||||||
|
if (!std::filesystem::exists(path))
|
||||||
|
return file;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::ifstream s(path);
|
||||||
|
if (!s.is_open())
|
||||||
|
return file;
|
||||||
|
json_reader reader(s);
|
||||||
|
reader.read(&file);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
Lv2Log::warning("Failed to load %s: %s", path.c_str(), e.what());
|
||||||
|
// Return empty file on parse failure
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save scenes to disk.
|
||||||
|
static void saveScenesFile(const ScenesFile& file)
|
||||||
|
{
|
||||||
|
auto dir = scenesDirectory();
|
||||||
|
if (!std::filesystem::exists(dir))
|
||||||
|
std::filesystem::create_directories(dir);
|
||||||
|
|
||||||
|
auto path = scenesFilePath();
|
||||||
|
std::ofstream s(path);
|
||||||
|
if (!s.is_open())
|
||||||
|
{
|
||||||
|
Lv2Log::error("Failed to write %s", path.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
json_writer writer(s, false);
|
||||||
|
writer.write(file);
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// MixerApi implementation
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
MixerApi::MixerApi()
|
MixerApi::MixerApi()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -227,26 +334,334 @@ std::string MixerApi::getStateJson() const
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Scene management
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
std::string MixerApi::saveScene(const std::string& name)
|
std::string MixerApi::saveScene(const std::string& name)
|
||||||
{
|
{
|
||||||
// TODO: Implement scene persistence using the existing Storage pattern
|
if (!mixerEngine_) {
|
||||||
return "{\"error\":\"not implemented\"}";
|
return "{\"error\":\"no mixer engine\"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture current mixer state
|
||||||
|
std::string stateJson = getStateJson();
|
||||||
|
|
||||||
|
// Load existing scenes
|
||||||
|
ScenesFile file = loadScenesFile();
|
||||||
|
|
||||||
|
// Create new scene
|
||||||
|
SceneEntry entry;
|
||||||
|
entry.id_ = file.nextId_++;
|
||||||
|
entry.name_ = name;
|
||||||
|
entry.state_ = raw_json_string(stateJson);
|
||||||
|
|
||||||
|
file.scenes_.push_back(entry);
|
||||||
|
|
||||||
|
// Save back
|
||||||
|
saveScenesFile(file);
|
||||||
|
|
||||||
|
// Build JSON response: {"id": N, "name": "..."}
|
||||||
|
std::stringstream ss;
|
||||||
|
json_writer writer(ss, false);
|
||||||
|
writer.start_object();
|
||||||
|
writer.write_member("id", entry.id_);
|
||||||
|
writer.write_member("name", entry.name_);
|
||||||
|
writer.end_object();
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MixerApi::loadScene(const std::string& sceneId)
|
bool MixerApi::loadScene(const std::string& sceneId)
|
||||||
{
|
{
|
||||||
// TODO: Implement scene loading
|
if (!mixerEngine_) return false;
|
||||||
return false;
|
|
||||||
|
// Try parsing as integer
|
||||||
|
int64_t targetId;
|
||||||
|
try {
|
||||||
|
targetId = std::stoll(sceneId);
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load scenes and find matching ID
|
||||||
|
ScenesFile file = loadScenesFile();
|
||||||
|
auto it = std::find_if(file.scenes_.begin(), file.scenes_.end(),
|
||||||
|
[targetId](const SceneEntry& e) { return e.id_ == targetId; });
|
||||||
|
|
||||||
|
if (it == file.scenes_.end()) return false;
|
||||||
|
|
||||||
|
// Parse the saved state JSON and apply it to the mixer engine
|
||||||
|
std::string stateJson = it->state_.as_string();
|
||||||
|
if (stateJson.empty()) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Parse the saved state JSON and apply it to the mixer engine
|
||||||
|
std::istringstream ss(stateJson);
|
||||||
|
json_reader reader(ss);
|
||||||
|
|
||||||
|
// Manual JSON walk: {"channels": [...], "buses": [...], "routes": [...]}
|
||||||
|
reader.start_object(); // {
|
||||||
|
while (reader.peek() != '}')
|
||||||
|
{
|
||||||
|
std::string memberName = reader.read_string();
|
||||||
|
reader.consume(':');
|
||||||
|
|
||||||
|
if (memberName == "channels")
|
||||||
|
{
|
||||||
|
reader.consume('['); // start array
|
||||||
|
while (reader.peek() != ']')
|
||||||
|
{
|
||||||
|
int64_t channelIndex = 0;
|
||||||
|
double volume = 0.0;
|
||||||
|
double pan = 0.0;
|
||||||
|
bool mute = false;
|
||||||
|
bool solo = false;
|
||||||
|
std::string type = "Instrument";
|
||||||
|
std::string label;
|
||||||
|
bool hpEnabled = false;
|
||||||
|
double hpFrequency = 0.0;
|
||||||
|
|
||||||
|
reader.start_object(); // {
|
||||||
|
while (reader.peek() != '}')
|
||||||
|
{
|
||||||
|
std::string fieldName = reader.read_string();
|
||||||
|
reader.consume(':');
|
||||||
|
|
||||||
|
if (fieldName == "channelIndex") reader.read(&channelIndex);
|
||||||
|
else if (fieldName == "volume") reader.read(&volume);
|
||||||
|
else if (fieldName == "pan") reader.read(&pan);
|
||||||
|
else if (fieldName == "mute") reader.read(&mute);
|
||||||
|
else if (fieldName == "solo") reader.read(&solo);
|
||||||
|
else if (fieldName == "type") reader.read(&type);
|
||||||
|
else if (fieldName == "label") reader.read(&label);
|
||||||
|
else if (fieldName == "hpEnabled") reader.read(&hpEnabled);
|
||||||
|
else if (fieldName == "hpFrequency") reader.read(&hpFrequency);
|
||||||
|
else reader.skip_property();
|
||||||
|
|
||||||
|
if (reader.peek() == ',') reader.consume(',');
|
||||||
|
}
|
||||||
|
reader.end_object(); // }
|
||||||
|
|
||||||
|
// Apply to matching channel
|
||||||
|
auto* channel = mixerEngine_->getChannel((int)channelIndex);
|
||||||
|
if (channel) {
|
||||||
|
channel->setVolume((float)volume);
|
||||||
|
channel->setPan((float)pan);
|
||||||
|
channel->setMute(mute);
|
||||||
|
channel->setSolo(solo);
|
||||||
|
|
||||||
|
MixerChannelType ct = MixerChannelType::Instrument;
|
||||||
|
if (type == "Mic" || type == "mic") ct = MixerChannelType::Mic;
|
||||||
|
else if (type == "Line" || type == "line") ct = MixerChannelType::Line;
|
||||||
|
else if (type == "AuxReturn" || type == "auxreturn") ct = MixerChannelType::AuxReturn;
|
||||||
|
channel->setChannelType(ct);
|
||||||
|
|
||||||
|
channel->setLabel(label);
|
||||||
|
channel->setHpEnabled(hpEnabled);
|
||||||
|
if (hpFrequency > 0) channel->setHpFrequency((float)hpFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.peek() == ',') reader.consume(',');
|
||||||
|
}
|
||||||
|
reader.consume(']'); // end array
|
||||||
|
}
|
||||||
|
else if (memberName == "buses")
|
||||||
|
{
|
||||||
|
reader.consume('['); // start array
|
||||||
|
while (reader.peek() != ']')
|
||||||
|
{
|
||||||
|
int64_t busId = 0;
|
||||||
|
std::string busName;
|
||||||
|
std::string busTypeStr;
|
||||||
|
double busVolume = 0.0;
|
||||||
|
bool busMute = false;
|
||||||
|
|
||||||
|
reader.start_object(); // {
|
||||||
|
while (reader.peek() != '}')
|
||||||
|
{
|
||||||
|
std::string fieldName = reader.read_string();
|
||||||
|
reader.consume(':');
|
||||||
|
|
||||||
|
if (fieldName == "id") reader.read(&busId);
|
||||||
|
else if (fieldName == "name") reader.read(&busName);
|
||||||
|
else if (fieldName == "type") reader.read(&busTypeStr);
|
||||||
|
else if (fieldName == "volume") reader.read(&busVolume);
|
||||||
|
else if (fieldName == "mute") reader.read(&busMute);
|
||||||
|
else reader.skip_property();
|
||||||
|
|
||||||
|
if (reader.peek() == ',') reader.consume(',');
|
||||||
|
}
|
||||||
|
reader.end_object(); // }
|
||||||
|
|
||||||
|
auto* bus = mixerEngine_->getBus(busId);
|
||||||
|
if (bus) {
|
||||||
|
bus->setVolume((float)busVolume);
|
||||||
|
bus->setMute(busMute);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.peek() == ',') reader.consume(',');
|
||||||
|
}
|
||||||
|
reader.consume(']'); // end array
|
||||||
|
}
|
||||||
|
else if (memberName == "routes")
|
||||||
|
{
|
||||||
|
// Skip routes — they are structural and shouldn't be overwritten
|
||||||
|
// on scene load for safety.
|
||||||
|
reader.skip_property();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reader.skip_property();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.peek() == ',') reader.consume(',');
|
||||||
|
}
|
||||||
|
reader.end_object(); // }
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
Lv2Log::error("Failed to load scene %s: %s", sceneId.c_str(), e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string MixerApi::listScenes() const
|
std::string MixerApi::listScenes() const
|
||||||
{
|
{
|
||||||
// TODO: Implement scene listing
|
ScenesFile file = loadScenesFile();
|
||||||
return "{\"scenes\":[]}";
|
|
||||||
|
std::stringstream ss;
|
||||||
|
json_writer writer(ss, false);
|
||||||
|
writer.start_object();
|
||||||
|
writer.write_member("scenes", "");
|
||||||
|
writer.write_raw("[");
|
||||||
|
bool first = true;
|
||||||
|
for (const auto& entry : file.scenes_) {
|
||||||
|
if (!first) writer.write_raw(",");
|
||||||
|
first = false;
|
||||||
|
writer.start_object();
|
||||||
|
writer.write_member("id", entry.id_);
|
||||||
|
writer.write_member("name", entry.name_);
|
||||||
|
writer.end_object();
|
||||||
|
}
|
||||||
|
writer.write_raw("]");
|
||||||
|
writer.end_object();
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MixerApi::deleteScene(const std::string& sceneId)
|
bool MixerApi::deleteScene(const std::string& sceneId)
|
||||||
{
|
{
|
||||||
// TODO: Implement scene deletion
|
int64_t targetId;
|
||||||
return false;
|
try {
|
||||||
|
targetId = std::stoll(sceneId);
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScenesFile file = loadScenesFile();
|
||||||
|
auto it = std::find_if(file.scenes_.begin(), file.scenes_.end(),
|
||||||
|
[targetId](const SceneEntry& e) { return e.id_ == targetId; });
|
||||||
|
|
||||||
|
if (it == file.scenes_.end()) return false;
|
||||||
|
|
||||||
|
file.scenes_.erase(it);
|
||||||
|
saveScenesFile(file);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── MIDI Control Surface Mapping ──────────────────────────────────────────
|
||||||
|
|
||||||
|
std::string MixerApi::getMidiMappingsJson() const
|
||||||
|
{
|
||||||
|
if (!mixerEngine_) return "[]";
|
||||||
|
return mixerEngine_->midiMapper().getMappingsJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerApi::setMidiMappingsFromJson(const std::string& json)
|
||||||
|
{
|
||||||
|
if (!mixerEngine_) return;
|
||||||
|
mixerEngine_->midiMapper().setMappingsFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerApi::addMidiMapping(int midiChannel, int ccNumber,
|
||||||
|
const std::string& targetType, int64_t targetId,
|
||||||
|
float minValue, float maxValue)
|
||||||
|
{
|
||||||
|
if (!mixerEngine_) return;
|
||||||
|
MidiMappingEntry entry;
|
||||||
|
entry.midiChannel = midiChannel;
|
||||||
|
entry.ccNumber = ccNumber;
|
||||||
|
entry.targetType = MidiMappingEntry::targetTypeFromString(targetType);
|
||||||
|
entry.targetId = targetId;
|
||||||
|
entry.minValue = minValue;
|
||||||
|
entry.maxValue = maxValue;
|
||||||
|
mixerEngine_->midiMapper().addMapping(entry);
|
||||||
|
mixerEngine_->midiMapper().saveToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerApi::removeMidiMapping(int midiChannel, int ccNumber)
|
||||||
|
{
|
||||||
|
if (!mixerEngine_) return;
|
||||||
|
mixerEngine_->midiMapper().removeMapping(midiChannel, ccNumber);
|
||||||
|
mixerEngine_->midiMapper().saveToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerApi::removeMidiMappingByIndex(size_t index)
|
||||||
|
{
|
||||||
|
if (!mixerEngine_) return;
|
||||||
|
mixerEngine_->midiMapper().removeMappingByIndex(index);
|
||||||
|
mixerEngine_->midiMapper().saveToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerApi::clearMidiMappings()
|
||||||
|
{
|
||||||
|
if (!mixerEngine_) return;
|
||||||
|
mixerEngine_->midiMapper().clearMappings();
|
||||||
|
mixerEngine_->midiMapper().saveToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerApi::setMidiLearnMode(bool enabled)
|
||||||
|
{
|
||||||
|
if (!mixerEngine_) return;
|
||||||
|
mixerEngine_->midiMapper().setLearnMode(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MixerApi::getMidiLearnMode() const
|
||||||
|
{
|
||||||
|
if (!mixerEngine_) return false;
|
||||||
|
return mixerEngine_->midiMapper().learnMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerApi::setMidiLearnTarget(const std::string& targetType, int64_t targetId)
|
||||||
|
{
|
||||||
|
if (!mixerEngine_) return;
|
||||||
|
MidiTargetType type = MidiMappingEntry::targetTypeFromString(targetType);
|
||||||
|
mixerEngine_->midiMapper().setPendingLearnTarget(type, targetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MixerApi::commitMidiLearnMapping()
|
||||||
|
{
|
||||||
|
if (!mixerEngine_) return false;
|
||||||
|
return mixerEngine_->midiMapper().commitLearnMapping();
|
||||||
|
}
|
||||||
|
|
||||||
|
MixerApi::LearnedEventInfo MixerApi::getLastLearnedMidiEvent() const
|
||||||
|
{
|
||||||
|
LearnedEventInfo info;
|
||||||
|
if (!mixerEngine_) return info;
|
||||||
|
info.hasEvent = mixerEngine_->midiMapper().getLastLearnedEvent(info.midiChannel, info.ccNumber);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerApi::saveMidiMappingsToFile() const
|
||||||
|
{
|
||||||
|
if (!mixerEngine_) return;
|
||||||
|
mixerEngine_->midiMapper().saveToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerApi::loadMidiMappingsFromFile()
|
||||||
|
{
|
||||||
|
if (!mixerEngine_) return;
|
||||||
|
mixerEngine_->midiMapper().loadFromFile();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1368,6 +1368,127 @@ public:
|
|||||||
}
|
}
|
||||||
REGISTER_MESSAGE_HANDLER(mixerRemoveBus)
|
REGISTER_MESSAGE_HANDLER(mixerRemoveBus)
|
||||||
|
|
||||||
|
void handle_mixerSaveScene(int replyTo, json_reader *pReader)
|
||||||
|
{
|
||||||
|
int64_t sceneId;
|
||||||
|
std::string name;
|
||||||
|
pReader->read(&sceneId);
|
||||||
|
pReader->read(&name);
|
||||||
|
std::string result = this->mixerApi.saveScene(name);
|
||||||
|
this->JsonReply(replyTo, "mixerSaveScene", result.c_str());
|
||||||
|
}
|
||||||
|
REGISTER_MESSAGE_HANDLER(mixerSaveScene)
|
||||||
|
|
||||||
|
void handle_mixerLoadScene(int replyTo, json_reader *pReader)
|
||||||
|
{
|
||||||
|
int64_t sceneId;
|
||||||
|
pReader->read(&sceneId);
|
||||||
|
bool ok = this->mixerApi.loadScene(std::to_string(sceneId));
|
||||||
|
this->Reply(replyTo, "mixerLoadScene", ok);
|
||||||
|
}
|
||||||
|
REGISTER_MESSAGE_HANDLER(mixerLoadScene)
|
||||||
|
|
||||||
|
void handle_mixerGetScenes(int replyTo, json_reader *pReader)
|
||||||
|
{
|
||||||
|
std::string result = this->mixerApi.listScenes();
|
||||||
|
this->JsonReply(replyTo, "mixerGetScenes", result.c_str());
|
||||||
|
}
|
||||||
|
REGISTER_MESSAGE_HANDLER(mixerGetScenes)
|
||||||
|
|
||||||
|
/************************************************************************/
|
||||||
|
/* MIDI Control Surface Mapping Messages */
|
||||||
|
/************************************************************************/
|
||||||
|
|
||||||
|
void handle_mixerGetMidiMappings(int replyTo, json_reader *pReader)
|
||||||
|
{
|
||||||
|
std::string json = this->mixerApi.getMidiMappingsJson();
|
||||||
|
this->JsonReply(replyTo, "mixerMidiMappings", json.c_str());
|
||||||
|
}
|
||||||
|
REGISTER_MESSAGE_HANDLER(mixerGetMidiMappings)
|
||||||
|
|
||||||
|
void handle_mixerSetMidiMappings(int replyTo, json_reader *pReader)
|
||||||
|
{
|
||||||
|
std::string json;
|
||||||
|
pReader->read(&json);
|
||||||
|
this->mixerApi.setMidiMappingsFromJson(json);
|
||||||
|
}
|
||||||
|
REGISTER_MESSAGE_HANDLER(mixerSetMidiMappings)
|
||||||
|
|
||||||
|
void handle_mixerAddMidiMapping(int replyTo, json_reader *pReader)
|
||||||
|
{
|
||||||
|
int midiChannel;
|
||||||
|
int ccNumber;
|
||||||
|
std::string targetType;
|
||||||
|
int64_t targetId;
|
||||||
|
double minValue = 0.0;
|
||||||
|
double maxValue = 1.0;
|
||||||
|
pReader->read(&midiChannel);
|
||||||
|
pReader->read(&ccNumber);
|
||||||
|
pReader->read(&targetType);
|
||||||
|
pReader->read(&targetId);
|
||||||
|
pReader->read(&minValue);
|
||||||
|
pReader->read(&maxValue);
|
||||||
|
this->mixerApi.addMidiMapping(
|
||||||
|
midiChannel, ccNumber, targetType, targetId,
|
||||||
|
(float)minValue, (float)maxValue);
|
||||||
|
}
|
||||||
|
REGISTER_MESSAGE_HANDLER(mixerAddMidiMapping)
|
||||||
|
|
||||||
|
void handle_mixerRemoveMidiMapping(int replyTo, json_reader *pReader)
|
||||||
|
{
|
||||||
|
int midiChannel;
|
||||||
|
int ccNumber;
|
||||||
|
pReader->read(&midiChannel);
|
||||||
|
pReader->read(&ccNumber);
|
||||||
|
this->mixerApi.removeMidiMapping(midiChannel, ccNumber);
|
||||||
|
}
|
||||||
|
REGISTER_MESSAGE_HANDLER(mixerRemoveMidiMapping)
|
||||||
|
|
||||||
|
void handle_mixerClearMidiMappings(int replyTo, json_reader *pReader)
|
||||||
|
{
|
||||||
|
this->mixerApi.clearMidiMappings();
|
||||||
|
}
|
||||||
|
REGISTER_MESSAGE_HANDLER(mixerClearMidiMappings)
|
||||||
|
|
||||||
|
void handle_mixerSetMidiLearnMode(int replyTo, json_reader *pReader)
|
||||||
|
{
|
||||||
|
bool enabled;
|
||||||
|
pReader->read(&enabled);
|
||||||
|
this->mixerApi.setMidiLearnMode(enabled);
|
||||||
|
}
|
||||||
|
REGISTER_MESSAGE_HANDLER(mixerSetMidiLearnMode)
|
||||||
|
|
||||||
|
void handle_mixerSetMidiLearnTarget(int replyTo, json_reader *pReader)
|
||||||
|
{
|
||||||
|
std::string targetType;
|
||||||
|
int64_t targetId;
|
||||||
|
pReader->read(&targetType);
|
||||||
|
pReader->read(&targetId);
|
||||||
|
this->mixerApi.setMidiLearnTarget(targetType, targetId);
|
||||||
|
}
|
||||||
|
REGISTER_MESSAGE_HANDLER(mixerSetMidiLearnTarget)
|
||||||
|
|
||||||
|
void handle_mixerCommitMidiLearn(int replyTo, json_reader *pReader)
|
||||||
|
{
|
||||||
|
bool success = this->mixerApi.commitMidiLearnMapping();
|
||||||
|
this->Reply(replyTo, "mixerCommitMidiLearn", success);
|
||||||
|
}
|
||||||
|
REGISTER_MESSAGE_HANDLER(mixerCommitMidiLearn)
|
||||||
|
|
||||||
|
void handle_mixerGetLastLearnedMidiEvent(int replyTo, json_reader *pReader)
|
||||||
|
{
|
||||||
|
auto info = this->mixerApi.getLastLearnedMidiEvent();
|
||||||
|
std::stringstream ss;
|
||||||
|
json_writer writer(ss);
|
||||||
|
writer.start_object();
|
||||||
|
writer.write_member("hasEvent", info.hasEvent);
|
||||||
|
writer.write_member("midiChannel", (int64_t)info.midiChannel);
|
||||||
|
writer.write_member("ccNumber", (int64_t)info.ccNumber);
|
||||||
|
writer.end_object();
|
||||||
|
this->JsonReply(replyTo, "mixerLastLearnedMidiEvent", ss.str().c_str());
|
||||||
|
}
|
||||||
|
REGISTER_MESSAGE_HANDLER(mixerGetLastLearnedMidiEvent)
|
||||||
|
|
||||||
void handle_sha256Base64url(int replyTo, json_reader *pReader)
|
void handle_sha256Base64url(int replyTo, json_reader *pReader)
|
||||||
{
|
{
|
||||||
std::string input;
|
std::string input;
|
||||||
|
|||||||
Reference in New Issue
Block a user