diff options
author | Eddy Pedroni <epedroni@pm.me> | 2025-07-23 22:33:05 +0200 |
---|---|---|
committer | Eddy Pedroni <epedroni@pm.me> | 2025-07-23 22:33:05 +0200 |
commit | c5c195ff5318f00d544c0fbceb133abcc4ba7a5a (patch) | |
tree | 1c7a3e646f6eb72d748dd606fe9e8e12175b39bd | |
parent | 41f95375a65a3f67a75258680d6d8b03bb4d678b (diff) |
MVP
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | daemon/Makefile | 2 | ||||
m--------- | daemon/include/tomlplusplus | 0 | ||||
-rw-r--r-- | daemon/src/config.cpp | 179 | ||||
-rw-r--r-- | daemon/src/config.h | 33 | ||||
-rw-r--r-- | daemon/src/configuration.h | 17 | ||||
-rw-r--r-- | daemon/src/connection_manager.cpp | 21 | ||||
-rw-r--r-- | daemon/src/main.cpp | 29 | ||||
-rw-r--r-- | daemon/src/routing.cpp | 8 | ||||
-rw-r--r-- | daemon/src/routing.h | 2 | ||||
-rw-r--r-- | daemon/src/sender.h | 8 | ||||
-rw-r--r-- | daemon/src/types.h | 35 | ||||
-rw-r--r-- | pacman.txt | 1 |
13 files changed, 289 insertions, 49 deletions
diff --git a/.gitmodules b/.gitmodules index a85cfec..f69032b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "daemon/include/concurrentqueue"] path = daemon/include/concurrentqueue url = https://github.com/cameron314/concurrentqueue.git +[submodule "daemon/include/tomlplusplus"] + path = daemon/include/tomlplusplus + url = https://github.com/marzer/tomlplusplus.git diff --git a/daemon/Makefile b/daemon/Makefile index e38108f..9869b08 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -24,7 +24,7 @@ INC_FLAGS := $(addprefix -I,$(INC_DIRS)) # These files will have .d instead of .o as the output. CPPFLAGS := -O3 -std=c++23 $(INC_FLAGS) -MMD -MP -LDFLAGS := -lrtmidi +LDFLAGS := -lrtmidi -lcurl # The final build step. $(BUILD_DIR)/$(TARGET_EXEC): $(OBJS) diff --git a/daemon/include/tomlplusplus b/daemon/include/tomlplusplus new file mode 160000 +Subproject 30172438cee64926dc41fdd9c11fb3ba5b2ba9d diff --git a/daemon/src/config.cpp b/daemon/src/config.cpp new file mode 100644 index 0000000..b88fd0f --- /dev/null +++ b/daemon/src/config.cpp @@ -0,0 +1,179 @@ +#include "config.h" + +#include "tomlplusplus/toml.hpp" + +#include <curl/curl.h> + +#include <iostream> + +namespace midi_router +{ + +static size_t write_function(void *ptr, size_t size, size_t nmemb, std::string* data) { + data->append((char*) ptr, size * nmemb); + return size * nmemb; +} + +static long curl(std::string url, std::string & response) +{ + auto curl = curl_easy_init(); + if (!curl) { + return -1; + } + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_function); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + + curl_easy_perform(curl); + + long response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + + curl_easy_cleanup(curl); + + return response_code; +} + +Config_Loader::Config_Loader(std::string url) +{ + auto code = curl(url, m_file_contents); + if (code != 200) + { + std::cerr << "Failed to fetch config file with code " << code << "\n"; + return; + } + + auto toml_config = toml::parse(m_file_contents); + + auto* devices = toml_config["devices"].as_table(); + if (!devices) + { + std::cerr << "Expected \"devices\" to be a table.\n"; + return; + } + + for (auto&& [id, name] : *devices) + { + m_device_map[name.as_string()->get()] = id.str(); + } +} + +Device_Map const & +Config_Loader::get_device_map() const +{ + return m_device_map; +} + +static void +add_to_routes(Device_Id const & from_device, Route_Table & rt, toml::table & routes, Connection_Manager const & cm) +{ + for (auto&& [key, types_node] : routes) + { + std::string to_device {key.str()}; + auto types = types_node.as_array(); + if (!types) + { + std::cerr << "Unable to parse note types for route from " << from_device << " to " << to_device << "\n"; + continue; + } + for (auto & type : *types) + { + std::string type_name = type.as_string()->get(); + if (type_name == "all") + { + for (auto&& [name, mt] : string2type) + { + rt[from_device][mt].push_back(cm.get_sender(to_device)); + } + } + else + { + Message_Type mt = string2type.at(type.as_string()->get()); + rt[from_device][mt].push_back(cm.get_sender(to_device)); + } + } + } +} + +static void +add_type_routes(Device_Map const & device_map, Device_Id const & from_device, Route_Table & rt, toml::table & routes, Connection_Manager const & cm) +{ + for (auto&& [key, devices_node] : routes) + { + std::string type {key.str()}; + auto devices = devices_node.as_array(); + if (!devices) + { + std::cerr << "Unable to parse devices for route from " << from_device << " with type " << type << "\n"; + continue; + } + for (auto & device : *devices) + { + Message_Type mt = string2type.at(type); + std::string to_device = device.as_string()->get(); + if (to_device == "all") + { + for (auto&& [name, id] : device_map) + { + if (id == from_device) continue; + rt[from_device][mt].push_back(cm.get_sender(id)); + } + } + else + { + rt[from_device][mt].push_back(cm.get_sender(to_device)); + } + } + } +} + +Route_Table const & +Config_Loader::compile_route_table(Connection_Manager const & cm) +{ + m_route_table.clear(); + + auto toml_config = toml::parse(m_file_contents); + + for (auto && [name, id] : m_device_map) + { + auto * to_routes = toml_config["route"]["from"][id]["to"].as_table(); + if (to_routes) + { + add_to_routes(id, m_route_table, *to_routes, cm); + } + + auto * type_routes = toml_config["route"]["from"][id]["type"].as_table(); + if (type_routes) + { + add_type_routes(m_device_map, id, m_route_table, *type_routes, cm); + } + } + + return m_route_table; +} + +void +Config_Loader::print_route_table() const +{ + std::cout << "Routes ===========================================================\n"; + for (auto && [source_id, message_types] : m_route_table) + { + std::cout << "From " << source_id << ":\n"; + for (auto && [type, targets] : message_types) + { + std::cout << "\t" << type2string.at(type) << ": "; + for (auto const & target : targets) + { + std::cout << target.get().get_id() << " "; + } + std::cout << "\n"; + } + } + std::cout << "==================================================================\n"; +} + +} // namespace midi_router diff --git a/daemon/src/config.h b/daemon/src/config.h new file mode 100644 index 0000000..a3ec0af --- /dev/null +++ b/daemon/src/config.h @@ -0,0 +1,33 @@ +#pragma once + +#include "types.h" + +#include "connection_manager.h" + +#include <string> + +namespace midi_router +{ + +class Config_Loader +{ +public: + Config_Loader(std::string url); + + Device_Map const & + get_device_map() const; + + Route_Table const & + compile_route_table(Connection_Manager const & cm); + + void + print_route_table() const; + +private: + std::string m_file_contents; + Device_Map m_device_map {}; + Route_Table m_route_table {}; +}; + +} // namespace midi_router + diff --git a/daemon/src/configuration.h b/daemon/src/configuration.h deleted file mode 100644 index 4189315..0000000 --- a/daemon/src/configuration.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "types.h" - -#include <map> - -namespace midi_router -{ - -struct Configuration -{ - - Device_Map const devices; - Route_Map const routes; -}; - -} // namespace midi_router diff --git a/daemon/src/connection_manager.cpp b/daemon/src/connection_manager.cpp index f6a02c1..6de1800 100644 --- a/daemon/src/connection_manager.cpp +++ b/daemon/src/connection_manager.cpp @@ -21,13 +21,19 @@ struct Device_Connection : public Sender RtMidiOut midi_out {}; void - send(std::array<std::uint8_t, 3> const & payload) override + send(std::vector<std::uint8_t> const & payload) override { if (midi_out.isPortOpen()) { - midi_out.sendMessage(payload.data(), 3); + midi_out.sendMessage(&payload); } } + + std::string + get_id() const override + { + return source_id; + } }; static void @@ -35,12 +41,15 @@ callback(double time_stamp, std::vector<unsigned char> *raw, void *user_data) { Device_Connection* device = static_cast<Device_Connection*>(user_data); - if (raw->size() != 3) + if (raw->size() > 3) { - std::cerr << "Received message with wrong size from " << device->source_id << ", dropping\n"; + std::cerr << "Received message with wrong size (" << raw->size() << ") from " << device->source_id << ", dropping\n"; + for (auto const & byte : *raw) std::cerr << static_cast<unsigned>(byte); + std::cerr << "\n"; + return; } - Message message { &device->source_id, {raw->at(0), raw->at(1), raw->at(2)} }; + Message message { &device->source_id, *raw }; device->submitter.submit(message); } @@ -55,7 +64,7 @@ Connection_Manager::Connection_Manager(Device_Map const & device_map, Submitter auto & connection = m_connections[id]; connection->midi_in.setCallback(&callback, connection.get()); - connection->midi_in.ignoreTypes(true, true, true); + connection->midi_in.ignoreTypes(true, false, true); if (!open_port(&connection->midi_in, name)) { diff --git a/daemon/src/main.cpp b/daemon/src/main.cpp index 008fd2a..8ca0dd6 100644 --- a/daemon/src/main.cpp +++ b/daemon/src/main.cpp @@ -1,6 +1,7 @@ -#include "connection_manager.h" #include "types.h" +#include "connection_manager.h" #include "routing.h" +#include "config.h" #include <iostream> #include <thread> @@ -8,31 +9,21 @@ using namespace midi_router; +bool verbose = false; + int main() { - // LTG: move configuration to TOML file - Device_Map const devices { - { "Deluge MIDI 1", "deluge" }, - { "MPK mini Plus MIDI 1", "mpk" }, - { "Xjam MIDI 1", "xjam" }, - { "OP-1 MIDI 1", "op1" }, - }; - + Config_Loader loader {"https://files.0xf7.com/api/public/dl/NR1j-os8/midi-router/config.toml"}; Router router {}; - Connection_Manager cm { devices, router }; + Connection_Manager cm { loader.get_device_map(), router }; + + Route_Table const & routes = loader.compile_route_table(cm); - Route_Map const routes { - { "mpk", - { - { Message_Type::NOTE_ON, { cm.get_sender("deluge"), cm.get_sender("op1") } }, - { Message_Type::NOTE_OFF, { cm.get_sender("deluge"), cm.get_sender("op1")} }, - } - }, - }; + if (verbose) loader.print_route_table(); while(true) { - router.route(routes); + router.route(routes, verbose); //std::this_thread::sleep_for(std::chrono::milliseconds(500)); } } diff --git a/daemon/src/routing.cpp b/daemon/src/routing.cpp index ac15eff..8dce695 100644 --- a/daemon/src/routing.cpp +++ b/daemon/src/routing.cpp @@ -7,7 +7,7 @@ namespace midi_router { void -Router::route(Route_Map const & config) +Router::route(Route_Table const & config, bool verbose) { Message m; if (m_queue.wait_dequeue_timed(m, std::chrono::milliseconds(500))) @@ -20,6 +20,12 @@ Router::route(Route_Map const & config) // LTG: at least one of these map accesses can be avoided for (auto const & target: config.at(*m.source_id).at(m.type())) { + if (verbose) + { + std::cout << "[" << type2string.at(m.type()) << "] " << *m.source_id << " -> " << target.get().get_id() << " | "; + for (auto byte : m.bytes) std::cout << static_cast<unsigned>(byte) << " "; + std::cout << ")\n"; + } target.get().send(m.bytes); } } diff --git a/daemon/src/routing.h b/daemon/src/routing.h index 592606d..86f62d4 100644 --- a/daemon/src/routing.h +++ b/daemon/src/routing.h @@ -12,7 +12,7 @@ class Router : public Submitter { public: void - route(Route_Map const & config); + route(Route_Table const & config, bool verbose); void submit(Message const & message) override; diff --git a/daemon/src/sender.h b/daemon/src/sender.h index b7437c5..35f0fe3 100644 --- a/daemon/src/sender.h +++ b/daemon/src/sender.h @@ -1,7 +1,8 @@ #pragma once #include <cstdint> -#include <array> +#include <vector> +#include <string> namespace midi_router { @@ -10,7 +11,10 @@ class Sender { public: virtual void - send(std::array<std::uint8_t, 3> const & payload) = 0; + send(std::vector<std::uint8_t> const & payload) = 0; + + virtual std::string + get_id() const = 0; }; } // namespace midi_router diff --git a/daemon/src/types.h b/daemon/src/types.h index a5c6165..78e85df 100644 --- a/daemon/src/types.h +++ b/daemon/src/types.h @@ -27,17 +27,18 @@ enum class Message_Type START = 0xFAu, CONTINUE = 0xFBu, STOP = 0xFCu, + }; using Device_Id = std::string; using Device_Map = std::map<std::string, Device_Id>; using Target_List = std::vector<std::reference_wrapper<Sender>>; -using Route_Map = std::map<Device_Id, std::map<Message_Type, Target_List>>; +using Route_Table = std::map<Device_Id, std::map<Message_Type, Target_List>>; struct Message { Device_Id const * source_id; - std::array<std::uint8_t, 3> bytes; + std::vector<std::uint8_t> bytes; Message_Type type() const @@ -47,4 +48,34 @@ struct Message } }; +static const std::map<std::string, Message_Type> string2type = { + {"note_off", Message_Type::NOTE_OFF}, + {"note_on", Message_Type::NOTE_ON}, + {"poly_at", Message_Type::POLY_AT}, + {"control_change", Message_Type::CONTROL_CHANGE}, + {"program_change", Message_Type::PROGRAM_CHANGE}, + {"channel_at", Message_Type::CHANNEL_AT}, + {"pitch_wheel", Message_Type::PITCH_WHEEL}, + {"clock", Message_Type::CLOCK}, + {"meas_end", Message_Type::MEAS_END}, + {"start", Message_Type::START}, + {"continue", Message_Type::CONTINUE}, + {"stop", Message_Type::STOP}, +}; + +static const std::map<Message_Type, std::string> type2string = { + {Message_Type::NOTE_OFF, "note_off"}, + {Message_Type::NOTE_ON, "note_on"}, + {Message_Type::POLY_AT, "poly_at"}, + {Message_Type::CONTROL_CHANGE, "control_change"}, + {Message_Type::PROGRAM_CHANGE, "program_change"}, + {Message_Type::CHANNEL_AT, "channel_at"}, + {Message_Type::PITCH_WHEEL, "pitch_wheel"}, + {Message_Type::CLOCK, "clock"}, + {Message_Type::MEAS_END, "meas_end"}, + {Message_Type::START, "start"}, + {Message_Type::CONTINUE, "continue"}, + {Message_Type::STOP, "stop"} +}; + } // namespace midi_router @@ -1,3 +1,4 @@ make clang rtmidi +curl |