From f086834da614c8fb851d440ddd5891d467c0c9f2 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 5 Jan 2026 02:56:30 +0100 Subject: [PATCH 1/9] refctoring WIP --- src/app-manager.cpp | 146 +++++++++++++ src/app-manager.h | 172 ++------------- src/graph-manager.cpp | 439 ++++++++++++++++++++++++++++++++++++++ src/graph-manager.h | 474 ++++------------------------------------- src/midi-node.h | 1 + src/node-handle.h | 21 +- src/signal-manager.cpp | 131 ++++++++++++ src/signal-manager.h | 134 ++---------- 8 files changed, 804 insertions(+), 714 deletions(-) create mode 100644 src/app-manager.cpp create mode 100644 src/graph-manager.cpp create mode 100644 src/signal-manager.cpp diff --git a/src/app-manager.cpp b/src/app-manager.cpp new file mode 100644 index 0000000..a2b0f9b --- /dev/null +++ b/src/app-manager.cpp @@ -0,0 +1,146 @@ +#include "app-manager.h" + +void VISEQ::APP::AppManager::init(){ + canvas->camera.target = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; + canvas->camera.offset = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; + canvas->camera.rotation = 0.0f; + canvas->camera.zoom = 1.0f; +} + +void VISEQ::APP::AppManager::close(){ + std::ofstream file("state.json"); + file << json(*this); + std::cout << std::setw(4) << json(*this) << std::endl; +} + +void VISEQ::APP::AppManager::startSignals(){ + sm->startSignalThread(); +} + +void VISEQ::APP::AppManager::loadState(){ + if(std::filesystem::exists("state.json")){ + std::ifstream f("state.json"); + json graph = json::parse(f); + + for(auto n : graph.at("gm").at("nodes")){ + std::cout << "object? " << n << std::endl; + if(n.is_object()){ + std::cout << "nodeType = " << n.at("nodeType").get() << std::endl; + + if(n.at("nodeType").get() == NodeType::MIDINODE) gm->addNode(n.template get>()); + if(n.at("nodeType").get() == NodeType::CONFIGNODE) gm->addNode(n.template get>()); + } + } + + int highest_id = 0; + for(auto n : graph.at("gm").at("nodes")){ + std::cout << n << std::endl; + if(n.is_object()){ + if(n.at("id").get() > highest_id) { + highest_id = n.at("id").get(); + } + } + } + + for(auto e : graph.at("gm").at("edges")){ + if(e.at("id") > highest_id){ + highest_id = e.at("id"); + } + } + + for(auto e : graph.at("gm").at("edges")){ + int start_id = e.at("start").get(); + int end_id = e.at("end").get(); + + VEC_WPTR_Node start = gm->getNodesByID(start_id); + VEC_WPTR_Node end = gm->getNodesByID(end_id); + + if(!start.empty() && !end.empty()) { + std::cout << std::format("connection {} to {}", start_id, end_id) << std::endl; + gm->addConnection(start.front(), end.front()); + } + } + gm->lastEdge().lock()->newId(highest_id); + + for(auto s : graph.at("sm").at("signals")){ + int start_id = s.at("start").get(); + int next_id = s.at("next").get(); + int last_id = s.at("last").get(); + std::shared_ptr ms = s; + VEC_WPTR_Node start = gm->getNodesByID(start_id); + VEC_WPTR_Node last = gm->getNodesByID(last_id); + VEC_WPTR_Node next = gm->getNodesByID(next_id); + + ms->start = start.front(); + ms->next = next.front(); + ms->last = last.front(); + sm->signals.push_back(ms); + + std::cout << std::format("adding signal with start node {}", start_id) << std::endl; + } + + } else { + for(int i = 0; i < 4; ++i){ + gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 2 * state->settings.grid*4}); + } + for(int i = 3; i >= 0; --i){ + gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 3 * state->settings.grid*4}); + } + + for(int i = 0; i < 8; ++i){ + gm->addConnection(gm->getNode(i), gm->getNode((i+1) % 8)); + } + sm->addSignalAtNode(gm->getFirstNode()); + } + +} + +void VISEQ::APP::AppManager::updateMidiPorts(){ + for(auto& n : gm->getMidiNodes()){ + auto nc = std::dynamic_pointer_cast(n.lock()); + for(auto& mo : sm->midi.midi_out_ports){ + if(!nc->midi_outs.contains(mo.number) && mo.enabled){ + nc->midi_outs[mo.number] = {mo.number, mo.port.port_name, false}; + } + if(nc->midi_outs.contains(mo.number) && !mo.enabled){ + nc->midi_outs.erase(mo.number); + } + } + } +} + +void VISEQ::APP::AppManager::update(){ + state->update(canvas->camera); + canvas->update(); + gm->update(); + + mutex.lock(); + updateMidiPorts(); + mutex.unlock(); + + im.update(); + +} + +void VISEQ::APP::AppManager::draw(){ +BeginDrawing(); + ClearBackground((Color){46,46,46}); + canvas->drawStart(); + canvas->draw(); + + gm->drawNodes(SCREEN_MOUSE, canvas->camera); + gm->drawEdges(SCREEN_MOUSE, canvas->camera); + sm->drawSignals(SCREEN_MOUSE, canvas->camera); + + im.draw(); + + canvas->drawEnd(); + drawUI(); +EndDrawing(); +} + +void VISEQ::APP::AppManager::drawUI(){ + mutex.lock(); + ui.draw(SCREEN_MOUSE, canvas->camera); + mutex.unlock(); +} diff --git a/src/app-manager.h b/src/app-manager.h index 61b9d8c..6ae03fc 100644 --- a/src/app-manager.h +++ b/src/app-manager.h @@ -21,167 +21,29 @@ using json = nlohmann::json; namespace VISEQ::APP { class AppManager{ public: - AppManager() : im(gm->getPtr(), sm->getPtr(), canvas), ui(sm->getPtr(), gm->getPtr()) {} + AppManager() : im(gm->getPtr(), sm->getPtr(), canvas), ui(sm->getPtr(), gm->getPtr()) {} + std::mutex mutex; - std::mutex mutex; + std::shared_ptr state = APP::State::get(); - std::shared_ptr state = APP::State::get(); + std::shared_ptr canvas = std::make_shared(); + std::shared_ptr gm = std::make_shared(); + std::shared_ptr sm = std::make_shared(gm->getPtr(), &mutex); + VISEQ::INTERACTION::InteractionManager im; - std::shared_ptr canvas = std::make_shared(); - std::shared_ptr gm = std::make_shared(); - std::shared_ptr sm = std::make_shared(gm->getPtr(), &mutex); - VISEQ::INTERACTION::InteractionManager im; + UI::AppUI ui; - UI::AppUI ui; + NLOHMANN_DEFINE_TYPE_INTRUSIVE(AppManager, gm, sm); - NLOHMANN_DEFINE_TYPE_INTRUSIVE(AppManager, gm, sm); - - void init(){ - canvas->camera.target = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; - canvas->camera.offset = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; - canvas->camera.rotation = 0.0f; - canvas->camera.zoom = 1.0f; - } - - void close(){ - std::ofstream file("state.json"); - file << json(*this); - std::cout << std::setw(4) << json(*this) << std::endl; - } - - void startSignals(){ - sm->startSignalThread(); - } - - void loadState(){ - if(std::filesystem::exists("state.json")){ - std::ifstream f("state.json"); - json graph = json::parse(f); - - for(auto n : graph.at("gm").at("nodes")){ - std::cout << "object? " << n << std::endl; - if(n.is_object()){ - std::cout << "nodeType = " << n.at("nodeType").get() << std::endl; - - if(n.at("nodeType").get() == NodeType::MIDINODE) gm->addNode(n.template get>()); - if(n.at("nodeType").get() == NodeType::CONFIGNODE) gm->addNode(n.template get>()); - } - } - - int highest_id = 0; - for(auto n : graph.at("gm").at("nodes")){ - std::cout << n << std::endl; - if(n.is_object()){ - if(n.at("id").get() > highest_id) { - highest_id = n.at("id").get(); - } - } - } - - for(auto e : graph.at("gm").at("edges")){ - if(e.at("id") > highest_id){ - highest_id = e.at("id"); - } - } - - for(auto e : graph.at("gm").at("edges")){ - int start_id = e.at("start").get(); - int end_id = e.at("end").get(); - - VEC_WPTR_Node start = gm->getNodesByID(start_id); - VEC_WPTR_Node end = gm->getNodesByID(end_id); - - if(!start.empty() && !end.empty()) { - std::cout << std::format("connection {} to {}", start_id, end_id) << std::endl; - gm->addConnection(start.front(), end.front()); - } - } - gm->lastEdge().lock()->newId(highest_id); - - for(auto s : graph.at("sm").at("signals")){ - int start_id = s.at("start").get(); - int next_id = s.at("next").get(); - int last_id = s.at("last").get(); - std::shared_ptr ms = s; - VEC_WPTR_Node start = gm->getNodesByID(start_id); - VEC_WPTR_Node last = gm->getNodesByID(last_id); - VEC_WPTR_Node next = gm->getNodesByID(next_id); - - ms->start = start.front(); - ms->next = next.front(); - ms->last = last.front(); - sm->signals.push_back(ms); - - std::cout << std::format("adding signal with start node {}", start_id) << std::endl; - } - - } else { - for(int i = 0; i < 4; ++i){ - gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 2 * state->settings.grid*4}); - } - for(int i = 3; i >= 0; --i){ - gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 3 * state->settings.grid*4}); - } - - for(int i = 0; i < 8; ++i){ - gm->addConnection(gm->getNode(i), gm->getNode((i+1) % 8)); - } - sm->addSignalAtNode(gm->getFirstNode()); - } - - } - - void updateMidiPorts(){ - for(auto& n : gm->getMidiNodes()){ - auto nc = std::dynamic_pointer_cast(n.lock()); - for(auto& mo : sm->midi.midi_out_ports){ - if(!nc->midi_outs.contains(mo.number) && mo.enabled){ - nc->midi_outs[mo.number] = {mo.number, mo.port.port_name, false}; - } - if(nc->midi_outs.contains(mo.number) && !mo.enabled){ - nc->midi_outs.erase(mo.number); - } - } - } - } - - void update(){ - state->update(canvas->camera); - canvas->update(); - gm->update(); - - mutex.lock(); - updateMidiPorts(); - mutex.unlock(); - - im.update(); - - } - - void draw(){ - BeginDrawing(); - ClearBackground((Color){46,46,46}); - canvas->drawStart(); - canvas->draw(); - - gm->drawNodes(SCREEN_MOUSE, canvas->camera); - gm->drawEdges(SCREEN_MOUSE, canvas->camera); - sm->drawSignals(SCREEN_MOUSE, canvas->camera); - - im.draw(); - - canvas->drawEnd(); - drawUI(); - EndDrawing(); - } - - void drawUI(){ - mutex.lock(); - ui.draw(SCREEN_MOUSE, canvas->camera); - mutex.unlock(); - } - + void init(); + void close(); + void startSignals(); + void loadState(); + void updateMidiPorts(); + void update(); + void draw(); + void drawUI(); }; } diff --git a/src/graph-manager.cpp b/src/graph-manager.cpp new file mode 100644 index 0000000..99b3970 --- /dev/null +++ b/src/graph-manager.cpp @@ -0,0 +1,439 @@ +#include "graph-manager.h" + + +std::shared_ptr VISEQ::GRAPH::GraphManager::getPtr(){ + return shared_from_this(); +} + +int VISEQ::GRAPH::GraphManager::getNodesSize(){ + return nodes.size(); +} + +void VISEQ::GRAPH::GraphManager::createMidiNode(Vec2Df position){ + nodes.emplace_back(std::make_shared(position)); + nodes.back()->update(); +} + +void VISEQ::GRAPH::GraphManager::createConfigNode(Vec2Df position){ + nodes.emplace_back(std::make_shared(position)); + nodes.back()->update(); +} + +void VISEQ::GRAPH::GraphManager::setMidiPortForAll(int number, bool _state){ + for(auto n : nodes){ + if(n->getNodeType() == NodeType::MIDINODE) std::dynamic_pointer_cast(n)->setMidiPort(number, _state); + } +} + +void VISEQ::GRAPH::GraphManager::addNode(SPTR_Node node){ + std::cout << "added node " << node->id << std::endl; + nodes.insert(nodes.end(), node); + node->update(); +} + +void VISEQ::GRAPH::GraphManager::addConnection(WPTR_Node from, WPTR_Node to){ + EdgeType et = EdgeType::MIDIEDGE; + if(from.lock()->getNodeType() == NodeType::CONFIGNODE || to.lock()->getNodeType() == NodeType::CONFIGNODE) et = EdgeType::CONFIGEDGE; + if(from.lock() != to.lock()) { + edges.emplace_back(std::make_shared()); + edges.back()->edgeType = et; + edges.back()->start = from; + edges.back()->end = to; + edges.back()->start_conn = from.lock()->connectors.back(); + edges.back()->end_conn = to.lock()->connectors.back(); + from.lock()->connections.push_back(edges.back()); + to.lock()->connections.push_back(edges.back()); + } +} + +void VISEQ::GRAPH::GraphManager::addToSelection(){ + auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->selected;}) | std::ranges::to(); + selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); +} + +void VISEQ::GRAPH::GraphManager::select(){ + auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->select();}) | std::ranges::to(); + if(!KEYS_DOWN.shift) selected_nodes.clear(); + selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); + std::for_each(selected_nodes.begin(), selected_nodes.end(), [](WPTR_Node n){n.lock()->selected = true;}); +} + +void VISEQ::GRAPH::GraphManager::deselect(){ + std::cout << "deselect" << std::endl; + for(auto s : selected_nodes){ + s->selected = false; + } + selected_nodes.clear(); +} + +VEC_SPTR_Node VISEQ::GRAPH::GraphManager::getSelection(){ + return selected_nodes; +} + +void VISEQ::GRAPH::GraphManager::copyNodes(VEC_SPTR_Node _nodes, Vec2Df position){ + for(auto n: _nodes){ + nodes.insert(nodes.end(), std::make_shared(*n)); + /*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ + /*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ + nodes.back()->setPosition(position + nodes.back()->drag_offset); + nodes.back()->selected = false; + } +} + +void VISEQ::GRAPH::GraphManager::copyNodes(std::vector> _nodes, Vec2Df position){ + for(auto n: _nodes){ + nodes.insert(nodes.end(), std::make_shared(*n)); + nodes.back()->setPosition(position + nodes.back()->drag_offset); + nodes.back()->selected = false; + } +} + +VEC_SPTR_Node VISEQ::GRAPH::GraphManager::getCopiesOfSelectedNodes(){ + VEC_SPTR_Node copied_nodes; + for(auto n : selected_nodes){ + copied_nodes.insert(copied_nodes.end(), std::make_shared(*n)); + /*if(n->getNodeType() == NodeType::MIDINODE) copied_nodes.insert(copied_nodes.end(), std::make_shared(*n));*/ + /*if(n->getNodeType() == NodeType::CONFIGNODE) copied_nodes.insert(copied_nodes.end(), std::make_shared(*n));*/ + copied_nodes.back()->deselect(); + } + return copied_nodes; +} + +SPTR_Node VISEQ::GRAPH::GraphManager::copyNode(SPTR_Node n, Vec2Df position){ + std::cout << "copy basenode" << std::endl; + nodes.insert(nodes.end(), std::make_shared(*n)); + /*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ + /*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ + nodes.back()->setPosition(position); + return nodes.back(); +} + +SPTR_Node VISEQ::GRAPH::GraphManager::copyNode(std::shared_ptr n, Vec2Df position){ + std::cout << "copy midinode" << std::endl; + nodes.insert(nodes.end(), std::make_shared(*n)); + nodes.back()->clearConnectors(); + nodes.back()->setPosition(position); + return nodes.back(); +} + +void VISEQ::GRAPH::GraphManager::setNodeMode(int mode){ + for(auto n: nodes){ + n->setMode(mode); + } +} + +void VISEQ::GRAPH::GraphManager::reset(){ + counter.clear(); +} +WPTR_Node VISEQ::GRAPH::GraphManager::getNode(int i){ + return nodes.at(i); +} + +WPTR_Node VISEQ::GRAPH::GraphManager::getFirstNode(){ + return nodes.front(); +} + +WPTR_Node VISEQ::GRAPH::GraphManager::getLastNode(){ + return nodes.back(); +} + +VEC_WPTR_Node VISEQ::GRAPH::GraphManager::getMidiNodes(){ + return nodes | RANGE_FILTER([](VISEQ::GRAPH::NODES::SPTR_Node n){return n->getNodeType() == NodeType::MIDINODE;}) | std::ranges::to(); +} + +NODES::SPTR_Node VISEQ::GRAPH::GraphManager::getRandomNode(){ + VEC_SPTR_GraphItem o; + std::ranges::sample(nodes, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); + return std::dynamic_pointer_cast(o.front()); +} + +EDGES::SPTR_Edge VISEQ::GRAPH::GraphManager::getRandomEdge(){ + VEC_SPTR_GraphItem o; + std::ranges::sample(edges, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); + return std::dynamic_pointer_cast(o.front()); +} + +SPTR_Node VISEQ::GRAPH::GraphManager::getClosestNode(Vec2Df position){ + auto neighbors = nodes | RANGE_FILTER([](SPTR_Node n){ return !n->picked_up;}) | std::ranges::to(); + std::sort(neighbors.begin(), neighbors.end(), [position](SPTR_Node na, SPTR_Node nb){ return na->position.dist(position) < nb->position.dist(position);}); + return neighbors.front(); +} +VEC_WPTR_Node VISEQ::GRAPH::GraphManager::getNodesByID(int _id){ + return nodes | RANGE_FILTER([_id](SPTR_Node n){return n->id == _id;}) | std::ranges::to(); // TODO: Multiple nodes with the same ID? Why not return only one node? +} + +WPTR_Edge VISEQ::GRAPH::GraphManager::lastEdge(){ + return edges.back(); +} + +WPTR_Node VISEQ::GRAPH::GraphManager::next(WPTR_Node current, WPTR_Node last){ + VEC_WPTR_Edge possible_connections = current.lock()->connections | RANGE_FILTER([current](WPTR_Edge c){ + return (c.lock()->getEdgeType() == EdgeType::MIDIEDGE) && ((c.lock()->start.lock() == current.lock() && c.lock()->pass_forward) || (c.lock()->end.lock() == current.lock() && c.lock()->pass_backward)); + }) | std::ranges::to(); + + if(possible_connections.empty()) return SPTR_Node(nullptr); + + int return_path = 0; + float angle = -1; + if(current.lock()->distributionMode == Distribution::GEOMETRIC){ + Vec2Df coming_from = (current.lock()->position - last.lock()->position).norm(); + int i = 0; + for(auto n : possible_connections){ + WPTR_Node next_node = n.lock()->getNextNode(current); + float angle_v1_v2 = coming_from.norm().dot((next_node.lock()->position - current.lock()->position).norm()); + if(angle_v1_v2 > angle) { + return_path = i; + angle = angle_v1_v2; + } + ++i; + } + } + + if(current.lock()->distributionMode == Distribution::ROUNDROBIN){ + if(counter.find(current.lock()) == counter.end()) { + counter[current.lock()] = 0; + } else { + counter[current.lock()] = (counter[current.lock()] + 1) % possible_connections.size(); + return_path = counter[current.lock()]; + } + } + + if(current.lock()->distributionMode == Distribution::RANDOM){ + return_path = rand() % possible_connections.size(); + } + + return possible_connections.at(return_path).lock()->getNextNode(current); +} + +void VISEQ::GRAPH::GraphManager::splitEdge(SPTR_Edge edge){ + split_edges.push_back(edge); +} + +void VISEQ::GRAPH::GraphManager::splitEdges(){ + for(auto s : split_edges){ + Vec2Df center = s.lock()->end.lock()->position.center(s.lock()->start.lock()->position); + SPTR_Node cn = copyNode(std::dynamic_pointer_cast(s.lock()->start.lock()), center); // TODO: copy connectors + addConnection(s.lock()->start.lock(), cn); + addConnection(cn, s.lock()->end.lock()); + remove_edges.push_back(s); + } + split_edges.clear(); +} + +void VISEQ::GRAPH::GraphManager::removeEdge(SPTR_Edge edge){ + remove_edges.push_back(edge); +} + +void VISEQ::GRAPH::GraphManager::removeEdges(){ + for(auto edge : remove_edges){ + edge.lock()->start.lock()->removeConnector(edge.lock()->start_conn); + edge.lock()->end.lock()->removeConnector(edge.lock()->end_conn); + std::erase(edge.lock()->start.lock()->connections, edge.lock()); + std::erase(edge.lock()->end.lock()->connections, edge.lock()); + std::erase(edges, edge.lock()); + } + remove_edges.clear(); +} + +VEC_WPTR_Node VISEQ::GRAPH::GraphManager::getAdjacentNodes(VEC_SPTR_Node selected_nodes){ + for(auto n : nodes){ + n->highlighted = false; + } + + auto erase_connections = edges | RANGE_FILTER([selected_nodes](SPTR_Edge c){ return std::ranges::contains(selected_nodes, c->start.lock()) || std::ranges::contains(selected_nodes, c->end.lock());}); + + VEC_WPTR_Node adjacent_nodes; + for(auto c : erase_connections){ + if(!c->start.lock()->selected) adjacent_nodes.push_back(c->start); + if(!c->end.lock()->selected) adjacent_nodes.push_back(c->end); + } + + return adjacent_nodes; +} + +void VISEQ::GRAPH::GraphManager::removeNodesAndEdges(VEC_SPTR_Node selected_nodes){ + VEC_WPTR_Node adjacent_nodes = getAdjacentNodes(selected_nodes); + + for(auto n : adjacent_nodes){ + std::erase_if(n.lock()->connections, [n, selected_nodes](VISEQ::GRAPH::EDGES::WPTR_Edge c){ return std::ranges::contains(selected_nodes, c.lock()->start.lock()) || std::ranges::contains(selected_nodes, c.lock()->end.lock());}); + } + + for(auto n : selected_nodes){ + std::erase_if(this->edges, [n](WPTR_Edge c){ return c.lock()->start.lock() == n || c.lock()->end.lock() == n;}); + } + + std::erase_if(this->nodes, [selected_nodes](SPTR_Node n){ return std::ranges::contains(selected_nodes, n);}); +} + +void VISEQ::GRAPH::GraphManager::pasteNodesAndEdges(VEC_SPTR_Node nodes, Vec2Df position){ + + VEC_SPTR_Edge copy_connections; + for(auto c : edges){ + if(std::ranges::contains(nodes, c->start.lock()) && std::ranges::contains(nodes, c->end.lock())) { + copy_connections.push_back(std::make_shared(*c)); + } + } + std::cout << copy_connections.size() << std::endl; + + for(auto old_node : nodes){ + SPTR_Node new_node; + + if(old_node->getNodeType() == NodeType::MIDINODE) { + new_node = copyNode(std::dynamic_pointer_cast(old_node), position + old_node->drag_offset); + } else { + new_node = copyNode(old_node, position + old_node->drag_offset); + } + + new_node->copied = false; + new_node->selected = true; + + //TODO copy connectors + + for(auto c : copy_connections){ + if(c->start.lock() == old_node) { + c->start = new_node; + c->start.lock()->connections.push_back(c); + } + if(c->end.lock() == old_node) { + c->end = new_node; + c->end.lock()->connections.push_back(c); + } + } + } + std::ranges::copy(copy_connections.begin(), copy_connections.end(), std::back_inserter(edges)); +} + +void VISEQ::GRAPH::GraphManager::update(){ + if(!remove_edges.empty()) removeEdges(); + if(!split_edges.empty()) splitEdges(); +} + +void VISEQ::GRAPH::GraphManager::drawEdges(Vec2Df mouse_pos, Camera2D cam){ + for(auto c : edges){ + Vec2Df v1_position = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->connector_offset); + Vec2Df v2_position = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->connector_offset); + float v1_v2_distance = v1_position.dist(&v2_position); + + if(v1_v2_distance < 100) c->switches_offset = v1_v2_distance/2.3f; + Vec2Df from_to_handle = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->switches_offset); + Vec2Df to_from_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->switches_offset); + Vec2Df middle_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.4f; + Vec2Df middle_handle_2 = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.6f; + Vec2Df flip_handle = middle_handle + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*0.2, 0.f, 20.f) : 1.0f); + Vec2Df flip_handle_2 = middle_handle_2 + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*-0.2, -20.f, 0.f) : 1.0f); + + // Dynamic opacity by distance to mouse + unsigned char opacity = 150.0f - Clamp(middle_handle.dist(mouse_pos), 0.0f, 150.0f); + unsigned char opacity_from = 250.0f - Clamp(from_to_handle.dist(mouse_pos), 0.0f, 220.0f); + unsigned char opacity_to = 250.0f - Clamp(to_from_handle.dist(mouse_pos), 0.0f, 220.0f); + + float midi_gate_v1 = 0.0f; + float midi_gate_v2 = 0.0f; + + if(c->start.lock()->getNodeType() == NodeType::MIDINODE){ + midi_gate_v1 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast(c->start.lock())->getNotes().gate, 1.f, 4.f); + } + if(c->end.lock()->getNodeType() == NodeType::MIDINODE){ + midi_gate_v2 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast(c->end.lock())->getNotes().gate, 1.f, 4.f); + } + // Line with small connector circles + if(c->end.lock()->getNodeType() == NodeType::MIDINODE && c->start.lock()->getNodeType() == NodeType::MIDINODE){ + + Color col = {229, 181, 103, 255}; + if(c->GActive()) col = {108, 153, 187, 255}; + DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, col); + { + float d = c->start.lock()->position.dist(c->end.lock()->position); + std::string frac = MIDI::Fractions.at(std::clamp(round(d/60) - 1, 0, 31)); + Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",d).c_str(), 10.0f, 2.0f); + Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); + + Vec2Df middle_handle = c->start.lock()->position + (c->end.lock()->position - c->start.lock()->position) * ((d - text_length.x)*0.5f)/d; + Vec2Df middle_handle_frac = c->start.lock()->position + (c->end.lock()->position - c->start.lock()->position) * ((d - text_length_frac.x)*0.5f)/d; + Vec2Df text_position = middle_handle + (c->start.lock()->position - c->end.lock()->position).norm().orth() * 12.0f; + Vec2Df text_position_frac = middle_handle_frac - (c->start.lock()->position - c->end.lock()->position).norm().orth() * 4.0f; + + float rotation = Vector2Angle((Vector2){1,0}, (c->end.lock()->position - c->start.lock()->position)) * RAD2DEG; + DrawTextPro(GetFontDefault(), std::format("{:.1f}",d).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + if( (int)d % 60 == 0) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + } + if(midi_gate_v1 == 4.0f) col = VS_COLOR_RED; + DrawCircle(v1_position.x, v1_position.y, midi_gate_v1, col); + + col = {229, 181, 103, 255}; + if(midi_gate_v2 == 4.0f) col = VS_COLOR_RED; + DrawCircle(v2_position.x, v2_position.y, midi_gate_v2, col); + + if(!c->start.lock()->picked_up && !c->end.lock()->picked_up){ + // On/Off switches on both sides + if(ViButton(mouse_pos, from_to_handle.x, from_to_handle.y, 8.0f, c->pass_forward ? (Color){120, 210, 115, opacity_from} : (Color){232, 50, 32, opacity_from} )) c->pass_forward = !c->pass_forward; + if(ViButton(mouse_pos, to_from_handle.x, to_from_handle.y, 8.0f, c->pass_backward ? (Color){120, 210, 115, opacity_to} : (Color){232, 50, 32, opacity_to} )) c->pass_backward = !c->pass_backward; + + if(!c->pass_forward){ + Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + from_to_handle; + Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + from_to_handle; + DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_from}); + } + + if(!c->pass_backward){ + Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + to_from_handle; + Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + to_from_handle; + DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_to}); + } + + if(ViButton(mouse_pos, flip_handle_2.x, flip_handle_2.y, 8.0f, (Color){232, 125, 62, opacity} )) splitEdge(c); + } + } + if(c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE){ + DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, VS_COLOR_BLUE); + } + + if(ViButton(mouse_pos, flip_handle.x, flip_handle.y, 8.0f, (Color){232, 50, 32, opacity} )) removeEdge(c); + } +} + +void VISEQ::GRAPH::GraphManager::drawNeighborLines(SPTR_Node node, Vec2Df mouse_pos, Camera2D camera){ + float min_d = 480; + auto neighbors_local = nodes | RANGE_FILTER([mouse_pos](SPTR_Node n){ return !n->picked_up && !n->selected && n->position.dist(mouse_pos) < 960;}) | std::ranges::to(); + std::sort(neighbors_local.begin(), neighbors_local.end(), [node, mouse_pos](SPTR_Node na, SPTR_Node nb){ return na->position.dist(mouse_pos) < nb->position.dist(mouse_pos);}); + if(!neighbors_local.empty()){ + min_d = neighbors_local.front()->position.dist(mouse_pos); + DrawCircleLinesV(neighbors_local.front()->position, 22.0f, VS_COLOR_PINK); + node->neighbors = neighbors_local | std::ranges::views::filter([mouse_pos, min_d](SPTR_Node n){return abs(n->position.dist(mouse_pos) - min_d) < 5;}) | std::ranges::to(); + for(auto nb : node->neighbors){ + DrawCircleLinesV(nb->position, 20.0f, VS_COLOR_RED); + float d = nb->position.dist(mouse_pos); + float dn = nb->position.dist(node->position); + if(d > 0 && d < min_d*1.5 && d < 960) { + std::string frac = MIDI::Fractions.at(std::clamp(round(dn/60) - 1, 0, 31)); + Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",dn).c_str(), 10.0f, 2.0f); + Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); + + Vec2Df middle_handle = nb->position + (node->position - nb->position) * ((dn - text_length.x)*0.5f)/dn; + Vec2Df middle_handle_frac = nb->position + (node->position - nb->position) * ((dn - text_length_frac.x)*0.5f)/dn; + Vec2Df text_position = middle_handle + (nb->position - node->position).norm().orth() * 12.0f; + Vec2Df text_position_frac = middle_handle_frac - (nb->position - node->position).norm().orth() * 4.0f; + + float rotation = Vector2Angle((Vector2){1,0}, (node->position - nb->position)) * RAD2DEG; + DrawTextPro(GetFontDefault(), std::format("{:.1f}",dn).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + if(IsKeyDown(KEY_LEFT_SHIFT)) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + Vec2Df n_position = node->position - ((node->position - nb->position)).norm() * 20.0f; + Vec2Df nb_position = nb->position - ((nb->position - node->position)).norm() * 20.0f; + DrawLineV(n_position, nb_position, VS_COLOR_PINK); + } + } + } +} + +void VISEQ::GRAPH::GraphManager::drawNodes(Vec2Df mouse_pos, Camera2D camera){ + for(auto n : nodes){ + if(KEYS_PRESSED.ctrl && n->hovering()) state->selection_origin = n->position; + } + BASE::Interactable::z_hovering = -1; + for(auto n : nodes){ + n->update(); + if(n->picked_up) drawNeighborLines(n, mouse_pos, camera); + n->draw(mouse_pos, camera); + } +} + diff --git a/src/graph-manager.h b/src/graph-manager.h index fc64799..ddfa50b 100644 --- a/src/graph-manager.h +++ b/src/graph-manager.h @@ -53,441 +53,45 @@ namespace VISEQ::GRAPH { GraphManager() {} - std::shared_ptr getPtr(){ - return shared_from_this(); - } - - int getNodesSize(){ - return nodes.size(); - } - - void createMidiNode(Vec2Df position){ - nodes.emplace_back(std::make_shared(position)); - nodes.back()->update(); - } - - void createConfigNode(Vec2Df position){ - nodes.emplace_back(std::make_shared(position)); - nodes.back()->update(); - } - - void setMidiPortForAll(int number, bool _state){ - for(auto n : nodes){ - if(n->getNodeType() == NodeType::MIDINODE) std::dynamic_pointer_cast(n)->setMidiPort(number, _state); - } - } - - void addNode(SPTR_Node node){ - std::cout << "added node " << node->id << std::endl; - nodes.insert(nodes.end(), node); - node->update(); - } - - void addConnection(WPTR_Node from, WPTR_Node to){ - EdgeType et = EdgeType::MIDIEDGE; - if(from.lock()->getNodeType() == NodeType::CONFIGNODE || to.lock()->getNodeType() == NodeType::CONFIGNODE) et = EdgeType::CONFIGEDGE; - if(from.lock() != to.lock()) { - edges.emplace_back(std::make_shared()); - edges.back()->edgeType = et; - edges.back()->start = from; - edges.back()->end = to; - edges.back()->start_conn = from.lock()->connectors.back(); - edges.back()->end_conn = to.lock()->connectors.back(); - from.lock()->connections.push_back(edges.back()); - to.lock()->connections.push_back(edges.back()); - } - } - - void addToSelection(){ - auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->selected;}) | std::ranges::to(); - selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); - } - - void select(){ - auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->select();}) | std::ranges::to(); - if(!KEYS_DOWN.shift) selected_nodes.clear(); - selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); - std::for_each(selected_nodes.begin(), selected_nodes.end(), [](WPTR_Node n){n.lock()->selected = true;}); - } - - void deselect(){ - std::cout << "deselect" << std::endl; - for(auto s : selected_nodes){ - s->selected = false; - } - selected_nodes.clear(); - } - - VEC_SPTR_Node getSelection(){ - return selected_nodes; - } - - void copyNodes(VEC_SPTR_Node _nodes, Vec2Df position){ - for(auto n: _nodes){ - nodes.insert(nodes.end(), std::make_shared(*n)); - /*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ - /*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ - nodes.back()->setPosition(position + nodes.back()->drag_offset); - nodes.back()->selected = false; - } - } - - void copyNodes(std::vector> _nodes, Vec2Df position){ - for(auto n: _nodes){ - nodes.insert(nodes.end(), std::make_shared(*n)); - nodes.back()->setPosition(position + nodes.back()->drag_offset); - nodes.back()->selected = false; - } - } - - VEC_SPTR_Node getCopiesOfSelectedNodes(){ - VEC_SPTR_Node copied_nodes; - for(auto n : selected_nodes){ - copied_nodes.insert(copied_nodes.end(), std::make_shared(*n)); - /*if(n->getNodeType() == NodeType::MIDINODE) copied_nodes.insert(copied_nodes.end(), std::make_shared(*n));*/ - /*if(n->getNodeType() == NodeType::CONFIGNODE) copied_nodes.insert(copied_nodes.end(), std::make_shared(*n));*/ - copied_nodes.back()->deselect(); - } - return copied_nodes; - } - - SPTR_Node copyNode(SPTR_Node n, Vec2Df position){ - std::cout << "copy basenode" << std::endl; - nodes.insert(nodes.end(), std::make_shared(*n)); - /*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ - /*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ - nodes.back()->setPosition(position); - return nodes.back(); - } - - SPTR_Node copyNode(std::shared_ptr n, Vec2Df position){ - std::cout << "copy midinode" << std::endl; - nodes.insert(nodes.end(), std::make_shared(*n)); - nodes.back()->clearConnectors(); - nodes.back()->setPosition(position); - return nodes.back(); - } - - void setNodeMode(int mode){ - for(auto n: nodes){ - n->setMode(mode); - } - } - - void reset(){ - counter.clear(); - } - WPTR_Node getNode(int i){ - return nodes.at(i); - } - - WPTR_Node getFirstNode(){ - return nodes.front(); - } - - WPTR_Node getLastNode(){ - return nodes.back(); - } - - VEC_WPTR_Node getMidiNodes(){ - return nodes | RANGE_FILTER([](VISEQ::GRAPH::NODES::SPTR_Node n){return n->getNodeType() == NodeType::MIDINODE;}) | std::ranges::to(); - } - - NODES::SPTR_Node getRandomNode(){ - VEC_SPTR_GraphItem o; - std::ranges::sample(nodes, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); - return std::dynamic_pointer_cast(o.front()); - } - - EDGES::SPTR_Edge getRandomEdge(){ - VEC_SPTR_GraphItem o; - std::ranges::sample(edges, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); - return std::dynamic_pointer_cast(o.front()); - } - - SPTR_Node getClosestNode(Vec2Df position){ - auto neighbors = nodes | RANGE_FILTER([](SPTR_Node n){ return !n->picked_up;}) | std::ranges::to(); - std::sort(neighbors.begin(), neighbors.end(), [position](SPTR_Node na, SPTR_Node nb){ return na->position.dist(position) < nb->position.dist(position);}); - return neighbors.front(); - } - VEC_WPTR_Node getNodesByID(int _id){ - return nodes | RANGE_FILTER([_id](SPTR_Node n){return n->id == _id;}) | std::ranges::to(); // TODO: Multiple nodes with the same ID? Why not return only one node? - } - - WPTR_Edge lastEdge(){ - return edges.back(); - } - - WPTR_Node next(WPTR_Node current, WPTR_Node last){ - VEC_WPTR_Edge possible_connections = current.lock()->connections | RANGE_FILTER([current](WPTR_Edge c){ - return (c.lock()->getEdgeType() == EdgeType::MIDIEDGE) && ((c.lock()->start.lock() == current.lock() && c.lock()->pass_forward) || (c.lock()->end.lock() == current.lock() && c.lock()->pass_backward)); - }) | std::ranges::to(); - - if(possible_connections.empty()) return SPTR_Node(nullptr); - - int return_path = 0; - float angle = -1; - if(current.lock()->distributionMode == Distribution::GEOMETRIC){ - Vec2Df coming_from = (current.lock()->position - last.lock()->position).norm(); - int i = 0; - for(auto n : possible_connections){ - WPTR_Node next_node = n.lock()->getNextNode(current); - float angle_v1_v2 = coming_from.norm().dot((next_node.lock()->position - current.lock()->position).norm()); - if(angle_v1_v2 > angle) { - return_path = i; - angle = angle_v1_v2; - } - ++i; - } - } - - if(current.lock()->distributionMode == Distribution::ROUNDROBIN){ - if(counter.find(current.lock()) == counter.end()) { - counter[current.lock()] = 0; - } else { - counter[current.lock()] = (counter[current.lock()] + 1) % possible_connections.size(); - return_path = counter[current.lock()]; - } - } - - if(current.lock()->distributionMode == Distribution::RANDOM){ - return_path = rand() % possible_connections.size(); - } - - return possible_connections.at(return_path).lock()->getNextNode(current); - } - - void splitEdge(SPTR_Edge edge){ - split_edges.push_back(edge); - } - - void splitEdges(){ - for(auto s : split_edges){ - Vec2Df center = s.lock()->end.lock()->position.center(s.lock()->start.lock()->position); - SPTR_Node cn = copyNode(std::dynamic_pointer_cast(s.lock()->start.lock()), center); // TODO: copy connectors - addConnection(s.lock()->start.lock(), cn); - addConnection(cn, s.lock()->end.lock()); - remove_edges.push_back(s); - } - split_edges.clear(); - } - - void removeEdge(SPTR_Edge edge){ - remove_edges.push_back(edge); - } - - void removeEdges(){ - for(auto edge : remove_edges){ - edge.lock()->start.lock()->removeConnector(edge.lock()->start_conn); - edge.lock()->end.lock()->removeConnector(edge.lock()->end_conn); - std::erase(edge.lock()->start.lock()->connections, edge.lock()); - std::erase(edge.lock()->end.lock()->connections, edge.lock()); - std::erase(edges, edge.lock()); - } - remove_edges.clear(); - } - - VEC_WPTR_Node getAdjacentNodes(VEC_SPTR_Node selected_nodes){ - for(auto n : nodes){ - n->highlighted = false; - } - - auto erase_connections = edges | RANGE_FILTER([selected_nodes](SPTR_Edge c){ return std::ranges::contains(selected_nodes, c->start.lock()) || std::ranges::contains(selected_nodes, c->end.lock());}); - - VEC_WPTR_Node adjacent_nodes; - for(auto c : erase_connections){ - if(!c->start.lock()->selected) adjacent_nodes.push_back(c->start); - if(!c->end.lock()->selected) adjacent_nodes.push_back(c->end); - } - - return adjacent_nodes; - } - - void removeNodesAndEdges(VEC_SPTR_Node selected_nodes){ - VEC_WPTR_Node adjacent_nodes = getAdjacentNodes(selected_nodes); - - for(auto n : adjacent_nodes){ - std::erase_if(n.lock()->connections, [n, selected_nodes](VISEQ::GRAPH::EDGES::WPTR_Edge c){ return std::ranges::contains(selected_nodes, c.lock()->start.lock()) || std::ranges::contains(selected_nodes, c.lock()->end.lock());}); - } - - for(auto n : selected_nodes){ - std::erase_if(this->edges, [n](WPTR_Edge c){ return c.lock()->start.lock() == n || c.lock()->end.lock() == n;}); - } - - std::erase_if(this->nodes, [selected_nodes](SPTR_Node n){ return std::ranges::contains(selected_nodes, n);}); - } - - void pasteNodesAndEdges(VEC_SPTR_Node nodes, Vec2Df position){ - - VEC_SPTR_Edge copy_connections; - for(auto c : edges){ - if(std::ranges::contains(nodes, c->start.lock()) && std::ranges::contains(nodes, c->end.lock())) { - copy_connections.push_back(std::make_shared(*c)); - } - } - std::cout << copy_connections.size() << std::endl; - - for(auto old_node : nodes){ - SPTR_Node new_node; - - if(old_node->getNodeType() == NodeType::MIDINODE) { - new_node = copyNode(std::dynamic_pointer_cast(old_node), position + old_node->drag_offset); - } else { - new_node = copyNode(old_node, position + old_node->drag_offset); - } - - new_node->copied = false; - new_node->selected = true; - - //TODO copy connectors - - for(auto c : copy_connections){ - if(c->start.lock() == old_node) { - c->start = new_node; - c->start.lock()->connections.push_back(c); - } - if(c->end.lock() == old_node) { - c->end = new_node; - c->end.lock()->connections.push_back(c); - } - } - } - std::ranges::copy(copy_connections.begin(), copy_connections.end(), std::back_inserter(edges)); - } - - void update(){ - if(!remove_edges.empty()) removeEdges(); - if(!split_edges.empty()) splitEdges(); - } - - void drawEdges(Vec2Df mouse_pos, Camera2D cam){ - for(auto c : edges){ - Vec2Df v1_position = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->connector_offset); - Vec2Df v2_position = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->connector_offset); - float v1_v2_distance = v1_position.dist(&v2_position); - - if(v1_v2_distance < 100) c->switches_offset = v1_v2_distance/2.3f; - Vec2Df from_to_handle = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->switches_offset); - Vec2Df to_from_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->switches_offset); - Vec2Df middle_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.4f; - Vec2Df middle_handle_2 = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.6f; - Vec2Df flip_handle = middle_handle + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*0.2, 0.f, 20.f) : 1.0f); - Vec2Df flip_handle_2 = middle_handle_2 + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*-0.2, -20.f, 0.f) : 1.0f); - - // Dynamic opacity by distance to mouse - unsigned char opacity = 150.0f - Clamp(middle_handle.dist(mouse_pos), 0.0f, 150.0f); - unsigned char opacity_from = 250.0f - Clamp(from_to_handle.dist(mouse_pos), 0.0f, 220.0f); - unsigned char opacity_to = 250.0f - Clamp(to_from_handle.dist(mouse_pos), 0.0f, 220.0f); - - float midi_gate_v1 = 0.0f; - float midi_gate_v2 = 0.0f; - - if(c->start.lock()->getNodeType() == NodeType::MIDINODE){ - midi_gate_v1 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast(c->start.lock())->getNotes().gate, 1.f, 4.f); - } - if(c->end.lock()->getNodeType() == NodeType::MIDINODE){ - midi_gate_v2 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast(c->end.lock())->getNotes().gate, 1.f, 4.f); - } - // Line with small connector circles - if(c->end.lock()->getNodeType() == NodeType::MIDINODE && c->start.lock()->getNodeType() == NodeType::MIDINODE){ - - Color col = {229, 181, 103, 255}; - if(c->GActive()) col = {108, 153, 187, 255}; - DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, col); - { - float d = c->start.lock()->position.dist(c->end.lock()->position); - std::string frac = MIDI::Fractions.at(std::clamp(round(d/60) - 1, 0, 31)); - Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",d).c_str(), 10.0f, 2.0f); - Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); - - Vec2Df middle_handle = c->start.lock()->position + (c->end.lock()->position - c->start.lock()->position) * ((d - text_length.x)*0.5f)/d; - Vec2Df middle_handle_frac = c->start.lock()->position + (c->end.lock()->position - c->start.lock()->position) * ((d - text_length_frac.x)*0.5f)/d; - Vec2Df text_position = middle_handle + (c->start.lock()->position - c->end.lock()->position).norm().orth() * 12.0f; - Vec2Df text_position_frac = middle_handle_frac - (c->start.lock()->position - c->end.lock()->position).norm().orth() * 4.0f; - - float rotation = Vector2Angle((Vector2){1,0}, (c->end.lock()->position - c->start.lock()->position)) * RAD2DEG; - DrawTextPro(GetFontDefault(), std::format("{:.1f}",d).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - if( (int)d % 60 == 0) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - } - if(midi_gate_v1 == 4.0f) col = VS_COLOR_RED; - DrawCircle(v1_position.x, v1_position.y, midi_gate_v1, col); - - col = {229, 181, 103, 255}; - if(midi_gate_v2 == 4.0f) col = VS_COLOR_RED; - DrawCircle(v2_position.x, v2_position.y, midi_gate_v2, col); - - if(!c->start.lock()->picked_up && !c->end.lock()->picked_up){ - // On/Off switches on both sides - if(ViButton(mouse_pos, from_to_handle.x, from_to_handle.y, 8.0f, c->pass_forward ? (Color){120, 210, 115, opacity_from} : (Color){232, 50, 32, opacity_from} )) c->pass_forward = !c->pass_forward; - if(ViButton(mouse_pos, to_from_handle.x, to_from_handle.y, 8.0f, c->pass_backward ? (Color){120, 210, 115, opacity_to} : (Color){232, 50, 32, opacity_to} )) c->pass_backward = !c->pass_backward; - - if(!c->pass_forward){ - Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + from_to_handle; - Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + from_to_handle; - DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_from}); - } - - if(!c->pass_backward){ - Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + to_from_handle; - Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + to_from_handle; - DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_to}); - } - - if(ViButton(mouse_pos, flip_handle_2.x, flip_handle_2.y, 8.0f, (Color){232, 125, 62, opacity} )) splitEdge(c); - } - } - if(c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE){ - DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, VS_COLOR_BLUE); - } - - if(ViButton(mouse_pos, flip_handle.x, flip_handle.y, 8.0f, (Color){232, 50, 32, opacity} )) removeEdge(c); - } - } - - void drawNeighborLines(SPTR_Node node, Vec2Df mouse_pos, Camera2D camera){ - float min_d = 480; - auto neighbors_local = nodes | RANGE_FILTER([mouse_pos](SPTR_Node n){ return !n->picked_up && !n->selected && n->position.dist(mouse_pos) < 960;}) | std::ranges::to(); - std::sort(neighbors_local.begin(), neighbors_local.end(), [node, mouse_pos](SPTR_Node na, SPTR_Node nb){ return na->position.dist(mouse_pos) < nb->position.dist(mouse_pos);}); - if(!neighbors_local.empty()){ - min_d = neighbors_local.front()->position.dist(mouse_pos); - DrawCircleLinesV(neighbors_local.front()->position, 22.0f, VS_COLOR_PINK); - node->neighbors = neighbors_local | std::ranges::views::filter([mouse_pos, min_d](SPTR_Node n){return abs(n->position.dist(mouse_pos) - min_d) < 5;}) | std::ranges::to(); - for(auto nb : node->neighbors){ - DrawCircleLinesV(nb->position, 20.0f, VS_COLOR_RED); - float d = nb->position.dist(mouse_pos); - float dn = nb->position.dist(node->position); - if(d > 0 && d < min_d*1.5 && d < 960) { - std::string frac = MIDI::Fractions.at(std::clamp(round(dn/60) - 1, 0, 31)); - Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",dn).c_str(), 10.0f, 2.0f); - Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); - - Vec2Df middle_handle = nb->position + (node->position - nb->position) * ((dn - text_length.x)*0.5f)/dn; - Vec2Df middle_handle_frac = nb->position + (node->position - nb->position) * ((dn - text_length_frac.x)*0.5f)/dn; - Vec2Df text_position = middle_handle + (nb->position - node->position).norm().orth() * 12.0f; - Vec2Df text_position_frac = middle_handle_frac - (nb->position - node->position).norm().orth() * 4.0f; - - float rotation = Vector2Angle((Vector2){1,0}, (node->position - nb->position)) * RAD2DEG; - DrawTextPro(GetFontDefault(), std::format("{:.1f}",dn).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - if(IsKeyDown(KEY_LEFT_SHIFT)) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - Vec2Df n_position = node->position - ((node->position - nb->position)).norm() * 20.0f; - Vec2Df nb_position = nb->position - ((nb->position - node->position)).norm() * 20.0f; - DrawLineV(n_position, nb_position, VS_COLOR_PINK); - } - } - } - } - - void drawNodes(Vec2Df mouse_pos, Camera2D camera){ - for(auto n : nodes){ - if(KEYS_PRESSED.ctrl && n->hovering()) state->selection_origin = n->position; - } - BASE::Interactable::z_hovering = -1; - for(auto n : nodes){ - n->update(); - if(n->picked_up) drawNeighborLines(n, mouse_pos, camera); - n->draw(mouse_pos, camera); - } - } + std::shared_ptr getPtr(); + int getNodesSize(); + void createMidiNode(Vec2Df position); + void createConfigNode(Vec2Df position); + void setMidiPortForAll(int number, bool _state); + void addNode(SPTR_Node node); + void addConnection(WPTR_Node from, WPTR_Node to); + void addToSelection(); + void select(); + void deselect(); + VEC_SPTR_Node getSelection(); + void copyNodes(VEC_SPTR_Node _nodes, Vec2Df position); + void copyNodes(std::vector> _nodes, Vec2Df position); + VEC_SPTR_Node getCopiesOfSelectedNodes(); + SPTR_Node copyNode(SPTR_Node n, Vec2Df position); + SPTR_Node copyNode(std::shared_ptr n, Vec2Df position); + void setNodeMode(int mode); + void reset(); + WPTR_Node getNode(int i); + WPTR_Node getFirstNode(); + WPTR_Node getLastNode(); + VEC_WPTR_Node getMidiNodes(); + NODES::SPTR_Node getRandomNode(); + EDGES::SPTR_Edge getRandomEdge(); + SPTR_Node getClosestNode(Vec2Df position); + VEC_WPTR_Node getNodesByID(int _id); + WPTR_Edge lastEdge(); + WPTR_Node next(WPTR_Node current, WPTR_Node last); + void splitEdge(SPTR_Edge edge); + void splitEdges(); + void removeEdge(SPTR_Edge edge); + void removeEdges(); + VEC_WPTR_Node getAdjacentNodes(VEC_SPTR_Node selected_nodes); + void removeNodesAndEdges(VEC_SPTR_Node selected_nodes); + void pasteNodesAndEdges(VEC_SPTR_Node nodes, Vec2Df position); + void update(); + void drawEdges(Vec2Df mouse_pos, Camera2D cam); + void drawNeighborLines(SPTR_Node node, Vec2Df mouse_pos, Camera2D camera); + void drawNodes(Vec2Df mouse_pos, Camera2D camera); NLOHMANN_DEFINE_TYPE_INTRUSIVE(GraphManager, nodes, edges, selected_nodes) }; diff --git a/src/midi-node.h b/src/midi-node.h index c700772..d97eccb 100644 --- a/src/midi-node.h +++ b/src/midi-node.h @@ -8,6 +8,7 @@ #include "node.h" #include "raylib.h" #include "state.h" +#include #include #include diff --git a/src/node-handle.h b/src/node-handle.h index 69489cd..21b1d7f 100644 --- a/src/node-handle.h +++ b/src/node-handle.h @@ -17,8 +17,25 @@ namespace VISEQ::GRAPH::NODES{ class NodeHandle : public BASE::Draggable, public GraphItem, public std::enable_shared_from_this>{ public: NodeHandle() {} - NodeHandle(T &_assignedValue, Vec2Df parentPos, Color _col, std::string name, float orbit, int steps) : assignedValueRef(_assignedValue), parentPosition(parentPos), col(_col), name(name), orbit(orbit), steps(steps) {init();} - NodeHandle(T &_assignedValue, Vec2Df parentPos, Color _col, std::string name, float orbit, int steps, float min, float max, float minDist, float maxDist) : assignedValueRef(_assignedValue), parentPosition(parentPos), col(_col), name(name), minValue(min), maxValue(max), minDistance(minDist), maxDistance(maxDist), orbit(orbit), steps(steps) {init();} + NodeHandle(T &_assignedValue, Vec2Df parentPos, Color _col, std::string name, float orbit, int steps) : + assignedValueRef(_assignedValue), + parentPosition(parentPos), + col(_col), + name(name), + orbit(orbit), + steps(steps) {init();} + + NodeHandle(T &_assignedValue, Vec2Df parentPos, Color _col, std::string name, float orbit, int steps, float min, float max, float minDist, float maxDist) : + assignedValueRef(_assignedValue), + parentPosition(parentPos), + col(_col), + name(name), + minValue(min), + maxValue(max), + minDistance(minDist), + maxDistance(maxDist), + orbit(orbit), + steps(steps) {init();} virtual GRAPH::GraphItemType getType() override { return GraphItemType::HANDLE; diff --git a/src/signal-manager.cpp b/src/signal-manager.cpp new file mode 100644 index 0000000..487fda2 --- /dev/null +++ b/src/signal-manager.cpp @@ -0,0 +1,131 @@ +#include "signal-manager.h" + +#include "datatypes.h" +#include "midi-node.h" +#include "node.h" +#include "signal.h" +#include "midi.h" + +#include +#include +#include + + +void VISEQ::SIGNAL::SignalManager::pause(){ + play = false; +} + +void VISEQ::SIGNAL::SignalManager::stop(){ + play = false; + reset(); +} + +void VISEQ::SIGNAL::SignalManager::start(){ + play = true; +} + +void VISEQ::SIGNAL::SignalManager::reset(){ + for(auto &s : signals){ + s->position = s->start.lock()->position + Vec2Df{0.1, 0.1}; + s->next = s->start; + s->last = s->start; + } + gm->reset(); +} + +void VISEQ::SIGNAL::SignalManager::startSignalThread(){ + std::thread t1(&SignalManager::updateSignals, this); + t1.detach(); +} + +void VISEQ::SIGNAL::SignalManager::stopSignalThread(){ + loop = false; +} + +void VISEQ::SIGNAL::SignalManager::addSignalAtNode(WPTR_Node node){ + Color c = {(unsigned char)(20 + (rand() % 200)), 100, (unsigned char)(20 + (int)(rand() % 200)), 200}; + signals.emplace_back(std::make_shared(node.lock()->position + Vec2Df{0.5f, 0.5f}, 8.0f, 480, c)); // position + 1 or else it does not render. why? no idea! + signals.back()->next = node; + signals.back()->last = node; + signals.back()->start = node; +} + +void VISEQ::SIGNAL::SignalManager::rerouteSignal(VEC_SPTR_Node nodes, VEC_WPTR_Node adjacent_nodes){ + auto reroute_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->next.lock());}); + + for(auto s : reroute_signals){ + float dist = 10000000; + SPTR_Node next_node; + for(auto n : adjacent_nodes){ + if(n.lock()->position.dist(s->position) < dist) { + dist = n.lock()->position.dist(s->position); + next_node = n.lock(); + } + } + s->next = next_node; + s->last = next_node; + } + +} + +void VISEQ::SIGNAL::SignalManager::rebaseSignal(VEC_SPTR_Node nodes){ + auto rebase_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->start.lock());}); + + for(auto s : rebase_signals){ + s->start = s->next; + } +} + +void VISEQ::SIGNAL::SignalManager::removeSignal(std::shared_ptr s){ + erase_signals.push_back(s); +} + +void VISEQ::SIGNAL::SignalManager::updateSignals(){ + auto last = std::chrono::high_resolution_clock::now(); + while(loop){ + std::chrono::duration diff = std::chrono::high_resolution_clock::now() - last; + if((int)diff.count() >= 250){ + mutex->lock(); + if(play){ + for(auto& s : signals){ + s->direction = (s->next.lock()->position - s->position).norm(); + s->update(diff.count()); + if(s->position.dist(s->next.lock()->position) < 0.6f) { + WPTR_Node next_node = gm->next(s->next, s->last); + s->next.lock()->trigger(); + + if(next_node.lock() != s->next.lock() && !next_node.expired() && s->next.lock()->getNodeType() == NodeType::MIDINODE) { + auto midinode = std::dynamic_pointer_cast(s->next.lock()); + for(auto &m : midinode->midi_outs){ + if(m.second.enabled) { + if(midinode->send_note) midi.note(m.second.i, midinode->getNotes().channel, (int)Clamp(midinode->getNotes().note + s->midi_note_offset, 0, 127), midinode->getNotes().velocity, midinode->getNotes().gate); + if(midinode->send_cc) midi.cc(m.second.i, midinode->getCC().channel, midinode->getCC().control, (int)Clamp(midinode->getCC().value + s->midi_cc_offset, 0, 127)); + if(midinode->send_pitch_bend) midi.pitch_bend(m.second.i, midinode->getPitchBend().channel, midinode->getPitchBend().value); + } + } + s->last = s->next; + s->next = next_node; + } + } + } + } + midi.update(); + mutex->unlock(); + last = std::chrono::high_resolution_clock::now(); + } + } +} + +void VISEQ::SIGNAL::SignalManager::drawSignals(Vec2Df mouse_pos, Camera2D camera){ + mutex->lock(); + if(!erase_signals.empty()){ + for(auto s: erase_signals){ + std::erase(signals, s); + } + erase_signals.clear(); + } + for(auto s : signals){ + s->draw(mouse_pos, camera); + } + mutex->unlock(); +} diff --git a/src/signal-manager.h b/src/signal-manager.h index 92aa129..f29fa2a 100644 --- a/src/signal-manager.h +++ b/src/signal-manager.h @@ -1,15 +1,11 @@ #pragma once -#include "datatypes.h" #include "graph-manager.h" -#include "midi-node.h" #include "node.h" #include "signal.h" #include "midi.h" -#include #include -#include using namespace VISEQ::GRAPH; @@ -40,124 +36,18 @@ namespace VISEQ::SIGNAL { bool loop = true; bool play = false; - void pause(){ - play = false; - } - - void stop(){ - play = false; - reset(); - } - - void start(){ - play = true; - } - - void reset(){ - for(auto &s : signals){ - s->position = s->start.lock()->position + Vec2Df{0.1, 0.1}; - s->next = s->start; - s->last = s->start; - } - gm->reset(); - } - - void startSignalThread(){ - std::thread t1(&SignalManager::updateSignals, this); - t1.detach(); - } - - void stopSignalThread(){ - loop = false; - } - - void addSignalAtNode(WPTR_Node node){ - Color c = {(unsigned char)(20 + (rand() % 200)), 100, (unsigned char)(20 + (int)(rand() % 200)), 200}; - signals.emplace_back(std::make_shared(node.lock()->position + Vec2Df{0.5f, 0.5f}, 8.0f, 480, c)); // position + 1 or else it does not render. why? no idea! - signals.back()->next = node; - signals.back()->last = node; - signals.back()->start = node; - } - - void rerouteSignal(VEC_SPTR_Node nodes, VEC_WPTR_Node adjacent_nodes){ - auto reroute_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->next.lock());}); - - for(auto s : reroute_signals){ - float dist = 10000000; - SPTR_Node next_node; - for(auto n : adjacent_nodes){ - if(n.lock()->position.dist(s->position) < dist) { - dist = n.lock()->position.dist(s->position); - next_node = n.lock(); - } - } - s->next = next_node; - s->last = next_node; - } - - } - - void rebaseSignal(VEC_SPTR_Node nodes){ - auto rebase_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->start.lock());}); - - for(auto s : rebase_signals){ - s->start = s->next; - } - } - - void removeSignal(std::shared_ptr s){ - erase_signals.push_back(s); - } - - void updateSignals(){ - auto last = std::chrono::high_resolution_clock::now(); - while(loop){ - std::chrono::duration diff = std::chrono::high_resolution_clock::now() - last; - if((int)diff.count() >= 250){ - mutex->lock(); - if(play){ - for(auto& s : signals){ - s->direction = (s->next.lock()->position - s->position).norm(); - s->update(diff.count()); - if(s->position.dist(s->next.lock()->position) < 0.6f) { - WPTR_Node next_node = gm->next(s->next, s->last); - s->next.lock()->trigger(); - - if(next_node.lock() != s->next.lock() && !next_node.expired() && s->next.lock()->getNodeType() == NodeType::MIDINODE) { - auto midinode = std::dynamic_pointer_cast(s->next.lock()); - for(auto &m : midinode->midi_outs){ - if(m.second.enabled) { - if(midinode->send_note) midi.note(m.second.i, midinode->getNotes().channel, (int)Clamp(midinode->getNotes().note + s->midi_note_offset, 0, 127), midinode->getNotes().velocity, midinode->getNotes().gate); - if(midinode->send_cc) midi.cc(m.second.i, midinode->getCC().channel, midinode->getCC().control, (int)Clamp(midinode->getCC().value + s->midi_cc_offset, 0, 127)); - if(midinode->send_pitch_bend) midi.pitch_bend(m.second.i, midinode->getPitchBend().channel, midinode->getPitchBend().value); - } - } - s->last = s->next; - s->next = next_node; - } - } - } - } - midi.update(); - mutex->unlock(); - last = std::chrono::high_resolution_clock::now(); - } - } - } - - void drawSignals(Vec2Df mouse_pos, Camera2D camera){ - mutex->lock(); - if(!erase_signals.empty()){ - for(auto s: erase_signals){ - std::erase(signals, s); - } - erase_signals.clear(); - } - for(auto s : signals){ - s->draw(mouse_pos, camera); - } - mutex->unlock(); - } + void pause(); + void stop(); + void start(); + void reset(); + void startSignalThread(); + void stopSignalThread(); + void addSignalAtNode(WPTR_Node node); + void rerouteSignal(VEC_SPTR_Node nodes, VEC_WPTR_Node adjacent_nodes); + void rebaseSignal(VEC_SPTR_Node nodes); + void removeSignal(std::shared_ptr s); + void updateSignals(); + void drawSignals(Vec2Df mouse_pos, Camera2D camera); NLOHMANN_DEFINE_TYPE_INTRUSIVE(SignalManager, signals) From 77a227f40d4a2c782c3c3f79955ff96b3ab79889 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 6 Jan 2026 02:44:18 +0100 Subject: [PATCH 2/9] more refactoring and breaking things --- src/app-manager.cpp | 250 +++++------ src/canvas.cpp | 88 ++++ src/canvas.h | 98 +---- src/config-node.cpp | 285 +++++++++++++ src/config-node.h | 288 +------------ src/graph-manager.cpp | 822 +++++++++++++++++++----------------- src/graph-manager.h | 14 - src/interaction-manager.cpp | 255 +++++++++++ src/interaction-manager.h | 250 +---------- src/midi-node.cpp | 391 +++++++++++++++++ src/midi-node.h | 404 ++---------------- src/midi.cpp | 80 ++++ src/midi.h | 78 +--- src/modifier-node.cpp | 289 +++++++++++++ src/modifier-node.h | 297 +------------ src/mouse.h | 65 --- src/node.cpp | 73 ++++ src/node.h | 74 +--- src/signal.cpp | 25 ++ src/signal.h | 22 +- src/state-io.h | 46 -- src/state.cpp | 73 ++++ src/state.h | 67 +-- src/ui.cpp | 196 +++++++++ src/ui.h | 190 +-------- 25 files changed, 2416 insertions(+), 2304 deletions(-) create mode 100644 src/canvas.cpp create mode 100644 src/config-node.cpp create mode 100644 src/interaction-manager.cpp create mode 100644 src/midi-node.cpp create mode 100644 src/midi.cpp create mode 100644 src/modifier-node.cpp delete mode 100644 src/mouse.h create mode 100644 src/node.cpp create mode 100644 src/signal.cpp create mode 100644 src/state.cpp create mode 100644 src/ui.cpp diff --git a/src/app-manager.cpp b/src/app-manager.cpp index a2b0f9b..2d8ca17 100644 --- a/src/app-manager.cpp +++ b/src/app-manager.cpp @@ -1,146 +1,148 @@ #include "app-manager.h" -void VISEQ::APP::AppManager::init(){ - canvas->camera.target = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; - canvas->camera.offset = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; - canvas->camera.rotation = 0.0f; - canvas->camera.zoom = 1.0f; -} +namespace VISEQ::APP{ + void AppManager::init(){ + canvas->camera.target = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; + canvas->camera.offset = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; + canvas->camera.rotation = 0.0f; + canvas->camera.zoom = 1.0f; + } -void VISEQ::APP::AppManager::close(){ - std::ofstream file("state.json"); - file << json(*this); - std::cout << std::setw(4) << json(*this) << std::endl; -} + void AppManager::close(){ + std::ofstream file("state.json"); + file << json(*this); + std::cout << std::setw(4) << json(*this) << std::endl; + } -void VISEQ::APP::AppManager::startSignals(){ - sm->startSignalThread(); -} + void AppManager::startSignals(){ + sm->startSignalThread(); + } -void VISEQ::APP::AppManager::loadState(){ - if(std::filesystem::exists("state.json")){ - std::ifstream f("state.json"); - json graph = json::parse(f); + void AppManager::loadState(){ + if(std::filesystem::exists("state.json")){ + std::ifstream f("state.json"); + json graph = json::parse(f); - for(auto n : graph.at("gm").at("nodes")){ - std::cout << "object? " << n << std::endl; - if(n.is_object()){ - std::cout << "nodeType = " << n.at("nodeType").get() << std::endl; + for(auto n : graph.at("gm").at("nodes")){ + std::cout << "object? " << n << std::endl; + if(n.is_object()){ + std::cout << "nodeType = " << n.at("nodeType").get() << std::endl; - if(n.at("nodeType").get() == NodeType::MIDINODE) gm->addNode(n.template get>()); - if(n.at("nodeType").get() == NodeType::CONFIGNODE) gm->addNode(n.template get>()); + if(n.at("nodeType").get() == NodeType::MIDINODE) gm->addNode(n.template get>()); + if(n.at("nodeType").get() == NodeType::CONFIGNODE) gm->addNode(n.template get>()); + } } + + int highest_id = 0; + for(auto n : graph.at("gm").at("nodes")){ + std::cout << n << std::endl; + if(n.is_object()){ + if(n.at("id").get() > highest_id) { + highest_id = n.at("id").get(); + } + } + } + + for(auto e : graph.at("gm").at("edges")){ + if(e.at("id") > highest_id){ + highest_id = e.at("id"); + } + } + + for(auto e : graph.at("gm").at("edges")){ + int start_id = e.at("start").get(); + int end_id = e.at("end").get(); + + VEC_WPTR_Node start = gm->getNodesByID(start_id); + VEC_WPTR_Node end = gm->getNodesByID(end_id); + + if(!start.empty() && !end.empty()) { + std::cout << std::format("connection {} to {}", start_id, end_id) << std::endl; + gm->addConnection(start.front(), end.front()); + } + } + gm->lastEdge().lock()->newId(highest_id); + + for(auto s : graph.at("sm").at("signals")){ + int start_id = s.at("start").get(); + int next_id = s.at("next").get(); + int last_id = s.at("last").get(); + std::shared_ptr ms = s; + VEC_WPTR_Node start = gm->getNodesByID(start_id); + VEC_WPTR_Node last = gm->getNodesByID(last_id); + VEC_WPTR_Node next = gm->getNodesByID(next_id); + + ms->start = start.front(); + ms->next = next.front(); + ms->last = last.front(); + sm->signals.push_back(ms); + + std::cout << std::format("adding signal with start node {}", start_id) << std::endl; + } + + } else { + for(int i = 0; i < 4; ++i){ + gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 2 * state->settings.grid*4}); + } + for(int i = 3; i >= 0; --i){ + gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 3 * state->settings.grid*4}); + } + + for(int i = 0; i < 8; ++i){ + gm->addConnection(gm->getNode(i), gm->getNode((i+1) % 8)); + } + sm->addSignalAtNode(gm->getFirstNode()); } - int highest_id = 0; - for(auto n : graph.at("gm").at("nodes")){ - std::cout << n << std::endl; - if(n.is_object()){ - if(n.at("id").get() > highest_id) { - highest_id = n.at("id").get(); + } + + void AppManager::updateMidiPorts(){ + for(auto& n : gm->getMidiNodes()){ + auto nc = std::dynamic_pointer_cast(n.lock()); + for(auto& mo : sm->midi.midi_out_ports){ + if(!nc->midi_outs.contains(mo.number) && mo.enabled){ + nc->midi_outs[mo.number] = {mo.number, mo.port.port_name, false}; + } + if(nc->midi_outs.contains(mo.number) && !mo.enabled){ + nc->midi_outs.erase(mo.number); } } } + } - for(auto e : graph.at("gm").at("edges")){ - if(e.at("id") > highest_id){ - highest_id = e.at("id"); - } - } + void AppManager::update(){ + state->update(canvas->camera); + canvas->update(); + gm->update(); - for(auto e : graph.at("gm").at("edges")){ - int start_id = e.at("start").get(); - int end_id = e.at("end").get(); - - VEC_WPTR_Node start = gm->getNodesByID(start_id); - VEC_WPTR_Node end = gm->getNodesByID(end_id); - - if(!start.empty() && !end.empty()) { - std::cout << std::format("connection {} to {}", start_id, end_id) << std::endl; - gm->addConnection(start.front(), end.front()); - } - } - gm->lastEdge().lock()->newId(highest_id); - - for(auto s : graph.at("sm").at("signals")){ - int start_id = s.at("start").get(); - int next_id = s.at("next").get(); - int last_id = s.at("last").get(); - std::shared_ptr ms = s; - VEC_WPTR_Node start = gm->getNodesByID(start_id); - VEC_WPTR_Node last = gm->getNodesByID(last_id); - VEC_WPTR_Node next = gm->getNodesByID(next_id); - - ms->start = start.front(); - ms->next = next.front(); - ms->last = last.front(); - sm->signals.push_back(ms); - - std::cout << std::format("adding signal with start node {}", start_id) << std::endl; - } + mutex.lock(); + updateMidiPorts(); + mutex.unlock(); - } else { - for(int i = 0; i < 4; ++i){ - gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 2 * state->settings.grid*4}); - } - for(int i = 3; i >= 0; --i){ - gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 3 * state->settings.grid*4}); - } + im.update(); - for(int i = 0; i < 8; ++i){ - gm->addConnection(gm->getNode(i), gm->getNode((i+1) % 8)); - } - sm->addSignalAtNode(gm->getFirstNode()); } -} + void AppManager::draw(){ + BeginDrawing(); + ClearBackground((Color){46,46,46}); + canvas->drawStart(); + canvas->draw(); -void VISEQ::APP::AppManager::updateMidiPorts(){ - for(auto& n : gm->getMidiNodes()){ - auto nc = std::dynamic_pointer_cast(n.lock()); - for(auto& mo : sm->midi.midi_out_ports){ - if(!nc->midi_outs.contains(mo.number) && mo.enabled){ - nc->midi_outs[mo.number] = {mo.number, mo.port.port_name, false}; - } - if(nc->midi_outs.contains(mo.number) && !mo.enabled){ - nc->midi_outs.erase(mo.number); - } - } + gm->drawNodes(SCREEN_MOUSE, canvas->camera); + gm->drawEdges(SCREEN_MOUSE, canvas->camera); + sm->drawSignals(SCREEN_MOUSE, canvas->camera); + + im.draw(); + + canvas->drawEnd(); + drawUI(); + EndDrawing(); + } + + void AppManager::drawUI(){ + mutex.lock(); + ui.draw(SCREEN_MOUSE, canvas->camera); + mutex.unlock(); } } - -void VISEQ::APP::AppManager::update(){ - state->update(canvas->camera); - canvas->update(); - gm->update(); - - mutex.lock(); - updateMidiPorts(); - mutex.unlock(); - - im.update(); - -} - -void VISEQ::APP::AppManager::draw(){ -BeginDrawing(); - ClearBackground((Color){46,46,46}); - canvas->drawStart(); - canvas->draw(); - - gm->drawNodes(SCREEN_MOUSE, canvas->camera); - gm->drawEdges(SCREEN_MOUSE, canvas->camera); - sm->drawSignals(SCREEN_MOUSE, canvas->camera); - - im.draw(); - - canvas->drawEnd(); - drawUI(); -EndDrawing(); -} - -void VISEQ::APP::AppManager::drawUI(){ - mutex.lock(); - ui.draw(SCREEN_MOUSE, canvas->camera); - mutex.unlock(); -} diff --git a/src/canvas.cpp b/src/canvas.cpp new file mode 100644 index 0000000..5638aa0 --- /dev/null +++ b/src/canvas.cpp @@ -0,0 +1,88 @@ +#include "canvas.h" + +#include "datatypes.h" +#include "interaction-event.h" +#include "state.h" +#include "draggable.h" +#include "raylib.h" +#include "raymath.h" +#include "vmath.h" +#include + +namespace VISEQ::APP::CANVAS { + bool Canvas::hovering() { + return state->mouse_z == z_index; + } + + void Canvas::drag(Vec2Df position) { + Vec2Df select1 = {SCREEN_MOUSE.x > select_start.x ? select_start.x : SCREEN_MOUSE.x, SCREEN_MOUSE.y > select_start.y ? select_start.y : SCREEN_MOUSE.y}; + Vec2Df select2 = {SCREEN_MOUSE.x > select_start.x ? SCREEN_MOUSE.x - select_start.x : select_start.x - SCREEN_MOUSE.x, SCREEN_MOUSE.y > select_start.y ? SCREEN_MOUSE.y - select_start.y : select_start.y - SCREEN_MOUSE.y}; + state->selection = (Rectangle){select1.x, select1.y, select2.x, select2.y}; + } + + void Canvas::pickUp() { + std::cout << "canvas pickUp" << std::endl; + BASE::Draggable::pickUp(); + select_start = SCREEN_MOUSE; + state->pushEvent({getPtr(), Interaction::DESELECT}); + } + + void Canvas::putDown() { + std::cout << "canvas putDown" << std::endl; + BASE::Draggable::putDown(); + state->pushEvent({getPtr(), Interaction::SELECT}); + } + + void Canvas::clearSelectionRect(){ + state->selection = (Rectangle){0,0,0,0}; + } + + void Canvas::drawStart(){ + BeginMode2D(camera); + } + + void Canvas::drawEnd(){ + EndMode2D(); + } + + GRAPH::GraphItemType Canvas::getType() { + return GRAPH::GraphItemType::CANVAS; + } + + std::shared_ptr Canvas::getPtr(){ + return shared_from_this(); + } + + void Canvas::update() { + camera_delta = state->mouse_delta * (-1.0f/camera.zoom); + + if(state->mouse_right.down) { + camera.target = Vector2Add(camera.target, camera_delta); + } + + if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); + if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); + if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); + + float wheel = GetMouseWheelMove(); + if(wheel != 0){ + if(!KEYS_DOWN.shift && !KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ + camera.offset = state->raw_mouse; + camera.target = SCREEN_MOUSE; + float scaleFactor = 1.0f + (0.25f*fabsf(wheel)); + if (wheel < 0) scaleFactor = 1.0f/scaleFactor; + camera.zoom = Clamp(camera.zoom*scaleFactor, 0.125f, 64.0f); + } + } + } + + void Canvas::draw() { + for(int x = -SETTINGS.grid*100; x < SETTINGS.grid*100; x+=SETTINGS.grid){ + for(int y = -SETTINGS.grid*100; y < SETTINGS.grid*100; y+=SETTINGS.grid){ + DrawPixel(x, y, (Color){130,130,130,200}); + } + } + if(state->selection.width > 0 || state->selection.height > 0) DrawRectangleLines(state->selection.x, state->selection.y, state->selection.width, state->selection.height, (Color){150,150,150,150}); + + } +} diff --git a/src/canvas.h b/src/canvas.h index a871080..7bb2f75 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -1,99 +1,31 @@ #pragma once - #include "datatypes.h" -#include "interaction-event.h" -#include "state.h" #include "draggable.h" #include "raylib.h" -#include "raymath.h" #include "vmath.h" #include namespace VISEQ::APP::CANVAS { class Canvas : public BASE::Draggable, public std::enable_shared_from_this { public: - Canvas() {} + Canvas() {} - Camera2D camera; - Vec2Df camera_delta; - Vec2Df select_start; + Camera2D camera; + Vec2Df camera_delta; + Vec2Df select_start; - virtual bool hovering() override { - return state->mouse_z == z_index; - } - - virtual void drag(Vec2Df position) override { - Vec2Df select1 = {SCREEN_MOUSE.x > select_start.x ? select_start.x : SCREEN_MOUSE.x, SCREEN_MOUSE.y > select_start.y ? select_start.y : SCREEN_MOUSE.y}; - Vec2Df select2 = {SCREEN_MOUSE.x > select_start.x ? SCREEN_MOUSE.x - select_start.x : select_start.x - SCREEN_MOUSE.x, SCREEN_MOUSE.y > select_start.y ? SCREEN_MOUSE.y - select_start.y : select_start.y - SCREEN_MOUSE.y}; - state->selection = (Rectangle){select1.x, select1.y, select2.x, select2.y}; - } - - virtual void pickUp() override { - std::cout << "canvas pickUp" << std::endl; - BASE::Draggable::pickUp(); - select_start = SCREEN_MOUSE; - state->pushEvent({getPtr(), Interaction::DESELECT}); - } - - virtual void putDown() override { - std::cout << "canvas putDown" << std::endl; - BASE::Draggable::putDown(); - state->pushEvent({getPtr(), Interaction::SELECT}); - } - - void clearSelectionRect(){ - state->selection = (Rectangle){0,0,0,0}; - } - - virtual void drawStart(){ - BeginMode2D(camera); - } - - virtual void drawEnd(){ - EndMode2D(); - } - - GRAPH::GraphItemType getType() override { - return GRAPH::GraphItemType::CANVAS; - } - - std::shared_ptr getPtr(){ - return shared_from_this(); - } - - virtual void update() override { - camera_delta = state->mouse_delta * (-1.0f/camera.zoom); - - if(state->mouse_right.down) { - camera.target = Vector2Add(camera.target, camera_delta); - } - - if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); - if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); - if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); - - float wheel = GetMouseWheelMove(); - if(wheel != 0){ - if(!KEYS_DOWN.shift && !KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ - camera.offset = state->raw_mouse; - camera.target = SCREEN_MOUSE; - float scaleFactor = 1.0f + (0.25f*fabsf(wheel)); - if (wheel < 0) scaleFactor = 1.0f/scaleFactor; - camera.zoom = Clamp(camera.zoom*scaleFactor, 0.125f, 64.0f); - } - } - } - - virtual void draw() { - for(int x = -SETTINGS.grid*100; x < SETTINGS.grid*100; x+=SETTINGS.grid){ - for(int y = -SETTINGS.grid*100; y < SETTINGS.grid*100; y+=SETTINGS.grid){ - DrawPixel(x, y, (Color){130,130,130,200}); - } - } - if(state->selection.width > 0 || state->selection.height > 0) DrawRectangleLines(state->selection.x, state->selection.y, state->selection.width, state->selection.height, (Color){150,150,150,150}); - - } + virtual bool hovering() override; + virtual void drag(Vec2Df position) override; + virtual void pickUp() override; + virtual void putDown() override; + void clearSelectionRect(); + virtual void drawStart(); + virtual void drawEnd(); + GRAPH::GraphItemType getType() override; + std::shared_ptr getPtr(); + virtual void update() override; + virtual void draw(); }; } diff --git a/src/config-node.cpp b/src/config-node.cpp new file mode 100644 index 0000000..660a0bf --- /dev/null +++ b/src/config-node.cpp @@ -0,0 +1,285 @@ +#include "config-node.h" + +#include "datatypes.h" +#include "edge.h" +#include "node.h" +#include "raymath.h" +#include "state.h" +#include + +namespace VISEQ::GRAPH::NODES { + + std::shared_ptr ConfigNode::getPtr() { + return std::static_pointer_cast(shared_from_this()); + } + + NodeType ConfigNode::getNodeType() const {return nodeType;} + + void ConfigNode::trigger() {} + + void ConfigNode::setMode(int _mode) { + mode = _mode; + } + + MIDI::MidiNote ConfigNode::getNotes() const { + MIDI::MidiNote cNote; + for(auto cn : getConnectedConfigNodes()){ + cNote += cn.lock()->getNotes(); + } + return midiNote + cNote; + } + + MIDI::MidiCC ConfigNode::getCC() const { + MIDI::MidiCC cCC; + for(auto cn : getConnectedConfigNodes()){ + cCC += cn.lock()->getCC(); + } + return midiCC + cCC; + } + + MIDI::MidiPC ConfigNode::getPC() const { + MIDI::MidiPC cPC; + for(auto cn : getConnectedConfigNodes()){ + cPC += cn.lock()->getPC(); + } + return midiPC + cPC; + } + + MIDI::MidiPitchBend ConfigNode::getPitchBend() const { + MIDI::MidiPitchBend cPitchBend; + for(auto cn : getConnectedConfigNodes()){ + cPitchBend += cn.lock()->getPitchBend(); + } + return midiPitchBend + cPitchBend; + } + + MIDI::MidiAftertouch ConfigNode::getAftertouch() const { + MIDI::MidiAftertouch cAftertouch; + for(auto cn : getConnectedConfigNodes()){ + cAftertouch += cn.lock()->getAftertouch(); + } + return midiAftertouch + cAftertouch; + } + + MIDI::MidiPolyPressure ConfigNode::getPolyPressure() const { + MIDI::MidiPolyPressure cPolyPressure; + for(auto cn : getConnectedConfigNodes()){ + cPolyPressure += cn.lock()->getPolyPressure(); + } + return midiPolyPressure + cPolyPressure; + } + + std::vector> ConfigNode::getConnectedConfigNodes() const { // TODO: check for cyclic connections of config nodes (crashes) + auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE;}) | std::ranges::to>>(); + std::vector> config_nodes; + for(auto e : config_conns){ + if(e->start.lock().get() != this && e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); + //if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); + } + /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ + return config_nodes; + } + + void ConfigNode::update() { + Node::update(); + mode = state->node_mode; + } + + void ConfigNode::interact(){ + float wheel = GetMouseWheelMove(); + + if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ + switch(state->node_mode){ + case 0:{ + midiNote.note += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; + break;} + case 1:{ + midiCC.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; + break;} + case 2:{ + midiPitchBend.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; + break;} + }; + + if(midiNote.note < -127 ) midiNote.note = -127; + if(midiNote.note > 127 ) midiNote.note = 127; + + if(midiCC.value < -127 ) midiCC.value = -127; + if(midiCC.value > 127 ) midiCC.value = 127; + + if(midiPitchBend.value < -16383 ) midiPitchBend.value = -16383; + if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; + + } else if(KEYS_DOWN.alt) { + switch(state->node_mode){ + case 0:{ + midiNote.channel += wheel; + if(midiNote.channel < 0 ) midiNote.channel = 0; + if(midiNote.channel > 15) midiNote.channel = 15; + break;} + case 1:{ + midiCC.channel += wheel; + if(midiCC.channel < 0 ) midiCC.channel = 0; + if(midiCC.channel > 15 ) midiCC.channel = 15; + break;} + case 2:{ + midiPitchBend.channel += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 100 : wheel; + if(midiPitchBend.channel < 0 ) midiPitchBend.channel = 0; + if(midiPitchBend.channel > 15 ) midiPitchBend.channel = 15; + break;} + }; + } + } + + void ConfigNode::draw(Vec2Df mouse, Camera2D cam) { + Node::draw(); + if(hovering() || selected) interact(); + + DrawRing(position, radius, radius+4, 0, 360, 24, (Color){60, 60, 60, 255}); + DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); + + Color gate_color = VS_COLOR_GREEN; + + switch(mode){ + case 0:{ + gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); + break;} + case 1:{ + gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){229, 181, 103 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); + break;} + case 2:{ + gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){232, 125, 62 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); + break;} + } + if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, gate_color); + if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255}); + if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255}); + + if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); + if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW); + + if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); + + if(KEYS_DOWN.alt){ + float value = 0; + int offset_gate = 0; + switch(mode){ + case 0: + value = (float)getNotes().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 1: + value = (float)getCC().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 2: + value = (float)getPC().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 3: + value = (float)getPitchBend().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 4: + value = (float)getAftertouch().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 5: + value = (float)getPolyPressure().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + } + } else { + switch(mode){ + case 0:{ + int offset = MeasureText(std::format("{:+}", getNotes().note).c_str(), 12); + DrawText(std::format("{:+}", getNotes().note).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 1:{ + int offset = MeasureText(std::format("{:+}", getCC().value).c_str(), 12); + DrawText(std::format("{:+}", getCC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 2:{ + int offset = MeasureText(std::format("{:+}", getPC().value).c_str(), 12); + DrawText(std::format("{:+}", getPC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 3:{ + int offset = MeasureText(std::format("{:+}", getPitchBend().value).c_str(), 12); + DrawText(std::format("{:+}", getPitchBend().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 4:{ + int offset = MeasureText(std::format("{:+}", getAftertouch().value).c_str(), 12); + DrawText(std::format("{:+}", getAftertouch().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 5:{ + int offset = MeasureText(std::format("{:+}", getPolyPressure().value).c_str(), 12); + DrawText(std::format("{:+}", getPolyPressure().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + } + } + + if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200}); + //DrawText(std::format("{}", id).c_str(), position.x + 20.0f, position.y + 20.0f, 10, WHITE); + } + + void to_json(json& j, const ConfigNode& N){ + std::vector conns; + std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;}); + + j = json{ + {"id", N.id}, + {"position", N.position}, + {"distributionMode", N.distributionMode}, + {"nodeType", N.nodeType}, + {"midi_note_channel",N.midiNote.channel}, + {"midi_note_note",N.midiNote.note}, + {"midi_note_velocity",N.midiNote.velocity}, + {"midi_note_gate",N.midiNote.gate}, + {"midi_cc_channel",N.midiCC.channel}, + {"midi_cc_control",N.midiCC.control}, + {"midi_cc_value",N.midiCC.value}, + {"midi_pc_channel",N.midiPC.channel}, + {"midi_pc_program",N.midiPC.program}, + {"midi_pc_value",N.midiPC.value}, + {"midi_pitch_bend_channel",N.midiPitchBend.channel}, + {"midi_pitch_bend_value",N.midiPitchBend.value}, + {"midi_aftertouch_channel",N.midiAftertouch.channel}, + {"midi_aftertouch_value",N.midiAftertouch.value}, + {"midi_polypressure_channel",N.midiPolyPressure.channel}, + {"midi_polypressure_value",N.midiPolyPressure.value}, + {"connections",conns}}; + } + + void from_json(const json& j, ConfigNode& N){ + std::cout << "configNode from_json" << std::endl; + + j.at("id").get_to(N.id); + j.at("position").get_to(N.position); + j.at("distributionMode").get_to(N.distributionMode); + j.at("nodeType").get_to(N.nodeType); + j.at("midi_note_channel").get_to(N.midiNote.channel); + j.at("midi_note_note").get_to(N.midiNote.note); + j.at("midi_note_velocity").get_to(N.midiNote.velocity); + j.at("midi_note_gate").get_to(N.midiNote.gate); + j.at("midi_cc_channel").get_to(N.midiCC.channel); + j.at("midi_cc_control").get_to(N.midiCC.control); + j.at("midi_cc_value").get_to(N.midiCC.value); + j.at("midi_pc_channel").get_to(N.midiPC.channel); + j.at("midi_pc_program").get_to(N.midiPC.program); + j.at("midi_pc_value").get_to(N.midiPC.value); + j.at("midi_pitch_bend_channel").get_to(N.midiPitchBend.channel); + j.at("midi_pitch_bend_value").get_to(N.midiPitchBend.value); + j.at("midi_aftertouch_channel").get_to(N.midiAftertouch.channel); + j.at("midi_aftertouch_value").get_to(N.midiAftertouch.value); + j.at("midi_polypressure_channel").get_to(N.midiPolyPressure.channel); + j.at("midi_polypressure_value").get_to(N.midiPolyPressure.value); + + //j.at("connections").get_to(N.connections); + } +}; diff --git a/src/config-node.h b/src/config-node.h index ab70471..477afc2 100644 --- a/src/config-node.h +++ b/src/config-node.h @@ -1,10 +1,7 @@ #pragma once #include "datatypes.h" -#include "edge.h" #include "node.h" -#include "raymath.h" -#include "state.h" #include namespace VISEQ::GRAPH::NODES { @@ -15,9 +12,7 @@ namespace VISEQ::GRAPH::NODES { ConfigNode() {std::cout << "ConfigNode Constructor std" << std::endl;} ConfigNode(Vec2Df position) : Node(position) {} - std::shared_ptr getPtr() { - return std::static_pointer_cast(shared_from_this()); - } + std::shared_ptr getPtr(); NodeType nodeType = NodeType::CONFIGNODE; @@ -31,274 +26,23 @@ namespace VISEQ::GRAPH::NODES { int mode = 0; float radius = 20.0f; - virtual NodeType getNodeType() const override {return nodeType;} + virtual NodeType getNodeType() const override; - virtual void trigger() override {} + virtual void trigger() override; - virtual void setMode(int _mode) override { - mode = _mode; - } + virtual void setMode(int _mode) override; - MIDI::MidiNote getNotes() const { - MIDI::MidiNote cNote; - for(auto cn : getConnectedConfigNodes()){ - cNote += cn.lock()->getNotes(); - } - return midiNote + cNote; - } - - MIDI::MidiCC getCC() const { - MIDI::MidiCC cCC; - for(auto cn : getConnectedConfigNodes()){ - cCC += cn.lock()->getCC(); - } - return midiCC + cCC; - } - - MIDI::MidiPC getPC() const { - MIDI::MidiPC cPC; - for(auto cn : getConnectedConfigNodes()){ - cPC += cn.lock()->getPC(); - } - return midiPC + cPC; - } - - MIDI::MidiPitchBend getPitchBend() const { - MIDI::MidiPitchBend cPitchBend; - for(auto cn : getConnectedConfigNodes()){ - cPitchBend += cn.lock()->getPitchBend(); - } - return midiPitchBend + cPitchBend; - } - - MIDI::MidiAftertouch getAftertouch() const { - MIDI::MidiAftertouch cAftertouch; - for(auto cn : getConnectedConfigNodes()){ - cAftertouch += cn.lock()->getAftertouch(); - } - return midiAftertouch + cAftertouch; - } - - MIDI::MidiPolyPressure getPolyPressure() const { - MIDI::MidiPolyPressure cPolyPressure; - for(auto cn : getConnectedConfigNodes()){ - cPolyPressure += cn.lock()->getPolyPressure(); - } - return midiPolyPressure + cPolyPressure; - } - - std::vector> getConnectedConfigNodes() const { // TODO: check for cyclic connections of config nodes (crashes) - auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE;}) | std::ranges::to>>(); - std::vector> config_nodes; - for(auto e : config_conns){ - if(e->start.lock().get() != this && e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); - //if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); - } - /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ - return config_nodes; - } - - void update() override { - Node::update(); - mode = state->node_mode; - } - - void interact(){ - float wheel = GetMouseWheelMove(); - - if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ - switch(state->node_mode){ - case 0:{ - midiNote.note += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; - break;} - case 1:{ - midiCC.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; - break;} - case 2:{ - midiPitchBend.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; - break;} - }; - - if(midiNote.note < -127 ) midiNote.note = -127; - if(midiNote.note > 127 ) midiNote.note = 127; - - if(midiCC.value < -127 ) midiCC.value = -127; - if(midiCC.value > 127 ) midiCC.value = 127; - - if(midiPitchBend.value < -16383 ) midiPitchBend.value = -16383; - if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; - - } else if(KEYS_DOWN.alt) { - switch(state->node_mode){ - case 0:{ - midiNote.channel += wheel; - if(midiNote.channel < 0 ) midiNote.channel = 0; - if(midiNote.channel > 15) midiNote.channel = 15; - break;} - case 1:{ - midiCC.channel += wheel; - if(midiCC.channel < 0 ) midiCC.channel = 0; - if(midiCC.channel > 15 ) midiCC.channel = 15; - break;} - case 2:{ - midiPitchBend.channel += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 100 : wheel; - if(midiPitchBend.channel < 0 ) midiPitchBend.channel = 0; - if(midiPitchBend.channel > 15 ) midiPitchBend.channel = 15; - break;} - }; - } - } - - void draw(Vec2Df mouse, Camera2D cam) override { - Node::draw(); - if(hovering() || selected) interact(); - - DrawRing(position, radius, radius+4, 0, 360, 24, (Color){60, 60, 60, 255}); - DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); - - Color gate_color = VS_COLOR_GREEN; - - switch(mode){ - case 0:{ - gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); - break;} - case 1:{ - gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){229, 181, 103 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); - break;} - case 2:{ - gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){232, 125, 62 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); - break;} - } - if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, gate_color); - if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255}); - if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255}); - - if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); - if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW); - - if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); - - if(KEYS_DOWN.alt){ - float value = 0; - int offset_gate = 0; - switch(mode){ - case 0: - value = (float)getNotes().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 1: - value = (float)getCC().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 2: - value = (float)getPC().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 3: - value = (float)getPitchBend().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 4: - value = (float)getAftertouch().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 5: - value = (float)getPolyPressure().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - } - } else { - switch(mode){ - case 0:{ - int offset = MeasureText(std::format("{:+}", getNotes().note).c_str(), 12); - DrawText(std::format("{:+}", getNotes().note).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 1:{ - int offset = MeasureText(std::format("{:+}", getCC().value).c_str(), 12); - DrawText(std::format("{:+}", getCC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 2:{ - int offset = MeasureText(std::format("{:+}", getPC().value).c_str(), 12); - DrawText(std::format("{:+}", getPC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 3:{ - int offset = MeasureText(std::format("{:+}", getPitchBend().value).c_str(), 12); - DrawText(std::format("{:+}", getPitchBend().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 4:{ - int offset = MeasureText(std::format("{:+}", getAftertouch().value).c_str(), 12); - DrawText(std::format("{:+}", getAftertouch().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 5:{ - int offset = MeasureText(std::format("{:+}", getPolyPressure().value).c_str(), 12); - DrawText(std::format("{:+}", getPolyPressure().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - } - } - - if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200}); - //DrawText(std::format("{}", id).c_str(), position.x + 20.0f, position.y + 20.0f, 10, WHITE); - } - - friend void to_json(json& j, const ConfigNode& N){ - std::vector conns; - std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;}); - - j = json{ - {"id", N.id}, - {"position", N.position}, - {"distributionMode", N.distributionMode}, - {"nodeType", N.nodeType}, - {"midi_note_channel",N.midiNote.channel}, - {"midi_note_note",N.midiNote.note}, - {"midi_note_velocity",N.midiNote.velocity}, - {"midi_note_gate",N.midiNote.gate}, - {"midi_cc_channel",N.midiCC.channel}, - {"midi_cc_control",N.midiCC.control}, - {"midi_cc_value",N.midiCC.value}, - {"midi_pc_channel",N.midiPC.channel}, - {"midi_pc_program",N.midiPC.program}, - {"midi_pc_value",N.midiPC.value}, - {"midi_pitch_bend_channel",N.midiPitchBend.channel}, - {"midi_pitch_bend_value",N.midiPitchBend.value}, - {"midi_aftertouch_channel",N.midiAftertouch.channel}, - {"midi_aftertouch_value",N.midiAftertouch.value}, - {"midi_polypressure_channel",N.midiPolyPressure.channel}, - {"midi_polypressure_value",N.midiPolyPressure.value}, - {"connections",conns}}; - } - - friend void from_json(const json& j, ConfigNode& N){ - std::cout << "configNode from_json" << std::endl; - - j.at("id").get_to(N.id); - j.at("position").get_to(N.position); - j.at("distributionMode").get_to(N.distributionMode); - j.at("nodeType").get_to(N.nodeType); - j.at("midi_note_channel").get_to(N.midiNote.channel); - j.at("midi_note_note").get_to(N.midiNote.note); - j.at("midi_note_velocity").get_to(N.midiNote.velocity); - j.at("midi_note_gate").get_to(N.midiNote.gate); - j.at("midi_cc_channel").get_to(N.midiCC.channel); - j.at("midi_cc_control").get_to(N.midiCC.control); - j.at("midi_cc_value").get_to(N.midiCC.value); - j.at("midi_pc_channel").get_to(N.midiPC.channel); - j.at("midi_pc_program").get_to(N.midiPC.program); - j.at("midi_pc_value").get_to(N.midiPC.value); - j.at("midi_pitch_bend_channel").get_to(N.midiPitchBend.channel); - j.at("midi_pitch_bend_value").get_to(N.midiPitchBend.value); - j.at("midi_aftertouch_channel").get_to(N.midiAftertouch.channel); - j.at("midi_aftertouch_value").get_to(N.midiAftertouch.value); - j.at("midi_polypressure_channel").get_to(N.midiPolyPressure.channel); - j.at("midi_polypressure_value").get_to(N.midiPolyPressure.value); - - //j.at("connections").get_to(N.connections); - } + MIDI::MidiNote getNotes() const; + MIDI::MidiCC getCC() const; + MIDI::MidiPC getPC() const; + MIDI::MidiPitchBend getPitchBend() const; + MIDI::MidiAftertouch getAftertouch() const; + MIDI::MidiPolyPressure getPolyPressure() const; + std::vector> getConnectedConfigNodes() const; + void update() override; + void interact(); + void draw(Vec2Df mouse, Camera2D cam) override; + friend void to_json(json& j, const ConfigNode& N); + friend void from_json(const json& j, ConfigNode& N); }; } diff --git a/src/graph-manager.cpp b/src/graph-manager.cpp index 99b3970..1dab649 100644 --- a/src/graph-manager.cpp +++ b/src/graph-manager.cpp @@ -1,439 +1,463 @@ #include "graph-manager.h" +#include "config-node.h" +#include "datatypes.h" +#include "state.h" +#include "vmath.h" +#include "raylib.h" +#include "raymath.h" -std::shared_ptr VISEQ::GRAPH::GraphManager::getPtr(){ - return shared_from_this(); -} +#include "button.h" +#include "midi-node.h" +#include "edge.h" +#include "node.h" -int VISEQ::GRAPH::GraphManager::getNodesSize(){ - return nodes.size(); -} +#include +#include +#include +#include +#include +#include +#include +#include +#include -void VISEQ::GRAPH::GraphManager::createMidiNode(Vec2Df position){ - nodes.emplace_back(std::make_shared(position)); - nodes.back()->update(); -} +#include "state-io.h" -void VISEQ::GRAPH::GraphManager::createConfigNode(Vec2Df position){ - nodes.emplace_back(std::make_shared(position)); - nodes.back()->update(); -} - -void VISEQ::GRAPH::GraphManager::setMidiPortForAll(int number, bool _state){ - for(auto n : nodes){ - if(n->getNodeType() == NodeType::MIDINODE) std::dynamic_pointer_cast(n)->setMidiPort(number, _state); +namespace VISEQ::GRAPH{ + std::shared_ptr GraphManager::getPtr(){ + return shared_from_this(); } -} -void VISEQ::GRAPH::GraphManager::addNode(SPTR_Node node){ - std::cout << "added node " << node->id << std::endl; - nodes.insert(nodes.end(), node); - node->update(); -} - -void VISEQ::GRAPH::GraphManager::addConnection(WPTR_Node from, WPTR_Node to){ - EdgeType et = EdgeType::MIDIEDGE; - if(from.lock()->getNodeType() == NodeType::CONFIGNODE || to.lock()->getNodeType() == NodeType::CONFIGNODE) et = EdgeType::CONFIGEDGE; - if(from.lock() != to.lock()) { - edges.emplace_back(std::make_shared()); - edges.back()->edgeType = et; - edges.back()->start = from; - edges.back()->end = to; - edges.back()->start_conn = from.lock()->connectors.back(); - edges.back()->end_conn = to.lock()->connectors.back(); - from.lock()->connections.push_back(edges.back()); - to.lock()->connections.push_back(edges.back()); + int GraphManager::getNodesSize(){ + return nodes.size(); } -} -void VISEQ::GRAPH::GraphManager::addToSelection(){ - auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->selected;}) | std::ranges::to(); - selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); -} - -void VISEQ::GRAPH::GraphManager::select(){ - auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->select();}) | std::ranges::to(); - if(!KEYS_DOWN.shift) selected_nodes.clear(); - selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); - std::for_each(selected_nodes.begin(), selected_nodes.end(), [](WPTR_Node n){n.lock()->selected = true;}); -} - -void VISEQ::GRAPH::GraphManager::deselect(){ - std::cout << "deselect" << std::endl; - for(auto s : selected_nodes){ - s->selected = false; + void GraphManager::createMidiNode(Vec2Df position){ + nodes.emplace_back(std::make_shared(position)); + nodes.back()->update(); } - selected_nodes.clear(); -} -VEC_SPTR_Node VISEQ::GRAPH::GraphManager::getSelection(){ - return selected_nodes; -} + void GraphManager::createConfigNode(Vec2Df position){ + nodes.emplace_back(std::make_shared(position)); + nodes.back()->update(); + } -void VISEQ::GRAPH::GraphManager::copyNodes(VEC_SPTR_Node _nodes, Vec2Df position){ - for(auto n: _nodes){ + void GraphManager::setMidiPortForAll(int number, bool _state){ + for(auto n : nodes){ + if(n->getNodeType() == NodeType::MIDINODE) std::dynamic_pointer_cast(n)->setMidiPort(number, _state); + } + } + + void GraphManager::addNode(SPTR_Node node){ + std::cout << "added node " << node->id << std::endl; + nodes.insert(nodes.end(), node); + node->update(); + } + + void GraphManager::addConnection(WPTR_Node from, WPTR_Node to){ + EdgeType et = EdgeType::MIDIEDGE; + if(from.lock()->getNodeType() == NodeType::CONFIGNODE || to.lock()->getNodeType() == NodeType::CONFIGNODE) et = EdgeType::CONFIGEDGE; + if(from.lock() != to.lock()) { + edges.emplace_back(std::make_shared()); + edges.back()->edgeType = et; + edges.back()->start = from; + edges.back()->end = to; + edges.back()->start_conn = from.lock()->connectors.back(); + edges.back()->end_conn = to.lock()->connectors.back(); + from.lock()->connections.push_back(edges.back()); + to.lock()->connections.push_back(edges.back()); + } + } + + void GraphManager::addToSelection(){ + auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->selected;}) | std::ranges::to(); + selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); + } + + void GraphManager::select(){ + auto temp_nodes = nodes | RANGE_FILTER([](SPTR_Node n){return n->select();}) | std::ranges::to(); + if(!KEYS_DOWN.shift) selected_nodes.clear(); + selected_nodes.insert(selected_nodes.end(), temp_nodes.begin(), temp_nodes.end()); + std::for_each(selected_nodes.begin(), selected_nodes.end(), [](WPTR_Node n){n.lock()->selected = true;}); + } + + void GraphManager::deselect(){ + std::cout << "deselect" << std::endl; + for(auto s : selected_nodes){ + s->selected = false; + } + selected_nodes.clear(); + } + + VEC_SPTR_Node GraphManager::getSelection(){ + return selected_nodes; + } + + void GraphManager::copyNodes(VEC_SPTR_Node _nodes, Vec2Df position){ + for(auto n: _nodes){ + nodes.insert(nodes.end(), std::make_shared(*n)); + /*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ + /*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ + nodes.back()->setPosition(position + nodes.back()->drag_offset); + nodes.back()->selected = false; + } + } + + void GraphManager::copyNodes(std::vector> _nodes, Vec2Df position){ + for(auto n: _nodes){ + nodes.insert(nodes.end(), std::make_shared(*n)); + nodes.back()->setPosition(position + nodes.back()->drag_offset); + nodes.back()->selected = false; + } + } + + VEC_SPTR_Node GraphManager::getCopiesOfSelectedNodes(){ + VEC_SPTR_Node copied_nodes; + for(auto n : selected_nodes){ + copied_nodes.insert(copied_nodes.end(), std::make_shared(*n)); + /*if(n->getNodeType() == NodeType::MIDINODE) copied_nodes.insert(copied_nodes.end(), std::make_shared(*n));*/ + /*if(n->getNodeType() == NodeType::CONFIGNODE) copied_nodes.insert(copied_nodes.end(), std::make_shared(*n));*/ + copied_nodes.back()->deselect(); + } + return copied_nodes; + } + + SPTR_Node GraphManager::copyNode(SPTR_Node n, Vec2Df position){ + std::cout << "copy basenode" << std::endl; nodes.insert(nodes.end(), std::make_shared(*n)); /*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ /*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ - nodes.back()->setPosition(position + nodes.back()->drag_offset); - nodes.back()->selected = false; + nodes.back()->setPosition(position); + return nodes.back(); } -} -void VISEQ::GRAPH::GraphManager::copyNodes(std::vector> _nodes, Vec2Df position){ - for(auto n: _nodes){ + SPTR_Node GraphManager::copyNode(std::shared_ptr n, Vec2Df position){ + std::cout << "copy midinode" << std::endl; nodes.insert(nodes.end(), std::make_shared(*n)); - nodes.back()->setPosition(position + nodes.back()->drag_offset); - nodes.back()->selected = false; + nodes.back()->clearConnectors(); + nodes.back()->setPosition(position); + return nodes.back(); } -} - -VEC_SPTR_Node VISEQ::GRAPH::GraphManager::getCopiesOfSelectedNodes(){ - VEC_SPTR_Node copied_nodes; - for(auto n : selected_nodes){ - copied_nodes.insert(copied_nodes.end(), std::make_shared(*n)); - /*if(n->getNodeType() == NodeType::MIDINODE) copied_nodes.insert(copied_nodes.end(), std::make_shared(*n));*/ - /*if(n->getNodeType() == NodeType::CONFIGNODE) copied_nodes.insert(copied_nodes.end(), std::make_shared(*n));*/ - copied_nodes.back()->deselect(); - } - return copied_nodes; -} - -SPTR_Node VISEQ::GRAPH::GraphManager::copyNode(SPTR_Node n, Vec2Df position){ - std::cout << "copy basenode" << std::endl; - nodes.insert(nodes.end(), std::make_shared(*n)); - /*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ - /*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared(*n));*/ - nodes.back()->setPosition(position); - return nodes.back(); -} - -SPTR_Node VISEQ::GRAPH::GraphManager::copyNode(std::shared_ptr n, Vec2Df position){ - std::cout << "copy midinode" << std::endl; - nodes.insert(nodes.end(), std::make_shared(*n)); - nodes.back()->clearConnectors(); - nodes.back()->setPosition(position); - return nodes.back(); -} - -void VISEQ::GRAPH::GraphManager::setNodeMode(int mode){ - for(auto n: nodes){ - n->setMode(mode); - } -} - -void VISEQ::GRAPH::GraphManager::reset(){ - counter.clear(); -} -WPTR_Node VISEQ::GRAPH::GraphManager::getNode(int i){ - return nodes.at(i); -} - -WPTR_Node VISEQ::GRAPH::GraphManager::getFirstNode(){ - return nodes.front(); -} - -WPTR_Node VISEQ::GRAPH::GraphManager::getLastNode(){ - return nodes.back(); -} - -VEC_WPTR_Node VISEQ::GRAPH::GraphManager::getMidiNodes(){ - return nodes | RANGE_FILTER([](VISEQ::GRAPH::NODES::SPTR_Node n){return n->getNodeType() == NodeType::MIDINODE;}) | std::ranges::to(); -} - -NODES::SPTR_Node VISEQ::GRAPH::GraphManager::getRandomNode(){ - VEC_SPTR_GraphItem o; - std::ranges::sample(nodes, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); - return std::dynamic_pointer_cast(o.front()); -} - -EDGES::SPTR_Edge VISEQ::GRAPH::GraphManager::getRandomEdge(){ - VEC_SPTR_GraphItem o; - std::ranges::sample(edges, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); - return std::dynamic_pointer_cast(o.front()); -} - -SPTR_Node VISEQ::GRAPH::GraphManager::getClosestNode(Vec2Df position){ - auto neighbors = nodes | RANGE_FILTER([](SPTR_Node n){ return !n->picked_up;}) | std::ranges::to(); - std::sort(neighbors.begin(), neighbors.end(), [position](SPTR_Node na, SPTR_Node nb){ return na->position.dist(position) < nb->position.dist(position);}); - return neighbors.front(); -} -VEC_WPTR_Node VISEQ::GRAPH::GraphManager::getNodesByID(int _id){ - return nodes | RANGE_FILTER([_id](SPTR_Node n){return n->id == _id;}) | std::ranges::to(); // TODO: Multiple nodes with the same ID? Why not return only one node? -} - -WPTR_Edge VISEQ::GRAPH::GraphManager::lastEdge(){ - return edges.back(); -} - -WPTR_Node VISEQ::GRAPH::GraphManager::next(WPTR_Node current, WPTR_Node last){ - VEC_WPTR_Edge possible_connections = current.lock()->connections | RANGE_FILTER([current](WPTR_Edge c){ - return (c.lock()->getEdgeType() == EdgeType::MIDIEDGE) && ((c.lock()->start.lock() == current.lock() && c.lock()->pass_forward) || (c.lock()->end.lock() == current.lock() && c.lock()->pass_backward)); - }) | std::ranges::to(); - - if(possible_connections.empty()) return SPTR_Node(nullptr); - - int return_path = 0; - float angle = -1; - if(current.lock()->distributionMode == Distribution::GEOMETRIC){ - Vec2Df coming_from = (current.lock()->position - last.lock()->position).norm(); - int i = 0; - for(auto n : possible_connections){ - WPTR_Node next_node = n.lock()->getNextNode(current); - float angle_v1_v2 = coming_from.norm().dot((next_node.lock()->position - current.lock()->position).norm()); - if(angle_v1_v2 > angle) { - return_path = i; - angle = angle_v1_v2; - } - ++i; - } - } - - if(current.lock()->distributionMode == Distribution::ROUNDROBIN){ - if(counter.find(current.lock()) == counter.end()) { - counter[current.lock()] = 0; - } else { - counter[current.lock()] = (counter[current.lock()] + 1) % possible_connections.size(); - return_path = counter[current.lock()]; - } - } - - if(current.lock()->distributionMode == Distribution::RANDOM){ - return_path = rand() % possible_connections.size(); - } - - return possible_connections.at(return_path).lock()->getNextNode(current); -} - -void VISEQ::GRAPH::GraphManager::splitEdge(SPTR_Edge edge){ - split_edges.push_back(edge); -} - -void VISEQ::GRAPH::GraphManager::splitEdges(){ - for(auto s : split_edges){ - Vec2Df center = s.lock()->end.lock()->position.center(s.lock()->start.lock()->position); - SPTR_Node cn = copyNode(std::dynamic_pointer_cast(s.lock()->start.lock()), center); // TODO: copy connectors - addConnection(s.lock()->start.lock(), cn); - addConnection(cn, s.lock()->end.lock()); - remove_edges.push_back(s); - } - split_edges.clear(); -} - -void VISEQ::GRAPH::GraphManager::removeEdge(SPTR_Edge edge){ - remove_edges.push_back(edge); -} - -void VISEQ::GRAPH::GraphManager::removeEdges(){ - for(auto edge : remove_edges){ - edge.lock()->start.lock()->removeConnector(edge.lock()->start_conn); - edge.lock()->end.lock()->removeConnector(edge.lock()->end_conn); - std::erase(edge.lock()->start.lock()->connections, edge.lock()); - std::erase(edge.lock()->end.lock()->connections, edge.lock()); - std::erase(edges, edge.lock()); - } - remove_edges.clear(); -} - -VEC_WPTR_Node VISEQ::GRAPH::GraphManager::getAdjacentNodes(VEC_SPTR_Node selected_nodes){ - for(auto n : nodes){ - n->highlighted = false; - } - - auto erase_connections = edges | RANGE_FILTER([selected_nodes](SPTR_Edge c){ return std::ranges::contains(selected_nodes, c->start.lock()) || std::ranges::contains(selected_nodes, c->end.lock());}); - - VEC_WPTR_Node adjacent_nodes; - for(auto c : erase_connections){ - if(!c->start.lock()->selected) adjacent_nodes.push_back(c->start); - if(!c->end.lock()->selected) adjacent_nodes.push_back(c->end); - } - - return adjacent_nodes; -} - -void VISEQ::GRAPH::GraphManager::removeNodesAndEdges(VEC_SPTR_Node selected_nodes){ - VEC_WPTR_Node adjacent_nodes = getAdjacentNodes(selected_nodes); - - for(auto n : adjacent_nodes){ - std::erase_if(n.lock()->connections, [n, selected_nodes](VISEQ::GRAPH::EDGES::WPTR_Edge c){ return std::ranges::contains(selected_nodes, c.lock()->start.lock()) || std::ranges::contains(selected_nodes, c.lock()->end.lock());}); - } - - for(auto n : selected_nodes){ - std::erase_if(this->edges, [n](WPTR_Edge c){ return c.lock()->start.lock() == n || c.lock()->end.lock() == n;}); - } - - std::erase_if(this->nodes, [selected_nodes](SPTR_Node n){ return std::ranges::contains(selected_nodes, n);}); -} - -void VISEQ::GRAPH::GraphManager::pasteNodesAndEdges(VEC_SPTR_Node nodes, Vec2Df position){ - - VEC_SPTR_Edge copy_connections; - for(auto c : edges){ - if(std::ranges::contains(nodes, c->start.lock()) && std::ranges::contains(nodes, c->end.lock())) { - copy_connections.push_back(std::make_shared(*c)); - } - } - std::cout << copy_connections.size() << std::endl; - - for(auto old_node : nodes){ - SPTR_Node new_node; - - if(old_node->getNodeType() == NodeType::MIDINODE) { - new_node = copyNode(std::dynamic_pointer_cast(old_node), position + old_node->drag_offset); - } else { - new_node = copyNode(old_node, position + old_node->drag_offset); - } - - new_node->copied = false; - new_node->selected = true; - - //TODO copy connectors - - for(auto c : copy_connections){ - if(c->start.lock() == old_node) { - c->start = new_node; - c->start.lock()->connections.push_back(c); - } - if(c->end.lock() == old_node) { - c->end = new_node; - c->end.lock()->connections.push_back(c); - } - } - } - std::ranges::copy(copy_connections.begin(), copy_connections.end(), std::back_inserter(edges)); -} - -void VISEQ::GRAPH::GraphManager::update(){ - if(!remove_edges.empty()) removeEdges(); - if(!split_edges.empty()) splitEdges(); -} - -void VISEQ::GRAPH::GraphManager::drawEdges(Vec2Df mouse_pos, Camera2D cam){ - for(auto c : edges){ - Vec2Df v1_position = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->connector_offset); - Vec2Df v2_position = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->connector_offset); - float v1_v2_distance = v1_position.dist(&v2_position); - - if(v1_v2_distance < 100) c->switches_offset = v1_v2_distance/2.3f; - Vec2Df from_to_handle = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->switches_offset); - Vec2Df to_from_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->switches_offset); - Vec2Df middle_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.4f; - Vec2Df middle_handle_2 = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.6f; - Vec2Df flip_handle = middle_handle + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*0.2, 0.f, 20.f) : 1.0f); - Vec2Df flip_handle_2 = middle_handle_2 + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*-0.2, -20.f, 0.f) : 1.0f); - // Dynamic opacity by distance to mouse - unsigned char opacity = 150.0f - Clamp(middle_handle.dist(mouse_pos), 0.0f, 150.0f); - unsigned char opacity_from = 250.0f - Clamp(from_to_handle.dist(mouse_pos), 0.0f, 220.0f); - unsigned char opacity_to = 250.0f - Clamp(to_from_handle.dist(mouse_pos), 0.0f, 220.0f); - - float midi_gate_v1 = 0.0f; - float midi_gate_v2 = 0.0f; - - if(c->start.lock()->getNodeType() == NodeType::MIDINODE){ - midi_gate_v1 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast(c->start.lock())->getNotes().gate, 1.f, 4.f); + void GraphManager::setNodeMode(int mode){ + for(auto n: nodes){ + n->setMode(mode); } - if(c->end.lock()->getNodeType() == NodeType::MIDINODE){ - midi_gate_v2 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast(c->end.lock())->getNotes().gate, 1.f, 4.f); - } - // Line with small connector circles - if(c->end.lock()->getNodeType() == NodeType::MIDINODE && c->start.lock()->getNodeType() == NodeType::MIDINODE){ + } - Color col = {229, 181, 103, 255}; - if(c->GActive()) col = {108, 153, 187, 255}; - DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, col); - { - float d = c->start.lock()->position.dist(c->end.lock()->position); - std::string frac = MIDI::Fractions.at(std::clamp(round(d/60) - 1, 0, 31)); - Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",d).c_str(), 10.0f, 2.0f); - Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); - - Vec2Df middle_handle = c->start.lock()->position + (c->end.lock()->position - c->start.lock()->position) * ((d - text_length.x)*0.5f)/d; - Vec2Df middle_handle_frac = c->start.lock()->position + (c->end.lock()->position - c->start.lock()->position) * ((d - text_length_frac.x)*0.5f)/d; - Vec2Df text_position = middle_handle + (c->start.lock()->position - c->end.lock()->position).norm().orth() * 12.0f; - Vec2Df text_position_frac = middle_handle_frac - (c->start.lock()->position - c->end.lock()->position).norm().orth() * 4.0f; + void GraphManager::reset(){ + counter.clear(); + } + WPTR_Node GraphManager::getNode(int i){ + return nodes.at(i); + } - float rotation = Vector2Angle((Vector2){1,0}, (c->end.lock()->position - c->start.lock()->position)) * RAD2DEG; - DrawTextPro(GetFontDefault(), std::format("{:.1f}",d).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - if( (int)d % 60 == 0) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - } - if(midi_gate_v1 == 4.0f) col = VS_COLOR_RED; - DrawCircle(v1_position.x, v1_position.y, midi_gate_v1, col); - - col = {229, 181, 103, 255}; - if(midi_gate_v2 == 4.0f) col = VS_COLOR_RED; - DrawCircle(v2_position.x, v2_position.y, midi_gate_v2, col); + WPTR_Node GraphManager::getFirstNode(){ + return nodes.front(); + } + + WPTR_Node GraphManager::getLastNode(){ + return nodes.back(); + } + + VEC_WPTR_Node GraphManager::getMidiNodes(){ + return nodes | RANGE_FILTER([](NODES::SPTR_Node n){return n->getNodeType() == NodeType::MIDINODE;}) | std::ranges::to(); + } + + NODES::SPTR_Node GraphManager::getRandomNode(){ + VEC_SPTR_GraphItem o; + std::ranges::sample(nodes, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); + return std::dynamic_pointer_cast(o.front()); + } + + EDGES::SPTR_Edge GraphManager::getRandomEdge(){ + VEC_SPTR_GraphItem o; + std::ranges::sample(edges, std::back_inserter(o), 1, std::mt19937{std::random_device{}()}); + return std::dynamic_pointer_cast(o.front()); + } + + SPTR_Node GraphManager::getClosestNode(Vec2Df position){ + auto neighbors = nodes | RANGE_FILTER([](SPTR_Node n){ return !n->picked_up;}) | std::ranges::to(); + std::sort(neighbors.begin(), neighbors.end(), [position](SPTR_Node na, SPTR_Node nb){ return na->position.dist(position) < nb->position.dist(position);}); + return neighbors.front(); + } + VEC_WPTR_Node GraphManager::getNodesByID(int _id){ + return nodes | RANGE_FILTER([_id](SPTR_Node n){return n->id == _id;}) | std::ranges::to(); // TODO: Multiple nodes with the same ID? Why not return only one node? + } + + WPTR_Edge GraphManager::lastEdge(){ + return edges.back(); + } + + WPTR_Node GraphManager::next(WPTR_Node current, WPTR_Node last){ + VEC_WPTR_Edge possible_connections = current.lock()->connections | RANGE_FILTER([current](WPTR_Edge c){ + return (c.lock()->getEdgeType() == EdgeType::MIDIEDGE) && ((c.lock()->start.lock() == current.lock() && c.lock()->pass_forward) || (c.lock()->end.lock() == current.lock() && c.lock()->pass_backward)); + }) | std::ranges::to(); + + if(possible_connections.empty()) return SPTR_Node(nullptr); - if(!c->start.lock()->picked_up && !c->end.lock()->picked_up){ - // On/Off switches on both sides - if(ViButton(mouse_pos, from_to_handle.x, from_to_handle.y, 8.0f, c->pass_forward ? (Color){120, 210, 115, opacity_from} : (Color){232, 50, 32, opacity_from} )) c->pass_forward = !c->pass_forward; - if(ViButton(mouse_pos, to_from_handle.x, to_from_handle.y, 8.0f, c->pass_backward ? (Color){120, 210, 115, opacity_to} : (Color){232, 50, 32, opacity_to} )) c->pass_backward = !c->pass_backward; - - if(!c->pass_forward){ - Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + from_to_handle; - Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + from_to_handle; - DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_from}); + int return_path = 0; + float angle = -1; + if(current.lock()->distributionMode == Distribution::GEOMETRIC){ + Vec2Df coming_from = (current.lock()->position - last.lock()->position).norm(); + int i = 0; + for(auto n : possible_connections){ + WPTR_Node next_node = n.lock()->getNextNode(current); + float angle_v1_v2 = coming_from.norm().dot((next_node.lock()->position - current.lock()->position).norm()); + if(angle_v1_v2 > angle) { + return_path = i; + angle = angle_v1_v2; } - - if(!c->pass_backward){ - Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + to_from_handle; - Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + to_from_handle; - DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_to}); - } - - if(ViButton(mouse_pos, flip_handle_2.x, flip_handle_2.y, 8.0f, (Color){232, 125, 62, opacity} )) splitEdge(c); + ++i; } } - if(c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE){ - DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, VS_COLOR_BLUE); + + if(current.lock()->distributionMode == Distribution::ROUNDROBIN){ + if(counter.find(current.lock()) == counter.end()) { + counter[current.lock()] = 0; + } else { + counter[current.lock()] = (counter[current.lock()] + 1) % possible_connections.size(); + return_path = counter[current.lock()]; + } + } + + if(current.lock()->distributionMode == Distribution::RANDOM){ + return_path = rand() % possible_connections.size(); + } + + return possible_connections.at(return_path).lock()->getNextNode(current); + } + + void GraphManager::splitEdge(SPTR_Edge edge){ + split_edges.push_back(edge); + } + + void GraphManager::splitEdges(){ + for(auto s : split_edges){ + Vec2Df center = s.lock()->end.lock()->position.center(s.lock()->start.lock()->position); + SPTR_Node cn = copyNode(std::dynamic_pointer_cast(s.lock()->start.lock()), center); // TODO: copy connectors + addConnection(s.lock()->start.lock(), cn); + addConnection(cn, s.lock()->end.lock()); + remove_edges.push_back(s); + } + split_edges.clear(); + } + + void GraphManager::removeEdge(SPTR_Edge edge){ + remove_edges.push_back(edge); + } + + void GraphManager::removeEdges(){ + for(auto edge : remove_edges){ + edge.lock()->start.lock()->removeConnector(edge.lock()->start_conn); + edge.lock()->end.lock()->removeConnector(edge.lock()->end_conn); + std::erase(edge.lock()->start.lock()->connections, edge.lock()); + std::erase(edge.lock()->end.lock()->connections, edge.lock()); + std::erase(edges, edge.lock()); + } + remove_edges.clear(); + } + + VEC_WPTR_Node GraphManager::getAdjacentNodes(VEC_SPTR_Node selected_nodes){ + for(auto n : nodes){ + n->highlighted = false; } - if(ViButton(mouse_pos, flip_handle.x, flip_handle.y, 8.0f, (Color){232, 50, 32, opacity} )) removeEdge(c); + auto erase_connections = edges | RANGE_FILTER([selected_nodes](SPTR_Edge c){ return std::ranges::contains(selected_nodes, c->start.lock()) || std::ranges::contains(selected_nodes, c->end.lock());}); + + VEC_WPTR_Node adjacent_nodes; + for(auto c : erase_connections){ + if(!c->start.lock()->selected) adjacent_nodes.push_back(c->start); + if(!c->end.lock()->selected) adjacent_nodes.push_back(c->end); + } + + return adjacent_nodes; } -} -void VISEQ::GRAPH::GraphManager::drawNeighborLines(SPTR_Node node, Vec2Df mouse_pos, Camera2D camera){ - float min_d = 480; - auto neighbors_local = nodes | RANGE_FILTER([mouse_pos](SPTR_Node n){ return !n->picked_up && !n->selected && n->position.dist(mouse_pos) < 960;}) | std::ranges::to(); - std::sort(neighbors_local.begin(), neighbors_local.end(), [node, mouse_pos](SPTR_Node na, SPTR_Node nb){ return na->position.dist(mouse_pos) < nb->position.dist(mouse_pos);}); - if(!neighbors_local.empty()){ - min_d = neighbors_local.front()->position.dist(mouse_pos); - DrawCircleLinesV(neighbors_local.front()->position, 22.0f, VS_COLOR_PINK); - node->neighbors = neighbors_local | std::ranges::views::filter([mouse_pos, min_d](SPTR_Node n){return abs(n->position.dist(mouse_pos) - min_d) < 5;}) | std::ranges::to(); - for(auto nb : node->neighbors){ - DrawCircleLinesV(nb->position, 20.0f, VS_COLOR_RED); - float d = nb->position.dist(mouse_pos); - float dn = nb->position.dist(node->position); - if(d > 0 && d < min_d*1.5 && d < 960) { - std::string frac = MIDI::Fractions.at(std::clamp(round(dn/60) - 1, 0, 31)); - Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",dn).c_str(), 10.0f, 2.0f); - Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); + void GraphManager::removeNodesAndEdges(VEC_SPTR_Node selected_nodes){ + VEC_WPTR_Node adjacent_nodes = getAdjacentNodes(selected_nodes); + + for(auto n : adjacent_nodes){ + std::erase_if(n.lock()->connections, [n, selected_nodes](EDGES::WPTR_Edge c){ return std::ranges::contains(selected_nodes, c.lock()->start.lock()) || std::ranges::contains(selected_nodes, c.lock()->end.lock());}); + } + + for(auto n : selected_nodes){ + std::erase_if(this->edges, [n](WPTR_Edge c){ return c.lock()->start.lock() == n || c.lock()->end.lock() == n;}); + } + + std::erase_if(this->nodes, [selected_nodes](SPTR_Node n){ return std::ranges::contains(selected_nodes, n);}); + } + + void GraphManager::pasteNodesAndEdges(VEC_SPTR_Node nodes, Vec2Df position){ + + VEC_SPTR_Edge copy_connections; + for(auto c : edges){ + if(std::ranges::contains(nodes, c->start.lock()) && std::ranges::contains(nodes, c->end.lock())) { + copy_connections.push_back(std::make_shared(*c)); + } + } + std::cout << copy_connections.size() << std::endl; + + for(auto old_node : nodes){ + SPTR_Node new_node; + + if(old_node->getNodeType() == NodeType::MIDINODE) { + new_node = copyNode(std::dynamic_pointer_cast(old_node), position + old_node->drag_offset); + } else { + new_node = copyNode(old_node, position + old_node->drag_offset); + } + + new_node->copied = false; + new_node->selected = true; + + //TODO copy connectors + + for(auto c : copy_connections){ + if(c->start.lock() == old_node) { + c->start = new_node; + c->start.lock()->connections.push_back(c); + } + if(c->end.lock() == old_node) { + c->end = new_node; + c->end.lock()->connections.push_back(c); + } + } + } + std::ranges::copy(copy_connections.begin(), copy_connections.end(), std::back_inserter(edges)); + } + + void GraphManager::update(){ + if(!remove_edges.empty()) removeEdges(); + if(!split_edges.empty()) splitEdges(); + } + + void GraphManager::drawEdges(Vec2Df mouse_pos, Camera2D cam){ + for(auto c : edges){ + Vec2Df v1_position = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->connector_offset); + Vec2Df v2_position = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->connector_offset); + float v1_v2_distance = v1_position.dist(&v2_position); + + if(v1_v2_distance < 100) c->switches_offset = v1_v2_distance/2.3f; + Vec2Df from_to_handle = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->switches_offset); + Vec2Df to_from_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->switches_offset); + Vec2Df middle_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.4f; + Vec2Df middle_handle_2 = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.6f; + Vec2Df flip_handle = middle_handle + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*0.2, 0.f, 20.f) : 1.0f); + Vec2Df flip_handle_2 = middle_handle_2 + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*-0.2, -20.f, 0.f) : 1.0f); - Vec2Df middle_handle = nb->position + (node->position - nb->position) * ((dn - text_length.x)*0.5f)/dn; - Vec2Df middle_handle_frac = nb->position + (node->position - nb->position) * ((dn - text_length_frac.x)*0.5f)/dn; - Vec2Df text_position = middle_handle + (nb->position - node->position).norm().orth() * 12.0f; - Vec2Df text_position_frac = middle_handle_frac - (nb->position - node->position).norm().orth() * 4.0f; + // Dynamic opacity by distance to mouse + unsigned char opacity = 150.0f - Clamp(middle_handle.dist(mouse_pos), 0.0f, 150.0f); + unsigned char opacity_from = 250.0f - Clamp(from_to_handle.dist(mouse_pos), 0.0f, 220.0f); + unsigned char opacity_to = 250.0f - Clamp(to_from_handle.dist(mouse_pos), 0.0f, 220.0f); - float rotation = Vector2Angle((Vector2){1,0}, (node->position - nb->position)) * RAD2DEG; - DrawTextPro(GetFontDefault(), std::format("{:.1f}",dn).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - if(IsKeyDown(KEY_LEFT_SHIFT)) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); - Vec2Df n_position = node->position - ((node->position - nb->position)).norm() * 20.0f; - Vec2Df nb_position = nb->position - ((nb->position - node->position)).norm() * 20.0f; - DrawLineV(n_position, nb_position, VS_COLOR_PINK); + float midi_gate_v1 = 0.0f; + float midi_gate_v2 = 0.0f; + + if(c->start.lock()->getNodeType() == NodeType::MIDINODE){ + midi_gate_v1 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast(c->start.lock())->getNotes().gate, 1.f, 4.f); + } + if(c->end.lock()->getNodeType() == NodeType::MIDINODE){ + midi_gate_v2 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast(c->end.lock())->getNotes().gate, 1.f, 4.f); + } + // Line with small connector circles + if(c->end.lock()->getNodeType() == NodeType::MIDINODE && c->start.lock()->getNodeType() == NodeType::MIDINODE){ + + Color col = {229, 181, 103, 255}; + if(c->GActive()) col = {108, 153, 187, 255}; + DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, col); + { + float d = c->start.lock()->position.dist(c->end.lock()->position); + std::string frac = MIDI::Fractions.at(std::clamp(round(d/60) - 1, 0, 31)); + Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",d).c_str(), 10.0f, 2.0f); + Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); + + Vec2Df middle_handle = c->start.lock()->position + (c->end.lock()->position - c->start.lock()->position) * ((d - text_length.x)*0.5f)/d; + Vec2Df middle_handle_frac = c->start.lock()->position + (c->end.lock()->position - c->start.lock()->position) * ((d - text_length_frac.x)*0.5f)/d; + Vec2Df text_position = middle_handle + (c->start.lock()->position - c->end.lock()->position).norm().orth() * 12.0f; + Vec2Df text_position_frac = middle_handle_frac - (c->start.lock()->position - c->end.lock()->position).norm().orth() * 4.0f; + + float rotation = Vector2Angle((Vector2){1,0}, (c->end.lock()->position - c->start.lock()->position)) * RAD2DEG; + DrawTextPro(GetFontDefault(), std::format("{:.1f}",d).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + if( (int)d % 60 == 0) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + } + if(midi_gate_v1 == 4.0f) col = VS_COLOR_RED; + DrawCircle(v1_position.x, v1_position.y, midi_gate_v1, col); + + col = {229, 181, 103, 255}; + if(midi_gate_v2 == 4.0f) col = VS_COLOR_RED; + DrawCircle(v2_position.x, v2_position.y, midi_gate_v2, col); + + if(!c->start.lock()->picked_up && !c->end.lock()->picked_up){ + // On/Off switches on both sides + if(ViButton(mouse_pos, from_to_handle.x, from_to_handle.y, 8.0f, c->pass_forward ? (Color){120, 210, 115, opacity_from} : (Color){232, 50, 32, opacity_from} )) c->pass_forward = !c->pass_forward; + if(ViButton(mouse_pos, to_from_handle.x, to_from_handle.y, 8.0f, c->pass_backward ? (Color){120, 210, 115, opacity_to} : (Color){232, 50, 32, opacity_to} )) c->pass_backward = !c->pass_backward; + + if(!c->pass_forward){ + Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + from_to_handle; + Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + from_to_handle; + DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_from}); + } + + if(!c->pass_backward){ + Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + to_from_handle; + Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + to_from_handle; + DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_to}); + } + + if(ViButton(mouse_pos, flip_handle_2.x, flip_handle_2.y, 8.0f, (Color){232, 125, 62, opacity} )) splitEdge(c); + } + } + if(c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE){ + DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, VS_COLOR_BLUE); + } + + if(ViButton(mouse_pos, flip_handle.x, flip_handle.y, 8.0f, (Color){232, 50, 32, opacity} )) removeEdge(c); + } + } + + void GraphManager::drawNeighborLines(SPTR_Node node, Vec2Df mouse_pos, Camera2D camera){ + float min_d = 480; + auto neighbors_local = nodes | RANGE_FILTER([mouse_pos](SPTR_Node n){ return !n->picked_up && !n->selected && n->position.dist(mouse_pos) < 960;}) | std::ranges::to(); + std::sort(neighbors_local.begin(), neighbors_local.end(), [node, mouse_pos](SPTR_Node na, SPTR_Node nb){ return na->position.dist(mouse_pos) < nb->position.dist(mouse_pos);}); + if(!neighbors_local.empty()){ + min_d = neighbors_local.front()->position.dist(mouse_pos); + DrawCircleLinesV(neighbors_local.front()->position, 22.0f, VS_COLOR_PINK); + node->neighbors = neighbors_local | std::ranges::views::filter([mouse_pos, min_d](SPTR_Node n){return abs(n->position.dist(mouse_pos) - min_d) < 5;}) | std::ranges::to(); + for(auto nb : node->neighbors){ + DrawCircleLinesV(nb->position, 20.0f, VS_COLOR_RED); + float d = nb->position.dist(mouse_pos); + float dn = nb->position.dist(node->position); + if(d > 0 && d < min_d*1.5 && d < 960) { + std::string frac = MIDI::Fractions.at(std::clamp(round(dn/60) - 1, 0, 31)); + Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",dn).c_str(), 10.0f, 2.0f); + Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f); + + Vec2Df middle_handle = nb->position + (node->position - nb->position) * ((dn - text_length.x)*0.5f)/dn; + Vec2Df middle_handle_frac = nb->position + (node->position - nb->position) * ((dn - text_length_frac.x)*0.5f)/dn; + Vec2Df text_position = middle_handle + (nb->position - node->position).norm().orth() * 12.0f; + Vec2Df text_position_frac = middle_handle_frac - (nb->position - node->position).norm().orth() * 4.0f; + + float rotation = Vector2Angle((Vector2){1,0}, (node->position - nb->position)) * RAD2DEG; + DrawTextPro(GetFontDefault(), std::format("{:.1f}",dn).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + if(IsKeyDown(KEY_LEFT_SHIFT)) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK); + Vec2Df n_position = node->position - ((node->position - nb->position)).norm() * 20.0f; + Vec2Df nb_position = nb->position - ((nb->position - node->position)).norm() * 20.0f; + DrawLineV(n_position, nb_position, VS_COLOR_PINK); + } } } } -} -void VISEQ::GRAPH::GraphManager::drawNodes(Vec2Df mouse_pos, Camera2D camera){ - for(auto n : nodes){ - if(KEYS_PRESSED.ctrl && n->hovering()) state->selection_origin = n->position; - } - BASE::Interactable::z_hovering = -1; - for(auto n : nodes){ - n->update(); - if(n->picked_up) drawNeighborLines(n, mouse_pos, camera); - n->draw(mouse_pos, camera); + void GraphManager::drawNodes(Vec2Df mouse_pos, Camera2D camera){ + for(auto n : nodes){ + if(KEYS_PRESSED.ctrl && n->hovering()) state->selection_origin = n->position; + } + BASE::Interactable::z_hovering = -1; + for(auto n : nodes){ + n->update(); + if(n->picked_up) drawNeighborLines(n, mouse_pos, camera); + n->draw(mouse_pos, camera); + } } } - diff --git a/src/graph-manager.h b/src/graph-manager.h index ddfa50b..a34dbb5 100644 --- a/src/graph-manager.h +++ b/src/graph-manager.h @@ -1,33 +1,19 @@ #pragma once - -#include "config-node.h" -#include "datatypes.h" #include "state.h" #include "vmath.h" #include "raylib.h" -#include "raymath.h" #include "nlohmann/json.hpp" -#include "button.h" #include "midi-node.h" #include "edge.h" #include "node.h" -#include -#include -#include -#include #include #include -#include -#include -#include #include -#include "state-io.h" - using json = nlohmann::json; using namespace VISEQ::GRAPH::NODES; diff --git a/src/interaction-manager.cpp b/src/interaction-manager.cpp new file mode 100644 index 0000000..1c9b6c8 --- /dev/null +++ b/src/interaction-manager.cpp @@ -0,0 +1,255 @@ +#include "interaction-manager.h" + +#include "clipboard.h" +#include "connector.h" +#include "datatypes.h" +#include "interaction-event.h" +#include "node.h" +#include "raylib.h" +#include "state.h" +#include +#include + +namespace VISEQ::INTERACTION{ + void InteractionManager::update(){ + if(KEYS_DOWN.ctrl && IsKeyPressed(KEY_C)){ + VEC_SPTR_Node selected_nodes = gm->getCopiesOfSelectedNodes(); + if(!selected_nodes.empty()) cb.copyNodes(selected_nodes); + } + + if(KEYS_DOWN.ctrl && IsKeyPressed(KEY_V)){ + VEC_SPTR_Node paste_nodes = cb.pasteNodes(); + if(!paste_nodes.empty()) gm->pasteNodesAndEdges(paste_nodes, MOD_MOUSE); + } + + if(IsKeyPressed(KEY_DELETE)){ + removeNodes(gm->getSelection()); + } + + if(!KEYS_DOWN.shift && IsKeyPressed(KEY_TAB)) { + state->node_mode = (state->node_mode + 1) % 7; + } + + if(KEYS_DOWN.shift && IsKeyPressed(KEY_TAB)) { + state->node_mode -= state->node_mode > 0 ? 1 : -6; + } + + } + + void InteractionManager::draw(){ + doInteraction(); + scaleSelection(); + if(!gm->getSelection().empty()){ + DrawRectangleRoundedLines(state->bounding_box, 0.1f, 3, VS_COLOR_LIGHTGREY); + scale_handle->draw(); + } + } + + void InteractionManager::removeNodes(VEC_SPTR_Node nodes){ + + VEC_WPTR_Node adjacent_nodes = gm->getAdjacentNodes(nodes); + + sm->rerouteSignal(nodes, adjacent_nodes); + sm->rebaseSignal(nodes); + + gm->removeNodesAndEdges(nodes); + } + + void InteractionManager::scaleSelection(){ + if(!gm->getSelection().empty()){ + float offset = 30; + Vec2Df corner_tl = {50000, 50000}; + Vec2Df corner_br = {-50000, -50000}; + std::shared_ptr node_tl; + std::shared_ptr node_br; + + for(auto n : gm->getSelection()){ + float x = n->position.x; + float y = n->position.y; + + if(x < corner_tl.x && y < corner_tl.y) node_tl = n; + if(x > corner_br.x && y > corner_br.y) node_br = n; + + if(x <= corner_tl.x) corner_tl.x = x; + if(x >= corner_br.x) corner_br.x = x; + if(y <= corner_tl.y) corner_tl.y = y; + if(y >= corner_br.y) corner_br.y = y; + } + corner_tl -= Vec2Df(offset, offset); + corner_br += Vec2Df(offset, offset); + state->bounding_box = (Rectangle){corner_tl.x, corner_tl.y, corner_br.x - corner_tl.x, corner_br.y - corner_tl.y}; + + scale_handle->update(); + + if(scale_handle->clicked()){ + scale_start = (corner_br - corner_tl); + for(auto n : gm->getSelection()){ + n->drag_offset = n->position - node_tl->position; + } + } + if(scale_handle->picked_up){ + Vec2Df scale = (scale_handle->position - corner_tl) / scale_start; + if(IsKeyDown(KEY_LEFT_SHIFT)) scale.y = scale.x; + DrawText(std::format("{:.2f} {:.2f}", scale.x, scale.y).c_str(), corner_br.x+5, corner_br.y+5, 10, RED); + for(auto n : gm->getSelection()){ + n->position = node_tl->position + (n->drag_offset * scale); + } + } else { + scale_handle->position = corner_br; + } + } + } + + void InteractionManager::snapPolar(std::shared_ptr dragNode){ + float steps = round((dragNode->neighbors.front()->position.dist(SCREEN_MOUSE)) / state->settings.grid) * state->settings.grid; + if(dragNode->neighbors.size() > 1){ + Vec2Df dn1 = dragNode->neighbors[0]->position; + Vec2Df dn2 = dragNode->neighbors[1]->position; + Vec2Df v1_orth = dn1.center(dn2); + + if(dn1.x < dn2.x) std::swap(dn1,dn2); + + float dist = dn1.dist(dn2); + float dist_orth = std::sqrt(std::pow(steps,2) - std::pow(dist/2,2)); + + if(SCREEN_MOUSE.y < v1_orth.y) dist_orth *= -1; + + Vec2Df v1_orth_t = v1_orth + (dn1-dn2).norm().orth() * dist_orth; + DrawLineV(dn1, dn2, VS_COLOR_RED); + DrawLineV(v1_orth, v1_orth_t, VS_COLOR_RED); + DrawCircleV(v1_orth, 3.0f, VS_COLOR_RED); + dragNode->position = v1_orth_t; + } else if(!dragNode->neighbors.empty()){ + float pol_r = steps; + float pol_theta = Vector2Angle(Vec2Df{1,0},(SCREEN_MOUSE - dragNode->neighbors.front()->position)); + Vec2Df polar = VISEQ::VMATH::polarToCartesian(pol_r, pol_theta); + dragNode->position = dragNode->neighbors.front()->position + polar; + } + } + + void InteractionManager::doInteraction(){ + bool exit = false; + bool once = false; + bool connect_once = false; + + state->mouse_z = canvas->z_index; + + for(auto e : APP::event_queue){ + if(!connect_once && e.action == APP::Interaction::CONNECT){ + APP::InteractionEvent partner; + bool found = false; + for(auto ei : APP::event_queue){ + if(ei.action == APP::Interaction::HOVER && ei.item->getType() == GraphItemType::CONNECTOR) { + std::cout << "YEAH" << std::endl; + partner = ei; + found = true; + break; + } + } + + auto connector = std::dynamic_pointer_cast(e.item); + if(found && partner != e){ + auto connector_to = std::dynamic_pointer_cast(partner.item); + connector->partner = connector_to; + connector_to->partner = connector; + gm->addConnection(std::dynamic_pointer_cast(connector->parent.lock()), std::dynamic_pointer_cast(connector_to->parent.lock())); + } else if (partner != e){ + if(KEYS_DOWN.alt){ + gm->createConfigNode(SCREEN_MOUSE); + } else { + gm->createMidiNode(SCREEN_MOUSE); + } + gm->addConnection(std::dynamic_pointer_cast(connector->parent.lock()), gm->getLastNode()); + } + connect_once = true; + break; + } + } + + while(APP::event_queue.size() > 0 && !exit){ + auto e = APP::event_queue.front(); + if(e.action == APP::Interaction::RIGHTCLICK){ + gm->addToSelection(); + state->context_menu = !state->context_menu; + } + if(state->context_menu) break; + + if(e.action == APP::Interaction::HOVER){ + state->mouse_z = e.item->z_index; + } + + if(e.item->getType() == GraphItemType::CANVAS) { + if(e.action == APP::Interaction::SELECT) { + gm->select(); + canvas->clearSelectionRect(); + } + if(e.action == APP::Interaction::DESELECT) { + gm->deselect(); + } + } + + if(!once && e.item->getType() == GraphItemType::NODE){ + auto node = std::dynamic_pointer_cast(e.item); + if(e.action == APP::Interaction::PICKUP) { + node->pickUp(); + if(node->selected){ + for(auto s : gm->getSelection()){ + s->pickUp(node->position); + } + } + once = true; + } + if(e.action == APP::Interaction::DRAG) { + if(KEYS_DOWN.ctrl) node->drag(MOD_MOUSE); + else if(KEYS_DOWN.shift) { + snapPolar(node); + } + else node->drag(MOD_MOUSE + node->drag_offset); + + if(node->selected){ + for(auto s : gm->getSelection()){ + s->drag(MOD_MOUSE + s->drag_offset); + } + } + once = true; + } + if(e.action == APP::Interaction::PUTDOWN) { + node->putDown(); + if(node->selected){ + for(auto s : gm->getSelection()){ + s->putDown(); + } + } + } + } + if(exit) break; + + if(!once && (e.item->getType() == GraphItemType::CONNECTOR || e.item->getType() == GraphItemType::CANVAS || e.item->getType() == GraphItemType::HANDLE)) { + switch(e.action){ + case VISEQ::APP::Interaction::PICKUP: + { + e.item->pickUp(); + once = true; + break; + } + case VISEQ::APP::Interaction::DRAG: + { + e.item->drag(MOD_MOUSE); + once = true; + break; + } + case VISEQ::APP::Interaction::PUTDOWN: + { + e.item->putDown(); + break; + } + default: + {} + } + } + + APP::event_queue.pop_front(); + } + APP::event_queue.clear(); + } +} diff --git a/src/interaction-manager.h b/src/interaction-manager.h index 3838993..a1597d8 100644 --- a/src/interaction-manager.h +++ b/src/interaction-manager.h @@ -1,7 +1,6 @@ #pragma once #include "clipboard.h" -#include "connector.h" #include "datatypes.h" #include "draggable.h" #include "graph-item.h" @@ -12,7 +11,6 @@ #include "signal-manager.h" #include "state.h" #include "canvas.h" -#include #include namespace VISEQ::INTERACTION{ @@ -65,247 +63,11 @@ namespace VISEQ::INTERACTION{ Vec2Df scale_start; std::shared_ptr scale_handle = std::make_shared(); - void update(){ - if(KEYS_DOWN.ctrl && IsKeyPressed(KEY_C)){ - VEC_SPTR_Node selected_nodes = gm->getCopiesOfSelectedNodes(); - if(!selected_nodes.empty()) cb.copyNodes(selected_nodes); - } - - if(KEYS_DOWN.ctrl && IsKeyPressed(KEY_V)){ - VEC_SPTR_Node paste_nodes = cb.pasteNodes(); - if(!paste_nodes.empty()) gm->pasteNodesAndEdges(paste_nodes, MOD_MOUSE); - } - - if(IsKeyPressed(KEY_DELETE)){ - removeNodes(gm->getSelection()); - } - - if(!KEYS_DOWN.shift && IsKeyPressed(KEY_TAB)) { - state->node_mode = (state->node_mode + 1) % 7; - } - - if(KEYS_DOWN.shift && IsKeyPressed(KEY_TAB)) { - state->node_mode -= state->node_mode > 0 ? 1 : -6; - } - - } - - void draw(){ - doInteraction(); - scaleSelection(); - if(!gm->getSelection().empty()){ - DrawRectangleRoundedLines(state->bounding_box, 0.1f, 3, VS_COLOR_LIGHTGREY); - scale_handle->draw(); - } - } - - void removeNodes(VEC_SPTR_Node nodes){ - - VEC_WPTR_Node adjacent_nodes = gm->getAdjacentNodes(nodes); - - sm->rerouteSignal(nodes, adjacent_nodes); - sm->rebaseSignal(nodes); - - gm->removeNodesAndEdges(nodes); - } - - void scaleSelection(){ - if(!gm->getSelection().empty()){ - float offset = 30; - Vec2Df corner_tl = {50000, 50000}; - Vec2Df corner_br = {-50000, -50000}; - std::shared_ptr node_tl; - std::shared_ptr node_br; - - for(auto n : gm->getSelection()){ - float x = n->position.x; - float y = n->position.y; - - if(x < corner_tl.x && y < corner_tl.y) node_tl = n; - if(x > corner_br.x && y > corner_br.y) node_br = n; - - if(x <= corner_tl.x) corner_tl.x = x; - if(x >= corner_br.x) corner_br.x = x; - if(y <= corner_tl.y) corner_tl.y = y; - if(y >= corner_br.y) corner_br.y = y; - } - corner_tl -= Vec2Df(offset, offset); - corner_br += Vec2Df(offset, offset); - state->bounding_box = (Rectangle){corner_tl.x, corner_tl.y, corner_br.x - corner_tl.x, corner_br.y - corner_tl.y}; - - scale_handle->update(); - - if(scale_handle->clicked()){ - scale_start = (corner_br - corner_tl); - for(auto n : gm->getSelection()){ - n->drag_offset = n->position - node_tl->position; - } - } - if(scale_handle->picked_up){ - Vec2Df scale = (scale_handle->position - corner_tl) / scale_start; - if(IsKeyDown(KEY_LEFT_SHIFT)) scale.y = scale.x; - DrawText(std::format("{:.2f} {:.2f}", scale.x, scale.y).c_str(), corner_br.x+5, corner_br.y+5, 10, RED); - for(auto n : gm->getSelection()){ - n->position = node_tl->position + (n->drag_offset * scale); - } - } else { - scale_handle->position = corner_br; - } - } - } - - void snapPolar(std::shared_ptr dragNode){ - float steps = round((dragNode->neighbors.front()->position.dist(SCREEN_MOUSE)) / state->settings.grid) * state->settings.grid; - if(dragNode->neighbors.size() > 1){ - Vec2Df dn1 = dragNode->neighbors[0]->position; - Vec2Df dn2 = dragNode->neighbors[1]->position; - Vec2Df v1_orth = dn1.center(dn2); - - if(dn1.x < dn2.x) std::swap(dn1,dn2); - - float dist = dn1.dist(dn2); - float dist_orth = std::sqrt(std::pow(steps,2) - std::pow(dist/2,2)); - - if(SCREEN_MOUSE.y < v1_orth.y) dist_orth *= -1; - - Vec2Df v1_orth_t = v1_orth + (dn1-dn2).norm().orth() * dist_orth; - DrawLineV(dn1, dn2, VS_COLOR_RED); - DrawLineV(v1_orth, v1_orth_t, VS_COLOR_RED); - DrawCircleV(v1_orth, 3.0f, VS_COLOR_RED); - dragNode->position = v1_orth_t; - } else if(!dragNode->neighbors.empty()){ - float pol_r = steps; - float pol_theta = Vector2Angle(Vec2Df{1,0},(SCREEN_MOUSE - dragNode->neighbors.front()->position)); - Vec2Df polar = VISEQ::VMATH::polarToCartesian(pol_r, pol_theta); - dragNode->position = dragNode->neighbors.front()->position + polar; - } - } - - void doInteraction(){ - bool exit = false; - bool once = false; - bool connect_once = false; - - state->mouse_z = canvas->z_index; - - for(auto e : APP::event_queue){ - if(!connect_once && e.action == APP::Interaction::CONNECT){ - APP::InteractionEvent partner; - bool found = false; - for(auto ei : APP::event_queue){ - if(ei.action == APP::Interaction::HOVER && ei.item->getType() == GraphItemType::CONNECTOR) { - std::cout << "YEAH" << std::endl; - partner = ei; - found = true; - break; - } - } - - auto connector = std::dynamic_pointer_cast(e.item); - if(found && partner != e){ - auto connector_to = std::dynamic_pointer_cast(partner.item); - connector->partner = connector_to; - connector_to->partner = connector; - gm->addConnection(std::dynamic_pointer_cast(connector->parent.lock()), std::dynamic_pointer_cast(connector_to->parent.lock())); - } else if (partner != e){ - if(KEYS_DOWN.alt){ - gm->createConfigNode(SCREEN_MOUSE); - } else { - gm->createMidiNode(SCREEN_MOUSE); - } - gm->addConnection(std::dynamic_pointer_cast(connector->parent.lock()), gm->getLastNode()); - } - connect_once = true; - break; - } - } - - while(APP::event_queue.size() > 0 && !exit){ - auto e = APP::event_queue.front(); - if(e.action == APP::Interaction::RIGHTCLICK){ - gm->addToSelection(); - state->context_menu = !state->context_menu; - } - if(state->context_menu) break; - - if(e.action == APP::Interaction::HOVER){ - state->mouse_z = e.item->z_index; - } - - if(e.item->getType() == GraphItemType::CANVAS) { - if(e.action == APP::Interaction::SELECT) { - gm->select(); - canvas->clearSelectionRect(); - } - if(e.action == APP::Interaction::DESELECT) { - gm->deselect(); - } - } - - if(!once && e.item->getType() == GraphItemType::NODE){ - auto node = std::dynamic_pointer_cast(e.item); - if(e.action == APP::Interaction::PICKUP) { - node->pickUp(); - if(node->selected){ - for(auto s : gm->getSelection()){ - s->pickUp(node->position); - } - } - once = true; - } - if(e.action == APP::Interaction::DRAG) { - if(KEYS_DOWN.ctrl) node->drag(MOD_MOUSE); - else if(KEYS_DOWN.shift) { - snapPolar(node); - } - else node->drag(MOD_MOUSE + node->drag_offset); - - if(node->selected){ - for(auto s : gm->getSelection()){ - s->drag(MOD_MOUSE + s->drag_offset); - } - } - once = true; - } - if(e.action == APP::Interaction::PUTDOWN) { - node->putDown(); - if(node->selected){ - for(auto s : gm->getSelection()){ - s->putDown(); - } - } - } - } - if(exit) break; - - if(!once && (e.item->getType() == GraphItemType::CONNECTOR || e.item->getType() == GraphItemType::CANVAS || e.item->getType() == GraphItemType::HANDLE)) { - switch(e.action){ - case VISEQ::APP::Interaction::PICKUP: - { - e.item->pickUp(); - once = true; - break; - } - case VISEQ::APP::Interaction::DRAG: - { - e.item->drag(MOD_MOUSE); - once = true; - break; - } - case VISEQ::APP::Interaction::PUTDOWN: - { - e.item->putDown(); - break; - } - default: - {} - } - } - - APP::event_queue.pop_front(); - } - APP::event_queue.clear(); - } - + void update(); + void draw(); + void removeNodes(VEC_SPTR_Node nodes); + void scaleSelection(); + void snapPolar(std::shared_ptr dragNode); + void doInteraction(); }; } diff --git a/src/midi-node.cpp b/src/midi-node.cpp new file mode 100644 index 0000000..960b65f --- /dev/null +++ b/src/midi-node.cpp @@ -0,0 +1,391 @@ +#include "midi-node.h" + +#include "config-node.h" +#include "datatypes.h" +#include "edge.h" +#include "node-handle.h" +#include "node.h" +#include "raylib.h" +#include "state.h" +#include +#include +#include + +namespace VISEQ::GRAPH::NODES { + + void MidiNode::init(){ + float orbit_1 = radius + 25.0f; + float orbit_2 = orbit_1 + 20.0f; + float orbit_3 = orbit_2 + 20.0f; + float orbit_4 = orbit_3 + 20.0f; + float orbit_5 = orbit_4 + 30.0f; + + midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.note, position, VS_COLOR_GREEN, "NOTE", orbit_5, 12)); + midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 16, 1, 16, 40, 150)); + midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.gate, position, VS_COLOR_RED, "GATE", orbit_3, 30, 10, 30000, 40, 300)); + midiHandles[NOTE].back()->logScale = true; + midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.velocity, position, VS_COLOR_PURPLE, "VELOCITY", orbit_4, 32)); + + midiHandles[CC].emplace_back(std::make_shared>(midiCC.control, position, VS_COLOR_SUN, "CONTROL", orbit_3, 32)); + midiHandles[CC].emplace_back(std::make_shared>(midiCC.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); + midiHandles[CC].emplace_back(std::make_shared>(midiCC.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32)); + + midiHandles[PC].emplace_back(std::make_shared>(midiPC.program, position, VS_COLOR_SUN, "PROGRAM", orbit_3, 32)); + midiHandles[PC].emplace_back(std::make_shared>(midiPC.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); + midiHandles[PC].emplace_back(std::make_shared>(midiPC.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32)); + + midiHandles[PB].emplace_back(std::make_shared>(midiPitchBend.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); + midiHandles[PB].emplace_back(std::make_shared>(midiPitchBend.value, position, VS_COLOR_GREEN, "VALUE", orbit_3, 256, -8192, 8192, 40, 300)); + + midiHandles[AT].emplace_back(std::make_shared>(midiAftertouch.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); + midiHandles[AT].emplace_back(std::make_shared>(midiAftertouch.value, position, VS_COLOR_GREEN, "VALUE", orbit_3, 32)); + + midiHandles[PP].emplace_back(std::make_shared>(midiPolyPressure.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); + midiHandles[PP].emplace_back(std::make_shared>(midiPolyPressure.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32)); + midiHandles[PP].emplace_back(std::make_shared>(midiPolyPressure.note, position, VS_COLOR_SUN, "NOTE", orbit_5, 12)); + } + + std::shared_ptr MidiNode::getPtr(){ + return std::static_pointer_cast(shared_from_this()); + } + + NodeType MidiNode::getNodeType() const { + return nodeType; + } + + void MidiNode::setMidiPort(int midi_port, bool _state){ + midi_outs[midi_port].enabled = _state; + } + + void MidiNode::trigger() { + triggered = true; + start = GetTime(); + fade = getNotes().gate; + } + + void MidiNode::setMode(int _mode) { + mode = _mode; + } + + MIDI::MidiNote MidiNode::getNotes() const { + MIDI::MidiNote cNote; + for(auto cn : getConnectedConfigNodes()){ + cNote += cn.lock()->getNotes(); + } + return midiNote + cNote; + } + + MIDI::MidiCC MidiNode::getCC() const { + MIDI::MidiCC cCC; + for(auto cn : getConnectedConfigNodes()){ + cCC += cn.lock()->getCC(); + } + return midiCC + cCC; + } + + MIDI::MidiPC MidiNode::getPC() const { + MIDI::MidiPC cPC; + for(auto cn : getConnectedConfigNodes()){ + cPC += cn.lock()->getPC(); + } + return midiPC + cPC; + } + + MIDI::MidiPitchBend MidiNode::getPitchBend() const { + MIDI::MidiPitchBend cPitchBend; + for(auto cn : getConnectedConfigNodes()){ + cPitchBend += cn.lock()->getPitchBend(); + } + return midiPitchBend + cPitchBend; + } + + MIDI::MidiAftertouch MidiNode::getAftertouch() const { + MIDI::MidiAftertouch cAftertouch; + for(auto cn : getConnectedConfigNodes()){ + cAftertouch += cn.lock()->getAftertouch(); + } + return midiAftertouch + cAftertouch; + } + + MIDI::MidiPolyPressure MidiNode::getPolyPressure() const { + MIDI::MidiPolyPressure cPolyPressure; + for(auto cn : getConnectedConfigNodes()){ + cPolyPressure += cn.lock()->getPolyPressure(); + } + return midiPolyPressure + cPolyPressure; + } + + std::vector> MidiNode::getConnectedConfigNodes() const { + auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE;}) | std::ranges::to>>(); + std::vector> config_nodes; + for(auto e : config_conns){ + if(e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); + if(e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); + } + /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ + return config_nodes; + } + + void MidiNode::update() { + Node::update(); + mode = state->node_mode; + for(auto mh : midiHandles[NOTE]){ + mh->update(position); + } + for(auto mh : midiHandles[CC]){ + mh->update(position); + } + for(auto mh : midiHandles[PC]){ + mh->update(position); + } + for(auto mh : midiHandles[PB]){ + mh->update(position); + } + for(auto mh : midiHandles[AT]){ + mh->update(position); + } + for(auto mh : midiHandles[PP]){ + mh->update(position); + } + } + + void MidiNode::modMidiNote(int incr){ + midiNote.note += incr; + if(midiNote.note < 0 ) midiNote.note = VISEQ::MIDI::Notes.size()-1; + if(midiNote.note >= (int)VISEQ::MIDI::Notes.size()) midiNote.note = 0; + } + + void MidiNode::modMidiCC(int incr){ + midiCC.control += incr; + if(midiCC.control < 0 ) midiCC.control = 127; + if(midiCC.control > 127 ) midiCC.control = 0; + } + + void MidiNode::modMidiPC(int incr){ + midiPC.program += incr; + if(midiPC.program < 0 ) midiPC.program = 127; + if(midiPC.program > 127 ) midiPC.program = 0; + } + + void MidiNode::modMidiPitchBend(int incr){ + midiPitchBend.value += incr; + if(midiPitchBend.value < 0 ) midiPitchBend.value = 0; + if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; + } + + void MidiNode::modMidiAftertouch(int incr){ + midiAftertouch.value += incr; + if(midiAftertouch.value < 0 ) midiAftertouch.value = 0; + if(midiAftertouch.value > 127 ) midiAftertouch.value = 127; + } + + void MidiNode::modMidiPolyPressure(int incr){ + midiPolyPressure.value += incr; + if(midiPolyPressure.value < 0 ) midiPolyPressure.value = 0; + if(midiPolyPressure.value > 127 ) midiPolyPressure.value = 127; + } + + void MidiNode::interact(){ + float wheel = GetMouseWheelMove(); + + if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ + switch(state->node_mode){ + case 0:{ + modMidiNote(wheel); + break;} + case 1:{ + modMidiCC(wheel); + break;} + case 2:{ + modMidiPC(wheel); + break;} + case 3:{ + modMidiPitchBend(wheel); + break;} + case 4:{ + modMidiAftertouch(wheel); + break;} + case 5:{ + modMidiPolyPressure(wheel); + break;} + }; + } else if(KEYS_DOWN.shift && !KEYS_DOWN.alt) { + midiNote.velocity += wheel; + if(midiNote.velocity < 0 ) midiNote.velocity = 127; + if(midiNote.velocity > 127 ) midiNote.velocity = 0; + } else if(KEYS_DOWN.alt) { + switch(state->node_mode){ + case 0:{ + float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); + midiNote.gate += incr; + if(midiNote.gate < 0 ) midiNote.gate = 0; + break;} + case 1:{ + midiCC.value += wheel; + if(midiCC.value < 0 ) midiCC.value = 0; + if(midiCC.value > 127 ) midiCC.value = 127; + break;} + case 2:{ + midiPC.value += wheel; + if(midiPC.value < 0 ) midiPC.value = 0; + if(midiPC.value > 127 ) midiPC.value = 127; + break;} + case 3:{ + float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); + midiPitchBend.value += incr; + if(midiPitchBend.value < 0 ) midiPitchBend.value = 0; + if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; + break;} + case 4:{ + float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); + midiAftertouch.value += incr; + if(midiAftertouch.value < 0 ) midiAftertouch.value = 0; + if(midiAftertouch.value > 127 ) midiAftertouch.value = 127; + break;} + case 5:{ + float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); + midiPolyPressure.value += incr; + if(midiPolyPressure.value < 0 ) midiPolyPressure.value = 0; + if(midiPolyPressure.value > 127 ) midiPolyPressure.value = 127; + break;} + }; + } + } + + void MidiNode::draw(Vec2Df mouse, Camera2D cam) { + Node::draw(); + + for(int i = 0; i < 6; ++i){ + for(auto mh : midiHandles[(Handles)i]){ + mh->lock(); + } + } + + if(hovering() || selected) interact(); + + last = time; + time = GetTime(); + if(time - start >= (float)getNotes().gate / 1000.0f){ + triggered = false; + fade = 0; + } else { + fade -= (time - last)*1000.0f; + } + + DrawRing(position, radius, radius + 4, 0, 360, 24, (Color){60, 60, 60, 255}); + DrawRing(position, radius, radius + 4, 0.0f - 90.0f, (360.0f/127.0f) * getNotes().velocity - 90.0f, 24, (Color){158, 134, 200, 255}); + DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); + + float trigger_alpha = (1.f / getNotes().gate) * fade; + + if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, ColorLerp(state->mode_color, VS_COLOR_RED, trigger_alpha)); + if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255}); + if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255}); + + if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); + if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW); + + if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); + bool modified = false; + if(mode >= 0 && mode < 6){ + for(auto mh : midiHandles[(Handles)mode]){ + mh->unlock(); + mh->draw(); + std::string text = ""; + if(mh->hovering() || mh->picked_up){ + text = std::format("{}", mh->assignedValueRef); + if(mh->name == "NOTE") text = MIDI::Notes[mh->assignedValueRef]; + if(mh->name == "GATE") text = std::format("{:.2f}", mh->assignedValueRef / 1000.0f); + if(mh->name == "VALUE" && (Handles)mode == PB) text = std::format("{}", round(((float)mh->assignedValueRef / 8192.0f) * 100)); + DrawCenteredText(text, position, 12, WHITE); + modified = true; + } + } + } + + if(!modified){ + switch((Handles)mode){ + case NOTE:{ + DrawCenteredText(MIDI::Notes[getNotes().note], position, 12, WHITE); + break;} + case CC:{ + DrawCenteredText(std::format("{}", getCC().value), position, 12, WHITE); + break;} + case PC:{ + DrawCenteredText(std::format("{}", getPC().value), position, 12, WHITE); + break;} + case PB:{ + DrawCenteredText(std::format("{}", round(((float)getPitchBend().value / 8192.0f) * 100)), position, 12, WHITE); + break;} + case AT:{ + DrawCenteredText(std::format("{}", getAftertouch().value), position, 12, WHITE); + break;} + case PP:{ + DrawCenteredText(std::format("{}", getPolyPressure().value), position, 12, WHITE); + break;} + } + + } + + if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200}); + } + + void to_json(json& j, const MidiNode& N){ + std::vector conns; + std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;}); + + std::vector> handles; + + for(auto i : N.midiHandles){ + handles.emplace_back(); + for(auto n : i.second){ + handles.at(i.first).push_back(n->position); + } + } + + j = json{ + {"id", N.id}, + {"position", N.position}, + {"distributionMode", N.distributionMode}, + {"nodeType", N.nodeType}, + {"midiNote", N.midiNote}, + {"midiCC", N.midiCC}, + {"midiPC", N.midiPC}, + {"midiPitchBend", N.midiPitchBend}, + {"midiAftertouch", N.midiAftertouch}, + {"midiPolyPressure", N.midiPolyPressure}, + {"handles", handles}, + {"connections",conns}}; + + } + + void from_json(const json& j, MidiNode& N){ + std::cout << "MidiNode" << std::endl; + + j.at("id").get_to(N.id); + j.at("position").get_to(N.position); + j.at("distributionMode").get_to(N.distributionMode); + j.at("nodeType").get_to(N.nodeType); + j.at("midiNote").get_to(N.midiNote); + j.at("midiCC").get_to(N.midiCC); + j.at("midiPC").get_to(N.midiPC); + j.at("midiPitchBend").get_to(N.midiPitchBend); + j.at("midiAftertouch").get_to(N.midiAftertouch); + j.at("midiPolyPressure").get_to(N.midiPolyPressure); + + std::vector> handles; + + j.at("handles").get_to(handles); + + int i = 0; + for(auto n : handles){ + int j = 0; + for(auto nn : n){ + N.midiHandles[(Handles)i][j]->position = nn; + j++; + } + i++; + } + } + +} diff --git a/src/midi-node.h b/src/midi-node.h index d97eccb..a9975bc 100644 --- a/src/midi-node.h +++ b/src/midi-node.h @@ -1,14 +1,10 @@ #pragma once -#include "button.h" #include "config-node.h" #include "datatypes.h" -#include "edge.h" #include "node-handle.h" #include "node.h" #include "raylib.h" -#include "state.h" -#include #include #include @@ -17,6 +13,8 @@ namespace VISEQ::APP::UI{ } namespace VISEQ::GRAPH::NODES { + + enum Handles {NOTE = 0, CC, PC, PB, AT, PP}; class MidiNode : public Node{ friend class APP::UI::AppUI; @@ -29,8 +27,6 @@ namespace VISEQ::GRAPH::NODES { MIDI::MidiAftertouch midiAftertouch; MIDI::MidiPolyPressure midiPolyPressure; - enum Handles {NOTE = 0, CC, PC, PB, AT, PP}; - std::map>>> midiHandles; public: @@ -67,377 +63,29 @@ namespace VISEQ::GRAPH::NODES { float time = 0; float last = 0; - void init(){ - float orbit_1 = radius + 25.0f; - float orbit_2 = orbit_1 + 20.0f; - float orbit_3 = orbit_2 + 20.0f; - float orbit_4 = orbit_3 + 20.0f; - float orbit_5 = orbit_4 + 30.0f; - - midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.note, position, VS_COLOR_GREEN, "NOTE", orbit_5, 12)); - midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 16, 1, 16, 40, 150)); - midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.gate, position, VS_COLOR_RED, "GATE", orbit_3, 30, 10, 30000, 40, 300)); - midiHandles[NOTE].back()->logScale = true; - midiHandles[NOTE].emplace_back(std::make_shared>(midiNote.velocity, position, VS_COLOR_PURPLE, "VELOCITY", orbit_4, 32)); - - midiHandles[CC].emplace_back(std::make_shared>(midiCC.control, position, VS_COLOR_SUN, "CONTROL", orbit_3, 32)); - midiHandles[CC].emplace_back(std::make_shared>(midiCC.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); - midiHandles[CC].emplace_back(std::make_shared>(midiCC.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32)); - - midiHandles[PC].emplace_back(std::make_shared>(midiPC.program, position, VS_COLOR_SUN, "PROGRAM", orbit_3, 32)); - midiHandles[PC].emplace_back(std::make_shared>(midiPC.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); - midiHandles[PC].emplace_back(std::make_shared>(midiPC.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32)); - - midiHandles[PB].emplace_back(std::make_shared>(midiPitchBend.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); - midiHandles[PB].emplace_back(std::make_shared>(midiPitchBend.value, position, VS_COLOR_GREEN, "VALUE", orbit_3, 256, -8192, 8192, 40, 300)); - - midiHandles[AT].emplace_back(std::make_shared>(midiAftertouch.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); - midiHandles[AT].emplace_back(std::make_shared>(midiAftertouch.value, position, VS_COLOR_GREEN, "VALUE", orbit_3, 32)); - - midiHandles[PP].emplace_back(std::make_shared>(midiPolyPressure.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150)); - midiHandles[PP].emplace_back(std::make_shared>(midiPolyPressure.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32)); - midiHandles[PP].emplace_back(std::make_shared>(midiPolyPressure.note, position, VS_COLOR_SUN, "NOTE", orbit_5, 12)); - } - - std::shared_ptr getPtr(){ - return std::static_pointer_cast(shared_from_this()); - } - - virtual NodeType getNodeType() const override {return nodeType;} - - void setMidiPort(int midi_port, bool _state){ - midi_outs[midi_port].enabled = _state; - } - - void trigger() override { - triggered = true; - start = GetTime(); - fade = getNotes().gate; - } - - void setMode(int _mode) override { - mode = _mode; - } - - MIDI::MidiNote getNotes() const { - MIDI::MidiNote cNote; - for(auto cn : getConnectedConfigNodes()){ - cNote += cn.lock()->getNotes(); - } - return midiNote + cNote; - } - - MIDI::MidiCC getCC() const { - MIDI::MidiCC cCC; - for(auto cn : getConnectedConfigNodes()){ - cCC += cn.lock()->getCC(); - } - return midiCC + cCC; - } - - MIDI::MidiPC getPC() const { - MIDI::MidiPC cPC; - for(auto cn : getConnectedConfigNodes()){ - cPC += cn.lock()->getPC(); - } - return midiPC + cPC; - } - - MIDI::MidiPitchBend getPitchBend() const { - MIDI::MidiPitchBend cPitchBend; - for(auto cn : getConnectedConfigNodes()){ - cPitchBend += cn.lock()->getPitchBend(); - } - return midiPitchBend + cPitchBend; - } - - MIDI::MidiAftertouch getAftertouch() const { - MIDI::MidiAftertouch cAftertouch; - for(auto cn : getConnectedConfigNodes()){ - cAftertouch += cn.lock()->getAftertouch(); - } - return midiAftertouch + cAftertouch; - } - - MIDI::MidiPolyPressure getPolyPressure() const { - MIDI::MidiPolyPressure cPolyPressure; - for(auto cn : getConnectedConfigNodes()){ - cPolyPressure += cn.lock()->getPolyPressure(); - } - return midiPolyPressure + cPolyPressure; - } - - std::vector> getConnectedConfigNodes() const { - auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE;}) | std::ranges::to>>(); - std::vector> config_nodes; - for(auto e : config_conns){ - if(e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); - if(e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); - } - /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ - return config_nodes; - } - - void update() override { - Node::update(); - mode = state->node_mode; - for(auto mh : midiHandles[NOTE]){ - mh->update(position); - } - for(auto mh : midiHandles[CC]){ - mh->update(position); - } - for(auto mh : midiHandles[PC]){ - mh->update(position); - } - for(auto mh : midiHandles[PB]){ - mh->update(position); - } - for(auto mh : midiHandles[AT]){ - mh->update(position); - } - for(auto mh : midiHandles[PP]){ - mh->update(position); - } - } - - void modMidiNote(int incr){ - midiNote.note += incr; - if(midiNote.note < 0 ) midiNote.note = VISEQ::MIDI::Notes.size()-1; - if(midiNote.note >= (int)VISEQ::MIDI::Notes.size()) midiNote.note = 0; - } - - void modMidiCC(int incr){ - midiCC.control += incr; - if(midiCC.control < 0 ) midiCC.control = 127; - if(midiCC.control > 127 ) midiCC.control = 0; - } - - void modMidiPC(int incr){ - midiPC.program += incr; - if(midiPC.program < 0 ) midiPC.program = 127; - if(midiPC.program > 127 ) midiPC.program = 0; - } - - void modMidiPitchBend(int incr){ - midiPitchBend.value += incr; - if(midiPitchBend.value < 0 ) midiPitchBend.value = 0; - if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; - } - - void modMidiAftertouch(int incr){ - midiAftertouch.value += incr; - if(midiAftertouch.value < 0 ) midiAftertouch.value = 0; - if(midiAftertouch.value > 127 ) midiAftertouch.value = 127; - } - - void modMidiPolyPressure(int incr){ - midiPolyPressure.value += incr; - if(midiPolyPressure.value < 0 ) midiPolyPressure.value = 0; - if(midiPolyPressure.value > 127 ) midiPolyPressure.value = 127; - } - - void interact(){ - float wheel = GetMouseWheelMove(); - - if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ - switch(state->node_mode){ - case 0:{ - modMidiNote(wheel); - break;} - case 1:{ - modMidiCC(wheel); - break;} - case 2:{ - modMidiPC(wheel); - break;} - case 3:{ - modMidiPitchBend(wheel); - break;} - case 4:{ - modMidiAftertouch(wheel); - break;} - case 5:{ - modMidiPolyPressure(wheel); - break;} - }; - } else if(KEYS_DOWN.shift && !KEYS_DOWN.alt) { - midiNote.velocity += wheel; - if(midiNote.velocity < 0 ) midiNote.velocity = 127; - if(midiNote.velocity > 127 ) midiNote.velocity = 0; - } else if(KEYS_DOWN.alt) { - switch(state->node_mode){ - case 0:{ - float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); - midiNote.gate += incr; - if(midiNote.gate < 0 ) midiNote.gate = 0; - break;} - case 1:{ - midiCC.value += wheel; - if(midiCC.value < 0 ) midiCC.value = 0; - if(midiCC.value > 127 ) midiCC.value = 127; - break;} - case 2:{ - midiPC.value += wheel; - if(midiPC.value < 0 ) midiPC.value = 0; - if(midiPC.value > 127 ) midiPC.value = 127; - break;} - case 3:{ - float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); - midiPitchBend.value += incr; - if(midiPitchBend.value < 0 ) midiPitchBend.value = 0; - if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; - break;} - case 4:{ - float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); - midiAftertouch.value += incr; - if(midiAftertouch.value < 0 ) midiAftertouch.value = 0; - if(midiAftertouch.value > 127 ) midiAftertouch.value = 127; - break;} - case 5:{ - float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f); - midiPolyPressure.value += incr; - if(midiPolyPressure.value < 0 ) midiPolyPressure.value = 0; - if(midiPolyPressure.value > 127 ) midiPolyPressure.value = 127; - break;} - }; - } - } - - void draw(Vec2Df mouse, Camera2D cam) override { - Node::draw(); - - for(int i = 0; i < 6; ++i){ - for(auto mh : midiHandles[(Handles)i]){ - mh->lock(); - } - } - - if(hovering() || selected) interact(); - - last = time; - time = GetTime(); - if(time - start >= (float)getNotes().gate / 1000.0f){ - triggered = false; - fade = 0; - } else { - fade -= (time - last)*1000.0f; - } - - DrawRing(position, radius, radius + 4, 0, 360, 24, (Color){60, 60, 60, 255}); - DrawRing(position, radius, radius + 4, 0.0f - 90.0f, (360.0f/127.0f) * getNotes().velocity - 90.0f, 24, (Color){158, 134, 200, 255}); - DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); - - float trigger_alpha = (1.f / getNotes().gate) * fade; - - if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, ColorLerp(state->mode_color, VS_COLOR_RED, trigger_alpha)); - if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255}); - if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255}); - - if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); - if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW); - - if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); - bool modified = false; - if(mode >= 0 && mode < 6){ - for(auto mh : midiHandles[(Handles)mode]){ - mh->unlock(); - mh->draw(); - std::string text = ""; - if(mh->hovering() || mh->picked_up){ - text = std::format("{}", mh->assignedValueRef); - if(mh->name == "NOTE") text = MIDI::Notes[mh->assignedValueRef]; - if(mh->name == "GATE") text = std::format("{:.2f}", mh->assignedValueRef / 1000.0f); - if(mh->name == "VALUE" && (Handles)mode == PB) text = std::format("{}", round(((float)mh->assignedValueRef / 8192.0f) * 100)); - DrawCenteredText(text, position, 12, WHITE); - modified = true; - } - } - } - - if(!modified){ - switch((Handles)mode){ - case NOTE:{ - DrawCenteredText(MIDI::Notes[getNotes().note], position, 12, WHITE); - break;} - case CC:{ - DrawCenteredText(std::format("{}", getCC().value), position, 12, WHITE); - break;} - case PC:{ - DrawCenteredText(std::format("{}", getPC().value), position, 12, WHITE); - break;} - case PB:{ - DrawCenteredText(std::format("{}", round(((float)getPitchBend().value / 8192.0f) * 100)), position, 12, WHITE); - break;} - case AT:{ - DrawCenteredText(std::format("{}", getAftertouch().value), position, 12, WHITE); - break;} - case PP:{ - DrawCenteredText(std::format("{}", getPolyPressure().value), position, 12, WHITE); - break;} - } - - } - - if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200}); - } - - friend void to_json(json& j, const MidiNode& N){ - std::vector conns; - std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;}); - - std::vector> handles; - - for(auto i : N.midiHandles){ - handles.emplace_back(); - for(auto n : i.second){ - handles.at(i.first).push_back(n->position); - } - } - - j = json{ - {"id", N.id}, - {"position", N.position}, - {"distributionMode", N.distributionMode}, - {"nodeType", N.nodeType}, - {"midiNote", N.midiNote}, - {"midiCC", N.midiCC}, - {"midiPC", N.midiPC}, - {"midiPitchBend", N.midiPitchBend}, - {"midiAftertouch", N.midiAftertouch}, - {"midiPolyPressure", N.midiPolyPressure}, - {"handles", handles}, - {"connections",conns}}; - - } - - friend void from_json(const json& j, MidiNode& N){ - std::cout << "MidiNode" << std::endl; - - j.at("id").get_to(N.id); - j.at("position").get_to(N.position); - j.at("distributionMode").get_to(N.distributionMode); - j.at("nodeType").get_to(N.nodeType); - j.at("midiNote").get_to(N.midiNote); - j.at("midiCC").get_to(N.midiCC); - j.at("midiPC").get_to(N.midiPC); - j.at("midiPitchBend").get_to(N.midiPitchBend); - j.at("midiAftertouch").get_to(N.midiAftertouch); - j.at("midiPolyPressure").get_to(N.midiPolyPressure); - - std::vector> handles; - - j.at("handles").get_to(handles); - - int i = 0; - for(auto n : handles){ - int j = 0; - for(auto nn : n){ - N.midiHandles[(Handles)i][j]->position = nn; - j++; - } - i++; - } - } + void init(); + std::shared_ptr getPtr(); + virtual NodeType getNodeType() const override; + void setMidiPort(int midi_port, bool _state); + void trigger() override ; + void setMode(int _mode) override ; + MIDI::MidiNote getNotes() const ; + MIDI::MidiCC getCC() const ; + MIDI::MidiPC getPC() const ; + MIDI::MidiPitchBend getPitchBend() const ; + MIDI::MidiAftertouch getAftertouch() const ; + MIDI::MidiPolyPressure getPolyPressure() const ; + std::vector> getConnectedConfigNodes() const ; + void update() override ; + void modMidiNote(int incr); + void modMidiCC(int incr); + void modMidiPC(int incr); + void modMidiPitchBend(int incr); + void modMidiAftertouch(int incr); + void modMidiPolyPressure(int incr); + void interact(); + void draw(Vec2Df mouse, Camera2D cam) override ; + friend void to_json(json& j, const MidiNode& N); + friend void from_json(const json& j, MidiNode& N); }; } diff --git a/src/midi.cpp b/src/midi.cpp new file mode 100644 index 0000000..52a9b09 --- /dev/null +++ b/src/midi.cpp @@ -0,0 +1,80 @@ +#include "midi.h" + +#include "libremidi/libremidi.hpp" +#include "raylib.h" + +#include +#include +#include "datatypes.h" + +namespace VISEQ::MIDI { + + void Midi::enumerate(){ + std::cout << (int)obs.get_current_api() << std::endl; + int i = 0; + midi_out_ports.clear(); + for(const libremidi::output_port& port : obs.get_output_ports()){ + midi_out_ports.push_back({port, i, false}); + i++; + } + } + + void Midi::open_midi_out(MidiOutPort *out_port){ + if(!midi_outs.contains(out_port->number)){ + auto out = std::make_shared(); + out->open_port(out_port->port); + midi_outs[out_port->number] = out; + } + } + + void Midi::close_midi_out(MidiOutPort *out_port){ + if(midi_outs.contains(out_port->number)){ + midi_outs[out_port->number]->close_port(); + midi_outs.erase(out_port->number); + } + } + + void Midi::note(int midi_out, int channel, int note, int velocity, int gate){ + if(midi_outs.contains(midi_out)){ + if(midi_outs[midi_out]->is_port_open()){ + midi_outs[midi_out]->send_message(libremidi::channel_events::note_on(channel, note, velocity)); + notes.emplace_back(midi_out, channel, note, velocity, GetTime(), gate); + } + } + } + + void Midi::cc(int midi_out, int channel, int cc, int value){ + if(midi_outs.contains(midi_out)){ + if(midi_outs[midi_out]->is_port_open()){ + midi_outs[midi_out]->send_message(libremidi::channel_events::control_change(channel, cc, value)); + } + } + } + + void Midi::pitch_bend(int midi_out, int channel, int pitch_value){ + if(midi_outs.contains(midi_out)){ + if(midi_outs[midi_out]->is_port_open()){ + midi_outs[midi_out]->send_message(libremidi::channel_events::pitch_bend(channel, pitch_value)); + } + } + } + + void Midi::update(){ + float time = GetTime(); + for(auto &n : notes){ + if(time - n.start >= (float)n.gate / 1000.0f){ + if(midi_outs.contains(n.midi_out) && midi_outs[n.midi_out]->is_port_open()) midi_outs[n.midi_out]->send_message(libremidi::channel_events::note_off(n.channel, n.note, n.velocity)); + n.erase = true; + } + } + std::erase_if(notes, [](const auto& item){return item.erase;}); + + for(auto& p : midi_out_ports){ + if(p.enabled) { + open_midi_out(&p); + } else if(!p.enabled) { + close_midi_out(&p); + } + } + } +} diff --git a/src/midi.h b/src/midi.h index ed4c672..768d20e 100644 --- a/src/midi.h +++ b/src/midi.h @@ -1,11 +1,8 @@ #pragma once #include "libremidi/libremidi.hpp" -#include "libremidi/message.hpp" #include "libremidi/observer_configuration.hpp" -#include "raylib.h" -#include #include #include #include "datatypes.h" @@ -39,74 +36,13 @@ namespace VISEQ::MIDI { float timer; - void enumerate(){ - std::cout << (int)obs.get_current_api() << std::endl; - int i = 0; - midi_out_ports.clear(); - for(const libremidi::output_port& port : obs.get_output_ports()){ - midi_out_ports.push_back({port, i, false}); - i++; - } - } - - void open_midi_out(MidiOutPort *out_port){ - if(!midi_outs.contains(out_port->number)){ - auto out = std::make_shared(); - out->open_port(out_port->port); - midi_outs[out_port->number] = out; - } - } - - void close_midi_out(MidiOutPort *out_port){ - if(midi_outs.contains(out_port->number)){ - midi_outs[out_port->number]->close_port(); - midi_outs.erase(out_port->number); - } - } - - void note(int midi_out, int channel, int note, int velocity, int gate){ - if(midi_outs.contains(midi_out)){ - if(midi_outs[midi_out]->is_port_open()){ - midi_outs[midi_out]->send_message(libremidi::channel_events::note_on(channel, note, velocity)); - notes.emplace_back(midi_out, channel, note, velocity, GetTime(), gate); - } - } - } - - void cc(int midi_out, int channel, int cc, int value){ - if(midi_outs.contains(midi_out)){ - if(midi_outs[midi_out]->is_port_open()){ - midi_outs[midi_out]->send_message(libremidi::channel_events::control_change(channel, cc, value)); - } - } - } - - void pitch_bend(int midi_out, int channel, int pitch_value){ - if(midi_outs.contains(midi_out)){ - if(midi_outs[midi_out]->is_port_open()){ - midi_outs[midi_out]->send_message(libremidi::channel_events::pitch_bend(channel, pitch_value)); - } - } - } - - void update(){ - float time = GetTime(); - for(auto &n : notes){ - if(time - n.start >= (float)n.gate / 1000.0f){ - if(midi_outs.contains(n.midi_out) && midi_outs[n.midi_out]->is_port_open()) midi_outs[n.midi_out]->send_message(libremidi::channel_events::note_off(n.channel, n.note, n.velocity)); - n.erase = true; - } - } - std::erase_if(notes, [](const auto& item){return item.erase;}); - - for(auto& p : midi_out_ports){ - if(p.enabled) { - open_midi_out(&p); - } else if(!p.enabled) { - close_midi_out(&p); - } - } - } + void enumerate(); + void open_midi_out(MidiOutPort *out_port); + void close_midi_out(MidiOutPort *out_port); + void note(int midi_out, int channel, int note, int velocity, int gate); + void cc(int midi_out, int channel, int cc, int value); + void pitch_bend(int midi_out, int channel, int pitch_value); + void update(); }; } diff --git a/src/modifier-node.cpp b/src/modifier-node.cpp new file mode 100644 index 0000000..87c0234 --- /dev/null +++ b/src/modifier-node.cpp @@ -0,0 +1,289 @@ +#include "modifier-node.h" + +namespace VISEQ::GRAPH::NODES { + std::shared_ptr ModNode::getPtr() { + return std::static_pointer_cast(shared_from_this()); + } + + NodeType ModNode::getNodeType() const {return nodeType;} + + void ModNode::trigger() {} + + void ModNode::setMode(int _mode) { + mode = _mode; + } + + MIDI::MidiNote ModNode::getNotes() const { + MIDI::MidiNote cNote; + for(auto cn : getConnectedModNodes()){ + cNote += cn.lock()->getNotes(); + } + return midiNote + cNote; + } + + MIDI::MidiCC ModNode::getCC() const { + MIDI::MidiCC cCC; + for(auto cn : getConnectedModNodes()){ + cCC += cn.lock()->getCC(); + } + return midiCC + cCC; + } + + MIDI::MidiPC ModNode::getPC() const { + MIDI::MidiPC cPC; + for(auto cn : getConnectedModNodes()){ + cPC += cn.lock()->getPC(); + } + return midiPC + cPC; + } + + MIDI::MidiPitchBend ModNode::getPitchBend() const { + MIDI::MidiPitchBend cPitchBend; + for(auto cn : getConnectedModNodes()){ + cPitchBend += cn.lock()->getPitchBend(); + } + return midiPitchBend + cPitchBend; + } + + MIDI::MidiAftertouch ModNode::getAftertouch() const { + MIDI::MidiAftertouch cAftertouch; + for(auto cn : getConnectedModNodes()){ + cAftertouch += cn.lock()->getAftertouch(); + } + return midiAftertouch + cAftertouch; + } + + MIDI::MidiPolyPressure ModNode::getPolyPressure() const { + MIDI::MidiPolyPressure cPolyPressure; + for(auto cn : getConnectedModNodes()){ + cPolyPressure += cn.lock()->getPolyPressure(); + } + return midiPolyPressure + cPolyPressure; + } + + std::vector> ModNode::getConnectedModNodes() const { // TODO: check for cyclic connections of config nodes (crashes) + auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE;}) | std::ranges::to>>(); + std::vector> config_nodes; + for(auto e : config_conns){ + if(e->start.lock().get() != this && e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); + //if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); + } + /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ + return config_nodes; + } + + std::vector> ModNode::getConnectedMidiNodes() const { + auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::MIDINODE || c->end.lock()->getNodeType() == NodeType::MIDINODE;}) | std::ranges::to>>(); + std::vector> config_nodes; + for(auto e : config_conns){ + if(e->start.lock().get() != this && e->start.lock()->getNodeType() == NodeType::MIDINODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); + if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::MIDINODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); + } + /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ + return config_nodes; + } + + void ModNode::update() { + Node::update(); + mode = state->node_mode; + } + + void ModNode::interact(){ + float wheel = GetMouseWheelMove(); + + if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ + switch(state->node_mode){ + case 0:{ + midiNote.note += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; + break;} + case 1:{ + midiCC.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; + break;} + case 2:{ + midiPitchBend.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; + break;} + }; + + if(midiNote.note < -127 ) midiNote.note = -127; + if(midiNote.note > 127 ) midiNote.note = 127; + + if(midiCC.value < -127 ) midiCC.value = -127; + if(midiCC.value > 127 ) midiCC.value = 127; + + if(midiPitchBend.value < -16383 ) midiPitchBend.value = -16383; + if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; + + } else if(KEYS_DOWN.alt) { + switch(state->node_mode){ + case 0:{ + midiNote.channel += wheel; + if(midiNote.channel < 0 ) midiNote.channel = 0; + if(midiNote.channel > 15) midiNote.channel = 15; + break;} + case 1:{ + midiCC.channel += wheel; + if(midiCC.channel < 0 ) midiCC.channel = 0; + if(midiCC.channel > 15 ) midiCC.channel = 15; + break;} + case 2:{ + midiPitchBend.channel += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 100 : wheel; + if(midiPitchBend.channel < 0 ) midiPitchBend.channel = 0; + if(midiPitchBend.channel > 15 ) midiPitchBend.channel = 15; + break;} + }; + } + } + + void ModNode::draw(Vec2Df mouse, Camera2D cam) { + Node::draw(); + if(hovering() || selected) interact(); + + DrawRing(position, radius, radius+4, 0, 360, 24, (Color){60, 60, 60, 255}); + DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); + + Color gate_color = VS_COLOR_GREEN; + + switch(mode){ + case 0:{ + gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); + break;} + case 1:{ + gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){229, 181, 103 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); + break;} + case 2:{ + gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){232, 125, 62 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); + break;} + } + if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, gate_color); + if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255}); + if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255}); + + if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); + if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW); + + if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); + + if(KEYS_DOWN.alt){ + float value = 0; + int offset_gate = 0; + switch(mode){ + case 0: + value = (float)getNotes().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 1: + value = (float)getCC().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 2: + value = (float)getPC().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 3: + value = (float)getPitchBend().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 4: + value = (float)getAftertouch().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + case 5: + value = (float)getPolyPressure().channel; + offset_gate = MeasureText(std::format("{}", value).c_str(), 12); + DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break; + } + } else { + switch(mode){ + case 0:{ + int offset = MeasureText(std::format("{:+}", getNotes().note).c_str(), 12); + DrawText(std::format("{:+}", getNotes().note).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 1:{ + int offset = MeasureText(std::format("{:+}", getCC().value).c_str(), 12); + DrawText(std::format("{:+}", getCC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 2:{ + int offset = MeasureText(std::format("{:+}", getPC().value).c_str(), 12); + DrawText(std::format("{:+}", getPC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 3:{ + int offset = MeasureText(std::format("{:+}", getPitchBend().value).c_str(), 12); + DrawText(std::format("{:+}", getPitchBend().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 4:{ + int offset = MeasureText(std::format("{:+}", getAftertouch().value).c_str(), 12); + DrawText(std::format("{:+}", getAftertouch().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + case 5:{ + int offset = MeasureText(std::format("{:+}", getPolyPressure().value).c_str(), 12); + DrawText(std::format("{:+}", getPolyPressure().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); + break;} + } + } + + if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200}); + //DrawText(std::format("{}", id).c_str(), position.x + 20.0f, position.y + 20.0f, 10, WHITE); + } + + void to_json(json& j, const ModNode& N){ + std::vector conns; + std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;}); + + j = json{ + {"id", N.id}, + {"position", N.position}, + {"distributionMode", N.distributionMode}, + {"nodeType", N.nodeType}, + {"midi_note_channel",N.midiNote.channel}, + {"midi_note_note",N.midiNote.note}, + {"midi_note_velocity",N.midiNote.velocity}, + {"midi_note_gate",N.midiNote.gate}, + {"midi_cc_channel",N.midiCC.channel}, + {"midi_cc_control",N.midiCC.control}, + {"midi_cc_value",N.midiCC.value}, + {"midi_pc_channel",N.midiPC.channel}, + {"midi_pc_program",N.midiPC.program}, + {"midi_pc_value",N.midiPC.value}, + {"midi_pitch_bend_channel",N.midiPitchBend.channel}, + {"midi_pitch_bend_value",N.midiPitchBend.value}, + {"midi_aftertouch_channel",N.midiAftertouch.channel}, + {"midi_aftertouch_value",N.midiAftertouch.value}, + {"midi_polypressure_channel",N.midiPolyPressure.channel}, + {"midi_polypressure_value",N.midiPolyPressure.value}, + {"connections",conns}}; + } + + void from_json(const json& j, ModNode& N){ + std::cout << "configNode from_json" << std::endl; + + j.at("id").get_to(N.id); + j.at("position").get_to(N.position); + j.at("distributionMode").get_to(N.distributionMode); + j.at("nodeType").get_to(N.nodeType); + j.at("midi_note_channel").get_to(N.midiNote.channel); + j.at("midi_note_note").get_to(N.midiNote.note); + j.at("midi_note_velocity").get_to(N.midiNote.velocity); + j.at("midi_note_gate").get_to(N.midiNote.gate); + j.at("midi_cc_channel").get_to(N.midiCC.channel); + j.at("midi_cc_control").get_to(N.midiCC.control); + j.at("midi_cc_value").get_to(N.midiCC.value); + j.at("midi_pc_channel").get_to(N.midiPC.channel); + j.at("midi_pc_program").get_to(N.midiPC.program); + j.at("midi_pc_value").get_to(N.midiPC.value); + j.at("midi_pitch_bend_channel").get_to(N.midiPitchBend.channel); + j.at("midi_pitch_bend_value").get_to(N.midiPitchBend.value); + j.at("midi_aftertouch_channel").get_to(N.midiAftertouch.channel); + j.at("midi_aftertouch_value").get_to(N.midiAftertouch.value); + j.at("midi_polypressure_channel").get_to(N.midiPolyPressure.channel); + j.at("midi_polypressure_value").get_to(N.midiPolyPressure.value); + + //j.at("connections").get_to(N.connections); + } +} + diff --git a/src/modifier-node.h b/src/modifier-node.h index 236a251..2130a82 100644 --- a/src/modifier-node.h +++ b/src/modifier-node.h @@ -15,9 +15,7 @@ namespace VISEQ::GRAPH::NODES { ModNode() {std::cout << "ModNode Constructor std" << std::endl;} ModNode(Vec2Df position) : Node(position) {} - std::shared_ptr getPtr() { - return std::static_pointer_cast(shared_from_this()); - } + std::shared_ptr getPtr(); NodeType nodeType = NodeType::CONFIGNODE; @@ -31,285 +29,24 @@ namespace VISEQ::GRAPH::NODES { int mode = 0; float radius = 20.0f; - virtual NodeType getNodeType() const override {return nodeType;} + virtual NodeType getNodeType() const override; - virtual void trigger() override {} + virtual void trigger() override; - virtual void setMode(int _mode) override { - mode = _mode; - } + virtual void setMode(int _mode) override ; - MIDI::MidiNote getNotes() const { - MIDI::MidiNote cNote; - for(auto cn : getConnectedModNodes()){ - cNote += cn.lock()->getNotes(); - } - return midiNote + cNote; - } - - MIDI::MidiCC getCC() const { - MIDI::MidiCC cCC; - for(auto cn : getConnectedModNodes()){ - cCC += cn.lock()->getCC(); - } - return midiCC + cCC; - } - - MIDI::MidiPC getPC() const { - MIDI::MidiPC cPC; - for(auto cn : getConnectedModNodes()){ - cPC += cn.lock()->getPC(); - } - return midiPC + cPC; - } - - MIDI::MidiPitchBend getPitchBend() const { - MIDI::MidiPitchBend cPitchBend; - for(auto cn : getConnectedModNodes()){ - cPitchBend += cn.lock()->getPitchBend(); - } - return midiPitchBend + cPitchBend; - } - - MIDI::MidiAftertouch getAftertouch() const { - MIDI::MidiAftertouch cAftertouch; - for(auto cn : getConnectedModNodes()){ - cAftertouch += cn.lock()->getAftertouch(); - } - return midiAftertouch + cAftertouch; - } - - MIDI::MidiPolyPressure getPolyPressure() const { - MIDI::MidiPolyPressure cPolyPressure; - for(auto cn : getConnectedModNodes()){ - cPolyPressure += cn.lock()->getPolyPressure(); - } - return midiPolyPressure + cPolyPressure; - } - - std::vector> getConnectedModNodes() const { // TODO: check for cyclic connections of config nodes (crashes) - auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::CONFIGNODE || c->end.lock()->getNodeType() == NodeType::CONFIGNODE;}) | std::ranges::to>>(); - std::vector> config_nodes; - for(auto e : config_conns){ - if(e->start.lock().get() != this && e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); - //if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); - } - /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ - return config_nodes; - } - - std::vector> getConnectedMidiNodes() const { - auto config_conns = connections | RANGE_FILTER([](EDGES::SPTR_Edge c){ return c->start.lock()->getNodeType() == NodeType::MIDINODE || c->end.lock()->getNodeType() == NodeType::MIDINODE;}) | std::ranges::to>>(); - std::vector> config_nodes; - for(auto e : config_conns){ - if(e->start.lock().get() != this && e->start.lock()->getNodeType() == NodeType::MIDINODE) config_nodes.push_back(std::dynamic_pointer_cast(e->start.lock())); - if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::MIDINODE) config_nodes.push_back(std::dynamic_pointer_cast(e->end.lock())); - } - /*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/ - return config_nodes; - } - - void update() override { - Node::update(); - mode = state->node_mode; - } - - void interact(){ - float wheel = GetMouseWheelMove(); - - if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){ - switch(state->node_mode){ - case 0:{ - midiNote.note += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; - break;} - case 1:{ - midiCC.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; - break;} - case 2:{ - midiPitchBend.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel; - break;} - }; - - if(midiNote.note < -127 ) midiNote.note = -127; - if(midiNote.note > 127 ) midiNote.note = 127; - - if(midiCC.value < -127 ) midiCC.value = -127; - if(midiCC.value > 127 ) midiCC.value = 127; - - if(midiPitchBend.value < -16383 ) midiPitchBend.value = -16383; - if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383; - - } else if(KEYS_DOWN.alt) { - switch(state->node_mode){ - case 0:{ - midiNote.channel += wheel; - if(midiNote.channel < 0 ) midiNote.channel = 0; - if(midiNote.channel > 15) midiNote.channel = 15; - break;} - case 1:{ - midiCC.channel += wheel; - if(midiCC.channel < 0 ) midiCC.channel = 0; - if(midiCC.channel > 15 ) midiCC.channel = 15; - break;} - case 2:{ - midiPitchBend.channel += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 100 : wheel; - if(midiPitchBend.channel < 0 ) midiPitchBend.channel = 0; - if(midiPitchBend.channel > 15 ) midiPitchBend.channel = 15; - break;} - }; - } - } - - void draw(Vec2Df mouse, Camera2D cam) override { - Node::draw(); - if(hovering() || selected) interact(); - - DrawRing(position, radius, radius+4, 0, 360, 24, (Color){60, 60, 60, 255}); - DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255}); - - Color gate_color = VS_COLOR_GREEN; - - switch(mode){ - case 0:{ - gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); - break;} - case 1:{ - gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){229, 181, 103 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); - break;} - case 2:{ - gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){232, 125, 62 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE); - break;} - } - if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, gate_color); - if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255}); - if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255}); - - if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK); - if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW); - - if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); - - if(KEYS_DOWN.alt){ - float value = 0; - int offset_gate = 0; - switch(mode){ - case 0: - value = (float)getNotes().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 1: - value = (float)getCC().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 2: - value = (float)getPC().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 3: - value = (float)getPitchBend().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 4: - value = (float)getAftertouch().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - case 5: - value = (float)getPolyPressure().channel; - offset_gate = MeasureText(std::format("{}", value).c_str(), 12); - DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break; - } - } else { - switch(mode){ - case 0:{ - int offset = MeasureText(std::format("{:+}", getNotes().note).c_str(), 12); - DrawText(std::format("{:+}", getNotes().note).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 1:{ - int offset = MeasureText(std::format("{:+}", getCC().value).c_str(), 12); - DrawText(std::format("{:+}", getCC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 2:{ - int offset = MeasureText(std::format("{:+}", getPC().value).c_str(), 12); - DrawText(std::format("{:+}", getPC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 3:{ - int offset = MeasureText(std::format("{:+}", getPitchBend().value).c_str(), 12); - DrawText(std::format("{:+}", getPitchBend().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 4:{ - int offset = MeasureText(std::format("{:+}", getAftertouch().value).c_str(), 12); - DrawText(std::format("{:+}", getAftertouch().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - case 5:{ - int offset = MeasureText(std::format("{:+}", getPolyPressure().value).c_str(), 12); - DrawText(std::format("{:+}", getPolyPressure().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK ); - break;} - } - } - - if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200}); - //DrawText(std::format("{}", id).c_str(), position.x + 20.0f, position.y + 20.0f, 10, WHITE); - } - - friend void to_json(json& j, const ModNode& N){ - std::vector conns; - std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;}); - - j = json{ - {"id", N.id}, - {"position", N.position}, - {"distributionMode", N.distributionMode}, - {"nodeType", N.nodeType}, - {"midi_note_channel",N.midiNote.channel}, - {"midi_note_note",N.midiNote.note}, - {"midi_note_velocity",N.midiNote.velocity}, - {"midi_note_gate",N.midiNote.gate}, - {"midi_cc_channel",N.midiCC.channel}, - {"midi_cc_control",N.midiCC.control}, - {"midi_cc_value",N.midiCC.value}, - {"midi_pc_channel",N.midiPC.channel}, - {"midi_pc_program",N.midiPC.program}, - {"midi_pc_value",N.midiPC.value}, - {"midi_pitch_bend_channel",N.midiPitchBend.channel}, - {"midi_pitch_bend_value",N.midiPitchBend.value}, - {"midi_aftertouch_channel",N.midiAftertouch.channel}, - {"midi_aftertouch_value",N.midiAftertouch.value}, - {"midi_polypressure_channel",N.midiPolyPressure.channel}, - {"midi_polypressure_value",N.midiPolyPressure.value}, - {"connections",conns}}; - } - - friend void from_json(const json& j, ModNode& N){ - std::cout << "configNode from_json" << std::endl; - - j.at("id").get_to(N.id); - j.at("position").get_to(N.position); - j.at("distributionMode").get_to(N.distributionMode); - j.at("nodeType").get_to(N.nodeType); - j.at("midi_note_channel").get_to(N.midiNote.channel); - j.at("midi_note_note").get_to(N.midiNote.note); - j.at("midi_note_velocity").get_to(N.midiNote.velocity); - j.at("midi_note_gate").get_to(N.midiNote.gate); - j.at("midi_cc_channel").get_to(N.midiCC.channel); - j.at("midi_cc_control").get_to(N.midiCC.control); - j.at("midi_cc_value").get_to(N.midiCC.value); - j.at("midi_pc_channel").get_to(N.midiPC.channel); - j.at("midi_pc_program").get_to(N.midiPC.program); - j.at("midi_pc_value").get_to(N.midiPC.value); - j.at("midi_pitch_bend_channel").get_to(N.midiPitchBend.channel); - j.at("midi_pitch_bend_value").get_to(N.midiPitchBend.value); - j.at("midi_aftertouch_channel").get_to(N.midiAftertouch.channel); - j.at("midi_aftertouch_value").get_to(N.midiAftertouch.value); - j.at("midi_polypressure_channel").get_to(N.midiPolyPressure.channel); - j.at("midi_polypressure_value").get_to(N.midiPolyPressure.value); - - //j.at("connections").get_to(N.connections); - } + MIDI::MidiNote getNotes() const; + MIDI::MidiCC getCC() const; + MIDI::MidiPC getPC() const; + MIDI::MidiPitchBend getPitchBend() const; + MIDI::MidiAftertouch getAftertouch() const; + MIDI::MidiPolyPressure getPolyPressure() const; + std::vector> getConnectedModNodes() const; + std::vector> getConnectedMidiNodes() const; + void update() override; + void interact(); + void draw(Vec2Df mouse, Camera2D cam) override; + friend void to_json(json& j, const ModNode& N); + friend void from_json(const json& j, ModNode& N); }; } diff --git a/src/mouse.h b/src/mouse.h deleted file mode 100644 index dcd2e99..0000000 --- a/src/mouse.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "draggable.h" -#include "graph-manager.h" -#include "interaction-event.h" -#include -namespace VISEQ::APP { - class Mouse { - public: - Mouse() {} - Mouse(GraphManager *gm) : gm(gm) {} - - GraphManager *gm; - - void update(){ - /*auto hovering_nodes = gm->nodes | RANGE_FILTER([](SPTR_Node n) { return n->hovering() || n->picked_up;}) | std::ranges::to>>();*/ - /**/ - /*if(!hovering_nodes.empty()){*/ - /* hovering_nodes.front()->is_hovering = true;*/ - /**/ - /* if(hovering_nodes.front()->pickedUp()) hovering_nodes.front()->drag();*/ - /* if(hovering_nodes.front()->released()) hovering_nodes.front()->putDown();*/ - /**/ - /*}*/ - - } - - void interact(InteractionEvent e){ - - } - - /*virtual void select() override {*/ - /* if(STATE == IState::DRAW_SELECTION) in_selection_rect = CheckCollisionPointRec(position, state->selection);*/ - /* if(MOUSE_LEFT.released) selected = (KEYS_DOWN.shift && selected) || in_selection_rect;*/ - /*}*/ - /**/ - /*virtual void update() override {*/ - /* if(STATE != IState::HOVER_NODE) select();*/ - /* if((pickedUp() || hovering()) && z_hovering == -1) z_hovering = z_index;*/ - /**/ - /* if(z_index == z_hovering || selected){*/ - /* if(pickedUp()){*/ - /* drag();*/ - /* STATE = IState::DRAG_NODE;*/ - /* }*/ - /* }*/ - /* if(STATE != IState::DRAG_NODE){*/ - /* if(entered()){*/ - /* STATE = IState::HOVER_NODE;*/ - /* std::cout << "z_index=" << z_index << " z_hovering=" << z_hovering << std::endl;*/ - /* is_hovering = true;*/ - /* }*/ - /* if(exited()){*/ - /* STATE = IState::HOVER_CANVAS;*/ - /* is_hovering = false;*/ - /* }*/ - /* }*/ - /* if(released() && picked_up){*/ - /* putDown();*/ - /* STATE = IState::HOVER_CANVAS;*/ - /* if(hovering()) STATE = IState::HOVER_NODE;*/ - /* }*/ - /*}*/ - }; -} diff --git a/src/node.cpp b/src/node.cpp new file mode 100644 index 0000000..d986b81 --- /dev/null +++ b/src/node.cpp @@ -0,0 +1,73 @@ +#include "node.h" + +#include +#include + +#include "connector.h" +#include "datatypes.h" +#include "interaction-event.h" +#include "state.h" +#include "vmath.h" + +namespace VISEQ::GRAPH::NODES { + void Node::setMode(int _mode) {mode = _mode;} + + std::shared_ptr Node::getPtr(){ + return shared_from_this(); + } + + bool Node::hovering() { + return CheckCollisionPointCircle(SCREEN_MOUSE, position, radius); + } + + void Node::addConnector() { + connectors.emplace_back(std::make_shared(getPtr(), position, radius + 5.0f)); + } + + void Node::removeConnector(std::weak_ptr connector){ + std::erase(connectors, connector.lock()); + } + + NodeType Node::getNodeType() const {return nodeType;} + + void Node::deselect() { + selected = false; + } + + GraphItemType Node::getType() { + return GraphItemType::NODE; + } + + void Node::clearConnectors(){ + connectors.clear(); + update(); + } + + void Node::update() { + if(hovering()) state->pushEvent({getPtr(), APP::Interaction::HOVER}); + if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); + if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); + if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); + if(hovering() && MOUSE_RIGHT.clicked) { + selected = true; + config = true; + state->pushEvent({getPtr(), APP::Interaction::RIGHTCLICK}); + } + int free_connectors = 0; + for(auto c : connectors){ + if(c->partner.expired()) free_connectors++; + c->setPosition(position); + c->setOrbit(radius + 8.0f); + c->update(); + } + if(free_connectors == 0) addConnector(); + } + + void Node::draw(Vec2Df mp, Camera2D cam) {} + + void Node::draw() { + for(auto c : connectors){ + c->draw(); + } + } +} diff --git a/src/node.h b/src/node.h index 3bab18f..03a6d6a 100644 --- a/src/node.h +++ b/src/node.h @@ -7,8 +7,6 @@ #include "datatypes.h" #include "draggable.h" #include "graph-item.h" -#include "interaction-event.h" -#include "state.h" #include "vmath.h" namespace VISEQ::GRAPH::EDGES{ @@ -43,66 +41,18 @@ namespace VISEQ::GRAPH::NODES { int mode = 0; virtual void trigger() {} - virtual void setMode(int _mode) {mode = _mode;} - - std::shared_ptr getPtr(){ - return shared_from_this(); - } - - virtual bool hovering() override { - return CheckCollisionPointCircle(SCREEN_MOUSE, position, radius); - } - - void addConnector() { - connectors.emplace_back(std::make_shared(getPtr(), position, radius + 5.0f)); - } - - void removeConnector(std::weak_ptr connector){ - std::erase(connectors, connector.lock()); - } - - virtual NodeType getNodeType() const {return nodeType;} - - void deselect() override { - selected = false; - } - - GraphItemType getType() override{ - return GraphItemType::NODE; - } - - void clearConnectors(){ - connectors.clear(); - update(); - } - - void update() override{ - if(hovering()) state->pushEvent({getPtr(), APP::Interaction::HOVER}); - if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); - if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); - if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); - if(hovering() && MOUSE_RIGHT.clicked) { - selected = true; - config = true; - state->pushEvent({getPtr(), APP::Interaction::RIGHTCLICK}); - } - int free_connectors = 0; - for(auto c : connectors){ - if(c->partner.expired()) free_connectors++; - c->setPosition(position); - c->setOrbit(radius + 8.0f); - c->update(); - } - if(free_connectors == 0) addConnector(); - } - - void draw(Vec2Df mp, Camera2D cam) override {} - - void draw() override{ - for(auto c : connectors){ - c->draw(); - } - } + virtual void setMode(int _mode); + std::shared_ptr getPtr(); + virtual bool hovering() override; + void addConnector(); + void removeConnector(std::weak_ptr connector); + virtual NodeType getNodeType() const; + void deselect() override; + GraphItemType getType() override; + void clearConnectors(); + void update() override; + void draw(Vec2Df mp, Camera2D cam) override; + void draw() override; }; diff --git a/src/signal.cpp b/src/signal.cpp new file mode 100644 index 0000000..c123514 --- /dev/null +++ b/src/signal.cpp @@ -0,0 +1,25 @@ +#include "signal.h" + +#include +#include "node.h" + +namespace VISEQ::SIGNAL { + void Signal::reset(){ + toStart(); + } + + void Signal::toStart(){ + position = start.lock()->position; + } + + void Signal::update(double t_delta){ + // 120px / 16th = 1920px / whole note + // whole note = 2s at 120bpm + // 960px / s + if(run) position += direction.norm() * speed * multiplier * (float)t_delta / 1000000.0f; + } + + void Signal::draw(Vec2Df mouse, Camera2D cam) { + DrawCircle(position.x, position.y, radius, color); + } +} diff --git a/src/signal.h b/src/signal.h index 35ed1f6..5edb5d9 100644 --- a/src/signal.h +++ b/src/signal.h @@ -35,24 +35,10 @@ namespace VISEQ::SIGNAL { int midi_note_offset = 0; int midi_cc_offset = 0; - void reset(){ - toStart(); - } - - void toStart(){ - position = start.lock()->position; - } - - void update(double t_delta){ - // 120px / 16th = 1920px / whole note - // whole note = 2s at 120bpm - // 960px / s - if(run) position += direction.norm() * speed * multiplier * (float)t_delta / 1000000.0f; - } - - void draw(Vec2Df mouse, Camera2D cam) { - DrawCircle(position.x, position.y, radius, color); - } + void reset(); + void toStart(); + void update(double t_delta); + void draw(Vec2Df mouse, Camera2D cam) ; }; } diff --git a/src/state-io.h b/src/state-io.h index 3eb260d..ccd7154 100644 --- a/src/state-io.h +++ b/src/state-io.h @@ -3,7 +3,6 @@ #include "config-node.h" #include "midi-node.h" #include "graph-item.h" -#include "node-handle.h" #include "node.h" #include "edge.h" @@ -35,27 +34,6 @@ namespace nlohmann { } } }; - - /*template */ - /*struct adl_serializer> {*/ - /* template */ - /* static void to_json(J& j, const std::shared_ptr& opt) {*/ - /* if (opt) {*/ - /* j = *opt;*/ - /* } else {*/ - /* j = nullptr;*/ - /* }*/ - /* }*/ - /**/ - /* template */ - /* static void from_json(const J& j, std::shared_ptr& opt) {*/ - /* if (j.is_null()) {*/ - /* opt = nullptr;*/ - /* } else {*/ - /* opt.reset(new T(j.template get()));*/ - /* }*/ - /* }*/ - /*};*/ } namespace VISEQ::GRAPH::EDGES { @@ -84,36 +62,12 @@ namespace VISEQ::GRAPH::NODES { /* ------------------- MIDINODE ---------------------- */ - /*inline void to_json(json& j, const std::shared_ptr& N){*/ - /* j = *N;*/ - /*}*/ - /**/ - /*inline void from_json(const json& j, std::shared_ptr& N){*/ - /* N.reset(new MidiNode(j.get()));*/ - /*}*/ - inline void to_json(json& j, const MidiNode::NodeMidiOut& MO){ j = json{{"port_id", MO.i}, {"enabled", MO.enabled}, {"name", MO.name}}; } - /*template< typename T>*/ - /*inline void to_json(json& j, const std::shared_ptr>& NH){*/ - /* j = json{{"position", NH->position}};*/ - /*}*/ - /* ----------------- CONFIG NODE -----------------*/ - - - /*inline void to_json(json& j, const std::shared_ptr& N){*/ - /* j = *N;*/ - /*}*/ - /**/ - /*inline void from_json(const json& j, std::shared_ptr& N){*/ - /* N.reset(new ConfigNode(j.get()));*/ - /*}*/ - /* ------------------- NODE ---------------------- */ - inline void to_json(json& j, const Node& N){ if(N.getNodeType() == NodeType::MIDINODE){ j = dynamic_cast(N); diff --git a/src/state.cpp b/src/state.cpp new file mode 100644 index 0000000..d733e73 --- /dev/null +++ b/src/state.cpp @@ -0,0 +1,73 @@ +#include "state.h" + +#include "interaction-event.h" +#include "raylib.h" +#include "vmath.h" +#include + +namespace VISEQ::APP{ + + void State::pushEvent(InteractionEvent e){ + event_queue.push_back(e); + } + + InteractionEvent pullEvent(){ + if(!event_queue.empty()){ + InteractionEvent e = event_queue.front(); + event_queue.pop_front(); + return e; + } + return {nullptr, Interaction::NONE}; + } + + void State::update(Camera2D camera){ + + if(istate != old_istate) std::cout << state_string[istate] << std::endl; + + mouse_delta = {GetMouseDelta().x, GetMouseDelta().y}; + + settings.speed = (settings.grid * 16.0f) * (settings.bpm / 120.f); + + Vector2 mp = GetScreenToWorld2D(GetMousePosition(), camera); + Vec2Df mouse_pos = {(float)mp.x, (float)mp.y}; + + mouse_left.down = IsMouseButtonDown(MOUSE_BUTTON_LEFT); + mouse_left.clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); + mouse_left.released = IsMouseButtonReleased(MOUSE_BUTTON_LEFT); + + mouse_right.down = IsMouseButtonDown(MOUSE_BUTTON_RIGHT); + mouse_right.clicked = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); + mouse_right.released = IsMouseButtonReleased(MOUSE_BUTTON_RIGHT); + + keys_down.ctrl = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL); + keys_down.shift = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT); + keys_down.alt = IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT); + + keys_pressed.ctrl = IsKeyPressed(KEY_LEFT_CONTROL) || IsKeyPressed(KEY_RIGHT_CONTROL); + keys_pressed.shift = IsKeyPressed(KEY_LEFT_SHIFT) || IsKeyPressed(KEY_RIGHT_SHIFT); + keys_pressed.alt = IsKeyPressed(KEY_LEFT_ALT) || IsKeyPressed(KEY_RIGHT_ALT); + + keys_released.ctrl = IsKeyReleased(KEY_LEFT_CONTROL) || IsKeyReleased(KEY_RIGHT_CONTROL); + keys_released.shift = IsKeyReleased(KEY_LEFT_SHIFT) || IsKeyReleased(KEY_RIGHT_SHIFT); + keys_released.alt = IsKeyReleased(KEY_LEFT_ALT) || IsKeyReleased(KEY_RIGHT_ALT); + + screen_mouse = {round(mouse_pos.x), round(mouse_pos.y)}; + raw_mouse = {GetMousePosition().x, GetMousePosition().y}; + mod_mouse = {round(mouse_pos.x), round(mouse_pos.y)}; + + mode_color = settings.mode_colors[node_mode]; + + if(keys_down.ctrl) { + mod_mouse.x = round(mouse_pos.x / settings.grid) * settings.grid; + mod_mouse.y = round(mouse_pos.y / settings.grid) * settings.grid; + } + old_istate = istate; + } + + inline static std::shared_ptr get() { + if(!State::instance){ + State::instance.reset(new State()); + } + return State::instance; + } +} diff --git a/src/state.h b/src/state.h index 5f8944a..a2926ce 100644 --- a/src/state.h +++ b/src/state.h @@ -5,7 +5,6 @@ #include "raylib.h" #include "vmath.h" #include -#include #define STATE state->istate #define SETTINGS state->settings @@ -88,70 +87,12 @@ namespace VISEQ::APP{ Color mode_color = settings.mode_colors[0]; - void pushEvent(InteractionEvent e){ - event_queue.push_back(e); - } - - InteractionEvent pullEvent(){ - if(!event_queue.empty()){ - InteractionEvent e = event_queue.front(); - event_queue.pop_front(); - return e; - } - return {nullptr, Interaction::NONE}; - } - - void update(Camera2D camera){ - - if(istate != old_istate) std::cout << state_string[istate] << std::endl; - - mouse_delta = {GetMouseDelta().x, GetMouseDelta().y}; - - settings.speed = (settings.grid * 16.0f) * (settings.bpm / 120.f); - - Vector2 mp = GetScreenToWorld2D(GetMousePosition(), camera); - Vec2Df mouse_pos = {(float)mp.x, (float)mp.y}; - - mouse_left.down = IsMouseButtonDown(MOUSE_BUTTON_LEFT); - mouse_left.clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); - mouse_left.released = IsMouseButtonReleased(MOUSE_BUTTON_LEFT); - - mouse_right.down = IsMouseButtonDown(MOUSE_BUTTON_RIGHT); - mouse_right.clicked = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); - mouse_right.released = IsMouseButtonReleased(MOUSE_BUTTON_RIGHT); - - keys_down.ctrl = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL); - keys_down.shift = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT); - keys_down.alt = IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT); - - keys_pressed.ctrl = IsKeyPressed(KEY_LEFT_CONTROL) || IsKeyPressed(KEY_RIGHT_CONTROL); - keys_pressed.shift = IsKeyPressed(KEY_LEFT_SHIFT) || IsKeyPressed(KEY_RIGHT_SHIFT); - keys_pressed.alt = IsKeyPressed(KEY_LEFT_ALT) || IsKeyPressed(KEY_RIGHT_ALT); - - keys_released.ctrl = IsKeyReleased(KEY_LEFT_CONTROL) || IsKeyReleased(KEY_RIGHT_CONTROL); - keys_released.shift = IsKeyReleased(KEY_LEFT_SHIFT) || IsKeyReleased(KEY_RIGHT_SHIFT); - keys_released.alt = IsKeyReleased(KEY_LEFT_ALT) || IsKeyReleased(KEY_RIGHT_ALT); - - screen_mouse = {round(mouse_pos.x), round(mouse_pos.y)}; - raw_mouse = {GetMousePosition().x, GetMousePosition().y}; - mod_mouse = {round(mouse_pos.x), round(mouse_pos.y)}; - - mode_color = settings.mode_colors[node_mode]; - - if(keys_down.ctrl) { - mod_mouse.x = round(mouse_pos.x / settings.grid) * settings.grid; - mod_mouse.y = round(mouse_pos.y / settings.grid) * settings.grid; - } - old_istate = istate; - } + void pushEvent(InteractionEvent e); + InteractionEvent pullEvent(); + void update(Camera2D camera); inline static std::shared_ptr instance; - static std::shared_ptr get() { - if(!instance){ - instance.reset(new State()); - } - return instance; - } + inline static std::shared_ptr get(); }; } diff --git a/src/ui.cpp b/src/ui.cpp new file mode 100644 index 0000000..278f6f3 --- /dev/null +++ b/src/ui.cpp @@ -0,0 +1,196 @@ +#include "ui.h" + +#include "state.h" +#include "midi-node.h" +#include "raylib.h" +#include "raygui/raygui.h" +#include "raygui/dark/style_dark.h" + +#include "datatypes.h" + +#include +#include + +namespace VISEQ::APP::UI{ + void AppUI::init(){ + GuiLoadStyleDark(); + } + + void AppUI::drawSideBar(){ + + GuiPanel((Rectangle){-1,-30,380,(float)state->settings.height + 31}, ""); + const int s_controls = 25; + //GuiSetStyle(BUTTON, STATE_FOCUSED, 0xFF0000FF); + if(GuiButton((Rectangle){20, (float)s_controls, 80, 30}, "PLAY")) sm->play ? sm->pause() : sm->start(); + if(GuiButton((Rectangle){120, (float)s_controls, 60, 30}, "STOP")) sm->stop(); + if(GuiButton((Rectangle){200, (float)s_controls, 60, 30}, "RESET")) sm->reset(); + + const int s_bpm = s_controls + 45; + GuiLabel((Rectangle){20, (float)s_bpm, 120, 20}, "TIMING"); + GuiLabel((Rectangle){200, (float)s_bpm, 40, 20}, std::format("{}",state->settings.bpm).c_str()); + int _bpm = state->settings.bpm; +// GuiSlider((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", "", &_bpm, 1, 300); + if(GuiSpinner((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", &_bpm, 1, 300, bpm_spinner_edit_mode)) bpm_spinner_edit_mode = !bpm_spinner_edit_mode; + state->settings.bpm = _bpm; + + int s_midi = s_bpm + 75; + GuiLabel((Rectangle){20, (float)s_midi, 240, 20}, "MIDI OUT"); + if(GuiButton((Rectangle){200, (float)s_midi, 60, 20}, "RELOAD")) sm->midi.enumerate(); + + int i = 0; + for(auto& mo : sm->midi.midi_out_ports){ + GuiCheckBox((Rectangle){20, (float)s_midi + 25 + i*20.0f, 20, 20}, mo.port.port_name.substr(0,32).c_str(), &mo.enabled); + i++; + } + + if(GuiButton((Rectangle){20, (float)s_midi + 45 + i*20.0f, 240, 20}, "ENABLE FOR ALL")) { + for(auto &midis : sm->midi.midi_out_ports){ + if(midis.enabled){ + gm->setMidiPortForAll(midis.number, true); + } + } + } + + int s_signal = s_midi + 100 + i*20; + GuiLabel((Rectangle){20, (float)s_signal, 240, 20}, "SIGNALS"); + if(GuiButton((Rectangle){20, (float)s_signal + 35, 240, 20}, "ADD RANDOM SIGNAL")){ + std::shared_ptr ni = gm->getRandomNode(); + sm->addSignalAtNode(ni); + } + + int signal_configs = 0; + for(auto &s : sm->signals){ + if(GuiButton((Rectangle){20,(float)s_signal+70 + signal_configs*20, 20, 20},"X")) sm->removeSignal(s); + DrawCircle(55, s_signal+80 + signal_configs*20, 6.0F, s->color); + + int s_speed = round(s->speed); + s->multiplier = state->settings.bpm / 120.0f; + float signal_bpm = round(state->settings.bpm * s->multiplier * (s->speed / state->settings.speed)); + if(GuiSpinner((Rectangle){70, (float)s_signal+70 + signal_configs*20, 100, 20}, "", &s_speed, 1, 16 * state->settings.grid * 4, s->ui_edit_speed)) s->ui_edit_speed = !s->ui_edit_speed; + if(GuiButton((Rectangle){200,(float)s_signal+70 + signal_configs*20, 30, 20}, "1/2")) s_speed = state->settings.speed / 2; + //if(GuiButton((Rectangle){200,(float)s_signal+70 + signal_configs*20, 30, 20},"1/4")) s_speed = state->settings.speed / 4; + GuiLabel((Rectangle){195, (float)s_signal+70 + signal_configs*20, 30, 20}, std::format("{}",signal_bpm).c_str()); + GuiToggle((Rectangle){240, (float)s_signal+70 + signal_configs*20, 20, 20},"P", &s->run); + s->speed = s_speed; + signal_configs++; + } + + int s_nodes = s_signal + 70 + signal_configs*20; + GuiLabel((Rectangle){20, (float)s_nodes + 30, 240, 20}, "NODES"); + if(GuiButton((Rectangle){20, (float)s_nodes + 60, 240, 20}, "ADD RANDOM NODE")){ + gm->createMidiNode(Vec2Df{200.0f, 200.0f}); + } + DrawText(std::format("{}", gm->getNodesSize()).c_str(), 20, state->settings.height - 70, 15, VS_COLOR_WHITE); + DrawFPS(20, state->settings.height - 30); + + std::string shorts[6] = {"NT","CC","PC","PB","AT","PP"}; + for(int i = 0; i < 6; ++i){ + Color col = state->settings.mode_colors[i]; + Vec2Df pos = {state->settings.width - (6.0f*75.0f) + i*75.0f, state->settings.height - 75.0f}; + DrawRing(pos, 30.0f, 34.0f, 0.0f, 360.0f, 24, state->node_mode == i ? VS_COLOR_WHITE : (Color){0,0,0,0}); + DrawCircleV(pos, 30.0f, col); + DrawCenteredText(shorts[i], pos, 20, WHITE); + } + } + + void AppUI::DrawCenteredText(std::string text, Vec2Df position, int size, Color color){ + float offset = MeasureText(text.c_str(), size); + DrawText(text.c_str(), position.x-offset/2.0f, position.y-(float)size/2.0f+1, size, color ); + } + + void AppUI::drawContextMenu(Camera2D camera){ + for(auto n : gm->getSelection() | std::views::filter([](std::shared_ptr n){ return n->config; })){ + Vector2 np = GetWorldToScreen2D((Vector2){n->position.x, n->position.y}, camera); + + state->context_menu = !GuiWindowBox((Rectangle){np.x + 10, np.y + 10, 240, 440}, "config"); + n->config = state->context_menu; + + int option = n->distributionMode; + GuiToggleGroup((Rectangle){ np.x + 20, np.y + 40, 73, 20 }, "GEO;RR;RAND", &option); + n->distributionMode = (Distribution)option; + + GuiLabel((Rectangle){np.x + 20, np.y + 65, 220, 20}, "SIGNALS"); + if(GuiButton((Rectangle){np.x + 20, np.y + 90, 220, 20}, "ADD SIGNAL")){ + sm->addSignalAtNode(n); + } + + int y_midi = 120; + GuiLabel((Rectangle){np.x + 20, np.y + y_midi, 220, 20}, "MIDI OUT"); + + int i = 0; + if(n->getNodeType() == NodeType::MIDINODE){ + std::shared_ptr mn = std::dynamic_pointer_cast(n); + for(auto& mo : mn->midi_outs){ + GuiCheckBox((Rectangle){np.x + 20, np.y + y_midi + 20 + i*20.0f, 20, 20}, mo.second.name.c_str(), &mo.second.enabled); + i++; + } + + int y_channel = y_midi + 30 + i*20; + GuiCheckBox((Rectangle){np.x + 20, np.y + y_channel, 20, 20}, "NOTE", &mn->send_note); + GuiCheckBox((Rectangle){np.x + 90, np.y + y_channel, 20, 20}, "CC", &mn->send_cc); + GuiCheckBox((Rectangle){np.x + 160, np.y + y_channel, 20, 20}, "PITCH", &mn->send_pitch_bend); + + int y_cc_note = y_channel + 120; + switch(state->node_mode){ + case 0:{ + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "NOTE CHANNEL"); + GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midiNote.channel); + + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "NOTE"); + float v = mn->midiNote.note; + GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note , 20, 20}, MIDI::Notes[(int)round(v)].c_str()); + GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midiNote.note).c_str()); + GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 127); + mn->midiNote.note = round(v); + + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 60, 100, 20}, "VELOCITY"); + v = mn->midiNote.velocity; + GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 60, 20, 20}, std::format("{}",mn->midiNote.velocity).c_str()); + GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 90, 220, 20}, "", "", &v, 0, 127); + mn->midiNote.velocity = round(v); + + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 120, 100, 20}, "GATE"); + v = mn->midiNote.gate; + GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 120, 20, 20}, std::format("{}",(float)mn->midiNote.gate/1000.0f).c_str()); + GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 150, 220, 20}, "", "", &v, 0, 30000); + mn->midiNote.gate= round(v); + break;} + case 1: + { + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "CC CHANNEL"); + GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midiCC.channel); + + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "CC"); + float v = mn->midiCC.control; + GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midiCC.control).c_str()); + GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 127); + mn->midiCC.control = round(v); + + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 60, 100, 20}, "CC VALUE"); + v = mn->midiCC.value; + GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 60, 20, 20}, std::format("{}",mn->midiCC.value).c_str()); + GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 90, 220, 20}, "", "", &v, 0, 127); + mn->midiCC.value = round(v); + + break;} + case 2: + { + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "PITCH BEND CHANNEL"); + GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midiPitchBend.channel); + + GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "CC"); + float v = mn->midiPitchBend.value; + GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midiPitchBend.value).c_str()); + GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 16383); + mn->midiPitchBend.value = round(v); + break;} + } + } + } + } + + void AppUI::draw(Vec2Df mouse_pos, Camera2D camera){ + drawSideBar(); + if(state->context_menu) drawContextMenu(camera); + } +} diff --git a/src/ui.h b/src/ui.h index 7bdc049..06d443d 100644 --- a/src/ui.h +++ b/src/ui.h @@ -1,17 +1,14 @@ #pragma once #include "state.h" -#include "midi-node.h" #include "raylib.h" #include "raygui/raygui.h" #include "raygui/dark/style_dark.h" -#include "datatypes.h" #include "graph-manager.h" #include "signal-manager.h" #include -#include namespace VISEQ::APP::UI{ class AppUI{ @@ -26,187 +23,10 @@ namespace VISEQ::APP::UI{ bool bpm_spinner_edit_mode = false; - void init(){ - GuiLoadStyleDark(); - } - - void drawSideBar(){ - - GuiPanel((Rectangle){-1,-30,380,(float)state->settings.height + 31}, ""); - const int s_controls = 25; - //GuiSetStyle(BUTTON, STATE_FOCUSED, 0xFF0000FF); - if(GuiButton((Rectangle){20, (float)s_controls, 80, 30}, "PLAY")) sm->play ? sm->pause() : sm->start(); - if(GuiButton((Rectangle){120, (float)s_controls, 60, 30}, "STOP")) sm->stop(); - if(GuiButton((Rectangle){200, (float)s_controls, 60, 30}, "RESET")) sm->reset(); - - const int s_bpm = s_controls + 45; - GuiLabel((Rectangle){20, (float)s_bpm, 120, 20}, "TIMING"); - GuiLabel((Rectangle){200, (float)s_bpm, 40, 20}, std::format("{}",state->settings.bpm).c_str()); - int _bpm = state->settings.bpm; -// GuiSlider((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", "", &_bpm, 1, 300); - if(GuiSpinner((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", &_bpm, 1, 300, bpm_spinner_edit_mode)) bpm_spinner_edit_mode = !bpm_spinner_edit_mode; - state->settings.bpm = _bpm; - - int s_midi = s_bpm + 75; - GuiLabel((Rectangle){20, (float)s_midi, 240, 20}, "MIDI OUT"); - if(GuiButton((Rectangle){200, (float)s_midi, 60, 20}, "RELOAD")) sm->midi.enumerate(); - - int i = 0; - for(auto& mo : sm->midi.midi_out_ports){ - GuiCheckBox((Rectangle){20, (float)s_midi + 25 + i*20.0f, 20, 20}, mo.port.port_name.substr(0,32).c_str(), &mo.enabled); - i++; - } - - if(GuiButton((Rectangle){20, (float)s_midi + 45 + i*20.0f, 240, 20}, "ENABLE FOR ALL")) { - for(auto &midis : sm->midi.midi_out_ports){ - if(midis.enabled){ - gm->setMidiPortForAll(midis.number, true); - } - } - } - - int s_signal = s_midi + 100 + i*20; - GuiLabel((Rectangle){20, (float)s_signal, 240, 20}, "SIGNALS"); - if(GuiButton((Rectangle){20, (float)s_signal + 35, 240, 20}, "ADD RANDOM SIGNAL")){ - std::shared_ptr ni = gm->getRandomNode(); - sm->addSignalAtNode(ni); - } - - int signal_configs = 0; - for(auto &s : sm->signals){ - if(GuiButton((Rectangle){20,(float)s_signal+70 + signal_configs*20, 20, 20},"X")) sm->removeSignal(s); - DrawCircle(55, s_signal+80 + signal_configs*20, 6.0F, s->color); - - int s_speed = round(s->speed); - s->multiplier = state->settings.bpm / 120.0f; - float signal_bpm = round(state->settings.bpm * s->multiplier * (s->speed / state->settings.speed)); - if(GuiSpinner((Rectangle){70, (float)s_signal+70 + signal_configs*20, 100, 20}, "", &s_speed, 1, 16 * state->settings.grid * 4, s->ui_edit_speed)) s->ui_edit_speed = !s->ui_edit_speed; - if(GuiButton((Rectangle){200,(float)s_signal+70 + signal_configs*20, 30, 20}, "1/2")) s_speed = state->settings.speed / 2; - //if(GuiButton((Rectangle){200,(float)s_signal+70 + signal_configs*20, 30, 20},"1/4")) s_speed = state->settings.speed / 4; - GuiLabel((Rectangle){195, (float)s_signal+70 + signal_configs*20, 30, 20}, std::format("{}",signal_bpm).c_str()); - GuiToggle((Rectangle){240, (float)s_signal+70 + signal_configs*20, 20, 20},"P", &s->run); - s->speed = s_speed; - signal_configs++; - } - - int s_nodes = s_signal + 70 + signal_configs*20; - GuiLabel((Rectangle){20, (float)s_nodes + 30, 240, 20}, "NODES"); - if(GuiButton((Rectangle){20, (float)s_nodes + 60, 240, 20}, "ADD RANDOM NODE")){ - gm->createMidiNode(Vec2Df{200.0f, 200.0f}); - } - DrawText(std::format("{}", gm->getNodesSize()).c_str(), 20, state->settings.height - 70, 15, VS_COLOR_WHITE); - DrawFPS(20, state->settings.height - 30); - - std::string shorts[6] = {"NT","CC","PC","PB","AT","PP"}; - for(int i = 0; i < 6; ++i){ - Color col = state->settings.mode_colors[i]; - Vec2Df pos = {state->settings.width - (6.0f*75.0f) + i*75.0f, state->settings.height - 75.0f}; - DrawRing(pos, 30.0f, 34.0f, 0.0f, 360.0f, 24, state->node_mode == i ? VS_COLOR_WHITE : (Color){0,0,0,0}); - DrawCircleV(pos, 30.0f, col); - DrawCenteredText(shorts[i], pos, 20, WHITE); - } - } - - void DrawCenteredText(std::string text, Vec2Df position, int size, Color color){ - float offset = MeasureText(text.c_str(), size); - DrawText(text.c_str(), position.x-offset/2.0f, position.y-(float)size/2.0f+1, size, color ); - } - - void drawContextMenu(Camera2D camera){ - for(auto n : gm->getSelection() | std::views::filter([](std::shared_ptr n){ return n->config; })){ - Vector2 np = GetWorldToScreen2D((Vector2){n->position.x, n->position.y}, camera); - - state->context_menu = !GuiWindowBox((Rectangle){np.x + 10, np.y + 10, 240, 440}, "config"); - n->config = state->context_menu; - - int option = n->distributionMode; - GuiToggleGroup((Rectangle){ np.x + 20, np.y + 40, 73, 20 }, "GEO;RR;RAND", &option); - n->distributionMode = (Distribution)option; - - GuiLabel((Rectangle){np.x + 20, np.y + 65, 220, 20}, "SIGNALS"); - if(GuiButton((Rectangle){np.x + 20, np.y + 90, 220, 20}, "ADD SIGNAL")){ - sm->addSignalAtNode(n); - } - - int y_midi = 120; - GuiLabel((Rectangle){np.x + 20, np.y + y_midi, 220, 20}, "MIDI OUT"); - - int i = 0; - if(n->getNodeType() == NodeType::MIDINODE){ - std::shared_ptr mn = std::dynamic_pointer_cast(n); - for(auto& mo : mn->midi_outs){ - GuiCheckBox((Rectangle){np.x + 20, np.y + y_midi + 20 + i*20.0f, 20, 20}, mo.second.name.c_str(), &mo.second.enabled); - i++; - } - - int y_channel = y_midi + 30 + i*20; - GuiCheckBox((Rectangle){np.x + 20, np.y + y_channel, 20, 20}, "NOTE", &mn->send_note); - GuiCheckBox((Rectangle){np.x + 90, np.y + y_channel, 20, 20}, "CC", &mn->send_cc); - GuiCheckBox((Rectangle){np.x + 160, np.y + y_channel, 20, 20}, "PITCH", &mn->send_pitch_bend); - - int y_cc_note = y_channel + 120; - switch(state->node_mode){ - case 0:{ - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "NOTE CHANNEL"); - GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midiNote.channel); - - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "NOTE"); - float v = mn->midiNote.note; - GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note , 20, 20}, MIDI::Notes[(int)round(v)].c_str()); - GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midiNote.note).c_str()); - GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 127); - mn->midiNote.note = round(v); - - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 60, 100, 20}, "VELOCITY"); - v = mn->midiNote.velocity; - GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 60, 20, 20}, std::format("{}",mn->midiNote.velocity).c_str()); - GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 90, 220, 20}, "", "", &v, 0, 127); - mn->midiNote.velocity = round(v); - - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 120, 100, 20}, "GATE"); - v = mn->midiNote.gate; - GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 120, 20, 20}, std::format("{}",(float)mn->midiNote.gate/1000.0f).c_str()); - GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 150, 220, 20}, "", "", &v, 0, 30000); - mn->midiNote.gate= round(v); - break;} - case 1: - { - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "CC CHANNEL"); - GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midiCC.channel); - - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "CC"); - float v = mn->midiCC.control; - GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midiCC.control).c_str()); - GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 127); - mn->midiCC.control = round(v); - - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 60, 100, 20}, "CC VALUE"); - v = mn->midiCC.value; - GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 60, 20, 20}, std::format("{}",mn->midiCC.value).c_str()); - GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 90, 220, 20}, "", "", &v, 0, 127); - mn->midiCC.value = round(v); - - break;} - case 2: - { - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "PITCH BEND CHANNEL"); - GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midiPitchBend.channel); - - GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "CC"); - float v = mn->midiPitchBend.value; - GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midiPitchBend.value).c_str()); - GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 16383); - mn->midiPitchBend.value = round(v); - break;} - } - } - } - } - - void draw(Vec2Df mouse_pos, Camera2D camera){ - drawSideBar(); - if(state->context_menu) drawContextMenu(camera); - } - + void init(); + void drawSideBar(); + void drawContextMenu(Camera2D camera); + void draw(Vec2Df mouse_pos, Camera2D camera); + void DrawCenteredText(std::string text, Vec2Df position, int size, Color color); }; } From d881ef43a35e3d73bd0d35f64032ff949c11727b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 12 Jan 2026 00:16:36 +0100 Subject: [PATCH 3/9] compiling but mouse interaction is dead --- Makefile | 24 +---- src/Makefile | 40 +++++++++ src/app-manager.cpp | 10 +++ src/app-manager.h | 9 +- src/config-node.cpp | 8 ++ src/config-node.h | 3 + src/edge.cpp | 36 ++++++++ src/edge.h | 17 ++-- src/graph-manager.cpp | 8 ++ src/graph-manager.h | 3 + src/main.cpp | 1 - src/midi-node.cpp | 8 ++ src/midi-node.h | 8 +- src/node.cpp | 36 ++++++++ src/node.h | 8 +- src/signal-manager.cpp | 198 ++++++++++++++++++++++------------------- src/signal-manager.h | 5 +- src/signal.cpp | 30 +++++++ src/signal.h | 8 +- src/state-io.h | 166 ++++++++++------------------------ src/state.cpp | 2 +- src/state.h | 2 +- src/ui.cpp | 1 - 23 files changed, 374 insertions(+), 257 deletions(-) create mode 100644 src/Makefile create mode 100644 src/edge.cpp diff --git a/Makefile b/Makefile index 343962e..a639f4a 100644 --- a/Makefile +++ b/Makefile @@ -1,32 +1,16 @@ CXX = g++ -CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1 -CXXINC = -isysteminclude LDLIBS = -lraylib -lasound -llibremidi -pthread LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib -hdr_files = ${wildcard src/*.h} - -src_files = ${wildcard src/*.cpp} -#src_files += ${wildcard include/libremidi/*.cpp} +hdr_files = $(wildcard src/*.h) all: viseq -viseq: $(src_files) $(hdr_files) +viseq: mkdir -p build - $(CXX) ${wildcard build/*.o} -o viseq $(LDFLAGS) $(LDLIBS) - -$(src_files): %.cpp : %_src - $(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC) - -$(hdr_files): %.h : %_hdr - $(NOECHO) $(NOOP) - -%_src: - $(NOECHO) $(NOOP) - -%_hdr: - $(NOECHO) $(NOOP) + cd src; $(MAKE) + $(CXX) $(wildcard build/*.o) -o viseq $(LDFLAGS) $(LDLIBS) clean: rm -f build/* diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..ba078b7 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,40 @@ +CXX = g++ +CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1 +CXXINC = -I../include -isysteminclude + +obj_files = main.o app-manager.o graph-manager.o signal-manager.o interaction-manager.o ui.o node.o midi-node.o config-node.o modifier-node.o midi.o signal.o canvas.o state.o edge.o + + +all: $(obj_files) + +$(obj_files): %.o : %.cpp + $(CXX) -c $^ -o ../build/$@ $(CXXFLAGS) $(CXXINC) + +#all: viseq + +#viseq: main.o app-manager.o graph-manager.o signal-manager.o interaction-manager.o ui.o node.o midi-node.o config-node.o modifier-node.o midi.o signal.o canvas.o state.o edge.o +# $(CXX) ${wildcard build/*.o} -o viseq $(LDFLAGS) $(LDLIBS) + +#main.o: +# $(CXX) -c src/main.cpp -o ../build/main.o $(CXXFLAGS) $(CXXINC) +# +#app-manager.o: +# $(CXX) -c src/app-manager.cpp -o ../build/app-manager.o $(CXXFLAGS) $(CXXINC) + + + +#src/canvas.cpp +#src/signal.cpp +#src/midi.cpp +#src/node.cpp +#src/graph-manager.cpp +#src/modifier-node.cpp +#src/config-node.cpp +#src/midi-node.cpp +#src/ui.cpp +#src/state.cpp +#src/edge.cpp +#src/interaction-manager.cpp +#src/signal-manager.cpp +#src/app-manager.cpp +#src/main.cpp diff --git a/src/app-manager.cpp b/src/app-manager.cpp index 2d8ca17..89abaa8 100644 --- a/src/app-manager.cpp +++ b/src/app-manager.cpp @@ -1,5 +1,15 @@ #include "app-manager.h" +#include "interaction-manager.h" +#include "nlohmann/json.hpp" +#include "graph-manager.h" +#include "midi-node.h" +#include "state.h" +#include "ui.h" + +#include +#include + namespace VISEQ::APP{ void AppManager::init(){ canvas->camera.target = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f }; diff --git a/src/app-manager.h b/src/app-manager.h index 6ae03fc..c0f80f3 100644 --- a/src/app-manager.h +++ b/src/app-manager.h @@ -1,15 +1,10 @@ #pragma once -#include -#include #include "interaction-manager.h" #include "nlohmann/json.hpp" -#include "datatypes.h" #include "canvas.h" #include "graph-manager.h" #include "midi-node.h" -#include "node.h" -#include "raylib.h" #include "signal-manager.h" #include "state.h" #include "ui.h" @@ -34,8 +29,6 @@ namespace VISEQ::APP { UI::AppUI ui; - NLOHMANN_DEFINE_TYPE_INTRUSIVE(AppManager, gm, sm); - void init(); void close(); void startSignals(); @@ -45,5 +38,7 @@ namespace VISEQ::APP { void draw(); void drawUI(); }; + + NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AppManager, gm, sm) } diff --git a/src/config-node.cpp b/src/config-node.cpp index 660a0bf..5557bc5 100644 --- a/src/config-node.cpp +++ b/src/config-node.cpp @@ -282,4 +282,12 @@ namespace VISEQ::GRAPH::NODES { //j.at("connections").get_to(N.connections); } + + void to_json(json& j, const std::shared_ptr& N){ + j = *N; + } + + void from_json(const json& j, std::shared_ptr& N){ + N.reset(new ConfigNode(j.get())); + } }; diff --git a/src/config-node.h b/src/config-node.h index 477afc2..952f3a4 100644 --- a/src/config-node.h +++ b/src/config-node.h @@ -42,7 +42,10 @@ namespace VISEQ::GRAPH::NODES { void update() override; void interact(); void draw(Vec2Df mouse, Camera2D cam) override; + friend void to_json(json& j, const ConfigNode& N); friend void from_json(const json& j, ConfigNode& N); + friend void to_json(json& j, const std::shared_ptr& N); + friend void from_json(const json& j, std::shared_ptr& N); }; } diff --git a/src/edge.cpp b/src/edge.cpp new file mode 100644 index 0000000..9e4dd70 --- /dev/null +++ b/src/edge.cpp @@ -0,0 +1,36 @@ +#include "edge.h" + +#include +#include "node.h" + +namespace VISEQ::GRAPH::EDGES { + bool Edge::GActive() {return active;} + + EdgeType Edge::getEdgeType() const {return edgeType;} + + std::weak_ptr Edge::getNextNode(std::weak_ptr origin){ + return start.lock() == origin.lock() ? end : start; + } + + void Edge::draw(Vec2Df mouse_pos, Camera2D cam) {} + + void to_json(json& j, const VISEQ::GRAPH::EDGES::Edge& E){ + j = json{{"id",E.id}, {"start", E.start.lock()->id}, {"end",E.end.lock()->id}, {"pass_forward",E.pass_forward}, {"pass_backward", E.pass_backward}}; + } + + void from_json(const json& j, Edge& E){ + j.at("id").get_to(E.id); + j.at("pass_forward").get_to(E.pass_forward); + j.at("pass_backward").get_to(E.pass_backward); + } + + void to_json(json& j, const VISEQ::GRAPH::EDGES::SPTR_Edge& E){ + j = *E; + } + + void from_json(const json& j, VISEQ::GRAPH::EDGES::SPTR_Edge& E){ + E.reset(new Edge(j.get())); + } + + +} diff --git a/src/edge.h b/src/edge.h index 4515109..24517f2 100644 --- a/src/edge.h +++ b/src/edge.h @@ -35,15 +35,14 @@ namespace VISEQ::GRAPH::EDGES { float connector_offset = 8.0f; float switches_offset = 25.0f; - virtual bool GActive() {return active;} - - virtual EdgeType getEdgeType() const {return edgeType;} - - std::weak_ptr getNextNode(std::weak_ptr origin){ - return start.lock() == origin.lock() ? end : start; - } - - void draw(Vec2Df mouse_pos, Camera2D cam) {} + virtual bool GActive(); + virtual EdgeType getEdgeType() const; + std::weak_ptr getNextNode(std::weak_ptr origin); + void draw(Vec2Df mouse_pos, Camera2D cam); + friend void to_json(json& j, const Edge& E); + friend void from_json(const json& j, Edge& E); + friend void to_json(json& j, const std::shared_ptr& E); + friend void from_json(const json& j, std::shared_ptr& E); }; typedef std::shared_ptr SPTR_Edge; diff --git a/src/graph-manager.cpp b/src/graph-manager.cpp index 1dab649..ce6731e 100644 --- a/src/graph-manager.cpp +++ b/src/graph-manager.cpp @@ -460,4 +460,12 @@ namespace VISEQ::GRAPH{ n->draw(mouse_pos, camera); } } + + void to_json(json& j, const std::shared_ptr& G){ + j = *G; + } + + void from_json(const json& j, std::shared_ptr& G){ + G.reset(new GraphManager(j.get())); + } } diff --git a/src/graph-manager.h b/src/graph-manager.h index a34dbb5..4e0c86d 100644 --- a/src/graph-manager.h +++ b/src/graph-manager.h @@ -80,6 +80,9 @@ namespace VISEQ::GRAPH { void drawNodes(Vec2Df mouse_pos, Camera2D camera); NLOHMANN_DEFINE_TYPE_INTRUSIVE(GraphManager, nodes, edges, selected_nodes) + + friend void from_json(const json& j, std::shared_ptr& G); + friend void to_json(json& j, const std::shared_ptr& G); }; } diff --git a/src/main.cpp b/src/main.cpp index b9ea6a9..faf141a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,6 @@ #include #include -#include #include diff --git a/src/midi-node.cpp b/src/midi-node.cpp index 960b65f..e36a0a6 100644 --- a/src/midi-node.cpp +++ b/src/midi-node.cpp @@ -387,5 +387,13 @@ namespace VISEQ::GRAPH::NODES { i++; } } + + void to_json(json& j, const std::shared_ptr& N){ + j = *N; + } + + void from_json(const json& j, std::shared_ptr& N){ + N.reset(new MidiNode(j.get())); + } } diff --git a/src/midi-node.h b/src/midi-node.h index a9975bc..e9cc84a 100644 --- a/src/midi-node.h +++ b/src/midi-node.h @@ -85,7 +85,11 @@ namespace VISEQ::GRAPH::NODES { void modMidiPolyPressure(int incr); void interact(); void draw(Vec2Df mouse, Camera2D cam) override ; - friend void to_json(json& j, const MidiNode& N); - friend void from_json(const json& j, MidiNode& N); + + friend void to_json(json& j, const MidiNode& N); + friend void from_json(const json& j, MidiNode& N); + friend void to_json(json& j, const std::shared_ptr& N); + friend void from_json(const json& j, std::shared_ptr& N); }; + } diff --git a/src/node.cpp b/src/node.cpp index d986b81..6735099 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -9,6 +9,9 @@ #include "state.h" #include "vmath.h" +#include "midi-node.h" +#include "config-node.h" + namespace VISEQ::GRAPH::NODES { void Node::setMode(int _mode) {mode = _mode;} @@ -70,4 +73,37 @@ namespace VISEQ::GRAPH::NODES { c->draw(); } } + + void to_json(json& j, const Node& N){ + if(N.getNodeType() == NodeType::MIDINODE){ + j = dynamic_cast(N); + } + if(N.getNodeType() == NodeType::CONFIGNODE){ + j = dynamic_cast(N); + } + } + + void from_json(const json& j, Node& N){ + + if(j.at("nodeType") == NodeType::MIDINODE){ + N = j.template get(); + } + if(j.at("nodeType") == NodeType::CONFIGNODE){ + N = j.template get(); + } + } + + void to_json(json& j, const SPTR_Node& N){ + j = *N; + } + + void from_json(const json& j, std::shared_ptr& N){ + std::cout << "SPTR_Node" << std::endl; + if(N->getNodeType() == NodeType::MIDINODE){ + N.reset(new MidiNode(j.get())); + } + if(N->getNodeType() == NodeType::CONFIGNODE){ + N.reset(new ConfigNode(j.get())); + } + } } diff --git a/src/node.h b/src/node.h index 03a6d6a..5693408 100644 --- a/src/node.h +++ b/src/node.h @@ -18,8 +18,7 @@ namespace VISEQ::GRAPH::NODES { class Node : public BASE::Draggable, public GraphItem, public std::enable_shared_from_this { public: - - Node() {std::cout << "Node constructor" << std::endl;} + Node() {} Node(float x, float y) : Draggable(Vec2Df(x, y)) {} Node(Vec2Df p) : Draggable(p) {} @@ -54,6 +53,11 @@ namespace VISEQ::GRAPH::NODES { void draw(Vec2Df mp, Camera2D cam) override; void draw() override; + friend void to_json(json& j, const Node& N); + friend void from_json(const json& j, Node& N); + friend void to_json(json& j, const std::shared_ptr& N); + friend void from_json(const json& j, std::shared_ptr& N); + }; typedef std::shared_ptr SPTR_Node; diff --git a/src/signal-manager.cpp b/src/signal-manager.cpp index 487fda2..e1fe7ac 100644 --- a/src/signal-manager.cpp +++ b/src/signal-manager.cpp @@ -10,122 +10,132 @@ #include #include +namespace VISEQ::SIGNAL { -void VISEQ::SIGNAL::SignalManager::pause(){ - play = false; -} - -void VISEQ::SIGNAL::SignalManager::stop(){ - play = false; - reset(); -} - -void VISEQ::SIGNAL::SignalManager::start(){ - play = true; -} - -void VISEQ::SIGNAL::SignalManager::reset(){ - for(auto &s : signals){ - s->position = s->start.lock()->position + Vec2Df{0.1, 0.1}; - s->next = s->start; - s->last = s->start; + void SignalManager::pause(){ + play = false; } - gm->reset(); -} -void VISEQ::SIGNAL::SignalManager::startSignalThread(){ - std::thread t1(&SignalManager::updateSignals, this); - t1.detach(); -} + void SignalManager::stop(){ + play = false; + reset(); + } -void VISEQ::SIGNAL::SignalManager::stopSignalThread(){ - loop = false; -} + void SignalManager::start(){ + play = true; + } -void VISEQ::SIGNAL::SignalManager::addSignalAtNode(WPTR_Node node){ - Color c = {(unsigned char)(20 + (rand() % 200)), 100, (unsigned char)(20 + (int)(rand() % 200)), 200}; - signals.emplace_back(std::make_shared(node.lock()->position + Vec2Df{0.5f, 0.5f}, 8.0f, 480, c)); // position + 1 or else it does not render. why? no idea! - signals.back()->next = node; - signals.back()->last = node; - signals.back()->start = node; -} - -void VISEQ::SIGNAL::SignalManager::rerouteSignal(VEC_SPTR_Node nodes, VEC_WPTR_Node adjacent_nodes){ - auto reroute_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->next.lock());}); - - for(auto s : reroute_signals){ - float dist = 10000000; - SPTR_Node next_node; - for(auto n : adjacent_nodes){ - if(n.lock()->position.dist(s->position) < dist) { - dist = n.lock()->position.dist(s->position); - next_node = n.lock(); - } + void SignalManager::reset(){ + for(auto &s : signals){ + s->position = s->start.lock()->position + Vec2Df{0.1, 0.1}; + s->next = s->start; + s->last = s->start; } - s->next = next_node; - s->last = next_node; + gm->reset(); } -} - -void VISEQ::SIGNAL::SignalManager::rebaseSignal(VEC_SPTR_Node nodes){ - auto rebase_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->start.lock());}); - - for(auto s : rebase_signals){ - s->start = s->next; + void SignalManager::startSignalThread(){ + std::thread t1(&SignalManager::updateSignals, this); + t1.detach(); } -} -void VISEQ::SIGNAL::SignalManager::removeSignal(std::shared_ptr s){ - erase_signals.push_back(s); -} + void SignalManager::stopSignalThread(){ + loop = false; + } -void VISEQ::SIGNAL::SignalManager::updateSignals(){ - auto last = std::chrono::high_resolution_clock::now(); - while(loop){ - std::chrono::duration diff = std::chrono::high_resolution_clock::now() - last; - if((int)diff.count() >= 250){ - mutex->lock(); - if(play){ - for(auto& s : signals){ - s->direction = (s->next.lock()->position - s->position).norm(); - s->update(diff.count()); - if(s->position.dist(s->next.lock()->position) < 0.6f) { - WPTR_Node next_node = gm->next(s->next, s->last); - s->next.lock()->trigger(); + void SignalManager::addSignalAtNode(WPTR_Node node){ + Color c = {(unsigned char)(20 + (rand() % 200)), 100, (unsigned char)(20 + (int)(rand() % 200)), 200}; + signals.emplace_back(std::make_shared(node.lock()->position + Vec2Df{0.5f, 0.5f}, 8.0f, 480, c)); // position + 1 or else it does not render. why? no idea! + signals.back()->next = node; + signals.back()->last = node; + signals.back()->start = node; + } - if(next_node.lock() != s->next.lock() && !next_node.expired() && s->next.lock()->getNodeType() == NodeType::MIDINODE) { - auto midinode = std::dynamic_pointer_cast(s->next.lock()); - for(auto &m : midinode->midi_outs){ - if(m.second.enabled) { - if(midinode->send_note) midi.note(m.second.i, midinode->getNotes().channel, (int)Clamp(midinode->getNotes().note + s->midi_note_offset, 0, 127), midinode->getNotes().velocity, midinode->getNotes().gate); - if(midinode->send_cc) midi.cc(m.second.i, midinode->getCC().channel, midinode->getCC().control, (int)Clamp(midinode->getCC().value + s->midi_cc_offset, 0, 127)); - if(midinode->send_pitch_bend) midi.pitch_bend(m.second.i, midinode->getPitchBend().channel, midinode->getPitchBend().value); + void SignalManager::rerouteSignal(VEC_SPTR_Node nodes, VEC_WPTR_Node adjacent_nodes){ + auto reroute_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->next.lock());}); + + for(auto s : reroute_signals){ + float dist = 10000000; + SPTR_Node next_node; + for(auto n : adjacent_nodes){ + if(n.lock()->position.dist(s->position) < dist) { + dist = n.lock()->position.dist(s->position); + next_node = n.lock(); + } + } + s->next = next_node; + s->last = next_node; + } + + } + + void SignalManager::rebaseSignal(VEC_SPTR_Node nodes){ + auto rebase_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->start.lock());}); + + for(auto s : rebase_signals){ + s->start = s->next; + } + } + + void SignalManager::removeSignal(std::shared_ptr s){ + erase_signals.push_back(s); + } + + void SignalManager::updateSignals(){ + auto last = std::chrono::high_resolution_clock::now(); + while(loop){ + std::chrono::duration diff = std::chrono::high_resolution_clock::now() - last; + if((int)diff.count() >= 250){ + mutex->lock(); + if(play){ + for(auto& s : signals){ + s->direction = (s->next.lock()->position - s->position).norm(); + s->update(diff.count()); + if(s->position.dist(s->next.lock()->position) < 0.6f) { + WPTR_Node next_node = gm->next(s->next, s->last); + s->next.lock()->trigger(); + + if(next_node.lock() != s->next.lock() && !next_node.expired() && s->next.lock()->getNodeType() == NodeType::MIDINODE) { + auto midinode = std::dynamic_pointer_cast(s->next.lock()); + for(auto &m : midinode->midi_outs){ + if(m.second.enabled) { + if(midinode->send_note) midi.note(m.second.i, midinode->getNotes().channel, (int)Clamp(midinode->getNotes().note + s->midi_note_offset, 0, 127), midinode->getNotes().velocity, midinode->getNotes().gate); + if(midinode->send_cc) midi.cc(m.second.i, midinode->getCC().channel, midinode->getCC().control, (int)Clamp(midinode->getCC().value + s->midi_cc_offset, 0, 127)); + if(midinode->send_pitch_bend) midi.pitch_bend(m.second.i, midinode->getPitchBend().channel, midinode->getPitchBend().value); + } } + s->last = s->next; + s->next = next_node; } - s->last = s->next; - s->next = next_node; } } } + midi.update(); + mutex->unlock(); + last = std::chrono::high_resolution_clock::now(); } - midi.update(); - mutex->unlock(); - last = std::chrono::high_resolution_clock::now(); } } -} -void VISEQ::SIGNAL::SignalManager::drawSignals(Vec2Df mouse_pos, Camera2D camera){ - mutex->lock(); - if(!erase_signals.empty()){ - for(auto s: erase_signals){ - std::erase(signals, s); + void SignalManager::drawSignals(Vec2Df mouse_pos, Camera2D camera){ + mutex->lock(); + if(!erase_signals.empty()){ + for(auto s: erase_signals){ + std::erase(signals, s); + } + erase_signals.clear(); } - erase_signals.clear(); + for(auto s : signals){ + s->draw(mouse_pos, camera); + } + mutex->unlock(); } - for(auto s : signals){ - s->draw(mouse_pos, camera); + + void to_json(json& j, const std::shared_ptr& G){ + j = *G; + } + + void from_json(const json& j, std::shared_ptr& G){ + G.reset(new SignalManager(j.get())); } - mutex->unlock(); } diff --git a/src/signal-manager.h b/src/signal-manager.h index f29fa2a..005a53a 100644 --- a/src/signal-manager.h +++ b/src/signal-manager.h @@ -50,6 +50,9 @@ namespace VISEQ::SIGNAL { void drawSignals(Vec2Df mouse_pos, Camera2D camera); NLOHMANN_DEFINE_TYPE_INTRUSIVE(SignalManager, signals) - + + friend void from_json(const json& j, std::shared_ptr& G); + friend void to_json(json& j, const std::shared_ptr& G); + }; } diff --git a/src/signal.cpp b/src/signal.cpp index c123514..6058eb5 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -22,4 +22,34 @@ namespace VISEQ::SIGNAL { void Signal::draw(Vec2Df mouse, Camera2D cam) { DrawCircle(position.x, position.y, radius, color); } + + void to_json(json& j, const Signal& S){ + std::map fc = {{"r",S.color.r},{"g",S.color.g},{"b",S.color.b},{"a",S.color.a}}; + j = json{{"color",fc},{"position", S.position},{"radius",S.radius},{"start",S.start.lock()->id},{"next",S.next.lock()->id},{"last",S.last.lock()->id},{"speed",S.speed},{"midi_note_offset",S.midi_note_offset},{"midi_cc_offset",S.midi_cc_offset},{"multiplier",S.multiplier}}; + } + + void from_json(const json& j, Signal& S){ + + Color c; + c.r = j.at("color")["r"].get(); + c.g = j.at("color")["g"].get(); + c.b = j.at("color")["b"].get(); + c.a = j.at("color")["a"].get(); + + j.at("position").get_to(S.position); + j.at("speed").get_to(S.speed); + j.at("radius").get_to(S.radius); + j.at("midi_note_offset").get_to(S.midi_note_offset); + j.at("midi_cc_offset").get_to(S.midi_cc_offset); + + S.color = c; + } + + void to_json(json& j, const std::shared_ptr& S){ + j = *S; + } + + void from_json(const json& j, std::shared_ptr& S){ + S.reset(new Signal(j.get())); + } } diff --git a/src/signal.h b/src/signal.h index 5edb5d9..587faf5 100644 --- a/src/signal.h +++ b/src/signal.h @@ -38,7 +38,13 @@ namespace VISEQ::SIGNAL { void reset(); void toStart(); void update(double t_delta); - void draw(Vec2Df mouse, Camera2D cam) ; + void draw(Vec2Df mouse, Camera2D cam); + + friend void to_json(json& j, const Signal& S); + friend void from_json(const json& j, Signal& S); + friend void to_json(json& j, const std::shared_ptr& S); + friend void from_json(const json& j, std::shared_ptr& S); + }; } diff --git a/src/state-io.h b/src/state-io.h index ccd7154..1bc3b39 100644 --- a/src/state-io.h +++ b/src/state-io.h @@ -15,94 +15,26 @@ typedef std::shared_ptr SPTR_GraphItem; typedef std::vector VEC_SPTR_GraphItem; -namespace nlohmann { - template - struct adl_serializer> { - static void to_json(json& j, const std::shared_ptr& opt) { - if (opt) { - j = *opt; - } else { - j = nullptr; - } - } - - static void from_json(const json& j, std::shared_ptr& opt) { - if (j.is_null()) { - opt = nullptr; - } else { - opt.reset(new T(j.get())); - } - } - }; -} - -namespace VISEQ::GRAPH::EDGES { - using namespace VISEQ::GRAPH::NODES; - - inline void to_json(json& j, const VISEQ::GRAPH::EDGES::Edge& E){ - j = json{{"id",E.id}, {"start", E.start.lock()->id}, {"end",E.end.lock()->id}, {"pass_forward",E.pass_forward}, {"pass_backward", E.pass_backward}}; - } - - inline void from_json(const json& j, Edge& E){ - j.at("id").get_to(E.id); - j.at("pass_forward").get_to(E.pass_forward); - j.at("pass_backward").get_to(E.pass_backward); - } - - inline void to_json(json& j, const VISEQ::GRAPH::EDGES::SPTR_Edge& E){ - j = *E; - } - - inline void from_json(const json& j, VISEQ::GRAPH::EDGES::SPTR_Edge& E){ - E.reset(new Edge(j.get())); - } -} - -namespace VISEQ::GRAPH::NODES { - - /* ------------------- MIDINODE ---------------------- */ - - inline void to_json(json& j, const MidiNode::NodeMidiOut& MO){ - j = json{{"port_id", MO.i}, {"enabled", MO.enabled}, {"name", MO.name}}; - } - - /* ------------------- NODE ---------------------- */ - - inline void to_json(json& j, const Node& N){ - if(N.getNodeType() == NodeType::MIDINODE){ - j = dynamic_cast(N); - } - if(N.getNodeType() == NodeType::CONFIGNODE){ - j = dynamic_cast(N); - } - } - - inline void from_json(const json& j, Node& N){ - - if(j.at("nodeType") == NodeType::MIDINODE){ - N = j.template get(); - } - if(j.at("nodeType") == NodeType::CONFIGNODE){ - N = j.template get(); - } - } - - inline void to_json(json& j, const SPTR_Node& N){ - j = *N; - } - - inline void from_json(const json& j, std::shared_ptr& N){ - std::cout << "SPTR_Node" << std::endl; - if(N->getNodeType() == NodeType::MIDINODE){ - N.reset(new MidiNode(j.get())); - } - if(N->getNodeType() == NodeType::CONFIGNODE){ - N.reset(new ConfigNode(j.get())); - } - } - -} - +//namespace nlohmann { +// template +// struct adl_serializer> { +// static void to_json(json& j, const std::shared_ptr& opt) { +// if (opt) { +// j = *opt; +// } else { +// j = nullptr; +// } +// } +// +// static void from_json(const json& j, std::shared_ptr& opt) { +// if (j.is_null()) { +// opt = nullptr; +// } else { +// opt.reset(new T(j.get())); +// } +// } +// }; +//} namespace VISEQ::GRAPH { inline void to_json(json& j, const GraphItem& G){ @@ -136,35 +68,35 @@ namespace VISEQ::BASE{ } namespace VISEQ::SIGNAL{ - inline void to_json(json& j, const Signal& G){ - std::map fc = {{"r",G.color.r},{"g",G.color.g},{"b",G.color.b},{"a",G.color.a}}; - j = json{{"color",fc},{"position", G.position},{"radius",G.radius},{"start",G.start.lock()->id},{"next",G.next.lock()->id},{"last",G.last.lock()->id},{"speed",G.speed},{"midi_note_offset",G.midi_note_offset},{"midi_cc_offset",G.midi_cc_offset},{"multiplier",G.multiplier}}; - } - - inline void from_json(const json& j, Signal& S){ - - Color c; - c.r = j.at("color")["r"].get(); - c.g = j.at("color")["g"].get(); - c.b = j.at("color")["b"].get(); - c.a = j.at("color")["a"].get(); - - j.at("position").get_to(S.position); - j.at("speed").get_to(S.speed); - j.at("radius").get_to(S.radius); - j.at("midi_note_offset").get_to(S.midi_note_offset); - j.at("midi_cc_offset").get_to(S.midi_cc_offset); - - S.color = c; - } - - inline void to_json(json& j, const std::shared_ptr& G){ - j = *G; - } - - inline void from_json(const json& j, std::shared_ptr& S){ - S.reset(new Signal(j.get())); - } +// inline void to_json(json& j, const Signal& G){ +// std::map fc = {{"r",G.color.r},{"g",G.color.g},{"b",G.color.b},{"a",G.color.a}}; +// j = json{{"color",fc},{"position", G.position},{"radius",G.radius},{"start",G.start.lock()->id},{"next",G.next.lock()->id},{"last",G.last.lock()->id},{"speed",G.speed},{"midi_note_offset",G.midi_note_offset},{"midi_cc_offset",G.midi_cc_offset},{"multiplier",G.multiplier}}; +// } +// +// inline void from_json(const json& j, Signal& S){ +// +// Color c; +// c.r = j.at("color")["r"].get(); +// c.g = j.at("color")["g"].get(); +// c.b = j.at("color")["b"].get(); +// c.a = j.at("color")["a"].get(); +// +// j.at("position").get_to(S.position); +// j.at("speed").get_to(S.speed); +// j.at("radius").get_to(S.radius); +// j.at("midi_note_offset").get_to(S.midi_note_offset); +// j.at("midi_cc_offset").get_to(S.midi_cc_offset); +// +// S.color = c; +// } +// +// inline void to_json(json& j, const std::shared_ptr& G){ +// j = *G; +// } +// +// inline void from_json(const json& j, std::shared_ptr& S){ +// S.reset(new Signal(j.get())); +// } } diff --git a/src/state.cpp b/src/state.cpp index d733e73..2fb8393 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -64,7 +64,7 @@ namespace VISEQ::APP{ old_istate = istate; } - inline static std::shared_ptr get() { + std::shared_ptr State::get() { if(!State::instance){ State::instance.reset(new State()); } diff --git a/src/state.h b/src/state.h index a2926ce..a331dd4 100644 --- a/src/state.h +++ b/src/state.h @@ -92,7 +92,7 @@ namespace VISEQ::APP{ void update(Camera2D camera); inline static std::shared_ptr instance; - inline static std::shared_ptr get(); + static std::shared_ptr get(); }; } diff --git a/src/ui.cpp b/src/ui.cpp index 278f6f3..951edd4 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -4,7 +4,6 @@ #include "midi-node.h" #include "raylib.h" #include "raygui/raygui.h" -#include "raygui/dark/style_dark.h" #include "datatypes.h" From 5886052228528347c6e844d18e812d2870a6c5f6 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 4 May 2026 14:21:48 +0200 Subject: [PATCH 4/9] makefile updated --- Makefile | 24 +++++++++++++++--------- src/Makefile | 40 ---------------------------------------- src/state.cpp | 1 + 3 files changed, 16 insertions(+), 49 deletions(-) delete mode 100644 src/Makefile diff --git a/Makefile b/Makefile index a639f4a..c770a86 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,22 @@ -CXX = g++ +.PHONE: clean -LDLIBS = -lraylib -lasound -llibremidi -pthread -LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib +CXX := g++ +CXXFLAGS := -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1 +CXXINC := -Iinclude -isysteminclude -hdr_files = $(wildcard src/*.h) +LDLIBS := -lraylib -lasound -llibremidi -pthread +LDFLAGS := -Llib -L/usr/lib -L/usr/local/lib -all: viseq - -viseq: +SRCS := $(wildcard src/*.cpp) +OBJECTS := $(patsubst src/%.cpp,build/%.o, $(SRCS)) + + +viseq: $(OBJECTS) + $(CXX) $(OBJECTS) -o $@ $(LDFLAGS) $(LDLIBS) + +$(OBJECTS): build/%.o : src/%.cpp mkdir -p build - cd src; $(MAKE) - $(CXX) $(wildcard build/*.o) -o viseq $(LDFLAGS) $(LDLIBS) + $(CXX) -c $< -o $@ $(CXXFLAGS) $(CXXINC) clean: rm -f build/* diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index ba078b7..0000000 --- a/src/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -CXX = g++ -CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1 -CXXINC = -I../include -isysteminclude - -obj_files = main.o app-manager.o graph-manager.o signal-manager.o interaction-manager.o ui.o node.o midi-node.o config-node.o modifier-node.o midi.o signal.o canvas.o state.o edge.o - - -all: $(obj_files) - -$(obj_files): %.o : %.cpp - $(CXX) -c $^ -o ../build/$@ $(CXXFLAGS) $(CXXINC) - -#all: viseq - -#viseq: main.o app-manager.o graph-manager.o signal-manager.o interaction-manager.o ui.o node.o midi-node.o config-node.o modifier-node.o midi.o signal.o canvas.o state.o edge.o -# $(CXX) ${wildcard build/*.o} -o viseq $(LDFLAGS) $(LDLIBS) - -#main.o: -# $(CXX) -c src/main.cpp -o ../build/main.o $(CXXFLAGS) $(CXXINC) -# -#app-manager.o: -# $(CXX) -c src/app-manager.cpp -o ../build/app-manager.o $(CXXFLAGS) $(CXXINC) - - - -#src/canvas.cpp -#src/signal.cpp -#src/midi.cpp -#src/node.cpp -#src/graph-manager.cpp -#src/modifier-node.cpp -#src/config-node.cpp -#src/midi-node.cpp -#src/ui.cpp -#src/state.cpp -#src/edge.cpp -#src/interaction-manager.cpp -#src/signal-manager.cpp -#src/app-manager.cpp -#src/main.cpp diff --git a/src/state.cpp b/src/state.cpp index 2fb8393..6a09051 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -8,6 +8,7 @@ namespace VISEQ::APP{ void State::pushEvent(InteractionEvent e){ + std::cout << "PUSHED" << std::endl; event_queue.push_back(e); } From 657c23702cbb48f3107390c1d4a2bf2357049d1b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 4 May 2026 18:40:00 +0200 Subject: [PATCH 5/9] moved event_queue to state singleton --- .clangd | 3 ++ Makefile | 1 - selectable.cpp | 1 + src/canvas.cpp | 2 +- src/clipboard.cpp | 16 ++++++ src/clipboard.h | 13 ++--- src/connector.h | 2 +- src/draggable.cpp | 41 +++++++++++++++ src/draggable.h | 47 ++++-------------- src/interactable.cpp | 17 +++++++ src/interactable.h | 12 ++--- src/interaction-event.h | 1 - src/interaction-manager.cpp | 18 ++++--- src/main.cpp | 1 - src/node-handle.cpp | 98 ++++++++++++++++++++++++++++++++++++ src/node-handle.h | 99 ++++++------------------------------- src/node.cpp | 1 - src/selectable.cpp | 7 +++ src/selectable.h | 4 +- src/state.cpp | 5 +- src/state.h | 2 + 21 files changed, 233 insertions(+), 158 deletions(-) create mode 100644 .clangd create mode 100644 selectable.cpp create mode 100644 src/clipboard.cpp create mode 100644 src/draggable.cpp create mode 100644 src/interactable.cpp create mode 100644 src/node-handle.cpp create mode 100644 src/selectable.cpp diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..06baf60 --- /dev/null +++ b/.clangd @@ -0,0 +1,3 @@ +CompileFlags: + Add: [-I/home/sebastian/code/viseq/include, -std=c++23] + diff --git a/Makefile b/Makefile index c770a86..5ad4c7b 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,6 @@ LDFLAGS := -Llib -L/usr/lib -L/usr/local/lib SRCS := $(wildcard src/*.cpp) OBJECTS := $(patsubst src/%.cpp,build/%.o, $(SRCS)) - viseq: $(OBJECTS) $(CXX) $(OBJECTS) -o $@ $(LDFLAGS) $(LDLIBS) diff --git a/selectable.cpp b/selectable.cpp new file mode 100644 index 0000000..48cedf1 --- /dev/null +++ b/selectable.cpp @@ -0,0 +1 @@ +#include "selectable.h diff --git a/src/canvas.cpp b/src/canvas.cpp index 5638aa0..c0f08b5 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -1,7 +1,7 @@ #include "canvas.h" #include "datatypes.h" -#include "interaction-event.h" +//#include "interaction-event.h" #include "state.h" #include "draggable.h" #include "raylib.h" diff --git a/src/clipboard.cpp b/src/clipboard.cpp new file mode 100644 index 0000000..ab16089 --- /dev/null +++ b/src/clipboard.cpp @@ -0,0 +1,16 @@ +#include "clipboard.h" + +namespace VISEQ::APP { + GRAPH::NODES::VEC_SPTR_Node Clipboard::pasteNodes(){ + return clipboard.back(); + } + + void Clipboard::copyNodes(GRAPH::NODES::VEC_SPTR_Node selected_nodes){ + if(clipboard.size() == 20) clipboard.pop_back(); + clipboard.emplace(clipboard.begin()); + for(auto n : selected_nodes){ + clipboard.front().push_back(n); + } + } + +} diff --git a/src/clipboard.h b/src/clipboard.h index e345567..98a61e6 100644 --- a/src/clipboard.h +++ b/src/clipboard.h @@ -1,6 +1,7 @@ #pragma once #include "node.h" +#include namespace VISEQ::APP { class Clipboard { @@ -9,17 +10,9 @@ namespace VISEQ::APP { std::vector clipboard; - GRAPH::NODES::VEC_SPTR_Node pasteNodes(){ - return clipboard.back(); - } + GRAPH::NODES::VEC_SPTR_Node pasteNodes(); - void copyNodes(GRAPH::NODES::VEC_SPTR_Node selected_nodes){ - if(clipboard.size() == 20) clipboard.pop_back(); - clipboard.emplace(clipboard.begin()); - for(auto n : selected_nodes){ - clipboard.front().push_back(n); - } - } + void copyNodes(GRAPH::NODES::VEC_SPTR_Node selected_nodes); }; } diff --git a/src/connector.h b/src/connector.h index da77833..8323a58 100644 --- a/src/connector.h +++ b/src/connector.h @@ -3,7 +3,7 @@ #include "connectable.h" #include "datatypes.h" #include "graph-item.h" -#include "interaction-event.h" +//#include "interaction-event.h" #include "raymath.h" #include "state.h" #include "vmath.h" diff --git a/src/draggable.cpp b/src/draggable.cpp new file mode 100644 index 0000000..2e12ef3 --- /dev/null +++ b/src/draggable.cpp @@ -0,0 +1,41 @@ +#include "draggable.h" + +namespace VISEQ::BASE { + bool Draggable::entered() { + return !is_hovering && hovering(); + } + + bool Draggable::exited() { + return is_hovering && !hovering(); + } + + bool Draggable::pickedUp() { + return held() && picked_up; + } + + void Draggable::pickUp() { + picked_up = true; + drag_offset = position - SCREEN_MOUSE; + } + + void Draggable::pickUp(Vec2Df selection_origin) { + picked_up = true; + drag_offset = position - selection_origin; + } + + void Draggable::drag(Vec2Df new_position) { + position = new_position; + } + + void Draggable::putDown() { + picked_up = false; + } + + bool Draggable::select() { + return CheckCollisionPointRec(position, state->selection); + } + + void Draggable::deselect() { + selected = false; + } +} diff --git a/src/draggable.h b/src/draggable.h index 896c41c..f8d7e66 100644 --- a/src/draggable.h +++ b/src/draggable.h @@ -20,43 +20,14 @@ namespace VISEQ::BASE { bool picked_up = false; - virtual bool entered() override { - return !is_hovering && hovering(); - } - - virtual bool exited() override { - return is_hovering && !hovering(); - } - - virtual bool pickedUp() { - return held() && picked_up; - } - - virtual void pickUp() override { - picked_up = true; - drag_offset = position - SCREEN_MOUSE; - } - - virtual void pickUp(Vec2Df selection_origin) override { - picked_up = true; - drag_offset = position - selection_origin; - } - - virtual void drag(Vec2Df new_position) override { - position = new_position; - } - - virtual void putDown() override { - picked_up = false; - } - - virtual bool select() override { - return CheckCollisionPointRec(position, state->selection); - } - - virtual void deselect() override { - selected = false; - } - + virtual bool entered() override; + virtual bool exited() override; + virtual bool pickedUp(); + virtual void pickUp() override; + virtual void pickUp(Vec2Df selection_origin) override; + virtual void drag(Vec2Df new_position) override; + virtual void putDown() override; + virtual bool select() override; + virtual void deselect() override; }; } diff --git a/src/interactable.cpp b/src/interactable.cpp new file mode 100644 index 0000000..7d16976 --- /dev/null +++ b/src/interactable.cpp @@ -0,0 +1,17 @@ +#include "interactable.h" + +namespace VISEQ::BASE { + + bool Interactable::clicked() { + return hovering() && IsMouseButtonPressed(MOUSE_BUTTON_LEFT); + } + + bool Interactable::held() { + return IsMouseButtonDown(MOUSE_BUTTON_LEFT); + } + + bool Interactable::released() { + return IsMouseButtonReleased(MOUSE_BUTTON_LEFT); + } + +} diff --git a/src/interactable.h b/src/interactable.h index dbc8a53..51f7b75 100644 --- a/src/interactable.h +++ b/src/interactable.h @@ -24,17 +24,11 @@ namespace VISEQ::BASE { virtual bool entered() {return false;}; virtual bool exited() {return false;}; - virtual bool clicked() { - return hovering() && IsMouseButtonPressed(MOUSE_BUTTON_LEFT); - } + virtual bool clicked(); - virtual bool held() { - return IsMouseButtonDown(MOUSE_BUTTON_LEFT); - } + virtual bool held(); - virtual bool released() { - return IsMouseButtonReleased(MOUSE_BUTTON_LEFT); - } + virtual bool released(); virtual GRAPH::GraphItemType getType() = 0; virtual void pickUp() = 0; diff --git a/src/interaction-event.h b/src/interaction-event.h index 5431f20..ff7b933 100644 --- a/src/interaction-event.h +++ b/src/interaction-event.h @@ -17,5 +17,4 @@ namespace VISEQ::APP{ return lhs.item == rhs.item; } - inline static std::deque event_queue; } diff --git a/src/interaction-manager.cpp b/src/interaction-manager.cpp index 1c9b6c8..f9c15b0 100644 --- a/src/interaction-manager.cpp +++ b/src/interaction-manager.cpp @@ -3,7 +3,6 @@ #include "clipboard.h" #include "connector.h" #include "datatypes.h" -#include "interaction-event.h" #include "node.h" #include "raylib.h" #include "state.h" @@ -133,12 +132,13 @@ namespace VISEQ::INTERACTION{ bool connect_once = false; state->mouse_z = canvas->z_index; + std::cout << state->event_queue.size() << " "; - for(auto e : APP::event_queue){ + for(auto e : state->event_queue){ if(!connect_once && e.action == APP::Interaction::CONNECT){ APP::InteractionEvent partner; bool found = false; - for(auto ei : APP::event_queue){ + for(auto ei : state->event_queue){ if(ei.action == APP::Interaction::HOVER && ei.item->getType() == GraphItemType::CONNECTOR) { std::cout << "YEAH" << std::endl; partner = ei; @@ -166,8 +166,12 @@ namespace VISEQ::INTERACTION{ } } - while(APP::event_queue.size() > 0 && !exit){ - auto e = APP::event_queue.front(); + while(state->event_queue.size() > 0 && !exit){ + auto e = state->event_queue.front(); + std::cout << e.action; + if(e.action == APP::Interaction::CLICK){ + gm->addToSelection(); + } if(e.action == APP::Interaction::RIGHTCLICK){ gm->addToSelection(); state->context_menu = !state->context_menu; @@ -248,8 +252,8 @@ namespace VISEQ::INTERACTION{ } } - APP::event_queue.pop_front(); + state->event_queue.pop_front(); } - APP::event_queue.clear(); + state->event_queue.clear(); } } diff --git a/src/main.cpp b/src/main.cpp index faf141a..38c4084 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,7 +16,6 @@ #include #include - VISEQ::APP::AppManager app; std::shared_ptr state = VISEQ::APP::State::get(); diff --git a/src/node-handle.cpp b/src/node-handle.cpp new file mode 100644 index 0000000..74dd471 --- /dev/null +++ b/src/node-handle.cpp @@ -0,0 +1,98 @@ +#include "node-handle.h" + +namespace VISEQ::GRAPH::NODES { + template + void NodeHandle::init(){ + position = parentPosition.orbitAtAngle(orbit, 0) - parentPosition; + } + + template + float NodeHandle::distance(){ + return (parentPosition + position).dist(parentPosition); + } + + template + void NodeHandle::drag(Vec2Df mouse_pos) { + float range = (maxValue - minValue) + 1; + float stepping = 360.0f / range; + + if(KEYS_DOWN.shift) stepping = stepping * steps; + + float angle = Vector2Angle((SCREEN_MOUSE - parentPosition), Vector2(0,1)) * RAD2DEG - 180; // -180 because round(180 / 33.75) * 33.75 is not 0 + + if(KEYS_PRESSED.ctrl) angle_start = angle; + if(KEYS_DOWN.ctrl) angle = angle_start + (angle - angle_start) / 10; + + float stepped_angle = (round(angle / stepping) * stepping + 180) * DEG2RAD; // see above. -180 + 180 to get 0 right round(0 / 33.75) * 33.75 is still 0 + position = parentPosition.orbitAtAngle(orbit, stepped_angle) - parentPosition; + } + + template + void NodeHandle::lock(){ + visible = false; + locked = true; + } + + template + void NodeHandle::unlock(){ + visible = true; + locked = false; + } + + template + bool NodeHandle::hovering() { + return CheckCollisionPointCircle(SCREEN_MOUSE, position + parentPosition, radius); + } + + template + void NodeHandle::update(Vec2Df pos) { + if(!locked && visible){ + if(hovering()) state->pushEvent({getPtr(), APP::Interaction::HOVER}); + if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); + if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); + } + if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); + + parentPosition = pos; + if(!locked && visible) updateAssignedValue(); + + minP = parentPosition.orbitAlongVector((parentPosition + position), minDistance); + maxP = parentPosition.orbitAlongVector((parentPosition + position), maxDistance); + } + + template + void NodeHandle::draw() { + if(!locked && visible){ + bool show = hovering() || picked_up; + Color dcol = ColorBrightness(col, -0.5f); + Color dyncol = show ? col : dcol; + Color dGrey = ColorBrightness(VS_COLOR_GREY, -0.2f); + Color dynGrey = show ? VS_COLOR_GREY : dGrey; + + if(steps > 0){ + for(int i = 0; i < steps; i++){ + Vec2Df pos = parentPosition.orbitAtAngle(orbit, DEG2RAD * i * (360.0f/steps)); + Vec2Df pos1 = parentPosition.orbitAtAngle(orbit - 3.0f, DEG2RAD * i * (360.0f/steps)); + DrawLineV(pos, pos1, dynGrey); + } + } + + float r = (parentPosition + position).dist(parentPosition); + DrawRing(parentPosition, r - 5, r + 5, 0, 360, 90, ColorAlpha(dyncol, 0.1)); + DrawRing(parentPosition, r - 5, r + 5, -90, angle_g - 90, 90, ColorAlpha(dyncol, 0.3)); + DrawCircleV(position + parentPosition, radius, dyncol); + //VISEQ::HELPER::DrawSuperText(std::format("{}", assignedValueRef), position + parentPosition, position + parentPosition + Vec2Df(50,0), 1, 0, 8, dyncol); + + /*if(show) {*/ + /* std::string text = std::format("{}", assignedValueRef);*/ + /* DrawCenteredText(text, parentPosition, 12, ColorBrightness(dyncol, 0.75f));*/ + /*}*/ + } + } + + template + bool NodeHandle::select() { + return false; + } + +} diff --git a/src/node-handle.h b/src/node-handle.h index 21b1d7f..2c57750 100644 --- a/src/node-handle.h +++ b/src/node-handle.h @@ -70,44 +70,13 @@ namespace VISEQ::GRAPH::NODES{ bool locked = true; bool visible = false; - void init(){ - position = parentPosition.orbitAtAngle(orbit, 0) - parentPosition; - } + void init(); void assignValue(T &value){ assignedValueRef = value; } - - float distance(){ - return (parentPosition + position).dist(parentPosition); - } - - virtual void drag(Vec2Df mouse_pos) override { - float range = (maxValue - minValue) + 1; - float stepping = 360.0f / range; - if(KEYS_DOWN.shift) stepping = stepping * steps; - - float angle = Vector2Angle((SCREEN_MOUSE - parentPosition), Vector2(0,1)) * RAD2DEG - 180; // -180 because round(180 / 33.75) * 33.75 is not 0 - - if(KEYS_PRESSED.ctrl) angle_start = angle; - if(KEYS_DOWN.ctrl) angle = angle_start + (angle - angle_start) / 10; - - float stepped_angle = (round(angle / stepping) * stepping + 180) * DEG2RAD; // see above. -180 + 180 to get 0 right round(0 / 33.75) * 33.75 is still 0 - position = parentPosition.orbitAtAngle(orbit, stepped_angle) - parentPosition; - } - - void lock(){ - visible = false; - locked = true; - } - - void unlock(){ - visible = true; - locked = false; - } - - void assignValue(T &value, float min, float max, float minDistance, float maxDistance){ + void assignValue(T &value, float min, float max, float minDistance, float maxDistance){ assignedValueRef = value; } @@ -124,58 +93,22 @@ namespace VISEQ::GRAPH::NODES{ } assignedValueRef = value; } + + float distance(); - virtual bool hovering() override { - return CheckCollisionPointCircle(SCREEN_MOUSE, position + parentPosition, radius); - } - - virtual void update(Vec2Df pos) override { - if(!locked && visible){ - if(hovering()) state->pushEvent({getPtr(), APP::Interaction::HOVER}); - if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); - if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); - } - if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); - - parentPosition = pos; - if(!locked && visible) updateAssignedValue(); - - minP = parentPosition.orbitAlongVector((parentPosition + position), minDistance); - maxP = parentPosition.orbitAlongVector((parentPosition + position), maxDistance); - } - - virtual void draw() override { - if(!locked && visible){ - bool show = hovering() || picked_up; - Color dcol = ColorBrightness(col, -0.5f); - Color dyncol = show ? col : dcol; - Color dGrey = ColorBrightness(VS_COLOR_GREY, -0.2f); - Color dynGrey = show ? VS_COLOR_GREY : dGrey; - - if(steps > 0){ - for(int i = 0; i < steps; i++){ - Vec2Df pos = parentPosition.orbitAtAngle(orbit, DEG2RAD * i * (360.0f/steps)); - Vec2Df pos1 = parentPosition.orbitAtAngle(orbit - 3.0f, DEG2RAD * i * (360.0f/steps)); - DrawLineV(pos, pos1, dynGrey); - } - } - - float r = (parentPosition + position).dist(parentPosition); - DrawRing(parentPosition, r - 5, r + 5, 0, 360, 90, ColorAlpha(dyncol, 0.1)); - DrawRing(parentPosition, r - 5, r + 5, -90, angle_g - 90, 90, ColorAlpha(dyncol, 0.3)); - DrawCircleV(position + parentPosition, radius, dyncol); - //VISEQ::HELPER::DrawSuperText(std::format("{}", assignedValueRef), position + parentPosition, position + parentPosition + Vec2Df(50,0), 1, 0, 8, dyncol); - - /*if(show) {*/ - /* std::string text = std::format("{}", assignedValueRef);*/ - /* DrawCenteredText(text, parentPosition, 12, ColorBrightness(dyncol, 0.75f));*/ - /*}*/ - } - } + virtual void drag(Vec2Df mouse_pos) override; - virtual bool select() override { - return false; - } + void lock(); + + void unlock(); + + virtual bool hovering() override; + + virtual void update(Vec2Df pos) override; + + virtual void draw() override; + + virtual bool select() override; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(NodeHandle, position, name); }; diff --git a/src/node.cpp b/src/node.cpp index 6735099..54b686a 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -5,7 +5,6 @@ #include "connector.h" #include "datatypes.h" -#include "interaction-event.h" #include "state.h" #include "vmath.h" diff --git a/src/selectable.cpp b/src/selectable.cpp new file mode 100644 index 0000000..9ed3150 --- /dev/null +++ b/src/selectable.cpp @@ -0,0 +1,7 @@ +#include "selectable.h" + +namespace VISEQ::BASE { + bool Selectable::clicked() { + return (selected || hovering()) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT); + } +} diff --git a/src/selectable.h b/src/selectable.h index 391d344..9213a48 100644 --- a/src/selectable.h +++ b/src/selectable.h @@ -16,9 +16,7 @@ namespace VISEQ::BASE { bool erase = false; Vec2Df selection_offset; - virtual bool clicked() override { - return (selected || hovering()) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT); - } + virtual bool clicked() override; virtual bool select() = 0; virtual void deselect() = 0; diff --git a/src/state.cpp b/src/state.cpp index 6a09051..63599f9 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -8,11 +8,12 @@ namespace VISEQ::APP{ void State::pushEvent(InteractionEvent e){ - std::cout << "PUSHED" << std::endl; + std::cout << "push event" << std::endl; event_queue.push_back(e); } - InteractionEvent pullEvent(){ + InteractionEvent State::pullEvent(){ + std::cout << "pull event" << std::endl; if(!event_queue.empty()){ InteractionEvent e = event_queue.front(); event_queue.pop_front(); diff --git a/src/state.h b/src/state.h index a331dd4..49b71b2 100644 --- a/src/state.h +++ b/src/state.h @@ -93,6 +93,8 @@ namespace VISEQ::APP{ inline static std::shared_ptr instance; static std::shared_ptr get(); + + std::deque event_queue; }; } From b8438ade341f66d8669079e0d85d16dbd6870266 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 4 May 2026 20:30:16 +0200 Subject: [PATCH 6/9] merge conflict stuff --- Makefile | 23 +++++++++++------ lib/{libraylib.a => libraylib.a_} | Bin src/Makefile | 40 ------------------------------ src/interaction-manager.cpp | 1 + src/ui.cpp | 2 +- 5 files changed, 17 insertions(+), 49 deletions(-) rename lib/{libraylib.a => libraylib.a_} (100%) delete mode 100644 src/Makefile diff --git a/Makefile b/Makefile index a639f4a..257b7f5 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,23 @@ -CXX = g++ +.PHONY: all clean -LDLIBS = -lraylib -lasound -llibremidi -pthread -LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib +CXX := g++ +CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1 +CXXINC := -Iinclude -isysteminclude -hdr_files = $(wildcard src/*.h) +LDLIBS := -lraylib -lasound -llibremidi -pthread +LDFLAGS := -Llib -L/usr/lib -L/usr/local/lib + +SOURCES := $(wildcard src/*.cpp) +OBJECTS := $(SOURCES:src%.cpp=build%.o) all: viseq -viseq: - mkdir -p build - cd src; $(MAKE) - $(CXX) $(wildcard build/*.o) -o viseq $(LDFLAGS) $(LDLIBS) +viseq: $(OBJECTS) + @mkdir -p build + $(CXX) $(LDFLAGS) $(LDLIBS) -o viseq $^ + +build/%.o: src/%.cpp + $(CXX) $(CXXFLAGS) $(CXXINC) -c $< -o $@ clean: rm -f build/* diff --git a/lib/libraylib.a b/lib/libraylib.a_ similarity index 100% rename from lib/libraylib.a rename to lib/libraylib.a_ diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index ba078b7..0000000 --- a/src/Makefile +++ /dev/null @@ -1,40 +0,0 @@ -CXX = g++ -CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1 -CXXINC = -I../include -isysteminclude - -obj_files = main.o app-manager.o graph-manager.o signal-manager.o interaction-manager.o ui.o node.o midi-node.o config-node.o modifier-node.o midi.o signal.o canvas.o state.o edge.o - - -all: $(obj_files) - -$(obj_files): %.o : %.cpp - $(CXX) -c $^ -o ../build/$@ $(CXXFLAGS) $(CXXINC) - -#all: viseq - -#viseq: main.o app-manager.o graph-manager.o signal-manager.o interaction-manager.o ui.o node.o midi-node.o config-node.o modifier-node.o midi.o signal.o canvas.o state.o edge.o -# $(CXX) ${wildcard build/*.o} -o viseq $(LDFLAGS) $(LDLIBS) - -#main.o: -# $(CXX) -c src/main.cpp -o ../build/main.o $(CXXFLAGS) $(CXXINC) -# -#app-manager.o: -# $(CXX) -c src/app-manager.cpp -o ../build/app-manager.o $(CXXFLAGS) $(CXXINC) - - - -#src/canvas.cpp -#src/signal.cpp -#src/midi.cpp -#src/node.cpp -#src/graph-manager.cpp -#src/modifier-node.cpp -#src/config-node.cpp -#src/midi-node.cpp -#src/ui.cpp -#src/state.cpp -#src/edge.cpp -#src/interaction-manager.cpp -#src/signal-manager.cpp -#src/app-manager.cpp -#src/main.cpp diff --git a/src/interaction-manager.cpp b/src/interaction-manager.cpp index 1c9b6c8..85ebe24 100644 --- a/src/interaction-manager.cpp +++ b/src/interaction-manager.cpp @@ -11,6 +11,7 @@ #include namespace VISEQ::INTERACTION{ + void InteractionManager::update(){ if(KEYS_DOWN.ctrl && IsKeyPressed(KEY_C)){ VEC_SPTR_Node selected_nodes = gm->getCopiesOfSelectedNodes(); diff --git a/src/ui.cpp b/src/ui.cpp index 951edd4..5ce199d 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -17,7 +17,7 @@ namespace VISEQ::APP::UI{ void AppUI::drawSideBar(){ - GuiPanel((Rectangle){-1,-30,380,(float)state->settings.height + 31}, ""); + GuiPanel((Rectangle){-1,-30,280,(float)state->settings.height + 31}, ""); const int s_controls = 25; //GuiSetStyle(BUTTON, STATE_FOCUSED, 0xFF0000FF); if(GuiButton((Rectangle){20, (float)s_controls, 80, 30}, "PLAY")) sm->play ? sm->pause() : sm->start(); From dc0003232a23b52a786903a86d21931e474f99bf Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 4 May 2026 22:24:29 +0200 Subject: [PATCH 7/9] working again --- selectable.cpp | 1 - src/interaction-manager.cpp | 2 - src/midi-node.cpp | 2 +- src/node-handle.cpp | 98 ------------------------------------ src/node-handle.h | 99 +++++++++++++++++++++++++++++++------ src/state.cpp | 2 - 6 files changed, 84 insertions(+), 120 deletions(-) delete mode 100644 selectable.cpp delete mode 100644 src/node-handle.cpp diff --git a/selectable.cpp b/selectable.cpp deleted file mode 100644 index 48cedf1..0000000 --- a/selectable.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "selectable.h diff --git a/src/interaction-manager.cpp b/src/interaction-manager.cpp index 7b43d1b..8dca1a1 100644 --- a/src/interaction-manager.cpp +++ b/src/interaction-manager.cpp @@ -133,7 +133,6 @@ namespace VISEQ::INTERACTION{ bool connect_once = false; state->mouse_z = canvas->z_index; - std::cout << state->event_queue.size() << " "; for(auto e : state->event_queue){ if(!connect_once && e.action == APP::Interaction::CONNECT){ @@ -169,7 +168,6 @@ namespace VISEQ::INTERACTION{ while(state->event_queue.size() > 0 && !exit){ auto e = state->event_queue.front(); - std::cout << e.action; if(e.action == APP::Interaction::CLICK){ gm->addToSelection(); } diff --git a/src/midi-node.cpp b/src/midi-node.cpp index e36a0a6..6a5a95d 100644 --- a/src/midi-node.cpp +++ b/src/midi-node.cpp @@ -287,7 +287,7 @@ namespace VISEQ::GRAPH::NODES { if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255}); bool modified = false; - if(mode >= 0 && mode < 6){ + if(selected && mode >= 0 && mode < 6){ for(auto mh : midiHandles[(Handles)mode]){ mh->unlock(); mh->draw(); diff --git a/src/node-handle.cpp b/src/node-handle.cpp deleted file mode 100644 index 74dd471..0000000 --- a/src/node-handle.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "node-handle.h" - -namespace VISEQ::GRAPH::NODES { - template - void NodeHandle::init(){ - position = parentPosition.orbitAtAngle(orbit, 0) - parentPosition; - } - - template - float NodeHandle::distance(){ - return (parentPosition + position).dist(parentPosition); - } - - template - void NodeHandle::drag(Vec2Df mouse_pos) { - float range = (maxValue - minValue) + 1; - float stepping = 360.0f / range; - - if(KEYS_DOWN.shift) stepping = stepping * steps; - - float angle = Vector2Angle((SCREEN_MOUSE - parentPosition), Vector2(0,1)) * RAD2DEG - 180; // -180 because round(180 / 33.75) * 33.75 is not 0 - - if(KEYS_PRESSED.ctrl) angle_start = angle; - if(KEYS_DOWN.ctrl) angle = angle_start + (angle - angle_start) / 10; - - float stepped_angle = (round(angle / stepping) * stepping + 180) * DEG2RAD; // see above. -180 + 180 to get 0 right round(0 / 33.75) * 33.75 is still 0 - position = parentPosition.orbitAtAngle(orbit, stepped_angle) - parentPosition; - } - - template - void NodeHandle::lock(){ - visible = false; - locked = true; - } - - template - void NodeHandle::unlock(){ - visible = true; - locked = false; - } - - template - bool NodeHandle::hovering() { - return CheckCollisionPointCircle(SCREEN_MOUSE, position + parentPosition, radius); - } - - template - void NodeHandle::update(Vec2Df pos) { - if(!locked && visible){ - if(hovering()) state->pushEvent({getPtr(), APP::Interaction::HOVER}); - if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); - if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); - } - if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); - - parentPosition = pos; - if(!locked && visible) updateAssignedValue(); - - minP = parentPosition.orbitAlongVector((parentPosition + position), minDistance); - maxP = parentPosition.orbitAlongVector((parentPosition + position), maxDistance); - } - - template - void NodeHandle::draw() { - if(!locked && visible){ - bool show = hovering() || picked_up; - Color dcol = ColorBrightness(col, -0.5f); - Color dyncol = show ? col : dcol; - Color dGrey = ColorBrightness(VS_COLOR_GREY, -0.2f); - Color dynGrey = show ? VS_COLOR_GREY : dGrey; - - if(steps > 0){ - for(int i = 0; i < steps; i++){ - Vec2Df pos = parentPosition.orbitAtAngle(orbit, DEG2RAD * i * (360.0f/steps)); - Vec2Df pos1 = parentPosition.orbitAtAngle(orbit - 3.0f, DEG2RAD * i * (360.0f/steps)); - DrawLineV(pos, pos1, dynGrey); - } - } - - float r = (parentPosition + position).dist(parentPosition); - DrawRing(parentPosition, r - 5, r + 5, 0, 360, 90, ColorAlpha(dyncol, 0.1)); - DrawRing(parentPosition, r - 5, r + 5, -90, angle_g - 90, 90, ColorAlpha(dyncol, 0.3)); - DrawCircleV(position + parentPosition, radius, dyncol); - //VISEQ::HELPER::DrawSuperText(std::format("{}", assignedValueRef), position + parentPosition, position + parentPosition + Vec2Df(50,0), 1, 0, 8, dyncol); - - /*if(show) {*/ - /* std::string text = std::format("{}", assignedValueRef);*/ - /* DrawCenteredText(text, parentPosition, 12, ColorBrightness(dyncol, 0.75f));*/ - /*}*/ - } - } - - template - bool NodeHandle::select() { - return false; - } - -} diff --git a/src/node-handle.h b/src/node-handle.h index 2c57750..21b1d7f 100644 --- a/src/node-handle.h +++ b/src/node-handle.h @@ -70,13 +70,44 @@ namespace VISEQ::GRAPH::NODES{ bool locked = true; bool visible = false; - void init(); + void init(){ + position = parentPosition.orbitAtAngle(orbit, 0) - parentPosition; + } void assignValue(T &value){ assignedValueRef = value; } + + float distance(){ + return (parentPosition + position).dist(parentPosition); + } + + virtual void drag(Vec2Df mouse_pos) override { + float range = (maxValue - minValue) + 1; + float stepping = 360.0f / range; - void assignValue(T &value, float min, float max, float minDistance, float maxDistance){ + if(KEYS_DOWN.shift) stepping = stepping * steps; + + float angle = Vector2Angle((SCREEN_MOUSE - parentPosition), Vector2(0,1)) * RAD2DEG - 180; // -180 because round(180 / 33.75) * 33.75 is not 0 + + if(KEYS_PRESSED.ctrl) angle_start = angle; + if(KEYS_DOWN.ctrl) angle = angle_start + (angle - angle_start) / 10; + + float stepped_angle = (round(angle / stepping) * stepping + 180) * DEG2RAD; // see above. -180 + 180 to get 0 right round(0 / 33.75) * 33.75 is still 0 + position = parentPosition.orbitAtAngle(orbit, stepped_angle) - parentPosition; + } + + void lock(){ + visible = false; + locked = true; + } + + void unlock(){ + visible = true; + locked = false; + } + + void assignValue(T &value, float min, float max, float minDistance, float maxDistance){ assignedValueRef = value; } @@ -93,22 +124,58 @@ namespace VISEQ::GRAPH::NODES{ } assignedValueRef = value; } - - float distance(); - virtual void drag(Vec2Df mouse_pos) override; + virtual bool hovering() override { + return CheckCollisionPointCircle(SCREEN_MOUSE, position + parentPosition, radius); + } + + virtual void update(Vec2Df pos) override { + if(!locked && visible){ + if(hovering()) state->pushEvent({getPtr(), APP::Interaction::HOVER}); + if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP}); + if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG}); + } + if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN}); + + parentPosition = pos; + if(!locked && visible) updateAssignedValue(); + + minP = parentPosition.orbitAlongVector((parentPosition + position), minDistance); + maxP = parentPosition.orbitAlongVector((parentPosition + position), maxDistance); + } + + virtual void draw() override { + if(!locked && visible){ + bool show = hovering() || picked_up; + Color dcol = ColorBrightness(col, -0.5f); + Color dyncol = show ? col : dcol; + Color dGrey = ColorBrightness(VS_COLOR_GREY, -0.2f); + Color dynGrey = show ? VS_COLOR_GREY : dGrey; + + if(steps > 0){ + for(int i = 0; i < steps; i++){ + Vec2Df pos = parentPosition.orbitAtAngle(orbit, DEG2RAD * i * (360.0f/steps)); + Vec2Df pos1 = parentPosition.orbitAtAngle(orbit - 3.0f, DEG2RAD * i * (360.0f/steps)); + DrawLineV(pos, pos1, dynGrey); + } + } + + float r = (parentPosition + position).dist(parentPosition); + DrawRing(parentPosition, r - 5, r + 5, 0, 360, 90, ColorAlpha(dyncol, 0.1)); + DrawRing(parentPosition, r - 5, r + 5, -90, angle_g - 90, 90, ColorAlpha(dyncol, 0.3)); + DrawCircleV(position + parentPosition, radius, dyncol); + //VISEQ::HELPER::DrawSuperText(std::format("{}", assignedValueRef), position + parentPosition, position + parentPosition + Vec2Df(50,0), 1, 0, 8, dyncol); + + /*if(show) {*/ + /* std::string text = std::format("{}", assignedValueRef);*/ + /* DrawCenteredText(text, parentPosition, 12, ColorBrightness(dyncol, 0.75f));*/ + /*}*/ + } + } - void lock(); - - void unlock(); - - virtual bool hovering() override; - - virtual void update(Vec2Df pos) override; - - virtual void draw() override; - - virtual bool select() override; + virtual bool select() override { + return false; + } NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(NodeHandle, position, name); }; diff --git a/src/state.cpp b/src/state.cpp index 63599f9..1ca3a72 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -8,12 +8,10 @@ namespace VISEQ::APP{ void State::pushEvent(InteractionEvent e){ - std::cout << "push event" << std::endl; event_queue.push_back(e); } InteractionEvent State::pullEvent(){ - std::cout << "pull event" << std::endl; if(!event_queue.empty()){ InteractionEvent e = event_queue.front(); event_queue.pop_front(); From 51cd271b6ffe12e8764c178f85cdbc4521edaf6f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 23 Jun 2026 00:05:41 +0200 Subject: [PATCH 8/9] libremidi update to v5.4.3 and added virtual midi out as default --- include/libremidi/api-c.h | 17 +- include/libremidi/api.hpp | 12 +- include/libremidi/backends.hpp | 51 +- include/libremidi/backends/alsa_raw.hpp | 7 +- .../libremidi/backends/alsa_raw/config.hpp | 7 +- .../libremidi/backends/alsa_raw/helpers.hpp | 30 +- .../libremidi/backends/alsa_raw/midi_in.hpp | 53 +- .../libremidi/backends/alsa_raw/midi_out.hpp | 8 +- .../libremidi/backends/alsa_raw/observer.hpp | 181 ++-- include/libremidi/backends/alsa_raw_ump.hpp | 13 +- .../backends/alsa_raw_ump/config.hpp | 8 +- .../backends/alsa_raw_ump/helpers.hpp | 4 +- .../backends/alsa_raw_ump/midi_in.hpp | 66 +- .../backends/alsa_raw_ump/midi_out.hpp | 11 +- .../backends/alsa_raw_ump/observer.hpp | 13 +- include/libremidi/backends/alsa_seq.hpp | 15 +- .../libremidi/backends/alsa_seq/config.hpp | 2 +- .../libremidi/backends/alsa_seq/helpers.hpp | 55 +- .../libremidi/backends/alsa_seq/midi_in.hpp | 55 +- .../libremidi/backends/alsa_seq/midi_out.hpp | 24 +- .../libremidi/backends/alsa_seq/observer.hpp | 297 ++++-- .../backends/alsa_seq/shared_handler.hpp | 18 +- include/libremidi/backends/alsa_seq_ump.hpp | 17 +- .../backends/alsa_seq_ump/config.hpp | 2 +- .../backends/alsa_seq_ump/midi_out.hpp | 17 +- .../libremidi/backends/android/android.hpp | 33 + include/libremidi/backends/android/config.hpp | 23 + .../libremidi/backends/android/helpers.cpp | 239 +++++ .../libremidi/backends/android/helpers.hpp | 40 + .../libremidi/backends/android/midi_in.hpp | 165 ++++ .../libremidi/backends/android/midi_out.hpp | 126 +++ .../libremidi/backends/android/observer.hpp | 93 ++ include/libremidi/backends/coremidi.hpp | 8 +- .../libremidi/backends/coremidi/config.hpp | 2 +- .../backends/coremidi/error_domain.hpp | 2 +- .../libremidi/backends/coremidi/helpers.hpp | 40 +- .../libremidi/backends/coremidi/midi_in.hpp | 16 +- .../libremidi/backends/coremidi/midi_out.hpp | 28 +- .../libremidi/backends/coremidi/observer.hpp | 75 +- include/libremidi/backends/coremidi_ump.hpp | 8 +- .../backends/coremidi_ump/config.hpp | 2 +- .../backends/coremidi_ump/midi_in.hpp | 16 +- .../backends/coremidi_ump/midi_out.hpp | 21 +- .../backends/coremidi_ump/observer.hpp | 13 +- include/libremidi/backends/dummy.hpp | 44 +- include/libremidi/backends/emscripten.hpp | 8 +- .../libremidi/backends/emscripten/config.hpp | 2 +- .../libremidi/backends/emscripten/helpers.hpp | 2 +- .../backends/emscripten/midi_access.hpp | 22 +- .../libremidi/backends/emscripten/midi_in.cpp | 9 +- .../libremidi/backends/emscripten/midi_in.hpp | 4 +- .../backends/emscripten/midi_out.cpp | 20 +- .../backends/emscripten/midi_out.hpp | 4 +- .../backends/emscripten/observer.cpp | 5 +- .../backends/emscripten/observer.hpp | 2 +- include/libremidi/backends/jack.hpp | 8 +- include/libremidi/backends/jack/config.hpp | 2 +- .../libremidi/backends/jack/error_domain.hpp | 30 +- include/libremidi/backends/jack/helpers.hpp | 151 ++- include/libremidi/backends/jack/midi_in.hpp | 18 +- include/libremidi/backends/jack/midi_out.hpp | 86 +- include/libremidi/backends/jack/observer.hpp | 63 +- .../backends/jack/shared_handler.hpp | 25 +- include/libremidi/backends/jack_ump.hpp | 34 + .../libremidi/backends/jack_ump/config.hpp | 33 + .../libremidi/backends/jack_ump/midi_in.hpp | 119 +++ .../libremidi/backends/jack_ump/midi_out.hpp | 184 ++++ .../libremidi/backends/jack_ump/observer.hpp | 243 +++++ .../backends/jack_ump/shared_handler.hpp | 0 include/libremidi/backends/kdmapi.hpp | 43 + include/libremidi/backends/kdmapi/config.hpp | 23 + include/libremidi/backends/kdmapi/helpers.hpp | 178 ++++ include/libremidi/backends/kdmapi/midi_in.hpp | 11 + .../libremidi/backends/kdmapi/midi_out.hpp | 173 ++++ .../libremidi/backends/kdmapi/observer.hpp | 86 ++ include/libremidi/backends/keyboard.hpp | 8 +- .../libremidi/backends/keyboard/config.hpp | 206 +++-- .../libremidi/backends/keyboard/midi_in.hpp | 18 +- include/libremidi/backends/linux/alsa.hpp | 120 ++- .../libremidi/backends/linux/dylib_loader.hpp | 3 +- include/libremidi/backends/linux/helpers.hpp | 2 +- include/libremidi/backends/linux/pipewire.hpp | 79 +- include/libremidi/backends/linux/udev.hpp | 139 ++- include/libremidi/backends/net/config.hpp | 7 +- include/libremidi/backends/net/helpers.hpp | 31 +- include/libremidi/backends/net/midi_in.hpp | 11 +- include/libremidi/backends/net/midi_out.hpp | 4 +- include/libremidi/backends/net/observer.hpp | 4 +- include/libremidi/backends/network.hpp | 8 +- include/libremidi/backends/network_ump.hpp | 8 +- include/libremidi/backends/pipewire.hpp | 8 +- .../libremidi/backends/pipewire/config.hpp | 2 +- .../libremidi/backends/pipewire/context.hpp | 48 +- .../libremidi/backends/pipewire/helpers.hpp | 70 +- .../libremidi/backends/pipewire/midi_in.hpp | 13 +- .../libremidi/backends/pipewire/midi_out.hpp | 20 +- .../libremidi/backends/pipewire/observer.hpp | 32 +- .../backends/pipewire/shared_handler.hpp | 23 +- include/libremidi/backends/pipewire_ump.hpp | 39 + .../backends/pipewire_ump/config.hpp | 36 + .../backends/pipewire_ump/midi_in.hpp | 132 +++ .../backends/pipewire_ump/midi_out.hpp | 182 ++++ .../backends/pipewire_ump/observer.hpp | 70 ++ include/libremidi/backends/winmidi.hpp | 14 +- include/libremidi/backends/winmidi/config.hpp | 19 +- .../libremidi/backends/winmidi/helpers.hpp | 194 +++- .../libremidi/backends/winmidi/midi_in.hpp | 143 ++- .../libremidi/backends/winmidi/midi_out.hpp | 68 +- .../libremidi/backends/winmidi/observer.hpp | 270 ++++-- include/libremidi/backends/winmm.hpp | 8 +- include/libremidi/backends/winmm/config.hpp | 2 +- .../libremidi/backends/winmm/error_domain.hpp | 68 +- include/libremidi/backends/winmm/helpers.hpp | 6 +- include/libremidi/backends/winmm/midi_in.hpp | 21 +- include/libremidi/backends/winmm/midi_out.hpp | 14 +- include/libremidi/backends/winmm/observer.hpp | 25 +- include/libremidi/backends/winuwp.hpp | 10 +- include/libremidi/backends/winuwp/config.hpp | 2 +- include/libremidi/backends/winuwp/helpers.hpp | 8 +- include/libremidi/backends/winuwp/midi_in.hpp | 5 +- .../libremidi/backends/winuwp/midi_out.hpp | 2 +- .../libremidi/backends/winuwp/observer.hpp | 62 +- include/libremidi/client.cpp | 2 +- include/libremidi/client.hpp | 47 +- include/libremidi/cmidi2.hpp | 778 +++++++++------- include/libremidi/config.hpp | 109 ++- include/libremidi/configurations.hpp | 70 +- include/libremidi/defaults.hpp | 26 +- include/libremidi/detail/conversion.hpp | 306 ++++++- include/libremidi/detail/memory.hpp | 4 +- include/libremidi/detail/midi_api.hpp | 11 +- include/libremidi/detail/midi_in.hpp | 6 +- include/libremidi/detail/midi_out.hpp | 2 +- .../libremidi/detail/midi_stream_decoder.hpp | 91 +- include/libremidi/detail/observer.hpp | 4 +- include/libremidi/detail/semaphore.hpp | 1 - include/libremidi/detail/ump_stream.hpp | 3 +- include/libremidi/error.hpp | 25 +- include/libremidi/error_handler.hpp | 10 +- include/libremidi/input_configuration.hpp | 7 +- include/libremidi/libremidi-c.cpp | 41 +- include/libremidi/libremidi-c.h | 8 + include/libremidi/libremidi.cpp | 62 +- include/libremidi/libremidi.hpp | 20 +- include/libremidi/message.hpp | 15 +- include/libremidi/midi_in.cpp | 124 +-- include/libremidi/midi_out.cpp | 88 +- include/libremidi/observer.cpp | 53 +- include/libremidi/observer_configuration.hpp | 58 +- include/libremidi/output_configuration.hpp | 2 +- include/libremidi/port_comparison.hpp | 565 ++++++++++++ include/libremidi/port_information.hpp | 164 ++++ .../libremidi/protocols/remote_control.hpp | 858 ++++++++++++++++++ include/libremidi/reader.cpp | 118 +-- include/libremidi/reader.hpp | 4 +- include/libremidi/shared_context.hpp | 10 +- include/libremidi/system_error2.hpp | 50 +- include/libremidi/types.hpp | 70 ++ include/libremidi/ump.hpp | 54 +- include/libremidi/ump_events.hpp | 374 ++++++++ include/libremidi/writer.cpp | 54 +- include/libremidi/writer.hpp | 2 +- src/midi.cpp | 11 + src/midi.h | 5 + src/signal-manager.cpp | 2 + src/signal-manager.h | 1 + 166 files changed, 8135 insertions(+), 2009 deletions(-) create mode 100644 include/libremidi/backends/android/android.hpp create mode 100644 include/libremidi/backends/android/config.hpp create mode 100644 include/libremidi/backends/android/helpers.cpp create mode 100644 include/libremidi/backends/android/helpers.hpp create mode 100644 include/libremidi/backends/android/midi_in.hpp create mode 100644 include/libremidi/backends/android/midi_out.hpp create mode 100644 include/libremidi/backends/android/observer.hpp create mode 100644 include/libremidi/backends/jack_ump.hpp create mode 100644 include/libremidi/backends/jack_ump/config.hpp create mode 100644 include/libremidi/backends/jack_ump/midi_in.hpp create mode 100644 include/libremidi/backends/jack_ump/midi_out.hpp create mode 100644 include/libremidi/backends/jack_ump/observer.hpp create mode 100644 include/libremidi/backends/jack_ump/shared_handler.hpp create mode 100644 include/libremidi/backends/kdmapi.hpp create mode 100644 include/libremidi/backends/kdmapi/config.hpp create mode 100644 include/libremidi/backends/kdmapi/helpers.hpp create mode 100644 include/libremidi/backends/kdmapi/midi_in.hpp create mode 100644 include/libremidi/backends/kdmapi/midi_out.hpp create mode 100644 include/libremidi/backends/kdmapi/observer.hpp create mode 100644 include/libremidi/backends/pipewire_ump.hpp create mode 100644 include/libremidi/backends/pipewire_ump/config.hpp create mode 100644 include/libremidi/backends/pipewire_ump/midi_in.hpp create mode 100644 include/libremidi/backends/pipewire_ump/midi_out.hpp create mode 100644 include/libremidi/backends/pipewire_ump/observer.hpp create mode 100644 include/libremidi/port_comparison.hpp create mode 100644 include/libremidi/port_information.hpp create mode 100644 include/libremidi/protocols/remote_control.hpp create mode 100644 include/libremidi/types.hpp create mode 100644 include/libremidi/ump_events.hpp diff --git a/include/libremidi/api-c.h b/include/libremidi/api-c.h index db5f26a..510cd79 100644 --- a/include/libremidi/api-c.h +++ b/include/libremidi/api-c.h @@ -1,13 +1,15 @@ #pragma once +#ifndef LIBREMIDI_API_C_H +#define LIBREMIDI_API_C_H -#if __cplusplus +#if __cplusplus && !defined(LIBREMIDI_MODULE_BUILD) extern "C" { #endif //! MIDI API specifier arguments. //! To get information on which feature is supported by each back-end, check their backend file //! in e.g. backends/winmm.hpp, etc. -typedef enum libremidi_api +enum libremidi_api { UNSPECIFIED = 0x0, /*!< Search for a working compiled API. */ @@ -22,6 +24,8 @@ typedef enum libremidi_api PIPEWIRE, /*!< PipeWire */ KEYBOARD, /*!< Computer keyboard input */ NETWORK, /*!< MIDI over IP */ + ANDROID_AMIDI, /*!< Android AMidi API */ + KDMAPI, /*!< OmniMIDI KDMAPI (Windows) */ // MIDI 2.0 APIs ALSA_RAW_UMP = 0x1000, /*!< Raw ALSA API for MIDI 2.0 */ @@ -30,10 +34,15 @@ typedef enum libremidi_api WINDOWS_MIDI_SERVICES, /*!< Windows API for MIDI 2.0. Requires Windows 11 */ KEYBOARD_UMP, /*!< Computer keyboard input */ NETWORK_UMP, /*!< MIDI2 over IP */ + JACK_UMP, /*!< MIDI2 over JACK, type "32 bit raw UMP". Requires PipeWire v1.4+. */ + PIPEWIRE_UMP, /*!< MIDI2 over PipeWire. Requires v1.4+. */ DUMMY = 0xFFFF /*!< A compilable but non-functional API. */ -} libremidi_api; +}; -#if __cplusplus +typedef enum libremidi_api libremidi_api; + +#if __cplusplus && !defined(LIBREMIDI_MODULE_BUILD) } #endif +#endif diff --git a/include/libremidi/api.hpp b/include/libremidi/api.hpp index 208fd9d..2a957fc 100644 --- a/include/libremidi/api.hpp +++ b/include/libremidi/api.hpp @@ -2,11 +2,10 @@ #include #include -#include #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { //! MIDI API specifier arguments. //! To get information on which feature is supported by each back-end, check their backend file @@ -31,9 +30,6 @@ LIBREMIDI_EXPORT std::vector available_apis() noexcept; */ LIBREMIDI_EXPORT std::vector available_ump_apis() noexcept; -LIBREMIDI_EXPORT -libremidi::API midi_api(const std::any& conf); - //! A static function to determine the current version. LIBREMIDI_EXPORT std::string_view get_version() noexcept; @@ -64,10 +60,12 @@ inline constexpr libremidi::API default_api() noexcept return API::COREMIDI; #elif defined(_WIN32) return API::WINDOWS_MM; -#elif defined(__linux__) +#elif defined(LIBREMIDI_ALSA) return API::ALSA_SEQ; #elif defined(__emscripten__) return API::EMSCRIPTEN_WEBMIDI; +#elif defined(LIBREMIDI_ANDROID) + return API::ANDROID_AMIDI; #else return API::DUMMY; #endif @@ -83,7 +81,7 @@ inline constexpr libremidi::API default_api() noexcept return API::COREMIDI_UMP; #elif defined(_WIN32) return API::WINDOWS_MIDI_SERVICES; -#elif defined(__linux__) +#elif defined(LIBREMIDI_ALSA) return API::ALSA_SEQ_UMP; #elif defined(__emscripten__) return API::DUMMY; diff --git a/include/libremidi/backends.hpp b/include/libremidi/backends.hpp index fb289eb..5c445ed 100644 --- a/include/libremidi/backends.hpp +++ b/include/libremidi/backends.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #if !__has_include() && !__has_include() @@ -30,10 +29,16 @@ #if defined(LIBREMIDI_JACK) #include + #if defined(LIBREMIDI_HAS_JACK_GET_VERSION) + #include + #endif #endif #if defined(LIBREMIDI_PIPEWIRE) #include + #if defined(LIBREMIDI_PIPEWIRE_UMP) + #include + #endif #endif #if defined(LIBREMIDI_COREMIDI) @@ -45,6 +50,10 @@ #include #endif +#if defined(LIBREMIDI_KDMAPI) + #include +#endif + #if defined(LIBREMIDI_WINUWP) #include #endif @@ -68,7 +77,11 @@ #include #endif -namespace libremidi +#if defined(LIBREMIDI_ANDROID) + #include +#endif + +NAMESPACE_LIBREMIDI { // The order here will control the order of the API search in // the constructor. @@ -80,7 +93,7 @@ constexpr auto make_tl(unused, Args...) namespace midi1 { -static constexpr auto available_backends = make_tl( +LIBREMIDI_STATIC constexpr auto available_backends = make_tl( 0 #if defined(LIBREMIDI_ALSA) , @@ -98,6 +111,10 @@ static constexpr auto available_backends = make_tl( , winmm_backend{} #endif +#if defined(LIBREMIDI_KDMAPI) + , + kdmapi_backend{} +#endif #if defined(LIBREMIDI_WINUWP) , winuwp_backend{} @@ -121,6 +138,10 @@ static constexpr auto available_backends = make_tl( #if defined(LIBREMIDI_NETWORK) , net::backend{} +#endif +#if defined(LIBREMIDI_ANDROID) + , + android::backend{} #endif , dummy_backend{}); @@ -146,7 +167,7 @@ auto for_backend(libremidi::API api, F&& f) namespace midi2 { -static constexpr auto available_backends = make_tl( +LIBREMIDI_STATIC constexpr auto available_backends = make_tl( 0 #if defined(LIBREMIDI_ALSA) && LIBREMIDI_ALSA_HAS_UMP , @@ -163,6 +184,14 @@ static constexpr auto available_backends = make_tl( #if defined(LIBREMIDI_NETWORK) , net_ump::backend{} +#endif +#if defined(LIBREMIDI_JACK) && defined(LIBREMIDI_HAS_JACK_GET_VERSION) + , + jack_ump::backend{} +#endif +#if defined(LIBREMIDI_PIPEWIRE_UMP) + , + pipewire_ump::backend{} #endif , dummy_backend{}); @@ -173,7 +202,7 @@ static_assert(std::tuple_size_v >= 1); template auto for_all_backends(F&& f) { - std::apply([&](auto&&... x) { (f(x), ...); }, available_backends); + std::apply([&](auto&&... x) { ((x.available() && (f(x), true)), ...); }, available_backends); } template @@ -202,10 +231,10 @@ auto for_backend(libremidi::API api, F&& f) midi2::for_backend(api, f); } -void for_input_configuration(auto f, std::any& api_conf) +void for_input_configuration(auto f, libremidi::input_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = get_if(&api_conf)) { f(*conf); return true; @@ -217,10 +246,10 @@ void for_input_configuration(auto f, std::any& api_conf) std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends); } -void for_output_configuration(auto f, std::any& api_conf) +void for_output_configuration(auto f, libremidi::output_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = get_if(&api_conf)) { f(*conf); return true; @@ -232,10 +261,10 @@ void for_output_configuration(auto f, std::any& api_conf) std::apply([&](auto&&... b) { return (from_api(b) || ...); }, midi2::available_backends); } -void for_observer_configuration(auto f, std::any& api_conf) +void for_observer_configuration(auto f, libremidi::observer_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = get_if(&api_conf)) { f(*conf); return true; diff --git a/include/libremidi/backends/alsa_raw.hpp b/include/libremidi/backends/alsa_raw.hpp index 06556b6..275bb06 100644 --- a/include/libremidi/backends/alsa_raw.hpp +++ b/include/libremidi/backends/alsa_raw.hpp @@ -3,12 +3,13 @@ #include #include +#include // Credits: greatly inspired from // https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawmidiout.c // https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c // Thanks Craig Stuart Sapp -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { struct backend { @@ -19,8 +20,8 @@ struct backend using midi_out_configuration = alsa_raw_output_configuration; using midi_observer_configuration = alsa_raw_observer_configuration; static const constexpr auto API = libremidi::API::ALSA_RAW; - static const constexpr auto name = "alsa_raw"; - static const constexpr auto display_name = "ALSA (raw)"; + static const constexpr std::string_view name = "alsa_raw"; + static const constexpr std::string_view display_name = "ALSA (raw)"; static inline bool available() noexcept { diff --git a/include/libremidi/backends/alsa_raw/config.hpp b/include/libremidi/backends/alsa_raw/config.hpp index 446ce9b..b94e1e2 100644 --- a/include/libremidi/backends/alsa_raw/config.hpp +++ b/include/libremidi/backends/alsa_raw/config.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -9,12 +10,12 @@ #if __has_include() #include -namespace libremidi +NAMESPACE_LIBREMIDI { using poll_descriptors = pollfd; } #else -namespace libremidi +NAMESPACE_LIBREMIDI { struct poll_descriptors { @@ -25,7 +26,7 @@ struct poll_descriptors } #endif -namespace libremidi +NAMESPACE_LIBREMIDI { /** diff --git a/include/libremidi/backends/alsa_raw/helpers.hpp b/include/libremidi/backends/alsa_raw/helpers.hpp index 1783a5a..c5483f5 100644 --- a/include/libremidi/backends/alsa_raw/helpers.hpp +++ b/include/libremidi/backends/alsa_raw/helpers.hpp @@ -2,10 +2,17 @@ #include #include #include +#include -#include +#include + +#include +#include +#include +#include #include #include +#include #include // Credits: greatly inspired from @@ -13,9 +20,7 @@ // https://ccrma.stanford.edu/~craig/articles/linuxmidi/alsa-1.0/alsarawportlist.c // Thanks Craig Stuart Sapp -namespace libremidi -{ -namespace +NAMESPACE_LIBREMIDI { struct alsa_raw_port_id { @@ -27,25 +32,25 @@ struct alsa_raw_port_id }; inline constexpr port_handle raw_to_port_handle(alsa_raw_port_id id) noexcept { - return (uint64_t(id.card) << 32) + (uint64_t(id.dev) << 16) + id.port; + return (uint64_t(id.port) << 32) + (uint64_t(id.dev) << 16) + id.card; } inline constexpr alsa_raw_port_id raw_from_port_handle(port_handle p) noexcept { alsa_raw_port_id ret; - ret.card = (p & 0x00'00'FF'FF'00'00'00'00) >> 32; + ret.port = (p & 0x00'00'FF'FF'00'00'00'00) >> 32; ret.dev = (p & 0x00'00'00'00'FF'FF'00'00) >> 16; - ret.port = (p & 0x00'00'00'00'00'00'FF'FF); + ret.card = (p & 0x00'00'00'00'00'00'FF'FF); return ret; } static_assert(raw_from_port_handle(raw_to_port_handle({102, 7, 3})).card == 102); static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).dev == 7); static_assert(raw_from_port_handle(raw_to_port_handle({12, 7, 3})).port == 3); -} namespace alsa_raw { struct alsa_raw_port_info { + // hw:1,2,0 std::string device; std::string card_name; std::string device_name; @@ -91,7 +96,6 @@ struct enumerator : handler{self} , configuration{self.configuration} { - } // 1: is an input / output @@ -114,8 +118,9 @@ struct enumerator else if (status < 0 && status != -ENXIO) { handler.libremidi_handle_error( - configuration, "Cannot get rawmidi information: " + device_identifier(card, device, sub) - + " : " + snd.strerror(status)); + configuration, + "Cannot get rawmidi information: " + device_identifier(card, device, sub) + " : " + + snd.strerror(status)); return status; } else @@ -201,7 +206,8 @@ inline snd_ctl_wrapper::snd_ctl_wrapper(enumerator& self, const char* name) if (status < 0) { self.handler.libremidi_handle_error( - self.configuration, "cannot open control for card"s + name + " : " + snd.strerror(status)); + self.configuration, + "cannot open control for card"s + name + " : " + snd.strerror(status)); } } diff --git a/include/libremidi/backends/alsa_raw/midi_in.hpp b/include/libremidi/backends/alsa_raw/midi_in.hpp index fda1b36..3ae0972 100644 --- a/include/libremidi/backends/alsa_raw/midi_in.hpp +++ b/include/libremidi/backends/alsa_raw/midi_in.hpp @@ -10,7 +10,7 @@ #include #include -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { class midi_in_impl : public midi1::in_api @@ -200,7 +200,7 @@ public: midi_in_alsa_raw_threaded(input_configuration&& conf, alsa_raw_input_configuration&& apiconf) : midi_in_impl{std::move(conf), std::move(apiconf)} { - if (this->termination_event < 0) + if (this->m_termination_event < 0) { libremidi_handle_error(this->configuration, "error creating eventfd."); return; @@ -220,7 +220,7 @@ public: private: void run_thread(auto parse_func) { - fds_.push_back(this->termination_event); + fds_.push_back(this->m_termination_event); const auto period = std::chrono::duration_cast(this->configuration.poll_period) .count(); @@ -228,12 +228,12 @@ private: for (;;) { // Poll - ssize_t err = poll(fds_.data(), fds_.size(), period); + ssize_t err = poll(fds_.data(), fds_.size(), static_cast(period)); if (err == -EAGAIN) continue; else if (err < 0) return; - else if (termination_event.ready(fds_.back())) + else if (m_termination_event.ready(fds_.back())) break; err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1}); @@ -250,11 +250,11 @@ private: { if (configuration.timestamps == timestamp_mode::NoTimestamp) { - this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }}; + this->m_thread = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }}; } else { - this->thread_ = std::thread{ + this->m_thread = std::thread{ [this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }}; } return stdx::error{}; @@ -264,8 +264,7 @@ private: using namespace std::literals; libremidi_handle_error( - this->configuration, - "error starting MIDI input thread: "s + e.what()); + this->configuration, "error starting MIDI input thread: "s + e.what()); return e.code(); } return stdx::error{}; @@ -282,16 +281,16 @@ private: stdx::error close_port() override { - termination_event.notify(); - if (thread_.joinable()) - thread_.join(); - termination_event.consume(); // Reset to zero + m_termination_event.notify(); + if (m_thread.joinable()) + m_thread.join(); + m_termination_event.consume(); // Reset to zero return midi_in_impl::close_port(); } - std::thread thread_; - eventfd_notifier termination_event{}; + std::thread m_thread; + eventfd_notifier m_termination_event{}; }; class midi_in_alsa_raw_manual : public midi_in_impl @@ -316,19 +315,21 @@ private: { if (configuration.timestamps == timestamp_mode::NoTimestamp) { - configuration.manual_poll(manual_poll_parameters{ - .fds = {this->fds_.data(), this->fds_.size()}, - .callback = [this](std::span fds) { - return do_read_events(&midi_in_impl::read_input_buffer, fds); - }}); + configuration.manual_poll( + manual_poll_parameters{ + .fds = {this->fds_.data(), this->fds_.size()}, + .callback = [this](std::span fds) { + return do_read_events(&midi_in_impl::read_input_buffer, fds); + }}); } else { - configuration.manual_poll(manual_poll_parameters{ - .fds = {this->fds_.data(), this->fds_.size()}, - .callback = [this](std::span fds) { - return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); - }}); + configuration.manual_poll( + manual_poll_parameters{ + .fds = {this->fds_.data(), this->fds_.size()}, + .callback = [this](std::span fds) { + return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); + }}); } } @@ -342,7 +343,7 @@ private: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr make( diff --git a/include/libremidi/backends/alsa_raw/midi_out.hpp b/include/libremidi/backends/alsa_raw/midi_out.hpp index 227b883..7746718 100644 --- a/include/libremidi/backends/alsa_raw/midi_out.hpp +++ b/include/libremidi/backends/alsa_raw/midi_out.hpp @@ -8,7 +8,7 @@ #include #include -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { class midi_out_impl final : public midi1::out_api @@ -44,8 +44,7 @@ public: int status = snd.rawmidi.open(NULL, &midiport_, portname, mode); if (status < 0) { - libremidi_handle_error( - this->configuration, "cannot open device."); + libremidi_handle_error(this->configuration, "cannot open device."); return from_errc(status); } return stdx::error{}; @@ -87,8 +86,7 @@ public: { if (auto err = snd.rawmidi.write(midiport_, message, size); err < 0) { - libremidi_handle_error( - this->configuration, "cannot write message."); + libremidi_handle_error(this->configuration, "cannot write message."); return from_errc(err); } diff --git a/include/libremidi/backends/alsa_raw/observer.hpp b/include/libremidi/backends/alsa_raw/observer.hpp index 68f2afb..d52fe8c 100644 --- a/include/libremidi/backends/alsa_raw/observer.hpp +++ b/include/libremidi/backends/alsa_raw/observer.hpp @@ -6,9 +6,10 @@ #if LIBREMIDI_HAS_UDEV #include #include - #include +#endif +#include -namespace libremidi::alsa_raw +NAMESPACE_LIBREMIDI::alsa_raw { template class observer_impl_base @@ -27,28 +28,39 @@ public: explicit observer_impl_base( observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} + { + } + + ~observer_impl_base() + { +#if LIBREMIDI_HAS_UDEV + m_termination_event.notify(); + + if (m_thread.joinable()) + m_thread.join(); +#endif + } + + // Must be called for child classes due to virtual function calls + void finish_init() { if (!configuration.has_callbacks()) return; - fds[0] = this->udev; - fds[1] = termination_event; - fds[2] = timer_fd; +#if LIBREMIDI_HAS_UDEV + m_fds[0] = this->m_udev; + m_fds[1] = m_termination_event; + m_fds[2] = m_timer_fd; +#endif // Set-up initial state if (configuration.notify_in_constructor) this->check_devices(); +#if LIBREMIDI_HAS_UDEV // Start thread - thread = std::thread{[this] { this->run(); }}; - } - - ~observer_impl_base() - { - termination_event.notify(); - - if (thread.joinable()) - thread.join(); + m_thread = std::thread{[this] { this->run(); }}; +#endif } std::vector get_input_ports() const noexcept override @@ -77,12 +89,12 @@ public: return ret; } -private: +#if LIBREMIDI_HAS_UDEV void run() { for (;;) { - if (int err = poll(fds, 3, -1); err < 0) + if (int err = poll(m_fds, 3, -1); err < 0) { if (err == -EAGAIN) continue; @@ -90,59 +102,87 @@ private: return; } - // Check udev - if (fds[0].revents & POLLIN) + // Check eventfd to see if we need to exit + if (m_fds[1].revents & POLLIN) { - udev_device* dev = udev.udev.monitor_receive_device(udev.monitor); + break; + } + + // Check udev + if (m_fds[0].revents & POLLIN) + { + udev_device* dev = m_udev.udev.monitor_receive_device(m_udev.monitor); if (!dev) continue; - std::string_view act = udev.udev.device_get_action(dev); - std::string_view ss = udev.udev.device_get_subsystem(dev); + std::string_view act = m_udev.udev.device_get_action(dev); + std::string_view ss = m_udev.udev.device_get_subsystem(dev); if (!act.empty() && ss == "snd_seq") { if (act == "add" || act == "remove") { // Check every 100 milliseconds for ten seconds - this->timer_fd.restart(configuration.poll_period.count()); - timer_check_counts = 100; + this->m_timer_fd.restart(configuration.poll_period.count()); + m_timer_check_counts = 100; } } - udev.udev.device_unref(dev); + m_udev.udev.device_unref(dev); - fds[0].revents = 0; - } - - // Check eventfd - if (fds[1].revents & POLLIN) - { - break; + m_fds[0].revents = 0; } // Check timer - if (fds[2].revents & POLLIN) + if (m_fds[2].revents & POLLIN) { - if (this->timer_check_counts-- <= 0) - this->timer_fd.cancel(); - fds[2].revents = 0; + if (this->m_timer_check_counts-- <= 0) + this->m_timer_fd.cancel(); + m_fds[2].revents = 0; check_devices(); } } } +#endif template - auto to_port_info(alsa_raw::alsa_raw_port_info p) const noexcept + auto to_port_info(const alsa_raw::alsa_raw_port_info& p) const noexcept -> std::conditional_t { +#if LIBREMIDI_HAS_UDEV + auto [container, device, type, manufacturer, product, serial] + = get_udev_soundcard_info(m_udev, p.card); +#else + container_identifier container{}; + device_identifier device{}; + libremidi::transport_type type{}; + std::string manufacturer; + std::string product; + std::string serial; +#endif + std::string display_name = p.subdevice_name; + if (display_name.empty()) + display_name = p.device_name; + if (display_name.empty()) + display_name = p.card_name; + if (display_name.empty()) + display_name = product; + if (display_name.empty()) + display_name = "unknown"; + return { - {.client = 0, + {.api = get_current_api(), + .client = 0, + .container = container, + .device = device, .port = raw_to_port_handle({p.card, p.dev, p.sub}), - .manufacturer = p.card_name, + .manufacturer = manufacturer, + .product = product, + .serial = serial, .device_name = p.device_name, .port_name = p.subdevice_name, - .display_name = p.subdevice_name}}; + .display_name = std::move(display_name), + .type = type}}; } void check_devices() @@ -151,7 +191,7 @@ private: new_devs.enumerate_cards(); - for (auto& in_prev : current_inputs) + for (auto& in_prev : m_current_inputs) { if (auto it = std::find(new_devs.inputs.begin(), new_devs.inputs.end(), in_prev); it == new_devs.inputs.end()) @@ -165,8 +205,8 @@ private: for (auto& in_next : new_devs.inputs) { - if (auto it = std::find(current_inputs.begin(), current_inputs.end(), in_next); - it == current_inputs.end()) + if (auto it = std::find(m_current_inputs.begin(), m_current_inputs.end(), in_next); + it == m_current_inputs.end()) { if (auto& cb = this->configuration.input_added) { @@ -175,7 +215,7 @@ private: } } - for (auto& out_prev : current_outputs) + for (auto& out_prev : m_current_outputs) { if (auto it = std::find(new_devs.outputs.begin(), new_devs.outputs.end(), out_prev); it == new_devs.outputs.end()) @@ -189,8 +229,8 @@ private: for (auto& out_next : new_devs.outputs) { - if (auto it = std::find(current_outputs.begin(), current_outputs.end(), out_next); - it == current_outputs.end()) + if (auto it = std::find(m_current_outputs.begin(), m_current_outputs.end(), out_next); + it == m_current_outputs.end()) { if (auto& cb = this->configuration.output_added) { @@ -198,43 +238,34 @@ private: } } } - current_inputs = std::move(new_devs.inputs); - current_outputs = std::move(new_devs.outputs); + m_current_inputs = std::move(new_devs.inputs); + m_current_outputs = std::move(new_devs.outputs); } - udev_helper udev{}; - eventfd_notifier termination_event{}; - timerfd_timer timer_fd{}; - int timer_check_counts = 0; - std::thread thread; - std::vector current_inputs; - std::vector current_outputs; - - pollfd fds[3]{}; -}; -} -#else - #include -namespace libremidi::alsa_raw -{ -template -struct observer_impl_base : observer_dummy -{ - explicit observer_impl_base( - [[maybe_unused]] observer_configuration&& conf, - [[maybe_unused]] alsa_raw_observer_configuration&& apiconf) - : observer_dummy{dummy_configuration{}, dummy_configuration{}} - { - } -}; -} +#if LIBREMIDI_HAS_UDEV + udev_helper m_udev{}; + eventfd_notifier m_termination_event{}; + timerfd_timer m_timer_fd{}; + int m_timer_check_counts = 0; + std::thread m_thread; + pollfd m_fds[3]{}; #endif -namespace libremidi::alsa_raw + std::vector m_current_inputs; + std::vector m_current_outputs; +}; +} + +NAMESPACE_LIBREMIDI::alsa_raw { struct observer_impl : observer_impl_base { - using alsa_raw::observer_impl_base::observer_impl_base; + observer_impl(observer_configuration&& conf, alsa_raw_observer_configuration&& apiconf) + : observer_impl_base{std::move(conf), std::move(apiconf)} + { + finish_init(); + } + libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW; } }; } diff --git a/include/libremidi/backends/alsa_raw_ump.hpp b/include/libremidi/backends/alsa_raw_ump.hpp index 3fc7adf..fc1c4da 100644 --- a/include/libremidi/backends/alsa_raw_ump.hpp +++ b/include/libremidi/backends/alsa_raw_ump.hpp @@ -5,7 +5,9 @@ #include #include -namespace libremidi::alsa_raw_ump +#include + +NAMESPACE_LIBREMIDI::alsa_raw_ump { struct backend { @@ -14,12 +16,11 @@ struct backend using midi_observer = observer_impl; using midi_in_configuration = alsa_raw_ump::input_configuration; using midi_out_configuration = alsa_raw_ump::output_configuration; - struct midi_observer_configuration : alsa_raw_observer_configuration - { - }; + using midi_observer_configuration = alsa_raw_ump::observer_configuration; + static const constexpr auto API = libremidi::API::ALSA_RAW_UMP; - static const constexpr auto name = "alsa_raw_ump"; - static const constexpr auto display_name = "ALSA (raw UMP)"; + static const constexpr std::string_view name = "alsa_raw_ump"; + static const constexpr std::string_view display_name = "ALSA (raw UMP)"; static inline bool available() noexcept { diff --git a/include/libremidi/backends/alsa_raw_ump/config.hpp b/include/libremidi/backends/alsa_raw_ump/config.hpp index a89ccaf..d9e82f2 100644 --- a/include/libremidi/backends/alsa_raw_ump/config.hpp +++ b/include/libremidi/backends/alsa_raw_ump/config.hpp @@ -1,7 +1,10 @@ #pragma once #include -namespace libremidi::alsa_raw_ump +#include +#include + +NAMESPACE_LIBREMIDI::alsa_raw_ump { struct input_configuration { @@ -18,8 +21,7 @@ struct output_configuration std::optional chunking; }; -struct observer_configuration +struct observer_configuration : public alsa_raw_observer_configuration { - std::chrono::milliseconds poll_period{100}; }; } diff --git a/include/libremidi/backends/alsa_raw_ump/helpers.hpp b/include/libremidi/backends/alsa_raw_ump/helpers.hpp index 5b16ca9..a4c2e5b 100644 --- a/include/libremidi/backends/alsa_raw_ump/helpers.hpp +++ b/include/libremidi/backends/alsa_raw_ump/helpers.hpp @@ -3,7 +3,9 @@ #include #include -namespace libremidi::alsa_raw_ump +#include + +NAMESPACE_LIBREMIDI::alsa_raw_ump { struct midi2_enumerator : alsa_raw::enumerator { diff --git a/include/libremidi/backends/alsa_raw_ump/midi_in.hpp b/include/libremidi/backends/alsa_raw_ump/midi_in.hpp index e4b1640..6748286 100644 --- a/include/libremidi/backends/alsa_raw_ump/midi_in.hpp +++ b/include/libremidi/backends/alsa_raw_ump/midi_in.hpp @@ -10,7 +10,7 @@ #include #include -namespace libremidi::alsa_raw_ump +NAMESPACE_LIBREMIDI::alsa_raw_ump { class midi_in_impl : public midi2::in_api @@ -44,7 +44,8 @@ public: SND_RAWMIDI_NONBLOCK; // fixme if (int err = snd.ump.open(&midiport_, 0, portname, mode); err < 0) { - libremidi_handle_error(this->configuration, "alsa_raw_ump::ump::open_port: cannot open device."); + libremidi_handle_error( + this->configuration, "alsa_raw_ump::ump::open_port: cannot open device."); return from_errc(err); } @@ -143,7 +144,8 @@ public: while ((err = snd.ump.read(this->midiport_, words, nwords * 4)) > 0) { const auto to_ns = [this] { return absolute_timestamp(); }; - m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp(to_ns, 0)); + m_processing.on_bytes( + {words, words + err / 4}, m_processing.timestamp(to_ns, 0)); } return err; } @@ -166,7 +168,8 @@ public: const auto to_ns = [ts] { return static_cast(ts.tv_sec) * 1'000'000'000 + static_cast(ts.tv_nsec); }; - m_processing.on_bytes({words, words + err / 4}, m_processing.timestamp(to_ns, 0)); + m_processing.on_bytes( + {words, words + err / 4}, m_processing.timestamp(to_ns, 0)); } return err; } @@ -194,7 +197,7 @@ public: libremidi::ump_input_configuration&& conf, alsa_raw_ump::input_configuration&& apiconf) : midi_in_impl{std::move(conf), std::move(apiconf)} { - if (this->termination_event < 0) + if (this->m_termination_event < 0) { libremidi_handle_error(this->configuration, "error creating eventfd."); return; @@ -206,14 +209,14 @@ public: ~midi_in_impl_threaded() { // Close a connection if it exists. - this->close_port(); + midi_in_impl_threaded::close_port(); client_open_ = std::errc::not_connected; } private: void run_thread(auto parse_func) { - fds_.push_back(this->termination_event); + fds_.push_back(this->m_termination_event); const auto period = std::chrono::duration_cast(this->configuration.poll_period) .count(); @@ -221,12 +224,12 @@ private: for (;;) { // Poll - ssize_t err = poll(fds_.data(), fds_.size(), period); + ssize_t err = poll(fds_.data(), fds_.size(), static_cast(period)); if (err == -EAGAIN) continue; else if (err < 0) return; - else if (termination_event.ready(fds_.back())) + else if (m_termination_event.ready(fds_.back())) break; err = do_read_events(parse_func, {fds_.data(), fds_.size() - 1}); @@ -243,11 +246,11 @@ private: { if (configuration.timestamps == timestamp_mode::NoTimestamp) { - this->thread_ = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }}; + this->m_thread = std::thread{[this] { run_thread(&midi_in_impl::read_input_buffer); }}; } else { - this->thread_ = std::thread{ + this->m_thread = std::thread{ [this] { run_thread(&midi_in_impl::read_input_buffer_with_timestamps); }}; } return stdx::error{}; @@ -257,8 +260,7 @@ private: using namespace std::literals; libremidi_handle_error( - this->configuration, - "error starting MIDI input thread: "s + e.what()); + this->configuration, "error starting MIDI input thread: "s + e.what()); return e.code(); } return stdx::error{}; @@ -275,16 +277,16 @@ private: stdx::error close_port() override { - termination_event.notify(); - if (thread_.joinable()) - thread_.join(); - termination_event.consume(); // Reset to zero + m_termination_event.notify(); + if (m_thread.joinable()) + m_thread.join(); + m_termination_event.consume(); // Reset to zero return midi_in_impl::close_port(); } - std::thread thread_; - eventfd_notifier termination_event{}; + std::thread m_thread; + eventfd_notifier m_termination_event{}; }; class midi_in_impl_manual : public midi_in_impl @@ -299,7 +301,7 @@ public: ~midi_in_impl_manual() { // Close a connection if it exists. - this->close_port(); + midi_in_impl_manual::close_port(); client_open_ = std::errc::not_connected; } @@ -309,19 +311,21 @@ private: { if (configuration.timestamps == timestamp_mode::NoTimestamp) { - configuration.manual_poll(manual_poll_parameters{ - .fds = {this->fds_.data(), this->fds_.size()}, - .callback = [this](std::span fds) { - return do_read_events(&midi_in_impl::read_input_buffer, fds); - }}); + configuration.manual_poll( + manual_poll_parameters{ + .fds = {this->fds_.data(), this->fds_.size()}, + .callback = [this](std::span fds) { + return do_read_events(&midi_in_impl::read_input_buffer, fds); + }}); } else { - configuration.manual_poll(manual_poll_parameters{ - .fds = {this->fds_.data(), this->fds_.size()}, - .callback = [this](std::span fds) { - return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); - }}); + configuration.manual_poll( + manual_poll_parameters{ + .fds = {this->fds_.data(), this->fds_.size()}, + .callback = [this](std::span fds) { + return do_read_events(&midi_in_impl::read_input_buffer_with_timestamps, fds); + }}); } } @@ -335,7 +339,7 @@ private: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr make( diff --git a/include/libremidi/backends/alsa_raw_ump/midi_out.hpp b/include/libremidi/backends/alsa_raw_ump/midi_out.hpp index f350cde..0ca5b87 100644 --- a/include/libremidi/backends/alsa_raw_ump/midi_out.hpp +++ b/include/libremidi/backends/alsa_raw_ump/midi_out.hpp @@ -1,14 +1,19 @@ #pragma once #include #include +#include #include +#include +#include #include -#include -#include +#include +#include +#include +#include -namespace libremidi::alsa_raw_ump +NAMESPACE_LIBREMIDI::alsa_raw_ump { class midi_out_impl final : public midi2::out_api diff --git a/include/libremidi/backends/alsa_raw_ump/observer.hpp b/include/libremidi/backends/alsa_raw_ump/observer.hpp index 03e3286..0c44805 100644 --- a/include/libremidi/backends/alsa_raw_ump/observer.hpp +++ b/include/libremidi/backends/alsa_raw_ump/observer.hpp @@ -2,13 +2,18 @@ #include #include -namespace libremidi::alsa_raw_ump +NAMESPACE_LIBREMIDI::alsa_raw_ump { - class observer_impl : public alsa_raw::observer_impl_base { - using alsa_raw::observer_impl_base::observer_impl_base; +public: + observer_impl( + libremidi::observer_configuration&& conf, alsa_raw_ump::observer_configuration&& apiconf) + : observer_impl_base{std::move(conf), std::move(apiconf)} + { + finish_init(); + } + libremidi::API get_current_api() const noexcept override { return libremidi::API::ALSA_RAW_UMP; } }; - } diff --git a/include/libremidi/backends/alsa_seq.hpp b/include/libremidi/backends/alsa_seq.hpp index 0d2c43b..bac33c3 100644 --- a/include/libremidi/backends/alsa_seq.hpp +++ b/include/libremidi/backends/alsa_seq.hpp @@ -16,7 +16,11 @@ #include #include -namespace libremidi::alsa_seq +#include + +#include + +NAMESPACE_LIBREMIDI::alsa_seq { struct backend @@ -29,13 +33,16 @@ struct backend using midi_out_configuration = alsa_seq::output_configuration; using midi_observer_configuration = alsa_seq::observer_configuration; static const constexpr auto API = libremidi::API::ALSA_SEQ; - static const constexpr auto name = "alsa_seq"; - static const constexpr auto display_name = "ALSA (sequencer)"; + static const constexpr std::string_view name = "alsa_seq"; + static const constexpr std::string_view display_name = "ALSA (sequencer)"; static inline bool available() noexcept { static const libasound& snd = libasound::instance(); - return snd.available && snd.seq.available; + if (!snd.available || !snd.seq.available) + return false; + + return ::access("/dev/snd/seq", F_OK | R_OK | W_OK) == 0; } }; diff --git a/include/libremidi/backends/alsa_seq/config.hpp b/include/libremidi/backends/alsa_seq/config.hpp index 8153fb3..26ff5d5 100644 --- a/include/libremidi/backends/alsa_seq/config.hpp +++ b/include/libremidi/backends/alsa_seq/config.hpp @@ -15,7 +15,7 @@ typedef struct snd_seq_addr } #endif -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { // Possible parameters: diff --git a/include/libremidi/backends/alsa_seq/helpers.hpp b/include/libremidi/backends/alsa_seq/helpers.hpp index 0500a28..8f5a72c 100644 --- a/include/libremidi/backends/alsa_seq/helpers.hpp +++ b/include/libremidi/backends/alsa_seq/helpers.hpp @@ -9,7 +9,7 @@ #include #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { struct event_handle { @@ -42,23 +42,39 @@ struct event_handle ~event_handle() { snd.seq.free_event(ev); } }; -namespace -{ inline constexpr port_handle seq_to_port_handle(uint64_t client, uint64_t port) noexcept { - return (client << 32) + port; + return (port << 32) + client; } inline constexpr std::pair seq_from_port_handle(port_handle p) noexcept { - int client = p >> 32; - int port = p & 0xFFFFFFFF; + int port = p >> 32; + int client = p & 0xFFFFFFFF; return {client, port}; } +inline void for_all_clients( + const libasound& snd, snd_seq_t* seq, const std::function& func) +{ + snd_seq_client_info_t* cinfo{}; + snd_seq_client_info_alloca(&cinfo); + snd_seq_port_info_t* pinfo{}; + snd_seq_port_info_alloca(&pinfo); + + snd.seq.client_info_set_client(cinfo, -1); + while (snd.seq.query_next_client(seq, cinfo) >= 0) + { + int client = snd.seq.client_info_get_client(cinfo); + if (client == 0) + continue; + func(*cinfo); + } +} + inline void for_all_ports( const libasound& snd, snd_seq_t* seq, - std::function func) + const std::function& func) { snd_seq_client_info_t* cinfo{}; snd_seq_client_info_alloca(&cinfo); @@ -82,6 +98,21 @@ inline void for_all_ports( } } +inline void for_all_ports( + const libasound& snd, snd_seq_t* seq, int client, + const std::function& func) +{ + snd_seq_port_info_t* pinfo{}; + snd_seq_port_info_alloca(&pinfo); + + snd.seq.port_info_set_client(pinfo, client); + snd.seq.port_info_set_port(pinfo, -1); + while (snd.seq.query_next_port(seq, pinfo) >= 0) + { + func(*pinfo); + } +} + // This function is used to count or get the pinfo structure for a given port // number. inline unsigned int iterate_port_info( @@ -125,7 +156,6 @@ inline unsigned int iterate_port_info( return count; return 0; } -} // A structure to hold variables related to the ALSA API // implementation. @@ -159,15 +189,15 @@ struct alsa_data snd.seq.set_client_name(seq, configuration.client_name.data()); #if __has_include() - if (snd.seq.ump.set_client_midi_version) + if (snd.seq.set_client_midi_version) { switch (configuration.midi_version) { case 1: - snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_LEGACY_MIDI); + snd.seq.set_client_midi_version(seq, SND_SEQ_CLIENT_LEGACY_MIDI); break; case 2: - snd.seq.ump.set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0); + snd.seq.set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0); break; } } @@ -272,8 +302,7 @@ struct alsa_data // Make subscription if (int err = snd.seq.port_subscribe_malloc(&this->subscription); err < 0) { - self.libremidi_handle_error( - self.configuration, "ALSA error allocation port subscription."); + self.libremidi_handle_error(self.configuration, "ALSA error allocation port subscription."); return err; } diff --git a/include/libremidi/backends/alsa_seq/midi_in.hpp b/include/libremidi/backends/alsa_seq/midi_in.hpp index 8065097..8d0be5d 100644 --- a/include/libremidi/backends/alsa_seq/midi_in.hpp +++ b/include/libremidi/backends/alsa_seq/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { struct dummy_processing { @@ -195,11 +195,6 @@ public: return stdx::error{}; } - stdx::error set_client_name(std::string_view clientName) override - { - return alsa_data::set_client_name(clientName); - } - stdx::error set_port_name(std::string_view portName) override { return alsa_data::set_port_name(portName); @@ -212,7 +207,7 @@ public: .count(); } - int process_event(const snd_seq_event_t& ev) + int64_t process_event(const snd_seq_event_t& ev) { if constexpr (ConfigurationImpl::midi_version == 1) { @@ -258,17 +253,17 @@ public: return 0; } - int process_events() + int64_t process_events() { if constexpr (ConfigurationImpl::midi_version == 1) { snd_seq_event_t* ev{}; event_handle handle{snd}; - int result = 0; + int64_t result = 0; if ((result = snd.seq.event_input(seq, &ev)) > 0) { handle.reset(ev); - if (int err = process_event(*ev); err < 0) + if (auto err = process_event(*ev); err < 0) { return err; } @@ -353,7 +348,7 @@ public: midi_in_alsa_threaded(ConfigurationBase&& conf, ConfigurationImpl&& apiconf) : midi_in_impl{std::move(conf), std::move(apiconf)} { - if (this->termination_event < 0) + if (this->m_termination_event < 0) { this->libremidi_handle_error(this->configuration, "error creating eventfd."); return; @@ -364,7 +359,7 @@ public: ~midi_in_alsa_threaded() { - this->close_port(); + midi_in_alsa_threaded::close_port(); this->client_open_ = std::errc::not_connected; } @@ -399,7 +394,7 @@ private: { try { - this->thread = std::thread([this] { thread_handler(); }); + this->m_thread = std::thread([this] { thread_handler(); }); return stdx::error{}; } catch (const std::system_error& e) @@ -415,12 +410,12 @@ private: stdx::error stop_thread() { - termination_event.notify(); + m_termination_event.notify(); - if (this->thread.joinable()) - this->thread.join(); + if (this->m_thread.joinable()) + this->m_thread.join(); - termination_event.consume(); + m_termination_event.consume(); return stdx::error{}; } @@ -428,7 +423,7 @@ private: { int poll_fd_count = alsa_data::snd.seq.poll_descriptors_count(this->seq, POLLIN) + 1; auto poll_fds = (struct pollfd*)alloca(poll_fd_count * sizeof(struct pollfd)); - poll_fds[0] = this->termination_event; + poll_fds[0] = this->m_termination_event; alsa_data::snd.seq.poll_descriptors(this->seq, poll_fds + 1, poll_fd_count - 1, POLLIN); const auto period @@ -439,10 +434,10 @@ private: if (alsa_data::snd.seq.event_input_pending(this->seq, 1) == 0) { // No data pending - if (poll(poll_fds, poll_fd_count, period) >= 0) + if (poll(poll_fds, poll_fd_count, static_cast(period)) >= 0) { // We got our stop-thread signal - if (termination_event.ready(poll_fds[0])) + if (m_termination_event.ready(poll_fds[0])) { break; } @@ -450,7 +445,7 @@ private: continue; } - int res{}; + int64_t res{}; if constexpr (ConfigurationImpl::midi_version == 1) { res = this->process_events(); @@ -467,8 +462,8 @@ private: } } - std::thread thread{}; - eventfd_notifier termination_event{}; + std::thread m_thread{}; + eventfd_notifier m_termination_event{}; }; template @@ -487,17 +482,17 @@ public: using poll_params = typename ConfigurationImpl::poll_parameters_type; this->configuration.manual_poll( poll_params{.addr = this->vaddr, .callback = [this](const auto& ev) { - if constexpr (ConfigurationImpl::midi_version == 1) - return this->process_event(ev); + if constexpr (ConfigurationImpl::midi_version == 1) + return this->process_event(ev); #if __has_include() - else - return this->process_ump_event(ev); + else + return this->process_ump_event(ev); #endif - }}); + }}); return 0; } - ~midi_in_alsa_manual() { this->close_port(); } + ~midi_in_alsa_manual() { midi_in_alsa_manual::close_port(); } stdx::error open_port(const input_port& pt, std::string_view local_port_name) override { @@ -530,7 +525,7 @@ public: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr diff --git a/include/libremidi/backends/alsa_seq/midi_out.hpp b/include/libremidi/backends/alsa_seq/midi_out.hpp index 0eea4ba..0865c5e 100644 --- a/include/libremidi/backends/alsa_seq/midi_out.hpp +++ b/include/libremidi/backends/alsa_seq/midi_out.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { class midi_out_impl final @@ -30,7 +30,7 @@ public: return; } - if (snd.midi.event_new(this->bufferSize, &this->coder) < 0) + if (snd.midi.event_new(this->m_bufferSize, &this->coder) < 0) { libremidi_handle_error(this->configuration, "error initializing MIDI event parser."); return; @@ -68,8 +68,9 @@ public: stdx::error open_port(const output_port& p, std::string_view portName) override { - unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); - if (nSrc < 1) + unsigned int n_src + = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); + if (n_src < 1) { libremidi_handle_error(this->configuration, "no MIDI output sources found!"); return make_error_code(std::errc::no_such_device); @@ -109,11 +110,6 @@ public: return stdx::error{}; } - stdx::error set_client_name(std::string_view clientName) override - { - return alsa_data::set_client_name(clientName); - } - stdx::error set_port_name(std::string_view portName) override { return alsa_data::set_port_name(portName); @@ -122,9 +118,9 @@ public: stdx::error send_message(const unsigned char* message, std::size_t size) override { int64_t result{}; - if (size > this->bufferSize) + if (size > this->m_bufferSize) { - this->bufferSize = size; + this->m_bufferSize = size; result = snd.midi.event_resize_buffer(this->coder, size); if (result != 0) { @@ -146,8 +142,8 @@ public: // FIXME direct is set but snd_seq_event_output_direct is not used... snd_seq_ev_set_direct(&ev); - const int64_t nBytes = size; // signed to avoir potential overflow with size - offset below - result = snd.midi.event_encode(this->coder, message + offset, (long)(nBytes - offset), &ev); + const int64_t n_bytes = size; // signed to avoir potential overflow with size - offset below + result = snd.midi.event_encode(this->coder, message + offset, (long)(n_bytes - offset), &ev); if (result < 0) { libremidi_handle_warning(this->configuration, "event parsing error!"); @@ -174,6 +170,6 @@ public: } private: - uint64_t bufferSize{32}; + uint64_t m_bufferSize{32}; }; } diff --git a/include/libremidi/backends/alsa_seq/observer.hpp b/include/libremidi/backends/alsa_seq/observer.hpp index fb101cb..77a331f 100644 --- a/include/libremidi/backends/alsa_seq/observer.hpp +++ b/include/libremidi/backends/alsa_seq/observer.hpp @@ -7,20 +7,32 @@ #include -#include +#if LIBREMIDI_HAS_UDEV + #include +#endif + #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { +struct client_info +{ + std::string client_name; + int client_id{}; + std::optional card{}; +}; + struct port_info { std::string client_name; std::string port_name; int client{}; int port{}; - bool isInput{}; - bool isOutput{}; + bool is_input{}; + bool is_output{}; + std::optional card{}; + libremidi::transport_type type{}; }; template @@ -88,12 +100,69 @@ public: } } - std::optional get_info(int client, int port) const noexcept + std::optional + get_client_info(int client, snd_seq_client_info_t& cinfo) const noexcept { + client_info p; + p.client_id = client; + + if (auto name = snd.seq.client_info_get_name(&cinfo)) + p.client_name = name; + + if (int card = snd.seq.client_info_get_card(&cinfo); card >= 0) + p.card = card; + + return p; + } + + std::optional get_info( + int client, int port, snd_seq_client_info_t& cinfo, + snd_seq_port_info_t& pinfo) const noexcept + { + const auto tp = snd.seq.port_info_get_type(&pinfo); + const auto cap = snd.seq.port_info_get_capability(&pinfo); + if ((cap & SND_SEQ_PORT_CAP_NO_EXPORT) != 0) + return std::nullopt; + port_info p; p.client = client; p.port = port; + bool ok = this->configuration.track_any; + + static constexpr auto virtual_port = SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER + | SND_SEQ_PORT_TYPE_APPLICATION; + + if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware) + { + p.type = libremidi::transport_type::hardware; + ok = true; + } + else if ((tp & virtual_port) && this->configuration.track_virtual) + { + p.type = libremidi::transport_type::software; + ok = true; + } + if (!ok) + return {}; + + if (auto name = snd.seq.client_info_get_name(&cinfo)) + p.client_name = name; + + if (auto name = snd.seq.port_info_get_name(&pinfo)) + p.port_name = name; + + if (int card = snd.seq.client_info_get_card(&cinfo); card >= 0) + p.card = card; + + p.is_input = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ); + p.is_output = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE); + + return p; + } + + std::optional get_info(int client, int port) const noexcept + { snd_seq_client_info_t* cinfo; snd_seq_client_info_alloca(&cinfo); if (int err = snd.seq.get_any_client_info(seq, client, cinfo); err < 0) @@ -104,55 +173,57 @@ public: if (int err = snd.seq.get_any_port_info(seq, client, port, pinfo); err < 0) return std::nullopt; - const auto tp = snd.seq.port_info_get_type(pinfo); - bool ok = this->configuration.track_any; - - static constexpr auto virtual_port - = SND_SEQ_PORT_TYPE_SOFTWARE | SND_SEQ_PORT_TYPE_SYNTHESIZER; - - if ((tp & SND_SEQ_PORT_TYPE_HARDWARE) && this->configuration.track_hardware) - ok = true; - else if ((tp & virtual_port) && this->configuration.track_virtual) - ok = true; - if (!ok) - return {}; - - if (auto name = snd.seq.client_info_get_name(cinfo)) - p.client_name = name; - - if (auto name = snd.seq.port_info_get_name(pinfo)) - p.port_name = name; - - auto cap = snd.seq.port_info_get_capability(pinfo); - p.isInput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_READ); - p.isOutput = (cap & SND_SEQ_PORT_CAP_DUPLEX) | (cap & SND_SEQ_PORT_CAP_WRITE); - - return p; + return get_info(client, port, *cinfo, *pinfo); } template - auto to_port_info(port_info p) const noexcept + auto to_port_info(const port_info& p) const noexcept -> std::conditional_t { static_assert(sizeof(this->seq) <= sizeof(libremidi::client_handle)); static_assert(sizeof(std::uintptr_t) <= sizeof(libremidi::client_handle)); + + container_identifier container{}; + device_identifier device{}; + libremidi::transport_type type = p.type; + std::string manufacturer; + std::string product; + std::string serial; +#if LIBREMIDI_HAS_UDEV + if (p.card) + { + auto res = get_udev_soundcard_info(m_udev, *p.card); + container = res.container; + device = res.path; + type = res.type; + manufacturer = res.vendor; + product = res.product; + serial = res.serial; + } +#endif return { - {.client = std::uintptr_t(this->seq), + {.api = get_current_api(), + .client = std::uintptr_t(this->seq), + .container = container, + .device = device, .port = alsa_seq::seq_to_port_handle(p.client, p.port), - .manufacturer = "", + .manufacturer = manufacturer, + .product = product, + .serial = serial, .device_name = p.client_name, .port_name = p.port_name, - .display_name = p.port_name}}; + .display_name = p.port_name, + .type = type}}; } void init_all_ports() { alsa_seq::for_all_ports( snd, this->seq, [this](snd_seq_client_info_t& client, snd_seq_port_info_t& port) { - int clt = snd.seq.client_info_get_client(&client); - int pt = snd.seq.port_info_get_port(&port); - register_port(clt, pt); - }); + int clt = snd.seq.client_info_get_client(&client); + int pt = snd.seq.port_info_get_port(&port); + register_port(clt, pt); + }); } libremidi::API get_current_api() const noexcept override @@ -168,12 +239,12 @@ public: std::vector ret; alsa_seq::for_all_ports( snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) { - int clt = snd.seq.client_info_get_client(&client); - int pt = snd.seq.port_info_get_port(&port); - if (auto p = get_info(clt, pt)) - if (p->isInput) - ret.push_back(to_port_info(*p)); - }); + int clt = snd.seq.client_info_get_client(&client); + int pt = snd.seq.port_info_get_port(&port); + if (auto p = get_info(clt, pt, client, port)) + if (p->is_input) + ret.push_back(to_port_info(*p)); + }); return ret; } @@ -182,12 +253,12 @@ public: std::vector ret; alsa_seq::for_all_ports( snd, this->seq, [this, &ret](snd_seq_client_info_t& client, snd_seq_port_info_t& port) { - int clt = snd.seq.client_info_get_client(&client); - int pt = snd.seq.port_info_get_port(&port); - if (auto p = get_info(clt, pt)) - if (p->isOutput) - ret.push_back(to_port_info(*p)); - }); + int clt = snd.seq.client_info_get_client(&client); + int pt = snd.seq.port_info_get_port(&port); + if (auto p = get_info(clt, pt, client, port)) + if (p->is_output) + ret.push_back(to_port_info(*p)); + }); return ret; } @@ -200,13 +271,13 @@ public: if (p.client == snd.seq.client_id(seq)) return; - knownClients_[{p.client, p.port}] = p; - if (p.isInput && configuration.input_added) + m_knownClients[{p.client, p.port}] = p; + if (p.is_input && configuration.input_added) { configuration.input_added(to_port_info(p)); } - if (p.isOutput && configuration.output_added) + if (p.is_output && configuration.output_added) { configuration.output_added(to_port_info(p)); } @@ -214,37 +285,61 @@ public: void unregister_port(int client, int port) { - auto it = knownClients_.find({client, port}); - if (it != knownClients_.end()) + auto it = m_knownClients.find({client, port}); + if (it != m_knownClients.end()) { auto p = it->second; - knownClients_.erase(it); + m_knownClients.erase(it); - if (p.isInput && configuration.input_removed) + if (p.is_input && configuration.input_removed) { configuration.input_removed(to_port_info(p)); } - if (p.isOutput && configuration.output_removed) + if (p.is_output && configuration.output_removed) { configuration.output_removed(to_port_info(p)); } } } - void handle_event(const snd_seq_event_t& ev) + void handle_event_direct(const snd_seq_event_t& ev) { switch (ev.type) { + case SND_SEQ_EVENT_CLIENT_START: { + // TODO + break; + } + case SND_SEQ_EVENT_CLIENT_EXIT: { + // TODO + break; + } + case SND_SEQ_EVENT_CLIENT_CHANGE: { + // TODO + break; + } +#if LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS + case SND_SEQ_EVENT_UMP_EP_CHANGE: { + // TODO + break; + } + case SND_SEQ_EVENT_UMP_BLOCK_CHANGE: { + // TODO + break; + } +#endif case SND_SEQ_EVENT_PORT_START: { - register_port(ev.data.addr.client, ev.data.addr.port); + this->register_port(ev.data.addr.client, ev.data.addr.port); break; } case SND_SEQ_EVENT_PORT_EXIT: { - unregister_port(ev.data.addr.client, ev.data.addr.port); + this->unregister_port(ev.data.addr.client, ev.data.addr.port); break; } case SND_SEQ_EVENT_PORT_CHANGE: + // TODO + break; default: break; } @@ -263,7 +358,11 @@ public: } private: - std::map, port_info> knownClients_; + std::map, port_info> m_knownClients; + +#if LIBREMIDI_HAS_UDEV + udev_helper m_udev{}; +#endif }; template @@ -275,38 +374,87 @@ public: { // Create relevant descriptors auto& snd = alsa_data::snd; - const auto N = snd.seq.poll_descriptors_count(this->seq, POLLIN); - descriptors_.resize(N + 1); - snd.seq.poll_descriptors(this->seq, descriptors_.data(), N, POLLIN); - descriptors_.back() = this->termination_event; + + // 1. Descriptor count + const auto n = snd.seq.poll_descriptors_count(this->seq, POLLIN); + int total_descriptors = n; + total_descriptors++; // eventfd for terminating the thread + + // 2. Create storage + descriptors.resize(total_descriptors); + + // 3. Store descriptors + snd.seq.poll_descriptors(this->seq, descriptors.data(), n, POLLIN); + descriptors[n] = this->termination_event; // Start the listening thread - thread = std::thread{[this] { + thread = std::thread{[this, n] { auto& snd = alsa_data::snd; const auto period = std::chrono::duration_cast(this->configuration.poll_period) .count(); for (;;) { - int err = poll(descriptors_.data(), descriptors_.size(), period); + int err = poll(descriptors.data(), descriptors.size(), static_cast(period)); if (err >= 0) { // We got our stop-thread signal - if (descriptors_.back().revents & POLLIN) + if (descriptors[n].revents & POLLIN) break; + // Put ALSA event in our queue snd_seq_event_t* ev{}; event_handle handle{snd}; while (snd.seq.event_input(this->seq, &ev) >= 0) { handle.reset(ev); - this->handle_event(*ev); + this->handle_event_delayed(*ev); + } + + // Process the events in a deferred way. + // This is because udev takes some milliseconds to populate its field after a + // port was added + auto tm = std::chrono::steady_clock::now(); + for (auto it = queued_events.begin(); it != queued_events.end();) + { + if ((tm - it->second) >= this->configuration.poll_period) + { + this->handle_event_direct(it->first); + it = queued_events.erase(it); + } + else + { + break; + } } } } }}; } + void handle_event_delayed(const snd_seq_event_t& ev) + { + switch (ev.type) + { + case SND_SEQ_EVENT_CLIENT_START: + case SND_SEQ_EVENT_CLIENT_EXIT: + case SND_SEQ_EVENT_CLIENT_CHANGE: + +#if LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS + case SND_SEQ_EVENT_UMP_EP_CHANGE: + case SND_SEQ_EVENT_UMP_BLOCK_CHANGE: +#endif + + case SND_SEQ_EVENT_PORT_START: + case SND_SEQ_EVENT_PORT_EXIT: + case SND_SEQ_EVENT_PORT_CHANGE: + queued_events.emplace_back(ev, std::chrono::steady_clock::now()); + break; + default: + break; + } + } + ~observer_threaded() { termination_event.notify(); @@ -317,7 +465,8 @@ public: eventfd_notifier termination_event{}; std::thread thread; - std::vector descriptors_; + std::vector descriptors; + std::vector> queued_events; }; template @@ -329,16 +478,16 @@ public: { this->configuration.manual_poll( poll_parameters{.addr = this->vaddr, .callback = [this](const auto& v) { - this->handle_event(v); - return 0; - }}); + this->handle_event_direct(v); + return 0; + }}); } ~observer_manual() { this->configuration.stop_poll(this->vaddr); } }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr diff --git a/include/libremidi/backends/alsa_seq/shared_handler.hpp b/include/libremidi/backends/alsa_seq/shared_handler.hpp index f9434a0..7404a46 100644 --- a/include/libremidi/backends/alsa_seq/shared_handler.hpp +++ b/include/libremidi/backends/alsa_seq/shared_handler.hpp @@ -9,7 +9,7 @@ #include -namespace libremidi::alsa_seq +NAMESPACE_LIBREMIDI::alsa_seq { struct shared_handler : public libremidi::shared_context @@ -61,7 +61,7 @@ struct shared_handler : public libremidi::shared_context { auto clt = std::make_shared(client_name); - auto cb = [client = std::weak_ptr{clt}](const libremidi::alsa_seq::poll_parameters& params) { + auto cb = [client = std::weak_ptr{clt}](libremidi::alsa_seq::poll_parameters params) { if (auto clt = client.lock()) { clt->events.push( @@ -74,8 +74,7 @@ struct shared_handler : public libremidi::shared_context auto stop_cb = [client = std::weak_ptr{clt}](snd_seq_addr_t id) { if (auto clt = client.lock()) { - clt->events.push( - {.type = shared_handler::event_type::callback_removed, .payload = std::move(id)}); + clt->events.push({.type = shared_handler::event_type::callback_removed, .payload = id}); clt->queue_event.notify(); } return true; @@ -128,13 +127,13 @@ struct shared_handler : public libremidi::shared_context { case callback_added: { auto [addr, cb] - = std::move(*std::get_if(&ev.payload)); + = std::move(*get_if(&ev.payload)); addresses.push_back(addr); callbacks.push_back(std::move(cb)); break; } case callback_removed: - auto addr = std::move(*std::get_if(&ev.payload)); + auto addr = *get_if(&ev.payload); if (auto index = index_of_address(addr); index >= 0) { addresses.erase(addresses.begin() + index); @@ -146,15 +145,14 @@ struct shared_handler : public libremidi::shared_context } // Look for who's ready - for (int64_t i = 0, N = std::ssize(fds) - 2; i < N; i++) + for (int64_t i = 0, n = std::ssize(fds) - 2; i < n; i++) { if (fds[i].revents & POLLIN) { // Read alsa event snd_seq_event_t* ev{}; event_handle handle{snd}; - int result = 0; - while ((result = snd.seq.event_input(client, &ev)) > 0) + while (snd.seq.event_input(client, &ev) > 0) { handle.reset(ev); @@ -181,7 +179,7 @@ struct shared_handler : public libremidi::shared_context struct event { event_type type; - std::variant payload; + libremidi_variant_alias::variant payload; }; snd_seq_t* client{}; diff --git a/include/libremidi/backends/alsa_seq_ump.hpp b/include/libremidi/backends/alsa_seq_ump.hpp index ae91f79..56b0f86 100644 --- a/include/libremidi/backends/alsa_seq_ump.hpp +++ b/include/libremidi/backends/alsa_seq_ump.hpp @@ -5,7 +5,11 @@ #include #include -namespace libremidi +#include + +#include + +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr @@ -38,7 +42,7 @@ inline std::unique_ptr make< } -namespace libremidi::alsa_seq_ump +NAMESPACE_LIBREMIDI::alsa_seq_ump { struct backend { @@ -50,13 +54,16 @@ struct backend using midi_out_configuration = alsa_seq_ump::output_configuration; using midi_observer_configuration = alsa_seq_ump::observer_configuration; static const constexpr auto API = libremidi::API::ALSA_SEQ_UMP; - static const constexpr auto name = "alsa_seq_ump"; - static const constexpr auto display_name = "ALSA (sequencer, UMP)"; + static const constexpr std::string_view name = "alsa_seq_ump"; + static const constexpr std::string_view display_name = "ALSA (sequencer, UMP)"; static inline bool available() noexcept { static const libasound& snd = libasound::instance(); - return snd.available && snd.seq.available && snd.seq.ump.available && snd.ump.available; + if (!snd.available || !snd.seq.available || !snd.seq.ump.available || !snd.ump.available) + return false; + + return ::access("/dev/snd/seq", F_OK | R_OK | W_OK) == 0; } }; } diff --git a/include/libremidi/backends/alsa_seq_ump/config.hpp b/include/libremidi/backends/alsa_seq_ump/config.hpp index f57a306..661de4a 100644 --- a/include/libremidi/backends/alsa_seq_ump/config.hpp +++ b/include/libremidi/backends/alsa_seq_ump/config.hpp @@ -11,7 +11,7 @@ extern "C" { typedef struct snd_seq_ump_event snd_seq_ump_event_t; } -namespace libremidi::alsa_seq_ump +NAMESPACE_LIBREMIDI::alsa_seq_ump { struct poll_parameters diff --git a/include/libremidi/backends/alsa_seq_ump/midi_out.hpp b/include/libremidi/backends/alsa_seq_ump/midi_out.hpp index 46760d6..bb9be5c 100644 --- a/include/libremidi/backends/alsa_seq_ump/midi_out.hpp +++ b/include/libremidi/backends/alsa_seq_ump/midi_out.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi::alsa_seq_ump +NAMESPACE_LIBREMIDI::alsa_seq_ump { class midi_out_impl final @@ -64,8 +64,9 @@ public: stdx::error open_port(const output_port& p, std::string_view portName) override { - unsigned int nSrc = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); - if (nSrc < 1) + unsigned int n_src + = this->get_port_count(SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); + if (n_src < 1) { libremidi_handle_error(this->configuration, "no MIDI output sources found!"); return make_error_code(std::errc::no_such_device); @@ -105,11 +106,6 @@ public: return stdx::error{}; } - stdx::error set_client_name(std::string_view clientName) override - { - return alsa_data::set_client_name(clientName); - } - stdx::error set_port_name(std::string_view portName) override { return alsa_data::set_port_name(portName); @@ -133,9 +129,10 @@ public: libremidi_handle_warning(this->configuration, "error sending MIDI message to port."); return static_cast(-ret); } - return std::errc{0}; + static_assert(std::errc{0} == std::errc{}); + return std::errc{}; }; - segment_ump_stream(ump_stream, count, write_func, []() {}); + segment_ump_stream(ump_stream, count, write_func, []() { }); snd.seq.drain_output(this->seq); return stdx::error{}; diff --git a/include/libremidi/backends/android/android.hpp b/include/libremidi/backends/android/android.hpp new file mode 100644 index 0000000..9dc3a7c --- /dev/null +++ b/include/libremidi/backends/android/android.hpp @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +struct backend +{ + using midi_in = android::midi_in; + using midi_out = android::midi_out; + using midi_observer = android::observer; + using midi_in_configuration = android::input_configuration; + using midi_out_configuration = android::output_configuration; + using midi_observer_configuration = android::observer_configuration; + static const constexpr auto API = libremidi::API::ANDROID_AMIDI; + static const constexpr std::string_view name = "android"; + static const constexpr std::string_view display_name = "Android MIDI API"; + + static bool available() noexcept + { +#if defined(__ANDROID__) + return true; +#else + return false; +#endif + } +}; +} +} diff --git a/include/libremidi/backends/android/config.hpp b/include/libremidi/backends/android/config.hpp new file mode 100644 index 0000000..55d8ae0 --- /dev/null +++ b/include/libremidi/backends/android/config.hpp @@ -0,0 +1,23 @@ +#pragma once +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +struct input_configuration +{ + std::string_view client_name = "libremidi client"; +}; + +struct output_configuration +{ + std::string_view client_name = "libremidi client"; +}; + +struct observer_configuration +{ + std::string_view client_name = "libremidi client"; +}; +} +} \ No newline at end of file diff --git a/include/libremidi/backends/android/helpers.cpp b/include/libremidi/backends/android/helpers.cpp new file mode 100644 index 0000000..da90bf3 --- /dev/null +++ b/include/libremidi/backends/android/helpers.cpp @@ -0,0 +1,239 @@ +#include +#include +#include + +NAMESPACE_LIBREMIDI::android +{ +// Main source of knowledge is RtMidi implementation +// which was done by Yellow Labrador + +JNIEnv* context::get_thread_env() +{ + JNIEnv* env; + JavaVM* jvm; + jsize count; + if (JNI_GetCreatedJavaVMs(&jvm, 1, &count) != JNI_OK || count < 1) + { + LOGE("No JVM found"); + return nullptr; + } + + if (jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED) + { + if (jvm->AttachCurrentThread(&env, nullptr) != JNI_OK) + { + LOGE("Failed to attach thread"); + return nullptr; + } + } + + return env; +} + +jobject context::get_context(JNIEnv* env) +{ + auto activity_thread = env->FindClass("android/app/ActivityThread"); + auto current_activity_thread = env->GetStaticMethodID( + activity_thread, "currentActivityThread", "()Landroid/app/ActivityThread;"); + auto at = env->CallStaticObjectMethod(activity_thread, current_activity_thread); + + if (!at) + { + LOGE("Failed to get ActivityThread"); + return nullptr; + } + + auto get_application + = env->GetMethodID(activity_thread, "getApplication", "()Landroid/app/Application;"); + auto app = env->CallObjectMethod(at, get_application); + + if (!app) + { + LOGE("Failed to get Application"); + return nullptr; + } + + return app; +} + +jobject context::get_midi_manager(JNIEnv* env, jobject ctx) +{ + auto context_class = env->FindClass("android/content/Context"); + auto get_system_service = env->GetMethodID( + context_class, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); + auto midi_service_str = env->NewStringUTF("midi"); + auto service = env->CallObjectMethod(ctx, get_system_service, midi_service_str); + env->DeleteLocalRef(midi_service_str); + return service; +} + +void context::refresh_midi_devices(JNIEnv* env, jobject ctx, bool is_output) +{ + cleanup_devices(env); + + auto midi_service = get_midi_manager(env, ctx); + if (!midi_service) + return; + + auto midi_mgr_class = env->FindClass("android/media/midi/MidiManager"); + auto get_devices_method + = env->GetMethodID(midi_mgr_class, "getDevices", "()[Landroid/media/midi/MidiDeviceInfo;"); + auto device_array = (jobjectArray)env->CallObjectMethod(midi_service, get_devices_method); + + auto device_info_class = env->FindClass("android/media/midi/MidiDeviceInfo"); + auto get_input_count = env->GetMethodID(device_info_class, "getInputPortCount", "()I"); + auto get_output_count = env->GetMethodID(device_info_class, "getOutputPortCount", "()I"); + + jsize count = env->GetArrayLength(device_array); + for (jsize i = 0; i < count; ++i) + { + auto device_info = env->GetObjectArrayElement(device_array, i); + int port_count = is_output ? env->CallIntMethod(device_info, get_input_count) + : env->CallIntMethod(device_info, get_output_count); + + if (port_count > 0) + { + midi_devices.push_back(env->NewGlobalRef(device_info)); + } + env->DeleteLocalRef(device_info); + } + + env->DeleteLocalRef(device_array); + env->DeleteLocalRef(midi_service); +} + +std::string context::port_name(JNIEnv* env, unsigned int port_number) +{ + if (port_number >= midi_devices.size()) + { + LOGE("Invalid port number"); + return ""; + } + + auto device_info_class = env->FindClass("android/media/midi/MidiDeviceInfo"); + auto get_props_method + = env->GetMethodID(device_info_class, "getProperties", "()Landroid/os/Bundle;"); + auto bundle = env->CallObjectMethod(midi_devices[port_number], get_props_method); + + auto bundle_class = env->FindClass("android/os/Bundle"); + auto get_string_method + = env->GetMethodID(bundle_class, "getString", "(Ljava/lang/String;)Ljava/lang/String;"); + auto key = env->NewStringUTF("name"); + auto name_str = (jstring)env->CallObjectMethod(bundle, get_string_method, key); + + std::string result; + if (name_str) + { + const char* name_chars = env->GetStringUTFChars(name_str, nullptr); + result = name_chars; + env->ReleaseStringUTFChars(name_str, name_chars); + env->DeleteLocalRef(name_str); + } + + env->DeleteLocalRef(key); + env->DeleteLocalRef(bundle); + + return result; +} + +void context::cleanup_devices(JNIEnv* env) +{ + for (jobject device : midi_devices) + { + env->DeleteGlobalRef(device); + } + midi_devices.clear(); +} + +void context::open_device(jobject device_info, void* target, bool is_output) +{ + auto env = get_thread_env(); + if (!env) + return; + + auto ctx = get_context(env); + if (!ctx) + return; + + auto midi_mgr = get_midi_manager(env, ctx); + if (!midi_mgr) + return; + + // Check if we're on the main thread + auto looper_class = env->FindClass("android/os/Looper"); + auto get_my_looper_method + = env->GetStaticMethodID(looper_class, "myLooper", "()Landroid/os/Looper;"); + auto my_looper = env->CallStaticObjectMethod(looper_class, get_my_looper_method); + + jobject handler = nullptr; + if (!my_looper) + { + LOGI("Not on a Looper thread, using main looper"); + auto get_main_looper_method + = env->GetStaticMethodID(looper_class, "getMainLooper", "()Landroid/os/Looper;"); + auto main_looper = env->CallStaticObjectMethod(looper_class, get_main_looper_method); + + auto handler_class = env->FindClass("android/os/Handler"); + auto handler_ctor = env->GetMethodID(handler_class, "", "(Landroid/os/Looper;)V"); + handler = env->NewObject(handler_class, handler_ctor, main_looper); + + env->DeleteLocalRef(main_looper); + } + + // Create the callback listener + auto callback_class = env->FindClass("dev/celtera/libremidi/MidiDeviceCallback"); + if (!callback_class) + { + LOGE("MidiDeviceCallback class not found - ensure the Java class is loaded"); + if (handler) + env->DeleteLocalRef(handler); + return; + } + + auto callback_ctor = env->GetMethodID(callback_class, "", "(JZ)V"); + auto callback + = env->NewObject(callback_class, callback_ctor, (jlong)target, (jboolean)is_output); + + // Open the device + auto midi_mgr_class = env->FindClass("android/media/midi/MidiManager"); + auto open_device_method = env->GetMethodID( + midi_mgr_class, "openDevice", + "(Landroid/media/midi/MidiDeviceInfo;Landroid/media/midi/" + "MidiManager$OnDeviceOpenedListener;Landroid/os/Handler;)V"); + + env->CallVoidMethod(midi_mgr, open_device_method, device_info, callback, handler); + + env->DeleteLocalRef(callback); + if (handler) + env->DeleteLocalRef(handler); +} + +extern "C" JNIEXPORT void JNICALL Java_dev_celtera_libremidi_MidiDeviceCallback_onDeviceOpened( + JNIEnv* env, jobject /*thiz*/, jobject midi_device, jlong target_ptr, jboolean is_output) +{ + if (!midi_device || target_ptr == 0) + { + LOGE("Invalid device or target pointer in callback"); + return; + } + + AMidiDevice* amidi_device = nullptr; + AMidiDevice_fromJava(env, midi_device, &amidi_device); + + if (!amidi_device) + { + LOGE("Failed to convert Java MIDI device to AMidiDevice"); + return; + } + + if (is_output) + { + midi_out::open_callback(reinterpret_cast(target_ptr), amidi_device); + } + else + { + midi_in::open_callback(reinterpret_cast(target_ptr), amidi_device); + } +} + +} diff --git a/include/libremidi/backends/android/helpers.hpp b/include/libremidi/backends/android/helpers.hpp new file mode 100644 index 0000000..7f4ce41 --- /dev/null +++ b/include/libremidi/backends/android/helpers.hpp @@ -0,0 +1,40 @@ +#pragma once +#include + +#include +#include + +#include +#include + +#include +#include + +#define LOG_TAG "libremidi" +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +struct context +{ + static inline std::string client_name; + static inline std::vector midi_devices; + + static JNIEnv* get_thread_env(); + static jobject get_context(JNIEnv* env); + static jobject get_midi_manager(JNIEnv* env, jobject context); + static void refresh_midi_devices(JNIEnv* env, jobject context, bool is_output); + static void open_device(jobject device_info, void* target, bool is_output); + static std::string port_name(JNIEnv* env, unsigned int port_number); + static void cleanup_devices(JNIEnv* env); +}; + +extern "C" JNIEXPORT void JNICALL Java_dev_celtera_libremidi_MidiDeviceCallback_onDeviceOpened( + JNIEnv* env, jobject /*thiz*/, jobject midi_device, jlong target_ptr, jboolean is_output); +} +} diff --git a/include/libremidi/backends/android/midi_in.hpp b/include/libremidi/backends/android/midi_in.hpp new file mode 100644 index 0000000..78fca93 --- /dev/null +++ b/include/libremidi/backends/android/midi_in.hpp @@ -0,0 +1,165 @@ +#pragma once +#include +#include +#include +#include + +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +class midi_in + : public midi1::in_api + , public error_handler +{ +public: + explicit midi_in( + libremidi::input_configuration&& conf, libremidi::android::input_configuration aconf) + : configuration{std::move(conf)} + { + if (!context::client_name.empty() && context::client_name != aconf.client_name) + { + libremidi_handle_error( + configuration, "Android backend only supports one client name per process"); + return; + } + context::client_name = std::string(aconf.client_name); + client_open_ = stdx::error{}; + } + + ~midi_in() override { close_port(); } + + libremidi::API get_current_api() const noexcept override + { + return libremidi::API::ANDROID_AMIDI; + } + + stdx::error open_port(const input_port& port, std::string_view /*name*/) override + { + if (is_port_open()) + { + libremidi_handle_error(configuration, "midi_in::open_port: a port is already open"); + return std::errc{}; + } + + const unsigned int port_number = port.port; + auto env = context::get_thread_env(); + if (!env) + { + libremidi_handle_error(configuration, "midi_in::open_port: failed to get JNI environment"); + return std::errc{}; + } + + auto ctx = context::get_context(env); + if (!ctx) + { + libremidi_handle_error(configuration, "midi_in::open_port: failed to get Android context"); + return std::errc{}; + } + + context::refresh_midi_devices(env, ctx, false); + + if (port_number >= context::midi_devices.size()) + { + libremidi_handle_error(configuration, "midi_in::open_port: invalid port number"); + return std::errc{}; + } + + port_open = true; + running = true; + context::open_device(context::midi_devices[port_number], this, false); + return stdx::error{}; + } + + stdx::error close_port() override + { + if (!is_port_open()) + return std::errc{}; + + running = false; + + if (poll_thread.joinable()) + { + poll_thread.join(); + } + + if (midi_output_port) + { + AMidiOutputPort_close(midi_output_port); + midi_output_port = nullptr; + } + + if (receive_device) + { + AMidiDevice_release(receive_device); + receive_device = nullptr; + } + + port_open = false; + return stdx::error{}; + } + + bool is_port_open() const noexcept { return port_open; } + + void start_midi_thread() { poll_thread = std::thread(&midi_in::poll_midi, this); } + + timestamp absolute_timestamp() const noexcept override { return system_ns(); } + void poll_midi() + { + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = false, + .absolute_is_monotonic = false, + .has_samples = false, + }; + + uint8_t buffer[1024]; + int32_t op_code; + int64_t timestamp; + size_t num_bytes; + + while (running) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + ssize_t num_messages = AMidiOutputPort_receive( + midi_output_port, &op_code, buffer, sizeof(buffer), &num_bytes, ×tamp); + + if (num_messages < 0) + { + LOGE("Error receiving MIDI data: %d", (int)num_messages); + continue; + } + + if (num_messages > 0 && num_bytes > 0) + { + const auto to_ns = [=] { return timestamp; }; + + m_processing.on_bytes_multi( + {buffer, buffer + num_bytes}, + m_processing.timestamp(to_ns, timestamp)); + } + } + } + + static void open_callback(midi_in* self, AMidiDevice* device) + { + self->receive_device = device; + AMidiOutputPort_open(device, 0, &self->midi_output_port); + self->start_midi_thread(); + } + +private: + libremidi::input_configuration configuration; + AMidiDevice* receive_device = nullptr; + AMidiOutputPort* midi_output_port = nullptr; + std::thread poll_thread; + std::atomic port_open{false}; + std::atomic running{false}; + midi1::input_state_machine m_processing{this->configuration}; +}; +} +} diff --git a/include/libremidi/backends/android/midi_out.hpp b/include/libremidi/backends/android/midi_out.hpp new file mode 100644 index 0000000..fb12986 --- /dev/null +++ b/include/libremidi/backends/android/midi_out.hpp @@ -0,0 +1,126 @@ +#pragma once +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +class midi_out final + : public midi1::out_api + , public error_handler +{ +public: + explicit midi_out( + libremidi::output_configuration&& conf, libremidi::android::output_configuration aconf) + : configuration{std::move(conf)} + { + if (!context::client_name.empty() && context::client_name != aconf.client_name) + { + libremidi_handle_error( + configuration, "Android backend only supports one client name per process"); + return; + } + context::client_name = std::string(aconf.client_name); + client_open_ = stdx::error{}; + } + + ~midi_out() override { close_port(); } + + libremidi::API get_current_api() const noexcept override + { + return libremidi::API::ANDROID_AMIDI; + } + + stdx::error open_port(const output_port& port, std::string_view /*name*/) override + { + if (is_port_open()) + { + libremidi_handle_error(configuration, "midi_out::open_port: a port is already open"); + return std::errc{}; + } + + const unsigned int port_number = port.port; + + auto env = context::get_thread_env(); + if (!env) + { + libremidi_handle_error(configuration, "midi_out::open_port: failed to get JNI environment"); + return std::errc{}; + } + + auto ctx = context::get_context(env); + if (!ctx) + { + libremidi_handle_error(configuration, "midi_out::open_port: failed to get Android context"); + return std::errc{}; + } + + context::refresh_midi_devices(env, ctx, true); + + if (port_number >= context::midi_devices.size()) + { + libremidi_handle_error(configuration, "midi_out::open_port: invalid port number"); + return std::errc{}; + } + + port_open = true; + context::open_device(context::midi_devices[port_number], this, true); + return stdx::error{}; + } + + stdx::error close_port() override + { + if (!is_port_open()) + return std::errc{}; + + if (midi_input_port) + { + AMidiInputPort_close(midi_input_port); + midi_input_port = nullptr; + } + + if (send_device) + { + AMidiDevice_release(send_device); + send_device = nullptr; + } + + port_open = false; + return stdx::error{}; + } + + bool is_port_open() const noexcept { return port_open; } + + stdx::error send_message(const unsigned char* message, size_t size) override + { + if (!is_port_open() || !midi_input_port) + { + libremidi_handle_error(configuration, "midi_out::send_message: port is not open"); + return std::errc{}; + } + + ssize_t result = AMidiInputPort_send(midi_input_port, message, size); + if (result < 0) + { + libremidi_handle_error(configuration, "midi_out::send_message: failed to send MIDI data"); + return std::errc{}; + } + return stdx::error{}; + } + + static void open_callback(midi_out* self, AMidiDevice* device) + { + self->send_device = device; + AMidiInputPort_open(device, 0, &self->midi_input_port); + } + +private: + libremidi::output_configuration configuration; + AMidiDevice* send_device = nullptr; + AMidiInputPort* midi_input_port = nullptr; + bool port_open = false; +}; +} +} diff --git a/include/libremidi/backends/android/observer.hpp b/include/libremidi/backends/android/observer.hpp new file mode 100644 index 0000000..f320002 --- /dev/null +++ b/include/libremidi/backends/android/observer.hpp @@ -0,0 +1,93 @@ +#pragma once +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +namespace android +{ +class observer final + : public libremidi::observer_api + , public error_handler +{ +public: + explicit observer( + libremidi::observer_configuration&& conf, libremidi::android::observer_configuration aconf) + : configuration{std::move(conf)} + { + if (!context::client_name.empty() && context::client_name != aconf.client_name) + { + LOGW("Android backend only supports one client name per process"); + } + context::client_name = std::string(aconf.client_name); + } + + ~observer() + { + auto env = context::get_thread_env(); + if (env) + { + context::cleanup_devices(env); + } + } + + libremidi::API get_current_api() const noexcept override + { + return libremidi::API::ANDROID_AMIDI; + } + + std::vector get_input_ports() const noexcept override + { + auto env = context::get_thread_env(); + if (!env) + return {}; + + auto ctx = context::get_context(env); + if (!ctx) + return {}; + + context::refresh_midi_devices(env, ctx, false); + + std::vector ports; + for (size_t i = 0; i < context::midi_devices.size(); ++i) + { + libremidi::input_port port; + port.api = libremidi::API::ANDROID_AMIDI; + port.port_name = context::port_name(env, i); + port.port = i; + ports.push_back(std::move(port)); + } + + return ports; + } + + std::vector get_output_ports() const noexcept override + { + auto env = context::get_thread_env(); + if (!env) + return {}; + + auto ctx = context::get_context(env); + if (!ctx) + return {}; + + context::refresh_midi_devices(env, ctx, true); + + std::vector ports; + for (size_t i = 0; i < context::midi_devices.size(); ++i) + { + libremidi::output_port port; + port.port_name = context::port_name(env, i); + port.port = i; + ports.push_back(std::move(port)); + } + + return ports; + } + +private: + libremidi::observer_configuration configuration; +}; +} +} diff --git a/include/libremidi/backends/coremidi.hpp b/include/libremidi/backends/coremidi.hpp index e6f2ee4..eab24e8 100644 --- a/include/libremidi/backends/coremidi.hpp +++ b/include/libremidi/backends/coremidi.hpp @@ -3,7 +3,9 @@ #include #include -namespace libremidi +#include + +NAMESPACE_LIBREMIDI { struct core_backend { @@ -14,8 +16,8 @@ struct core_backend using midi_out_configuration = coremidi_output_configuration; using midi_observer_configuration = coremidi_observer_configuration; static const constexpr auto API = libremidi::API::COREMIDI; - static const constexpr auto name = "core"; - static const constexpr auto display_name = "CoreMIDI"; + static const constexpr std::string_view name = "core"; + static const constexpr std::string_view display_name = "CoreMIDI"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/coremidi/config.hpp b/include/libremidi/backends/coremidi/config.hpp index 359dece..db31956 100644 --- a/include/libremidi/backends/coremidi/config.hpp +++ b/include/libremidi/backends/coremidi/config.hpp @@ -18,7 +18,7 @@ typedef MIDIObjectRef MIDIClientRef; using MIDIClientRef = uint32_t; using MIDIObjectRef = uint32_t; #endif -namespace libremidi +NAMESPACE_LIBREMIDI { struct coremidi_input_configuration diff --git a/include/libremidi/backends/coremidi/error_domain.hpp b/include/libremidi/backends/coremidi/error_domain.hpp index fb33d39..ff82ccc 100644 --- a/include/libremidi/backends/coremidi/error_domain.hpp +++ b/include/libremidi/backends/coremidi/error_domain.hpp @@ -3,7 +3,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct coremidi_error_domain : public stdx::error_domain { diff --git a/include/libremidi/backends/coremidi/helpers.hpp b/include/libremidi/backends/coremidi/helpers.hpp index 30f5eea..2d85134 100644 --- a/include/libremidi/backends/coremidi/helpers.hpp +++ b/include/libremidi/backends/coremidi/helpers.hpp @@ -1,9 +1,9 @@ #pragma once +#include #include #include #include #include -#include #include #include @@ -21,13 +21,12 @@ #define LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME AudioGetCurrentHostTime #endif -namespace libremidi +NAMESPACE_LIBREMIDI { using CFString_handle = unique_handle; using CFStringMutable_handle = unique_handle<__CFString, CFRelease>; -namespace -{ -static inline std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept + +LIBREMIDI_STATIC std::string get_string_property(MIDIObjectRef object, CFStringRef property) noexcept { CFStringRef res; if (MIDIObjectGetStringProperty(object, property, &res) || !res) @@ -39,14 +38,14 @@ static inline std::string get_string_property(MIDIObjectRef object, CFStringRef return name; } -static inline int32_t get_int_property(MIDIObjectRef object, CFStringRef property) noexcept +LIBREMIDI_STATIC int32_t get_int_property(MIDIObjectRef object, CFStringRef property) noexcept { SInt32 res; MIDIObjectGetIntegerProperty(object, property, &res); return res; } -static inline CFString_handle toCFString(std::string_view str) noexcept +LIBREMIDI_STATIC CFString_handle toCFString(std::string_view str) noexcept { return CFString_handle{CFStringCreateWithCString(nullptr, str.data(), kCFStringEncodingASCII)}; } @@ -71,20 +70,22 @@ inline uint64_t AudioConvertHostTimeToNanos(uint64_t hostTime) return static_cast(res); } #endif + +LIBREMIDI_STATIC auto get_cfstring_property(MIDIObjectRef prop, CFStringRef name) +{ + CFStringRef str = nullptr; + MIDIObjectGetStringProperty(prop, name, &str); + return CFString_handle{str}; +} + // This function was submitted by Douglas Casey Tucker and apparently // derived largely from PortMidi. inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) { CFMutableStringRef result = CFStringCreateMutable(nullptr, 0); - static constexpr auto getProp = [](MIDIObjectRef prop) { - CFStringRef str = nullptr; - MIDIObjectGetStringProperty(prop, kMIDIPropertyName, &str); - return CFString_handle{str}; - }; - // Begin with the endpoint's name. - if (auto endpoint_name = getProp(endpoint)) + if (auto endpoint_name = get_cfstring_property(endpoint, kMIDIPropertyName)) { CFStringAppend(result, endpoint_name.get()); } @@ -101,7 +102,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) if (CFStringGetLength(result) == 0) { // endpoint name has zero length -- try the entity - if (auto entity_name = getProp(entity)) + if (auto entity_name = get_cfstring_property(entity, kMIDIPropertyName)) { CFStringAppend(result, entity_name.get()); } @@ -112,7 +113,7 @@ inline CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) if (device == 0) goto finish; - if (auto dev_name = getProp(device)) + if (auto dev_name = get_cfstring_property(device, kMIDIPropertyName)) { const auto dev_strlen = CFStringGetLength(dev_name.get()); @@ -218,22 +219,19 @@ locate_object(auto& self, const port_information& info, MIDIObjectType requested auto ret = MIDIObjectFindByUniqueID(uid, &object, &type); if (ret != noErr) { - self.libremidi_handle_error( - self.configuration, "cannot find port: " + info.port_name); + self.libremidi_handle_error(self.configuration, "cannot find port: " + info.port_name); return 0; } if (type != requested_type || object == 0) { self.libremidi_handle_error( - self.configuration, "invalid object: " + info.port_name + " : " - + std::to_string(object)); + self.configuration, "invalid object: " + info.port_name + " : " + std::to_string(object)); return 0; } return object; } -} // A structure to hold variables related to the CoreMIDI API // implementation. diff --git a/include/libremidi/backends/coremidi/midi_in.hpp b/include/libremidi/backends/coremidi/midi_in.hpp index deaaf82..0f84246 100644 --- a/include/libremidi/backends/coremidi/midi_in.hpp +++ b/include/libremidi/backends/coremidi/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_core final : public midi1::in_api @@ -25,8 +25,7 @@ public: if (auto result = init_client(configuration); result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); client_open_ = from_osstatus(result); return; } @@ -64,8 +63,7 @@ public: { close_client(*this); libremidi_handle_error( - this->configuration, "error creating macOS MIDI input port: " - + std::to_string(result)); + this->configuration, "error creating macOS MIDI input port: " + std::to_string(result)); return from_osstatus(result); } @@ -74,8 +72,7 @@ public: { MIDIPortDispose(port); close_client(*this); - libremidi_handle_error( - this->configuration, "error connecting macOS MIDI input port."); + libremidi_handle_error(this->configuration, "error connecting macOS MIDI input port."); return from_osstatus(result); } @@ -105,10 +102,7 @@ public: return stdx::error{}; } - stdx::error close_port() override - { - return coremidi_data::close_port(); - } + stdx::error close_port() override { return coremidi_data::close_port(); } timestamp absolute_timestamp() const noexcept override { diff --git a/include/libremidi/backends/coremidi/midi_out.hpp b/include/libremidi/backends/coremidi/midi_out.hpp index e260475..39c76ac 100644 --- a/include/libremidi/backends/coremidi/midi_out.hpp +++ b/include/libremidi/backends/coremidi/midi_out.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_core final : public midi1::out_api @@ -24,8 +24,7 @@ public: if (auto result = init_client(configuration); result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); client_open_ = from_osstatus(result); return; } @@ -60,8 +59,7 @@ public: if (result != noErr) { close_client(*this); - libremidi_handle_error( - this->configuration, "error creating macOS MIDI output port."); + libremidi_handle_error(this->configuration, "error creating macOS MIDI output port."); return from_osstatus(result); } @@ -80,9 +78,7 @@ public: if (result != noErr) { - libremidi_handle_error( - this->configuration, - "error creating macOS virtual MIDI source."); + libremidi_handle_error(this->configuration, "error creating macOS virtual MIDI source."); return from_osstatus(result); } @@ -92,10 +88,7 @@ public: return stdx::error{}; } - stdx::error close_port() override - { - return coremidi_data::close_port(); - } + stdx::error close_port() override { return coremidi_data::close_port(); } stdx::error send_message(const unsigned char* message, size_t size) override { @@ -118,8 +111,8 @@ public: const MIDITimeStamp timestamp = LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME(); const ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes; - Byte buffer[bufsize + 16]; // pad for other struct members - ByteCount listSize = sizeof(buffer); + Byte* buffer = (Byte*)alloca(bufsize + 16); // pad for other struct members + ByteCount listSize = bufsize + 16; MIDIPacketList* packetList = (MIDIPacketList*)buffer; ByteCount remainingBytes = nBytes; @@ -137,8 +130,7 @@ public: if (!packet) { - libremidi_handle_error( - this->configuration, "could not allocate packet list"); + libremidi_handle_error(this->configuration, "could not allocate packet list"); return std::errc::message_size; } @@ -163,9 +155,7 @@ public: auto result = MIDISend(this->port, this->destinationId, packetList); if (result != noErr) { - libremidi_handle_warning( - this->configuration, - "error sending MIDI message to port."); + libremidi_handle_warning(this->configuration, "error sending MIDI message to port."); return std::errc::io_error; } } diff --git a/include/libremidi/backends/coremidi/observer.hpp b/include/libremidi/backends/coremidi/observer.hpp index 833be51..1a3a93e 100644 --- a/include/libremidi/backends/coremidi/observer.hpp +++ b/include/libremidi/backends/coremidi/observer.hpp @@ -7,7 +7,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_core : public observer_api @@ -22,6 +22,10 @@ public: explicit observer_core(observer_configuration&& conf, coremidi_observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} + { + } + + void finish_init() { if (configuration.client_name.empty()) configuration.client_name = "libremidi observer"; @@ -32,15 +36,13 @@ public: auto result = MIDIClientCreate( toCFString(configuration.client_name).get(), +[](const MIDINotification* message, void* ctx) { - ((observer_core*)ctx)->notify(message); - }, - this, &client); + ((observer_core*)ctx)->notify(message); + }, this, &client); if (result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); return; } @@ -78,13 +80,56 @@ public: if (!ok) return {}; + // Get the MIDI device from the entity + libremidi::transport_type type{}; + libremidi::container_identifier usb_location_id{}; + libremidi::device_identifier usb_vendor_product{}; + { + MIDIDeviceRef device{}; + if (MIDIEntityGetDevice(e, &device) == noErr) + { + if (device) + { + using enum libremidi::transport_type; + SInt32 devid_res{}; + if (MIDIObjectGetIntegerProperty(device, CFSTR("USBLocationID"), &devid_res) == noErr) + { + type = (libremidi::transport_type)(hardware | usb); + usb_location_id = (uint64_t)devid_res; + } + + SInt32 vendor_res{}; + if (MIDIObjectGetIntegerProperty(device, CFSTR("USBVendorProduct"), &vendor_res) + == noErr) + { + type = (libremidi::transport_type)(hardware | usb); + usb_vendor_product = (uint64_t)vendor_res; + } + + const auto driver = get_string_property(device, kMIDIPropertyDriverOwner); + if (driver == "com.apple.AppleMIDIUSBDriver") + type = (libremidi::transport_type)(hardware | usb); + if (driver == "com.apple.AppleMIDIBluetoothDriver") + type = (libremidi::transport_type)(hardware | bluetooth); + if (driver == "com.apple.AppleMIDIIACDriver") + type = (libremidi::transport_type)(software); + if (driver == "com.apple.AppleMIDIRTPDriver") + type = (libremidi::transport_type)(network); + } + } + } + return std::conditional_t{ - {.client = (std::uintptr_t)this->client, + {.api = get_current_api(), + .client = (std::uintptr_t)this->client, + .container = usb_location_id, + .device = usb_vendor_product, .port = std::bit_cast(get_int_property(obj, kMIDIPropertyUniqueID)), .manufacturer = get_string_property(obj, kMIDIPropertyManufacturer), .device_name = get_string_property(obj, kMIDIPropertyModel), .port_name = get_string_property(obj, kMIDIPropertyName), - .display_name = get_string_property(obj, kMIDIPropertyDisplayName)}}; + .display_name = get_string_property(obj, kMIDIPropertyDisplayName), + .type = type}}; } std::vector get_input_ports() const noexcept override @@ -173,3 +218,17 @@ private: MIDIClientRef client{}; }; } + +NAMESPACE_LIBREMIDI::coremidi +{ +struct observer_impl : observer_core +{ + observer_impl(observer_configuration&& conf, coremidi_observer_configuration&& apiconf) + : observer_core{std::move(conf), std::move(apiconf)} + { + finish_init(); + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; } +}; +} diff --git a/include/libremidi/backends/coremidi_ump.hpp b/include/libremidi/backends/coremidi_ump.hpp index 67f7728..3aa8457 100644 --- a/include/libremidi/backends/coremidi_ump.hpp +++ b/include/libremidi/backends/coremidi_ump.hpp @@ -3,7 +3,9 @@ #include #include -namespace libremidi::coremidi_ump +#include + +NAMESPACE_LIBREMIDI::coremidi_ump { struct backend { @@ -14,8 +16,8 @@ struct backend using midi_out_configuration = coremidi_ump::output_configuration; using midi_observer_configuration = coremidi_ump::observer_configuration; static const constexpr auto API = libremidi::API::COREMIDI_UMP; - static const constexpr auto name = "core_ump"; - static const constexpr auto display_name = "CoreMIDI UMP"; + static const constexpr std::string_view name = "core_ump"; + static const constexpr std::string_view display_name = "CoreMIDI UMP"; static constexpr inline bool available() noexcept { return true; /* todo? */ } }; diff --git a/include/libremidi/backends/coremidi_ump/config.hpp b/include/libremidi/backends/coremidi_ump/config.hpp index dee80f9..7d5f927 100644 --- a/include/libremidi/backends/coremidi_ump/config.hpp +++ b/include/libremidi/backends/coremidi_ump/config.hpp @@ -1,7 +1,7 @@ #pragma once #include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { struct input_configuration { diff --git a/include/libremidi/backends/coremidi_ump/midi_in.hpp b/include/libremidi/backends/coremidi_ump/midi_in.hpp index fb94fb7..019612c 100644 --- a/include/libremidi/backends/coremidi_ump/midi_in.hpp +++ b/include/libremidi/backends/coremidi_ump/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { class midi_in_impl final @@ -26,8 +26,7 @@ public: if (auto result = init_client(configuration); result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); client_open_ = from_osstatus(result); return; } @@ -69,8 +68,7 @@ public: { close_client(*this); libremidi_handle_error( - this->configuration, "error creating macOS MIDI input port: " - + std::to_string(result)); + this->configuration, "error creating macOS MIDI input port: " + std::to_string(result)); return from_osstatus(result); } @@ -79,8 +77,7 @@ public: { MIDIPortDispose(port); close_client(*this); - libremidi_handle_error( - this->configuration, "error connecting macOS MIDI input port."); + libremidi_handle_error(this->configuration, "error connecting macOS MIDI input port."); return from_osstatus(result); } @@ -113,10 +110,7 @@ public: return stdx::error{}; } - stdx::error close_port() override - { - return coremidi_data::close_port(); - } + stdx::error close_port() override { return coremidi_data::close_port(); } timestamp absolute_timestamp() const noexcept override { diff --git a/include/libremidi/backends/coremidi_ump/midi_out.hpp b/include/libremidi/backends/coremidi_ump/midi_out.hpp index 4ef9a67..e71d956 100644 --- a/include/libremidi/backends/coremidi_ump/midi_out.hpp +++ b/include/libremidi/backends/coremidi_ump/midi_out.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { class midi_out_impl final : public midi2::out_api @@ -27,8 +27,7 @@ public: if (auto result = init_client(configuration); result != noErr) { libremidi_handle_error( - this->configuration, - "error creating MIDI client object: " + std::to_string(result)); + this->configuration, "error creating MIDI client object: " + std::to_string(result)); client_open_ = from_osstatus(result); return; } @@ -63,8 +62,7 @@ public: if (result != noErr) { close_client(*this); - libremidi_handle_error( - this->configuration, "error creating macOS MIDI output port."); + libremidi_handle_error(this->configuration, "error creating macOS MIDI output port."); return from_osstatus(result); } @@ -84,9 +82,7 @@ public: if (result != noErr) { this->endpoint = 0; - libremidi_handle_error( - this->configuration, - "error creating macOS virtual MIDI source."); + libremidi_handle_error(this->configuration, "error creating macOS virtual MIDI source."); return from_osstatus(result); } @@ -94,10 +90,7 @@ public: return stdx::error{}; } - stdx::error close_port() override - { - return coremidi_data::close_port(); - } + stdx::error close_port() override { return coremidi_data::close_port(); } stdx::error send_ump(const uint32_t* ump_stream, std::size_t count) override { @@ -142,9 +135,7 @@ public: auto result = MIDISendEventList(this->port, this->destinationId, eventList); if (result != noErr) { - libremidi_handle_warning( - this->configuration, - "error sending MIDI message to port."); + libremidi_handle_warning(this->configuration, "error sending MIDI message to port."); return std::errc::io_error; } } diff --git a/include/libremidi/backends/coremidi_ump/observer.hpp b/include/libremidi/backends/coremidi_ump/observer.hpp index 35a3f18..18dad5f 100644 --- a/include/libremidi/backends/coremidi_ump/observer.hpp +++ b/include/libremidi/backends/coremidi_ump/observer.hpp @@ -1,17 +1,20 @@ #pragma once -#include #include +#include -namespace libremidi::coremidi_ump +NAMESPACE_LIBREMIDI::coremidi_ump { class observer_impl final : public libremidi::observer_core { public: - explicit observer_impl(libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf) - : observer_core{std::move(conf), coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}} + explicit observer_impl( + libremidi::observer_configuration&& conf, coremidi_ump::observer_configuration&& apiconf) + : observer_core{ + std::move(conf), + coremidi_observer_configuration{apiconf.client_name, apiconf.on_create_context}} { - + finish_init(); } libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI_UMP; } diff --git a/include/libremidi/backends/dummy.hpp b/include/libremidi/backends/dummy.hpp index b3fb029..0244997 100644 --- a/include/libremidi/backends/dummy.hpp +++ b/include/libremidi/backends/dummy.hpp @@ -5,7 +5,9 @@ #include #include -namespace libremidi +#include + +NAMESPACE_LIBREMIDI { class observer_dummy : public observer_api { @@ -23,11 +25,12 @@ class midi_in_dummy final , public error_handler { public: - explicit midi_in_dummy(const input_configuration& configuration, std::any) + explicit midi_in_dummy(const input_configuration& configuration, const input_api_configuration&) { libremidi_handle_warning(configuration, "This class provides no functionality."); } - explicit midi_in_dummy(const ump_input_configuration& configuration, std::any) + explicit midi_in_dummy( + const ump_input_configuration& configuration, const input_api_configuration&) { libremidi_handle_warning(configuration, "This class provides no functionality."); } @@ -37,19 +40,9 @@ public: { return stdx::error{}; } - stdx::error open_virtual_port(std::string_view /*portName*/) override - { - return stdx::error{}; - } + stdx::error open_virtual_port(std::string_view /*portName*/) override { return stdx::error{}; } stdx::error close_port() override { return stdx::error{}; } - stdx::error set_client_name(std::string_view /*clientName*/) override - { - return stdx::error{}; - } - stdx::error set_port_name(std::string_view /*portName*/) override - { - return stdx::error{}; - } + stdx::error set_port_name(std::string_view /*portName*/) override { return stdx::error{}; } timestamp absolute_timestamp() const noexcept override { return 0; } }; @@ -58,7 +51,8 @@ class midi_out_dummy final , public error_handler { public: - explicit midi_out_dummy(const output_configuration& configuration, std::any) + explicit midi_out_dummy( + const output_configuration& configuration, const output_api_configuration&) { libremidi_handle_warning(configuration, "This class provides no functionality."); } @@ -68,20 +62,10 @@ public: { return stdx::error{}; } - stdx::error open_virtual_port(std::string_view /*portName*/) override - { - return stdx::error{}; - } + stdx::error open_virtual_port(std::string_view /*portName*/) override { return stdx::error{}; } stdx::error close_port() override { return stdx::error{}; } - stdx::error set_client_name(std::string_view /*clientName*/) override - { - return stdx::error{}; - } - stdx::error set_port_name(std::string_view /*portName*/) override - { - return stdx::error{}; - } + stdx::error set_port_name(std::string_view /*portName*/) override { return stdx::error{}; } stdx::error send_message(const unsigned char* /*message*/, size_t /*size*/) override { return stdx::error{}; @@ -97,8 +81,8 @@ struct dummy_backend using midi_out_configuration = dummy_configuration; using midi_observer_configuration = dummy_configuration; static const constexpr auto API = libremidi::API::DUMMY; - static const constexpr auto name = "dummy"; - static const constexpr auto display_name = "Dummy"; + static const constexpr std::string_view name = "dummy"; + static const constexpr std::string_view display_name = "Dummy"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/emscripten.hpp b/include/libremidi/backends/emscripten.hpp index 867e943..d948fef 100644 --- a/include/libremidi/backends/emscripten.hpp +++ b/include/libremidi/backends/emscripten.hpp @@ -7,7 +7,9 @@ #include #include -namespace libremidi + #include + +NAMESPACE_LIBREMIDI { struct emscripten_backend @@ -19,8 +21,8 @@ struct emscripten_backend using midi_out_configuration = emscripten_output_configuration; using midi_observer_configuration = emscripten_observer_configuration; static const constexpr auto API = libremidi::API::WEBMIDI; - static const constexpr auto name = "webmidi"; - static const constexpr auto display_name = "WebMIDI"; + static const constexpr std::string_view name = "webmidi"; + static const constexpr std::string_view display_name = "WebMIDI"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/emscripten/config.hpp b/include/libremidi/backends/emscripten/config.hpp index 6a1d735..869ea96 100644 --- a/include/libremidi/backends/emscripten/config.hpp +++ b/include/libremidi/backends/emscripten/config.hpp @@ -1,7 +1,7 @@ #pragma once #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct emscripten_input_configuration diff --git a/include/libremidi/backends/emscripten/helpers.hpp b/include/libremidi/backends/emscripten/helpers.hpp index 3064ae1..9a30fa6 100644 --- a/include/libremidi/backends/emscripten/helpers.hpp +++ b/include/libremidi/backends/emscripten/helpers.hpp @@ -3,7 +3,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { namespace webmidi_helpers { diff --git a/include/libremidi/backends/emscripten/midi_access.hpp b/include/libremidi/backends/emscripten/midi_access.hpp index 3ca9f9e..fc5f171 100644 --- a/include/libremidi/backends/emscripten/midi_access.hpp +++ b/include/libremidi/backends/emscripten/midi_access.hpp @@ -16,7 +16,7 @@ EMSCRIPTEN_KEEPALIVE void libremidi_devices_input(int port, double timestamp, int len, char* bytes); } -namespace libremidi +NAMESPACE_LIBREMIDI { namespace webmidi_helpers { @@ -37,16 +37,25 @@ public: int input_count() const noexcept { + if (!available()) + return 0; + return EM_ASM_INT(return globalThis.__libreMidi_currentInputs.length;); } int output_count() const noexcept { + if (!available()) + return 0; + return EM_ASM_INT(return globalThis.__libreMidi_currentOutputs.length;); } void load_current_infos() noexcept { + if (!available()) + return; + #define get_js_string(variable_to_read, ...) \ (char*)EM_ASM_INT( \ { \ @@ -206,6 +215,9 @@ public: stdx::error send_message(int port_index, const char* bytes, int len) { + if (!available()) + return std::errc::operation_not_supported; + const auto& id = m_current_outputs[port_index].id; EM_ASM( { @@ -216,6 +228,8 @@ public: output.send(Array.from(bytes)); }, bytes, len, id.c_str()); + + return stdx::error{}; } const std::vector& inputs() const noexcept { return m_current_inputs; } @@ -248,6 +262,9 @@ private: void start_stream(int port_index) { + if (!available()) + return; + // Isn't life great... // https://github.com/Planeshifter/emscripten-examples/tree/master/01_PassingArrays const auto& id = m_current_inputs[port_index].id; @@ -282,6 +299,9 @@ private: void stop_stream(int port_index) { + if (!available()) + return; + const auto& id = m_current_inputs[port_index].id; EM_ASM(const id = UTF8ToString($1); diff --git a/include/libremidi/backends/emscripten/midi_in.cpp b/include/libremidi/backends/emscripten/midi_in.cpp index 3b919d3..3ca7476 100644 --- a/include/libremidi/backends/emscripten/midi_in.cpp +++ b/include/libremidi/backends/emscripten/midi_in.cpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE midi_in_emscripten::midi_in_emscripten( input_configuration&& conf, emscripten_input_configuration&& apiconf) @@ -32,13 +32,12 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::open_port(int portNumber, std:: if (portNumber < 0 || portNumber >= midi.input_count()) { - libremidi_handle_error( - this->configuration, "no MIDI output sources found."); + libremidi_handle_error(this->configuration, "no MIDI output sources found."); return std::errc::invalid_argument; } midi.open_input(portNumber, *this); - portNumber_ = portNumber; + m_portNumber = portNumber; return stdx::error{}; } @@ -52,7 +51,7 @@ LIBREMIDI_INLINE stdx::error midi_in_emscripten::close_port() { auto& midi = webmidi_helpers::midi_access_emscripten::instance(); - midi.close_input(portNumber_, *this); + midi.close_input(m_portNumber, *this); return stdx::error{}; } diff --git a/include/libremidi/backends/emscripten/midi_in.hpp b/include/libremidi/backends/emscripten/midi_in.hpp index 4833903..294c61f 100644 --- a/include/libremidi/backends/emscripten/midi_in.hpp +++ b/include/libremidi/backends/emscripten/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_emscripten final : public midi1::in_api @@ -31,7 +31,7 @@ public: void on_input(double ts, unsigned char* begin, unsigned char* end); private: - int portNumber_{}; + int m_portNumber{}; midi1::input_state_machine m_processing{this->configuration}; }; diff --git a/include/libremidi/backends/emscripten/midi_out.cpp b/include/libremidi/backends/emscripten/midi_out.cpp index e2bf024..e08d2ad 100644 --- a/include/libremidi/backends/emscripten/midi_out.cpp +++ b/include/libremidi/backends/emscripten/midi_out.cpp @@ -2,7 +2,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE midi_out_emscripten::midi_out_emscripten( output_configuration&& conf, emscripten_output_configuration&& apiconf) @@ -29,34 +29,36 @@ LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(int portNumber, std: if (portNumber >= midi.output_count()) { - libremidi_handle_error( - this->configuration, "no MIDI output sources found."); + libremidi_handle_error(this->configuration, "no MIDI output sources found."); return std::errc::invalid_argument; } - portNumber_ = portNumber; + m_portNumber = portNumber; return stdx::error{}; } -LIBREMIDI_INLINE stdx::error midi_out_emscripten::open_port(const output_port& p, std::string_view nm) +LIBREMIDI_INLINE stdx::error +midi_out_emscripten::open_port(const output_port& p, std::string_view nm) { return open_port(p.port, nm); } -LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() { +LIBREMIDI_INLINE stdx::error midi_out_emscripten::close_port() +{ return stdx::error{}; } -LIBREMIDI_INLINE stdx::error midi_out_emscripten::send_message(const unsigned char* message, size_t size) +LIBREMIDI_INLINE stdx::error +midi_out_emscripten::send_message(const unsigned char* message, size_t size) { - if (portNumber_ < 0) + if (m_portNumber < 0) libremidi_handle_error( this->configuration, "trying to send a message without an open " "port."); webmidi_helpers::midi_access_emscripten::instance().send_message( - portNumber_, reinterpret_cast(message), size); + m_portNumber, reinterpret_cast(message), size); return stdx::error{}; } } diff --git a/include/libremidi/backends/emscripten/midi_out.hpp b/include/libremidi/backends/emscripten/midi_out.hpp index f6f2137..62f3b55 100644 --- a/include/libremidi/backends/emscripten/midi_out.hpp +++ b/include/libremidi/backends/emscripten/midi_out.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_emscripten final : public midi1::out_api @@ -28,6 +28,6 @@ public: stdx::error send_message(const unsigned char* message, size_t size) override; private: - int portNumber_{-1}; + int m_portNumber{-1}; }; } diff --git a/include/libremidi/backends/emscripten/observer.cpp b/include/libremidi/backends/emscripten/observer.cpp index 9cdda2d..b67a38b 100644 --- a/include/libremidi/backends/emscripten/observer.cpp +++ b/include/libremidi/backends/emscripten/observer.cpp @@ -4,7 +4,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE observer_emscripten::observer_emscripten( observer_configuration&& conf, emscripten_observer_configuration&& apiconf) @@ -42,7 +42,8 @@ static auto to_port_info(int index, const webmidi_helpers::device_information& d -> std::conditional_t { return { - {.client = 0, + {.api = libremidi::API::WEBMIDI, + .client = 0, .port = (uint64_t)index, .manufacturer = "", .device_name = "", diff --git a/include/libremidi/backends/emscripten/observer.hpp b/include/libremidi/backends/emscripten/observer.hpp index 017785d..81fb730 100644 --- a/include/libremidi/backends/emscripten/observer.hpp +++ b/include/libremidi/backends/emscripten/observer.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_emscripten final : public observer_api { diff --git a/include/libremidi/backends/jack.hpp b/include/libremidi/backends/jack.hpp index 8367676..c703ecb 100644 --- a/include/libremidi/backends/jack.hpp +++ b/include/libremidi/backends/jack.hpp @@ -3,6 +3,8 @@ #include #include +#include + //*********************************************************************// // API: UNIX JACK // @@ -11,7 +13,7 @@ // // *********************************************************************// -namespace libremidi +NAMESPACE_LIBREMIDI { struct jack_backend { @@ -22,8 +24,8 @@ struct jack_backend using midi_out_configuration = jack_output_configuration; using midi_observer_configuration = jack_observer_configuration; static const constexpr auto API = libremidi::API::JACK_MIDI; - static const constexpr auto name = "jack"; - static const constexpr auto display_name = "JACK"; + static const constexpr std::string_view name = "jack"; + static const constexpr std::string_view display_name = "JACK"; static inline bool available() noexcept { diff --git a/include/libremidi/backends/jack/config.hpp b/include/libremidi/backends/jack/config.hpp index d99ed92..6feabcb 100644 --- a/include/libremidi/backends/jack/config.hpp +++ b/include/libremidi/backends/jack/config.hpp @@ -11,7 +11,7 @@ typedef uint32_t jack_nframes_t; typedef int (*JackProcessCallback)(jack_nframes_t nframes, void* arg); } -namespace libremidi +NAMESPACE_LIBREMIDI { using jack_callback_function = std::function; struct jack_callback diff --git a/include/libremidi/backends/jack/error_domain.hpp b/include/libremidi/backends/jack/error_domain.hpp index f63f27f..bc82ef8 100644 --- a/include/libremidi/backends/jack/error_domain.hpp +++ b/include/libremidi/backends/jack/error_domain.hpp @@ -11,7 +11,7 @@ #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { struct jack_error_domain : public stdx::error_domain { @@ -34,36 +34,36 @@ public: stdx::string_ref message(const stdx::error& e) const noexcept override { const auto status = error_cast(e); - if(status == jack_status_t{}) + if (status == jack_status_t{}) return "Success"; - if(status & JackInvalidOption) + if (status & JackInvalidOption) return "The operation contained an invalid or unsupported option"; - if(status & JackServerFailed) + if (status & JackServerFailed) return "Unable to connect to the JACK server"; - if(status & JackServerError) + if (status & JackServerError) return "Communication error with the JACK server"; - if(status & JackNoSuchClient) + if (status & JackNoSuchClient) return "Requested client does not exist"; - if(status & JackLoadFailure) + if (status & JackLoadFailure) return "Unable to load internal client"; - if(status & JackInitFailure) + if (status & JackInitFailure) return "Unable to initialize client"; - if(status & JackShmFailure) + if (status & JackShmFailure) return "Unable to access shared memory"; - if(status & JackVersionError) + if (status & JackVersionError) return "Client's protocol version does not match"; - if(status & JackBackendError) + if (status & JackBackendError) return "Backend error"; - if(status & JackClientZombie) + if (status & JackClientZombie) return "Client zombified failure"; - if(status & JackFailure) + if (status & JackFailure) return "Failure"; - if(status & JackNameNotUnique) + if (status & JackNameNotUnique) return "The desired client name was not unique"; // Can't happen in libremidi as we set JackNoStartServer - if(status & JackServerStarted) + if (status & JackServerStarted) return "Server was started"; return "Unknown JACK status code"; diff --git a/include/libremidi/backends/jack/helpers.hpp b/include/libremidi/backends/jack/helpers.hpp index 60eee90..1bf5f27 100644 --- a/include/libremidi/backends/jack/helpers.hpp +++ b/include/libremidi/backends/jack/helpers.hpp @@ -5,9 +5,11 @@ #include #include -#include +#include +#include +#include -namespace libremidi +NAMESPACE_LIBREMIDI { struct jack_client { @@ -39,31 +41,32 @@ struct jack_client } } - template + template static auto to_port_info(jack_client_t* client, jack_port_t* port) -> std::conditional_t { return {{ + .api = Api, .client = reinterpret_cast(client), .port = 0, .manufacturer = "", - .device_name = "", + .device_name = jack_get_client_name(client), .port_name = jack_port_name(port), .display_name = get_port_display_name(port), }}; } - template - static auto - get_ports(jack_client_t* client, const char* pattern, const JackPortFlags flags) noexcept - -> std::vector> + template + static auto get_ports( + jack_client_t* client, const char* pattern, const char* type, const JackPortFlags flags, + bool midi2) noexcept -> std::vector> { std::vector> ret; if (!client) return {}; - const char** ports = jack_get_ports(client, pattern, JACK_DEFAULT_MIDI_TYPE, flags); + const char** ports = jack_get_ports(client, pattern, type, flags); if (ports == nullptr) return {}; @@ -73,7 +76,11 @@ struct jack_client { // FIXME this does not take into account filtering sw / hw ports auto port = jack_port_by_name(client, ports[i]); - ret.push_back(to_port_info(client, port)); + if (port) + { + if (bool(midi2) == bool(jack_port_flags(port) & 0x20)) + ret.push_back(to_port_info(client, port)); + } i++; } @@ -131,13 +138,13 @@ struct jack_helpers : jack_client configuration.set_process_func( {.token = this_instance, .callback = [&self, p = std::weak_ptr{this->port.impl}](jack_nframes_t nf) -> int { - if (auto pt = p.lock()) - if (auto ppt = pt->load()) - self.process(nf); + if (auto pt = p.lock()) + if (pt->load()) + self.process(nf); - self.thread_lock.check_client_released(); - return 0; - }}); + self.thread_lock.check_client_released(); + return 0; + }}); this->client = configuration.context; return jack_status_t{}; @@ -149,26 +156,25 @@ struct jack_helpers : jack_client = jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status); if (this->client != nullptr) { - if(status & JackNameNotUnique) { - self.libremidi_handle_warning(self.configuration, "JACK client with the same name already exists, renamed."); + if (status & JackNameNotUnique) + { + self.libremidi_handle_warning( + self.configuration, "JACK client with the same name already exists, renamed."); } - jack_set_process_callback( - this->client, - +[](jack_nframes_t nf, void* ctx) -> int { - auto& self = *static_cast(ctx); - jack_port_t* port = self.port; + jack_set_process_callback(this->client, +[](jack_nframes_t nf, void* ctx) -> int { + auto& self = *static_cast(ctx); + jack_port_t* port = self.port; - // Is port created? - if (port == nullptr) - return 0; + // Is port created? + if (port == nullptr) + return 0; - self.process(nf); + self.process(nf); - self.thread_lock.check_client_released(); - return 0; - }, - &self); + self.thread_lock.check_client_released(); + return 0; + }, &self); jack_activate(this->client); } return status; @@ -192,8 +198,8 @@ struct jack_helpers : jack_client self.client_open_ = std::errc::not_connected; } - stdx::error - create_local_port(const auto& self, std::string_view portName, JackPortFlags flags) + stdx::error create_local_port( + const auto& self, std::string_view portName, const char* type, JackPortFlags flags) { // full name: "client_name:port_name\0" if (portName.empty()) @@ -202,15 +208,13 @@ struct jack_helpers : jack_client if (self.configuration.client_name.size() + portName.size() + 2u >= static_cast(jack_port_name_size())) { - self.libremidi_handle_error( - self.configuration, "port name length limit exceeded"); + self.libremidi_handle_error(self.configuration, "port name length limit exceeded"); return std::errc::invalid_argument; } if (!this->port) { - this->port - = jack_port_register(this->client, portName.data(), JACK_DEFAULT_MIDI_TYPE, flags, 0); + this->port = jack_port_register(this->client, portName.data(), type, flags, 0); } if (!this->port) @@ -238,4 +242,77 @@ struct jack_helpers : jack_client return from_errc(err); } }; + +struct jack_queue +{ +public: + static constexpr auto size_sz = sizeof(int32_t); + + jack_queue() = default; + jack_queue(const jack_queue&) = delete; + jack_queue(jack_queue&&) = delete; + jack_queue& operator=(const jack_queue&) = delete; + + jack_queue& operator=(jack_queue&& other) noexcept + { + ringbuffer = other.ringbuffer; + ringbuffer_space = other.ringbuffer_space; + other.ringbuffer = nullptr; + return *this; + } + + explicit jack_queue(int64_t sz) noexcept + { + ringbuffer = jack_ringbuffer_create(sz); + ringbuffer_space = jack_ringbuffer_write_space(ringbuffer); + } + + ~jack_queue() noexcept + { + if (ringbuffer) + jack_ringbuffer_free(ringbuffer); + } + + stdx::error write(const unsigned char* data, int64_t sz) const noexcept + { + if (static_cast(sz + size_sz) > ringbuffer_space) + return std::errc::no_buffer_space; + + while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz) + sched_yield(); + + jack_ringbuffer_write(ringbuffer, reinterpret_cast(&sz), size_sz); + jack_ringbuffer_write(ringbuffer, reinterpret_cast(data), sz); + + return stdx::error{}; + } + + void read(void* jack_events) const noexcept + { + int32_t sz; + while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast(&sz), size_sz) == size_sz + && jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz) + { + jack_ringbuffer_read_advance(ringbuffer, size_sz); + + if (auto midi = jack_midi_event_reserve(jack_events, 0, sz)) + jack_ringbuffer_read(ringbuffer, reinterpret_cast(midi), sz); + else + jack_ringbuffer_read_advance(ringbuffer, sz); + } + } + + jack_ringbuffer_t* ringbuffer{}; + std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer +}; + +struct jack_midi1 +{ + static constexpr const char* port_type = "8 bit raw midi"; +}; + +struct jack_midi2 +{ + static constexpr const char* port_type = "32 bit raw UMP"; +}; } diff --git a/include/libremidi/backends/jack/midi_in.hpp b/include/libremidi/backends/jack/midi_in.hpp index c51b9b6..2f7467b 100644 --- a/include/libremidi/backends/jack/midi_in.hpp +++ b/include/libremidi/backends/jack/midi_in.hpp @@ -4,13 +4,12 @@ #include #include -#include - -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_jack final : public midi1::in_api , public jack_helpers + , public jack_midi1 , public error_handler { public: @@ -46,15 +45,16 @@ public: stdx::error open_port(const input_port& port, std::string_view portName) override { - if (auto err = create_local_port(*this, portName, JackPortIsInput); err != stdx::error{}) + if (auto err = create_local_port(*this, portName, port_type, JackPortIsInput); + err != stdx::error{}) return err; if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port)); err != 0 && err != EEXIST) { libremidi_handle_error( - configuration, "could not connect to port: " + port.port_name + " -> " - + jack_port_name(this->port)); + configuration, + "could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port)); return from_errc(err); } return stdx::error{}; @@ -62,7 +62,7 @@ public: stdx::error open_virtual_port(std::string_view portName) override { - return create_local_port(*this, portName, JackPortIsInput); + return create_local_port(*this, portName, port_type, JackPortIsInput); } stdx::error close_port() override { return do_close_port(); } @@ -96,8 +96,8 @@ public: this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs); // We have midi events in buffer - uint32_t evCount = jack_midi_get_event_count(buff); - for (uint32_t j = 0; j < evCount; j++) + uint32_t ev_count = jack_midi_get_event_count(buff); + for (uint32_t j = 0; j < ev_count; j++) { jack_midi_event_t event{}; jack_midi_event_get(&event, buff, j); diff --git a/include/libremidi/backends/jack/midi_out.hpp b/include/libremidi/backends/jack/midi_out.hpp index 7077541..fcac4f9 100644 --- a/include/libremidi/backends/jack/midi_out.hpp +++ b/include/libremidi/backends/jack/midi_out.hpp @@ -3,76 +3,12 @@ #include #include -#include - -namespace libremidi +NAMESPACE_LIBREMIDI { -struct jack_queue -{ -public: - static constexpr auto size_sz = sizeof(int32_t); - - jack_queue() = default; - jack_queue(const jack_queue&) = delete; - jack_queue(jack_queue&&) = delete; - jack_queue& operator=(const jack_queue&) = delete; - - jack_queue& operator=(jack_queue&& other) noexcept - { - ringbuffer = other.ringbuffer; - ringbuffer_space = other.ringbuffer_space; - other.ringbuffer = nullptr; - return *this; - } - - explicit jack_queue(int64_t sz) noexcept - { - ringbuffer = jack_ringbuffer_create(sz); - ringbuffer_space = jack_ringbuffer_write_space(ringbuffer); - } - - ~jack_queue() noexcept - { - if (ringbuffer) - jack_ringbuffer_free(ringbuffer); - } - - stdx::error write(const unsigned char* data, int64_t sz) const noexcept - { - if (static_cast(sz + size_sz) > ringbuffer_space) - return std::errc::no_buffer_space; - - while (jack_ringbuffer_write_space(ringbuffer) < sz + size_sz) - sched_yield(); - - jack_ringbuffer_write(ringbuffer, reinterpret_cast(&sz), size_sz); - jack_ringbuffer_write(ringbuffer, reinterpret_cast(data), sz); - - return stdx::error{}; - } - - void read(void* jack_events) const noexcept - { - int32_t sz; - while (jack_ringbuffer_peek(ringbuffer, reinterpret_cast(&sz), size_sz) == size_sz - && jack_ringbuffer_read_space(ringbuffer) >= size_sz + sz) - { - jack_ringbuffer_read_advance(ringbuffer, size_sz); - - if (auto midi = jack_midi_event_reserve(jack_events, 0, sz)) - jack_ringbuffer_read(ringbuffer, reinterpret_cast(midi), sz); - else - jack_ringbuffer_read_advance(ringbuffer, sz); - } - } - - jack_ringbuffer_t* ringbuffer{}; - std::size_t ringbuffer_space{}; // actual writable size, usually 1 less than ringbuffer -}; - class midi_out_jack : public midi1::out_api , public jack_helpers + , public jack_midi1 , public error_handler { public: @@ -94,15 +30,15 @@ public: stdx::error open_port(const output_port& port, std::string_view portName) override { - if (auto err = create_local_port(*this, portName, JackPortIsOutput); err != stdx::error{}) + if (auto err = create_local_port(*this, portName, port_type, JackPortIsOutput); + err != stdx::error{}) return err; // Connecting to the output if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str()); err != 0 && err != EEXIST) { - libremidi_handle_error( - configuration, "could not connect to port" + port.port_name); + libremidi_handle_error(configuration, "could not connect to port" + port.port_name); return from_errc(err); } @@ -111,7 +47,7 @@ public: stdx::error open_virtual_port(std::string_view portName) override { - return create_local_port(*this, portName, JackPortIsOutput); + return create_local_port(*this, portName, port_type, JackPortIsOutput); } stdx::error close_port() override { return do_close_port(); } @@ -128,7 +64,7 @@ class midi_out_jack_queued final : public midi_out_jack public: midi_out_jack_queued(output_configuration&& conf, jack_output_configuration&& apiconf) : midi_out_jack{std::move(conf), std::move(apiconf)} - , queue{configuration.ringbuffer_size} + , m_queue{configuration.ringbuffer_size} { auto status = connect(*this); if (!this->client) @@ -150,7 +86,7 @@ public: stdx::error send_message(const unsigned char* message, std::size_t size) override { - return queue.write(message, size); + return m_queue.write(message, size); } int process(jack_nframes_t nframes) @@ -158,13 +94,13 @@ public: void* buff = jack_port_get_buffer(this->port, nframes); jack_midi_clear_buffer(buff); - this->queue.read(buff); + this->m_queue.read(buff); return 0; } private: - jack_queue queue; + jack_queue m_queue; }; class midi_out_jack_direct final : public midi_out_jack @@ -230,7 +166,7 @@ public: }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr make( diff --git a/include/libremidi/backends/jack/observer.hpp b/include/libremidi/backends/jack/observer.hpp index 01226b1..87e6651 100644 --- a/include/libremidi/backends/jack/observer.hpp +++ b/include/libremidi/backends/jack/observer.hpp @@ -5,11 +5,12 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_jack final : public observer_api , private jack_client + , public jack_midi1 , private error_handler { public: @@ -48,8 +49,7 @@ public: void initial_callback() { { - const char** ports - = jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput); + const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput); if (ports != nullptr) { @@ -70,7 +70,8 @@ public: { seen_input_ports.insert(ports[i]); if (this->configuration.input_added && configuration.notify_in_constructor) - this->configuration.input_added(to_port_info(client, port)); + this->configuration.input_added( + to_port_info(client, port)); } i++; } @@ -80,8 +81,7 @@ public: } { - const char** ports - = jack_get_ports(client, nullptr, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput); + const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput); if (ports != nullptr) { @@ -102,7 +102,8 @@ public: { seen_output_ports.insert(ports[i]); if (this->configuration.output_added && configuration.notify_in_constructor) - this->configuration.output_added(to_port_info(client, port)); + this->configuration.output_added( + to_port_info(client, port)); } i++; } @@ -119,7 +120,7 @@ public: if (reg) { std::string_view type = jack_port_type(port); - if (type != JACK_DEFAULT_MIDI_TYPE) + if (type != port_type) return; bool physical = flags & JackPortIsPhysical; @@ -139,13 +140,15 @@ public: { seen_input_ports.insert(name); if (this->configuration.input_added) - this->configuration.input_added(to_port_info(client, port)); + this->configuration.input_added( + to_port_info(client, port)); } else if (flags & JackPortIsInput) { seen_output_ports.insert(name); if (this->configuration.output_added) - this->configuration.output_added(to_port_info(client, port)); + this->configuration.output_added( + to_port_info(client, port)); } } else @@ -153,13 +156,15 @@ public: if (auto it = seen_input_ports.find(name); it != seen_input_ports.end()) { if (this->configuration.input_removed) - this->configuration.input_removed(to_port_info(client, port)); + this->configuration.input_removed( + to_port_info(client, port)); seen_input_ports.erase(it); } if (auto it = seen_output_ports.find(name); it != seen_output_ports.end()) { if (this->configuration.output_removed) - this->configuration.output_removed(to_port_info(client, port)); + this->configuration.output_removed( + to_port_info(client, port)); seen_output_ports.erase(it); } } @@ -172,39 +177,37 @@ public: if (!configuration.has_callbacks()) return; - jack_set_port_registration_callback( - this->client, - +[](jack_port_id_t p, int r, void* arg) { - auto& self = *(observer_jack*)arg; - if (auto port = jack_port_by_id(self.client, p)) - { - self.on_port_callback(port, r != 0); - } - }, - this); + jack_set_port_registration_callback(this->client, +[](jack_port_id_t p, int r, void* arg) { + auto& self = *(observer_jack*)arg; + if (auto port = jack_port_by_id(self.client, p)) + { + self.on_port_callback(port, r != 0); + } + }, this); jack_set_port_rename_callback( this->client, +[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) { - const auto& self = *static_cast(arg); + const auto& self = *static_cast(arg); - auto port = jack_port_by_id(self.client, p); - if (!port) - return; - }, - this); + auto port = jack_port_by_id(self.client, p); + if (!port) + return; + }, this); } libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_MIDI; } std::vector get_input_ports() const noexcept override { - return get_ports(this->client, nullptr, JackPortIsOutput); + return get_ports( + this->client, nullptr, port_type, JackPortIsOutput, false); } std::vector get_output_ports() const noexcept override { - return get_ports(this->client, nullptr, JackPortIsInput); + return get_ports( + this->client, nullptr, port_type, JackPortIsInput, false); } ~observer_jack() diff --git a/include/libremidi/backends/jack/shared_handler.hpp b/include/libremidi/backends/jack/shared_handler.hpp index 0ab18d0..b44f37d 100644 --- a/include/libremidi/backends/jack/shared_handler.hpp +++ b/include/libremidi/backends/jack/shared_handler.hpp @@ -8,7 +8,7 @@ #include -namespace libremidi::jack +NAMESPACE_LIBREMIDI::jack { // Create a JACK client which will be shared across objects @@ -23,13 +23,10 @@ struct shared_handler : public libremidi::shared_context client = jack_client_open(v.data(), JackNoStartServer, &status); assert(client); assert(status == 0); - jack_set_process_callback( - client, - +[](jack_nframes_t cnt, void* ctx) -> int { - ((shared_handler*)ctx)->jack_callback(cnt); - return 0; - }, - this); + jack_set_process_callback(client, +[](jack_nframes_t cnt, void* ctx) -> int { + ((shared_handler*)ctx)->jack_callback(cnt); + return 0; + }, this); } virtual void start_processing() override { jack_activate(client); } @@ -73,11 +70,10 @@ struct shared_handler : public libremidi::shared_context switch (ev.type) { case in_callback_added: - midiin_callbacks.push_back( - std::move(*std::get_if(&ev.payload))); + midiin_callbacks.push_back(std::move(*get_if(&ev.payload))); break; case in_callback_removed: { - auto idx = *std::get_if(&ev.payload); + auto idx = *get_if(&ev.payload); for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();) { if (it->token == idx) @@ -93,11 +89,10 @@ struct shared_handler : public libremidi::shared_context break; } case out_callback_added: - midiout_callbacks.push_back( - std::move(*std::get_if(&ev.payload))); + midiout_callbacks.push_back(std::move(*get_if(&ev.payload))); break; case out_callback_removed: - auto idx = *std::get_if(&ev.payload); + auto idx = *get_if(&ev.payload); for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();) { if (it->token == idx) @@ -141,7 +136,7 @@ struct shared_handler : public libremidi::shared_context struct event { event_type type; - std::variant payload; + libremidi_variant_alias::variant payload; }; boost::lockfree::spsc_queue events{16}; diff --git a/include/libremidi/backends/jack_ump.hpp b/include/libremidi/backends/jack_ump.hpp new file mode 100644 index 0000000..89b6a9e --- /dev/null +++ b/include/libremidi/backends/jack_ump.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include +#include + +#include + +NAMESPACE_LIBREMIDI::jack_ump +{ +struct backend +{ + using midi_in = midi_in_jack; + using midi_out = midi_out_jack; + using midi_observer = observer_jack; + using midi_in_configuration = jack_ump::input_configuration; + using midi_out_configuration = jack_ump::output_configuration; + using midi_observer_configuration = jack_ump::observer_configuration; + static const constexpr auto API = libremidi::API::JACK_UMP; + static const constexpr std::string_view name = "jack_ump"; + static const constexpr std::string_view display_name = "JACK (UMP)"; + + static inline bool available() noexcept + { +#if LIBREMIDI_WEAKJACK + if (WeakJack::instance().available() != 0) + return false; +#endif + + int major, minor, micro, patch; + jack_get_version(&major, &minor, µ, &patch); + return (major >= 3 && minor >= 1 && micro >= 4); + } +}; +} diff --git a/include/libremidi/backends/jack_ump/config.hpp b/include/libremidi/backends/jack_ump/config.hpp new file mode 100644 index 0000000..ab5fe8b --- /dev/null +++ b/include/libremidi/backends/jack_ump/config.hpp @@ -0,0 +1,33 @@ +#pragma once +#include + +NAMESPACE_LIBREMIDI::jack_ump +{ +struct input_configuration +{ + std::string client_name = "libremidi client"; + + jack_client_t* context{}; + std::function set_process_func{}; + std::function clear_process_func{}; +}; + +struct output_configuration +{ + std::string client_name = "libremidi client"; + + jack_client_t* context{}; + std::function set_process_func{}; + std::function clear_process_func{}; + + int32_t ringbuffer_size = 16384; + bool direct = false; +}; + +struct observer_configuration +{ + std::string client_name = "libremidi client"; + jack_client_t* context{}; +}; + +} diff --git a/include/libremidi/backends/jack_ump/midi_in.hpp b/include/libremidi/backends/jack_ump/midi_in.hpp new file mode 100644 index 0000000..6b185b3 --- /dev/null +++ b/include/libremidi/backends/jack_ump/midi_in.hpp @@ -0,0 +1,119 @@ +#pragma once +#include +#include +#include +#include + +NAMESPACE_LIBREMIDI::jack_ump +{ +class midi_in_jack final + : public midi2::in_api + , public jack_helpers + , private jack_midi1 + , public error_handler +{ +public: + using midi_api::client_open_; + struct + : libremidi::ump_input_configuration + , jack_ump::input_configuration + { + } configuration; + + explicit midi_in_jack( + libremidi::ump_input_configuration&& conf, jack_ump::input_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + auto status = connect(*this); + if (!this->client) + { + libremidi_handle_error(configuration, "Could not create JACK client"); + client_open_ = from_jack_status(status); + return; + } + + client_open_ = stdx::error{}; + } + + ~midi_in_jack() override + { + midi_in_jack::close_port(); + + disconnect(*this); + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; } + + stdx::error open_port(const input_port& port, std::string_view portName) override + { + if (auto err + = create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20)); + err != stdx::error{}) + return err; + + if (int err = jack_connect(this->client, port.port_name.c_str(), jack_port_name(this->port)); + err != 0 && err != EEXIST) + { + libremidi_handle_error( + configuration, + "could not connect to port: " + port.port_name + " -> " + jack_port_name(this->port)); + return from_errc(err); + } + return stdx::error{}; + } + + stdx::error open_virtual_port(std::string_view portName) override + { + return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsInput | 0x20)); + } + + stdx::error close_port() override { return do_close_port(); } + + stdx::error set_port_name(std::string_view portName) override + { + int ret = jack_port_rename(this->client, this->port, portName.data()); + return from_errc(ret); + } + + timestamp absolute_timestamp() const noexcept override + { + return 1000 * jack_frames_to_time(client, jack_frame_time(client)); + } + + int process(jack_nframes_t nframes) + { + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = true, + .absolute_is_monotonic = true, + .has_samples = true, + }; + void* buff = jack_port_get_buffer(this->port, nframes); + + // Timing + jack_nframes_t current_frames{}; + jack_time_t current_usecs{}; // roughly CLOCK_MONOTONIC + jack_time_t next_usecs{}; + float period_usecs{}; + jack_get_cycle_times( + this->client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs); + + // We have midi events in buffer + uint32_t ev_count = jack_midi_get_event_count(buff); + for (uint32_t j = 0; j < ev_count; j++) + { + jack_midi_event_t event{}; + jack_midi_event_get(&event, buff, j); + const auto to_ns + = [=, this] { return 1000 * jack_frames_to_time(client, current_frames + event.time); }; + + m_processing.on_bytes( + {(uint32_t*)event.buffer, (uint32_t*)(event.buffer + event.size)}, + m_processing.timestamp(to_ns, event.time)); + } + + return 0; + } + + midi2::input_state_machine m_processing{this->configuration}; +}; +} diff --git a/include/libremidi/backends/jack_ump/midi_out.hpp b/include/libremidi/backends/jack_ump/midi_out.hpp new file mode 100644 index 0000000..a6ebaa7 --- /dev/null +++ b/include/libremidi/backends/jack_ump/midi_out.hpp @@ -0,0 +1,184 @@ +#pragma once +#include +#include +#include + +NAMESPACE_LIBREMIDI::jack_ump +{ +class midi_out_jack + : public midi2::out_api + , public jack_helpers + , private jack_midi1 + , public error_handler +{ +public: + using midi_api::client_open_; + struct + : libremidi::output_configuration + , jack_ump::output_configuration + { + } configuration; + + midi_out_jack(libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + } + + ~midi_out_jack() override { } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; } + + stdx::error open_port(const output_port& port, std::string_view portName) override + { + if (auto err + = create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20)); + err != stdx::error{}) + return err; + + // Connecting to the output + if (int err = jack_connect(this->client, jack_port_name(this->port), port.port_name.c_str()); + err != 0 && err != EEXIST) + { + libremidi_handle_error(configuration, "could not connect to port" + port.port_name); + return from_errc(err); + } + + return stdx::error{}; + } + + stdx::error open_virtual_port(std::string_view portName) override + { + return create_local_port(*this, portName, port_type, JackPortFlags(JackPortIsOutput | 0x20)); + } + + stdx::error close_port() override { return do_close_port(); } + + stdx::error set_port_name(std::string_view portName) override + { + int ret = jack_port_rename(this->client, this->port, portName.data()); + return from_errc(ret); + } +}; + +class midi_out_jack_queued final : public midi_out_jack +{ +public: + midi_out_jack_queued( + libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf) + : midi_out_jack{std::move(conf), std::move(apiconf)} + , m_queue{configuration.ringbuffer_size} + { + auto status = connect(*this); + if (!this->client) + { + libremidi_handle_error(configuration, "Could not create JACK client"); + client_open_ = from_jack_status(status); + return; + } + + client_open_ = stdx::error{}; + } + + ~midi_out_jack_queued() override + { + midi_out_jack::close_port(); + + disconnect(*this); + } + + stdx::error send_ump(const uint32_t* message, std::size_t size) override + { + return m_queue.write((unsigned char*)message, size * sizeof(uint32_t)); + } + + int process(jack_nframes_t nframes) + { + void* buff = jack_port_get_buffer(this->port, nframes); + jack_midi_clear_buffer(buff); + + this->m_queue.read(buff); + + return 0; + } + +private: + libremidi::jack_queue m_queue; +}; + +class midi_out_jack_direct final : public midi_out_jack +{ +public: + midi_out_jack_direct( + libremidi::output_configuration&& conf, jack_ump::output_configuration&& apiconf) + : midi_out_jack{std::move(conf), std::move(apiconf)} + { + auto status = connect(*this); + if (!this->client) + { + libremidi_handle_error(configuration, "Could not create JACK client"); + client_open_ = from_jack_status(status); + return; + } + + buffer_size = jack_get_buffer_size(this->client); + client_open_ = stdx::error{}; + } + + ~midi_out_jack_direct() override + { + midi_out_jack::close_port(); + + disconnect(*this); + } + + int process(jack_nframes_t nframes) + { + void* buff = jack_port_get_buffer(this->port, nframes); + jack_midi_clear_buffer(buff); + return 0; + } + + stdx::error send_ump(const uint32_t* message, std::size_t size) override + { + void* buff = jack_port_get_buffer(this->port, buffer_size); + int ret = jack_midi_event_write(buff, 0, (unsigned char*)message, size * sizeof(uint32_t)); + return from_errc(ret); + } + + int convert_timestamp(int64_t user) const noexcept + { + switch (configuration.timestamps) + { + case timestamp_mode::AudioFrame: + return static_cast(user); + + default: + // TODO + return 0; + } + } + + stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override + { + void* buff = jack_port_get_buffer(this->port, buffer_size); + int ret = jack_midi_event_write( + buff, convert_timestamp(ts), (unsigned char*)message, size * sizeof(uint32_t)); + return from_errc(ret); + } + + int buffer_size{}; +}; +} + +NAMESPACE_LIBREMIDI +{ +template <> +inline std::unique_ptr +make(output_configuration&& conf, jack_ump::output_configuration&& api) +{ + if (api.direct) + return std::make_unique(std::move(conf), std::move(api)); + else + return std::make_unique(std::move(conf), std::move(api)); +} +} diff --git a/include/libremidi/backends/jack_ump/observer.hpp b/include/libremidi/backends/jack_ump/observer.hpp new file mode 100644 index 0000000..0db4160 --- /dev/null +++ b/include/libremidi/backends/jack_ump/observer.hpp @@ -0,0 +1,243 @@ +#pragma once +#include +#include +#include + +#include + +NAMESPACE_LIBREMIDI::jack_ump +{ +class observer_jack final + : public observer_api + , private jack_client + , private jack_midi1 // seems like a bug in PipeWire? MIDI 2 flag is set but the MIDI 1 type string is used + , private error_handler +{ +public: + struct + : libremidi::observer_configuration + , jack_ump::observer_configuration + { + } configuration; + + explicit observer_jack( + libremidi::observer_configuration&& conf, jack_ump::observer_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + // Initialize JACK client + if (configuration.context) + { + this->client = configuration.context; + set_callbacks(); + } + else + { + jack_status_t status{}; + this->client + = jack_client_open(configuration.client_name.c_str(), JackNoStartServer, &status); + if (status != jack_status_t{}) + libremidi_handle_error(configuration, std::to_string((int)status)); + + if (this->client != nullptr) + { + set_callbacks(); + + jack_activate(this->client); + } + } + } + + void initial_callback() + { + { + const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsOutput); + + if (ports != nullptr) + { + int i = 0; + while (ports[i] != nullptr) + { + const auto port = jack_port_by_name(client, ports[i]); + const auto flags = jack_port_flags(port); + + if (!(flags & 0x20)) // midi 2 check + { + i++; + continue; + } + + bool physical = flags & JackPortIsPhysical; + bool ok = configuration.track_any; + if (configuration.track_hardware) + ok |= physical; + if (configuration.track_virtual) + ok |= !physical; + + if (ok) + { + seen_input_ports.insert(ports[i]); + if (this->configuration.input_added && configuration.notify_in_constructor) + this->configuration.input_added( + to_port_info(client, port)); + } + i++; + } + } + + jack_free(ports); + } + + { + const char** ports = jack_get_ports(client, nullptr, port_type, JackPortIsInput); + + if (ports != nullptr) + { + int i = 0; + while (ports[i] != nullptr) + { + const auto port = jack_port_by_name(client, ports[i]); + const auto flags = jack_port_flags(port); + + if (!(flags & 0x20)) // midi 2 check + { + i++; + continue; + } + + bool physical = flags & JackPortIsPhysical; + bool ok = configuration.track_any; + if (configuration.track_hardware) + ok |= physical; + if (configuration.track_virtual) + ok |= !physical; + + if (ok) + { + seen_output_ports.insert(ports[i]); + if (this->configuration.output_added && configuration.notify_in_constructor) + this->configuration.output_added( + to_port_info(client, port)); + } + i++; + } + } + + jack_free(ports); + } + } + + void on_port_callback(jack_port_t* port, bool reg) + { + auto flags = jack_port_flags(port); + std::string name = jack_port_name(port); + if (reg) + { + std::string_view type = jack_port_type(port); + if (type != port_type) + return; + + if (!(flags & 0x20)) // midi 2 check + return; + + bool physical = flags & JackPortIsPhysical; + bool ok = configuration.track_any; + if (configuration.track_hardware) + ok |= physical; + if (configuration.track_virtual) + ok |= !physical; + if (!ok) + return; + + // Note: we keep track of the ports as + // when disconnecting, jack_port_type and jack_port_flags aren't correctly + // set anymore. + + if (flags & JackPortIsOutput) + { + seen_input_ports.insert(name); + if (this->configuration.input_added) + this->configuration.input_added( + to_port_info(client, port)); + } + else if (flags & JackPortIsInput) + { + seen_output_ports.insert(name); + if (this->configuration.output_added) + this->configuration.output_added( + to_port_info(client, port)); + } + } + else + { + if (auto it = seen_input_ports.find(name); it != seen_input_ports.end()) + { + if (this->configuration.input_removed) + this->configuration.input_removed( + to_port_info(client, port)); + seen_input_ports.erase(it); + } + if (auto it = seen_output_ports.find(name); it != seen_output_ports.end()) + { + if (this->configuration.output_removed) + this->configuration.output_removed( + to_port_info(client, port)); + seen_output_ports.erase(it); + } + } + } + + void set_callbacks() + { + initial_callback(); + + if (!configuration.has_callbacks()) + return; + + jack_set_port_registration_callback(this->client, +[](jack_port_id_t p, int r, void* arg) { + auto& self = *(observer_jack*)arg; + if (auto port = jack_port_by_id(self.client, p)) + { + self.on_port_callback(port, r != 0); + } + }, this); + + jack_set_port_rename_callback( + this->client, + +[](jack_port_id_t p, const char* /*old_name*/, const char* /*new_name*/, void* arg) { + const auto& self = *static_cast(arg); + + auto port = jack_port_by_id(self.client, p); + if (!port) + return; + }, this); + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::JACK_UMP; } + + std::vector get_input_ports() const noexcept override + { + return get_ports( + this->client, nullptr, port_type, JackPortIsOutput, true); + } + + std::vector get_output_ports() const noexcept override + { + return get_ports( + this->client, nullptr, port_type, JackPortIsInput, true); + } + + ~observer_jack() + { + if (client && !configuration.context) + { + // If we own the client, deactivate it + jack_deactivate(this->client); + jack_client_close(this->client); + this->client = nullptr; + } + } + + std::unordered_set seen_input_ports; + std::unordered_set seen_output_ports; +}; +} diff --git a/include/libremidi/backends/jack_ump/shared_handler.hpp b/include/libremidi/backends/jack_ump/shared_handler.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/libremidi/backends/kdmapi.hpp b/include/libremidi/backends/kdmapi.hpp new file mode 100644 index 0000000..46846f9 --- /dev/null +++ b/include/libremidi/backends/kdmapi.hpp @@ -0,0 +1,43 @@ +#pragma once +#include +#include +#include + +#include + +//*********************************************************************// +// API: OmniMIDI KDMAPI (Keppy's Direct MIDI API) +//*********************************************************************// + +// OmniMIDI is a low-latency MIDI synthesizer driver for Windows. +// KDMAPI provides a direct interface to OmniMIDI, bypassing the +// Windows Multimedia API for lower latency. +// +// Note: KDMAPI is output-only. The midi_in class is a placeholder +// that always fails - use a different backend for MIDI input. +// +// https://github.com/KeppySoftware/OmniMIDI + +namespace libremidi +{ + +struct kdmapi_backend +{ + using midi_in = kdmapi::midi_in; + using midi_out = kdmapi::midi_out; + using midi_observer = kdmapi::observer; + using midi_in_configuration = kdmapi::input_configuration; + using midi_out_configuration = kdmapi::output_configuration; + using midi_observer_configuration = kdmapi::observer_configuration; + static const constexpr auto API = libremidi::API::KDMAPI; + static const constexpr std::string_view name = "kdmapi"; + static const constexpr std::string_view display_name = "OmniMIDI (KDMAPI)"; + + static inline bool available() noexcept + { + return kdmapi::kdmapi_loader::instance().is_available(); + } +}; + +} + diff --git a/include/libremidi/backends/kdmapi/config.hpp b/include/libremidi/backends/kdmapi/config.hpp new file mode 100644 index 0000000..df55793 --- /dev/null +++ b/include/libremidi/backends/kdmapi/config.hpp @@ -0,0 +1,23 @@ +#pragma once +#include + +namespace libremidi::kdmapi +{ +// KDMAPI does not support MIDI input +struct input_configuration +{ +}; + +struct output_configuration +{ + // Use the no-buffer variant of SendDirectData for lowest latency + // This bypasses OmniMIDI's internal buffer + bool use_no_buffer = false; +}; + +struct observer_configuration +{ +}; + +} + diff --git a/include/libremidi/backends/kdmapi/helpers.hpp b/include/libremidi/backends/kdmapi/helpers.hpp new file mode 100644 index 0000000..affcf1b --- /dev/null +++ b/include/libremidi/backends/kdmapi/helpers.hpp @@ -0,0 +1,178 @@ +#pragma once +// clang-format off +#define NOMINMAX 1 +#define WIN32_LEAN_AND_MEAN 1 + +#include +#include +// clang-format on + +#include + +#include +#include + +namespace libremidi::kdmapi +{ + +using IsKDMAPIAvailable_t = BOOL(WINAPI*)(); +using InitializeKDMAPIStream_t = BOOL(WINAPI*)(); +using TerminateKDMAPIStream_t = BOOL(WINAPI*)(); +using ResetKDMAPIStream_t = VOID(WINAPI*)(); +using SendDirectData_t = VOID(WINAPI*)(DWORD); +using SendDirectDataNoBuf_t = VOID(WINAPI*)(DWORD); +using SendDirectLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT); +using SendDirectLongDataNoBuf_t = UINT(WINAPI*)(LPSTR, DWORD); +using PrepareLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT); +using UnprepareLongData_t = UINT(WINAPI*)(MIDIHDR*, UINT); +using ReturnKDMAPIVer_t = BOOL(WINAPI*)(LPDWORD, LPDWORD, LPDWORD, LPDWORD); + +class kdmapi_loader +{ +public: + static kdmapi_loader& instance() + { + static kdmapi_loader loader; + return loader; + } + + bool is_available() const noexcept { return m_available; } + + HMODULE handle() const noexcept { return m_handle; } + + // KDMAPI function pointers + IsKDMAPIAvailable_t IsKDMAPIAvailable{}; + InitializeKDMAPIStream_t InitializeKDMAPIStream{}; + TerminateKDMAPIStream_t TerminateKDMAPIStream{}; + ResetKDMAPIStream_t ResetKDMAPIStream{}; + SendDirectData_t SendDirectData{}; + SendDirectDataNoBuf_t SendDirectDataNoBuf{}; + SendDirectLongData_t SendDirectLongData{}; + SendDirectLongDataNoBuf_t SendDirectLongDataNoBuf{}; + PrepareLongData_t PrepareLongData{}; + UnprepareLongData_t UnprepareLongData{}; + ReturnKDMAPIVer_t ReturnKDMAPIVer{}; + +private: + kdmapi_loader() + { + // Try to get OmniMIDI if it's already loaded + m_handle = GetModuleHandleW(L"OmniMIDI"); + if (!m_handle) + { + // Try to load it explicitly + m_handle = LoadLibraryW(L"OmniMIDI.dll"); + } + + if (!m_handle) + { + m_available = false; + return; + } + +#if !defined(_MSC_VER) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + + // Load all function pointers + IsKDMAPIAvailable + = reinterpret_cast(GetProcAddress(m_handle, "IsKDMAPIAvailable")); + InitializeKDMAPIStream = reinterpret_cast( + GetProcAddress(m_handle, "InitializeKDMAPIStream")); + TerminateKDMAPIStream = reinterpret_cast( + GetProcAddress(m_handle, "TerminateKDMAPIStream")); + ResetKDMAPIStream + = reinterpret_cast(GetProcAddress(m_handle, "ResetKDMAPIStream")); + SendDirectData + = reinterpret_cast(GetProcAddress(m_handle, "SendDirectData")); + SendDirectDataNoBuf + = reinterpret_cast(GetProcAddress(m_handle, "SendDirectDataNoBuf")); + SendDirectLongData + = reinterpret_cast(GetProcAddress(m_handle, "SendDirectLongData")); + SendDirectLongDataNoBuf = reinterpret_cast( + GetProcAddress(m_handle, "SendDirectLongDataNoBuf")); + PrepareLongData + = reinterpret_cast(GetProcAddress(m_handle, "PrepareLongData")); + UnprepareLongData + = reinterpret_cast(GetProcAddress(m_handle, "UnprepareLongData")); + ReturnKDMAPIVer + = reinterpret_cast(GetProcAddress(m_handle, "ReturnKDMAPIVer")); + +#if !defined(_MSC_VER) +#pragma GCC diagnostic pop +#endif + + // Check if the minimum required functions are available + if (!IsKDMAPIAvailable || !InitializeKDMAPIStream || !TerminateKDMAPIStream || !SendDirectData) + { + m_available = false; + return; + } + + // Call IsKDMAPIAvailable to activate KDMAPI mode in OmniMIDI + m_available = IsKDMAPIAvailable() != FALSE; + } + + ~kdmapi_loader() + { + } + + kdmapi_loader(const kdmapi_loader&) = delete; + kdmapi_loader& operator=(const kdmapi_loader&) = delete; + + HMODULE m_handle{}; + bool m_available{false}; +}; + +class kdmapi_stream_manager +{ +public: + static kdmapi_stream_manager& instance() + { + static kdmapi_stream_manager mgr; + return mgr; + } + + bool acquire() + { + std::lock_guard lock{m_mutex}; + auto& loader = kdmapi_loader::instance(); + if (!loader.is_available()) + return false; + + if (m_rc == 0) + { + if (!loader.InitializeKDMAPIStream()) + return false; + } + ++m_rc; + return true; + } + + void release() + { + std::lock_guard lock{m_mutex}; + if (m_rc == 0) + return; + + --m_rc; + if (m_rc == 0) + { + auto& loader = kdmapi_loader::instance(); + if (loader.TerminateKDMAPIStream) + loader.TerminateKDMAPIStream(); + } + } + +private: + kdmapi_stream_manager() = default; + ~kdmapi_stream_manager() = default; + kdmapi_stream_manager(const kdmapi_stream_manager&) = delete; + kdmapi_stream_manager& operator=(const kdmapi_stream_manager&) = delete; + + std::mutex m_mutex; + std::size_t m_rc{0}; +}; + +} diff --git a/include/libremidi/backends/kdmapi/midi_in.hpp b/include/libremidi/backends/kdmapi/midi_in.hpp new file mode 100644 index 0000000..fa8eeaf --- /dev/null +++ b/include/libremidi/backends/kdmapi/midi_in.hpp @@ -0,0 +1,11 @@ +#pragma once +#include +#include +#include + +namespace libremidi::kdmapi +{ + +using midi_in = libremidi::midi_in_dummy; + +} diff --git a/include/libremidi/backends/kdmapi/midi_out.hpp b/include/libremidi/backends/kdmapi/midi_out.hpp new file mode 100644 index 0000000..5ade417 --- /dev/null +++ b/include/libremidi/backends/kdmapi/midi_out.hpp @@ -0,0 +1,173 @@ +#pragma once +#include +#include +#include +#include + +namespace libremidi::kdmapi +{ + +class midi_out final + : public midi1::out_api + , public error_handler +{ +public: + struct + : libremidi::output_configuration + , kdmapi::output_configuration + { + } configuration; + + midi_out(libremidi::output_configuration&& conf, kdmapi::output_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + auto& loader = kdmapi_loader::instance(); + if (!loader.is_available()) + { + libremidi_handle_error( + configuration, "KDMAPI is not available. Is OmniMIDI installed?"); + this->client_open_ = std::errc::no_such_device; + return; + } + + this->client_open_ = stdx::error{}; + } + + ~midi_out() override + { + midi_out::close_port(); + this->client_open_ = std::errc::not_connected; + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::KDMAPI; } + + stdx::error open_port(const output_port& port, std::string_view) override + { + if (port.api != libremidi::API::KDMAPI) + { + libremidi_handle_error(configuration, "port is not a KDMAPI port"); + return std::errc::invalid_argument; + } + + return do_open(); + } + + stdx::error do_open() + { + auto& mgr = kdmapi_stream_manager::instance(); + if (!mgr.acquire()) + { + libremidi_handle_error(configuration, "failed to initialize KDMAPI stream"); + return std::errc::io_error; + } + + connected_ = true; + return stdx::error{}; + } + + stdx::error close_port() override + { + if (connected_) + { + auto& mgr = kdmapi_stream_manager::instance(); + mgr.release(); + connected_ = false; + } + return stdx::error{}; + } + + stdx::error send_message(const unsigned char* message, size_t size) override + { + if (!connected_) + return std::errc::not_connected; + + if (size == 0) + { + libremidi_handle_warning(configuration, "message argument is empty!"); + return std::errc::invalid_argument; + } + + auto& loader = kdmapi_loader::instance(); + + if (message[0] == 0xF0) + { + // SysEx message + return send_sysex(message, size); + } + else + { + // Channel or system message + if (size > 3) + { + libremidi_handle_warning( + configuration, "message size is greater than 3 bytes (and not sysex)!"); + return std::errc::message_size; + } + + // Pack MIDI bytes into DWORD + DWORD packet = 0; + std::copy_n(message, size, reinterpret_cast(&packet)); + + // Send the message + if (configuration.use_no_buffer && loader.SendDirectDataNoBuf) + loader.SendDirectDataNoBuf(packet); + else + loader.SendDirectData(packet); + } + + return stdx::error{}; + } + +private: + stdx::error send_sysex(const unsigned char* message, size_t size) + { + auto& loader = kdmapi_loader::instance(); + + if (configuration.use_no_buffer && loader.SendDirectLongDataNoBuf) + { + // Use the no-buffer variant - it takes raw data directly + loader.SendDirectLongDataNoBuf( + const_cast(reinterpret_cast(message)), static_cast(size)); + return stdx::error{}; + } + + // Standard path with MIDIHDR + buffer_.assign(message, message + size); + + MIDIHDR sysex{}; + sysex.lpData = reinterpret_cast(buffer_.data()); + sysex.dwBufferLength = static_cast(size); + sysex.dwFlags = 0; + + if (loader.PrepareLongData) + { + UINT result = loader.PrepareLongData(&sysex, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) + { + libremidi_handle_error(configuration, "error preparing sysex header"); + return std::errc::io_error; + } + } + + if (loader.SendDirectLongData) + { + UINT result = loader.SendDirectLongData(&sysex, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) + { + libremidi_handle_error(configuration, "error sending sysex message"); + // Still try to unprepare + } + } + + if (loader.UnprepareLongData) + { + loader.UnprepareLongData(&sysex, sizeof(MIDIHDR)); + } + + return stdx::error{}; + } + + std::vector buffer_; +}; + +} diff --git a/include/libremidi/backends/kdmapi/observer.hpp b/include/libremidi/backends/kdmapi/observer.hpp new file mode 100644 index 0000000..19b5df2 --- /dev/null +++ b/include/libremidi/backends/kdmapi/observer.hpp @@ -0,0 +1,86 @@ +#pragma once +#include +#include +#include + +namespace libremidi::kdmapi +{ + +class observer final : public observer_api +{ +public: + struct + : libremidi::observer_configuration + , kdmapi::observer_configuration + { + } configuration; + + explicit observer( + libremidi::observer_configuration&& conf, kdmapi::observer_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + if (!configuration.has_callbacks()) + return; + + auto& loader = kdmapi_loader::instance(); + if (!loader.is_available()) + return; + + // KDMAPI provides a single virtual output port + if (configuration.notify_in_constructor) + { + if (configuration.output_added) + { + auto ports = get_output_ports(); + for (const auto& port : ports) + configuration.output_added(port); + } + } + } + + ~observer() = default; + + libremidi::API get_current_api() const noexcept override { return libremidi::API::KDMAPI; } + + std::vector get_input_ports() const noexcept override + { + // KDMAPI does not provide input ports + return {}; + } + + std::vector get_output_ports() const noexcept override + { + auto& loader = kdmapi_loader::instance(); + if (!loader.is_available()) + return {}; + + // Get KDMAPI version for display name + std::string display_name = "OmniMIDI"; + if (loader.ReturnKDMAPIVer) + { + DWORD major{}, minor{}, build{}, rev{}; + if (loader.ReturnKDMAPIVer(&major, &minor, &build, &rev)) + { + display_name += " (KDMAPI "; + display_name += std::to_string(major); + display_name += "."; + display_name += std::to_string(minor); + display_name += "."; + display_name += std::to_string(build); + display_name += ")"; + } + } + + return {libremidi::output_port{ + {.api = libremidi::API::KDMAPI, + .client = 0, + .port = 0, + .manufacturer = "KeppySoftware", + .device_name = "OmniMIDI", + .port_name = "OmniMIDI", + .display_name = std::move(display_name)}, + }}; + } +}; + +} diff --git a/include/libremidi/backends/keyboard.hpp b/include/libremidi/backends/keyboard.hpp index 4674341..650b510 100644 --- a/include/libremidi/backends/keyboard.hpp +++ b/include/libremidi/backends/keyboard.hpp @@ -2,7 +2,9 @@ #include #include -namespace libremidi +#include + +NAMESPACE_LIBREMIDI { struct kbd_backend { @@ -13,8 +15,8 @@ struct kbd_backend using midi_out_configuration = dummy_configuration; using midi_observer_configuration = dummy_configuration; static const constexpr auto API = libremidi::API::KEYBOARD; - static const constexpr auto name = "keyboard"; - static const constexpr auto display_name = "Computer keyboard"; + static const constexpr std::string_view name = "keyboard"; + static const constexpr std::string_view display_name = "Computer keyboard"; static inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/keyboard/config.hpp b/include/libremidi/backends/keyboard/config.hpp index 109122e..c469abf 100644 --- a/include/libremidi/backends/keyboard/config.hpp +++ b/include/libremidi/backends/keyboard/config.hpp @@ -4,8 +4,130 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { +enum kbd_event +{ + NOTE_0 = 0x0, // C + VEL_0 = NOTE_0 + 128, // Set velocity to 0 + OCT_0 = VEL_0 + 128, // Set octave to 0 + OCTAVE_PLUS = OCT_0 + 128, + OCTAVE_MINUS, + VELOCITY_PLUS, + VELOCITY_MINUS, +}; + +// Default map +// +// ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------, +// | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- | +// |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----| +// | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| | +// |-----',--',--',--',--',--',--',--',--',--',--',--',--'| | +// | Caps | C | D | E | F | G | A | B | C | D | E | F | G | | +// |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----| +// | -^ | | O-| O+| V-| V+| | | | | | | ----^ | +// |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------| +// | ctrl | | alt | |altgr | | ctrl | +// '------' '-----'--------------------------'------' '------' + +// clang-format off + +// actually based on macOS virtual codes as it seems +// impossible to get raw scan codes. +// https://eastmanreference.com/complete-list-of-applescript-key-codes +LIBREMIDI_STATIC const std::map& scancode_map_macos(){ + static const std::map ret{ + { 0, NOTE_0}, // C0 + { 13, NOTE_0 + 1}, + { 1, NOTE_0 + 2}, + { 14, NOTE_0 + 3}, + { 2, NOTE_0 + 4}, + { 3, NOTE_0 + 5}, + { 17, NOTE_0 + 6}, + { 5, NOTE_0 + 7}, + { 16, NOTE_0 + 8}, + { 4, NOTE_0 + 9}, + { 32, NOTE_0 + 10}, + { 38, NOTE_0 + 11}, + { 40, NOTE_0 + 12}, // C1 + { 31, NOTE_0 + 13}, + { 37, NOTE_0 + 14}, + { 35, NOTE_0 + 15}, + { 41, NOTE_0 + 16}, + { 39, NOTE_0 + 17}, + { 30, NOTE_0 + 18}, + { 42, NOTE_0 + 19}, + + { 50, VEL_0 + int(0)}, + { 18, VEL_0 + int(1 * 127 / 12)}, + { 19, VEL_0 + int(2 * 127 / 12)}, + { 20, VEL_0 + int(3 * 127 / 12)}, + { 21, VEL_0 + int(4 * 127 / 12)}, + { 23, VEL_0 + int(5 * 127 / 12)}, + { 22, VEL_0 + int(6 * 127 / 12)}, + { 26, VEL_0 + int(7 * 127 / 12)}, + { 28, VEL_0 + int(8 * 127 / 12)}, + { 25, VEL_0 + int(9 * 127 / 12)}, + { 29, VEL_0 + int(10 * 127 / 12)}, + { 27, VEL_0 + int(11 * 127 / 12)}, + { 24, VEL_0 + int(127)}, + + { 6, OCTAVE_MINUS }, + { 7, OCTAVE_PLUS }, + { 8, VELOCITY_MINUS }, + { 9, VELOCITY_PLUS}, + }; + return ret; +} + +// Use: https://kbdlayout.info/KBDUSX/scancodes +// Also valid for windows +LIBREMIDI_STATIC const std::map& scancode_map_linux() { + static const std::map ret{ + { 0x1E, NOTE_0}, // C0 + { 0x11, NOTE_0 + 1}, + { 0x1F, NOTE_0 + 2}, + { 0x12, NOTE_0 + 3}, + { 0x20, NOTE_0 + 4}, + { 0x21, NOTE_0 + 5}, + { 0x14, NOTE_0 + 6}, + { 0x22, NOTE_0 + 7}, + { 0x15, NOTE_0 + 8}, + { 0x23, NOTE_0 + 9}, + { 0x16, NOTE_0 + 10}, + { 0x24, NOTE_0 + 11}, + { 0x25, NOTE_0 + 12}, // C1 + { 0x18, NOTE_0 + 13}, + { 0x26, NOTE_0 + 14}, + { 0x19, NOTE_0 + 15}, + { 0x27, NOTE_0 + 16}, + { 0x28, NOTE_0 + 17}, + { 0x1B, NOTE_0 + 18}, + { 0x2B, NOTE_0 + 19}, + + { 0x29, VEL_0 + int(0)}, + { 0x02, VEL_0 + int(1 * 127 / 12)}, + { 0x03, VEL_0 + int(2 * 127 / 12)}, + { 0x04, VEL_0 + int(3 * 127 / 12)}, + { 0x05, VEL_0 + int(4 * 127 / 12)}, + { 0x06, VEL_0 + int(5 * 127 / 12)}, + { 0x07, VEL_0 + int(6 * 127 / 12)}, + { 0x08, VEL_0 + int(7 * 127 / 12)}, + { 0x09, VEL_0 + int(8 * 127 / 12)}, + { 0x0A, VEL_0 + int(9 * 127 / 12)}, + { 0x0B, VEL_0 + int(10 * 127 / 12)}, + { 0x0C, VEL_0 + int(11 * 127 / 12)}, + { 0x0D, VEL_0 + int(127)}, + + { 0x2C, OCTAVE_MINUS }, + { 0x2D, OCTAVE_PLUS }, + { 0x2E, VELOCITY_MINUS }, + { 0x2F, VELOCITY_PLUS}, + }; + return ret; +} +// clang-format on /** * Used to set up keyboard input. * Your app should pass a function that will give you a callback @@ -13,80 +135,20 @@ namespace libremidi */ struct kbd_input_configuration { + // Note that on Linux with X11 scancodes should be offset by 8 (an X11 legacy quirk) + // ex.: pass QKeyEvent::nativeScanCode() - 8 to this API using scancode_callback = std::function; + // First argument is on key press, second on key release std::function set_input_scancode_callbacks - = [](scancode_callback, scancode_callback) {}; + = [](const scancode_callback&, const scancode_callback&) { }; - enum kbd_event - { - NOTE_0 = 0x0, // C - VEL_0 = NOTE_0 + 128, // Set velocity to 0 - OCT_0 = VEL_0 + 128, // Set octave to 0 - OCTAVE_PLUS = OCT_0 + 128, - OCTAVE_MINUS, - VELOCITY_PLUS, - VELOCITY_MINUS, - }; - - // Default map - // - // ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------, - // | V0| V1| V2| V3| V4| V5| V6| V7| V8| V9|V10|V11|V12| <- | - // |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----| - // | ->| | | C#| D#| | F#| G#| A#| | C#| D#| | F#| | - // |-----',--',--',--',--',--',--',--',--',--',--',--',--'| | - // | Caps | C | D | E | F | G | A | B | C | D | E | F | G | | - // |----,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'---'----| - // | -^ | | O-| O+| V-| V+| | | | | | | ----^ | - // |----'-,-',--'--,'---'---'---'---'---'---'-,-'---',--,------| - // | ctrl | | alt | |altgr | | ctrl | - // '------' '-----'--------------------------'------' '------' - - // clang-format off - // Use: https://kbdlayout.info/KBDUSX/scancodes - std::map scancode_map{ - { 0x1E, NOTE_0}, // C0 - { 0x11, NOTE_0 + 1}, - { 0x1F, NOTE_0 + 2}, - { 0x12, NOTE_0 + 3}, - { 0x20, NOTE_0 + 4}, - { 0x21, NOTE_0 + 5}, - { 0x14, NOTE_0 + 6}, - { 0x22, NOTE_0 + 7}, - { 0x15, NOTE_0 + 8}, - { 0x23, NOTE_0 + 9}, - { 0x16, NOTE_0 + 10}, - { 0x24, NOTE_0 + 11}, - { 0x25, NOTE_0 + 12}, // C1 - { 0x18, NOTE_0 + 13}, - { 0x26, NOTE_0 + 14}, - { 0x19, NOTE_0 + 15}, - { 0x27, NOTE_0 + 16}, - { 0x28, NOTE_0 + 17}, - { 0x1B, NOTE_0 + 18}, - { 0x2B, NOTE_0 + 19}, - - { 0x29, VEL_0 + int(0)}, - { 0x02, VEL_0 + int(1 * 127 / 12)}, - { 0x03, VEL_0 + int(2 * 127 / 12)}, - { 0x04, VEL_0 + int(3 * 127 / 12)}, - { 0x05, VEL_0 + int(4 * 127 / 12)}, - { 0x06, VEL_0 + int(5 * 127 / 12)}, - { 0x07, VEL_0 + int(6 * 127 / 12)}, - { 0x08, VEL_0 + int(7 * 127 / 12)}, - { 0x09, VEL_0 + int(8 * 127 / 12)}, - { 0x0A, VEL_0 + int(9 * 127 / 12)}, - { 0x0B, VEL_0 + int(10 * 127 / 12)}, - { 0x0C, VEL_0 + int(11 * 127 / 12)}, - { 0x0D, VEL_0 + int(127)}, - - { 0x2C, OCTAVE_MINUS }, - { 0x2D, OCTAVE_PLUS }, - { 0x2E, VELOCITY_MINUS }, - { 0x2F, VELOCITY_PLUS}, - }; - // clang-format on + std::map scancode_map +#if defined(__APPLE__) + = scancode_map_macos() +#else + = scancode_map_linux() +#endif + ; }; - } diff --git a/include/libremidi/backends/keyboard/midi_in.hpp b/include/libremidi/backends/keyboard/midi_in.hpp index 5b11124..674ff38 100644 --- a/include/libremidi/backends/keyboard/midi_in.hpp +++ b/include/libremidi/backends/keyboard/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_kbd final : public midi1::in_api @@ -22,22 +22,22 @@ public: explicit midi_in_kbd(input_configuration&& conf, kbd_input_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { - configuration.set_input_scancode_callbacks( - [this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); }); } ~midi_in_kbd() override { } libremidi::API get_current_api() const noexcept override { return libremidi::API::KEYBOARD; } - stdx::error open_port(const input_port&, std::string_view) override { return stdx::error{}; } + stdx::error open_port(const input_port&, std::string_view) override + { + configuration.set_input_scancode_callbacks( + [this](int v) { on_keypress(v); }, [this](int v) { on_keyrelease(v); }); - stdx::error open_virtual_port(std::string_view) override { return stdx::error{}; } + return stdx::error{}; + } stdx::error close_port() override { return stdx::error{}; } - stdx::error set_port_name(std::string_view) override { return stdx::error{}; } - timestamp absolute_timestamp() const noexcept override { return std::chrono::steady_clock::now().time_since_epoch().count(); @@ -45,7 +45,7 @@ public: void on_keypress(int scancode) { - using kevent = kbd_input_configuration::kbd_event; + using kevent = libremidi::kbd_event; auto it = configuration.scancode_map.find(scancode); if (it == configuration.scancode_map.end()) @@ -88,7 +88,7 @@ public: void on_keyrelease(int scancode) { - using kevent = kbd_input_configuration::kbd_event; + using kevent = libremidi::kbd_event; auto it = configuration.scancode_map.find(scancode); if (it == configuration.scancode_map.end()) diff --git a/include/libremidi/backends/linux/alsa.hpp b/include/libremidi/backends/linux/alsa.hpp index 2820d5e..2846ea8 100644 --- a/include/libremidi/backends/linux/alsa.hpp +++ b/include/libremidi/backends/linux/alsa.hpp @@ -2,7 +2,7 @@ #include -#include +#include // IWYU pragma: export #if defined(SND_LIB_VERSION) #if __has_include() && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 6) @@ -13,9 +13,13 @@ #if __has_include() && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 10) #define LIBREMIDI_ALSA_HAS_UMP 1 #endif + + #if __has_include() && SND_LIB_VERSION >= ((1 << 16) | (2 << 8) | 14) + #define LIBREMIDI_ALSA_HAS_UMP_SEQ_EVENTS 1 + #endif #endif -namespace libremidi +NAMESPACE_LIBREMIDI { struct libasound @@ -241,6 +245,7 @@ struct libasound LIBREMIDI_SYMBOL_INIT(snd_seq, client_id) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_client) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_name) + LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_get_card) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_set_client) LIBREMIDI_SYMBOL_INIT(snd_seq, client_info_sizeof) LIBREMIDI_SYMBOL_INIT(snd_seq, close) @@ -277,6 +282,7 @@ struct libasound LIBREMIDI_SYMBOL_INIT(snd_seq, port_info_sizeof) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_free) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_malloc) + LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_sizeof) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_dest) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_sender) LIBREMIDI_SYMBOL_INIT(snd_seq, port_subscribe_set_time_real) @@ -291,6 +297,12 @@ struct libasound LIBREMIDI_SYMBOL_INIT(snd_seq, set_queue_tempo) LIBREMIDI_SYMBOL_INIT(snd_seq, subscribe_port) LIBREMIDI_SYMBOL_INIT(snd_seq, unsubscribe_port) + +#if LIBREMIDI_ALSA_HAS_UMP + LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version) + LIBREMIDI_SYMBOL_INIT(snd_seq, get_ump_endpoint_info) + LIBREMIDI_SYMBOL_INIT(snd_seq, get_ump_block_info) +#endif } bool available{true}; @@ -298,6 +310,7 @@ struct libasound LIBREMIDI_SYMBOL_DEF(snd_seq, client_id) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_client) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_name) + LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_get_card) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_set_client) LIBREMIDI_SYMBOL_DEF(snd_seq, client_info_sizeof) LIBREMIDI_SYMBOL_DEF(snd_seq, close) @@ -334,6 +347,7 @@ struct libasound LIBREMIDI_SYMBOL_DEF(snd_seq, port_info_sizeof) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_free) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_malloc) + LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_sizeof) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_dest) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_sender) LIBREMIDI_SYMBOL_DEF(snd_seq, port_subscribe_set_time_real) @@ -350,6 +364,12 @@ struct libasound LIBREMIDI_SYMBOL_DEF(snd_seq, subscribe_port) LIBREMIDI_SYMBOL_DEF(snd_seq, unsubscribe_port) +#if LIBREMIDI_ALSA_HAS_UMP + LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version) + LIBREMIDI_SYMBOL_DEF(snd_seq, get_ump_endpoint_info) + LIBREMIDI_SYMBOL_DEF(snd_seq, get_ump_block_info) +#endif + #if LIBREMIDI_ALSA_HAS_UMP struct ump_t { @@ -360,7 +380,6 @@ struct libasound available = false; return; } - LIBREMIDI_SYMBOL_INIT(snd_seq, set_client_midi_version) LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_input) LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output) LIBREMIDI_SYMBOL_INIT(snd_seq_ump, event_output_direct) @@ -368,10 +387,10 @@ struct libasound bool available{true}; - LIBREMIDI_SYMBOL_DEF(snd_seq, set_client_midi_version) LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_input) LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output) LIBREMIDI_SYMBOL_DEF(snd_seq_ump, event_output_direct) + } ump; #endif } seq{library}; @@ -387,39 +406,98 @@ struct libasound return; } - LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name) - LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof) - LIBREMIDI_SYMBOL_INIT(snd_ump, close) - LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name) - LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof) + // Core UMP functions LIBREMIDI_SYMBOL_INIT(snd_ump, open) - LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors) - LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count) - LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents) + LIBREMIDI_SYMBOL_INIT(snd_ump, close) LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi) LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params) LIBREMIDI_SYMBOL_INIT(snd_ump, rawmidi_params_current) LIBREMIDI_SYMBOL_INIT(snd_ump, read) LIBREMIDI_SYMBOL_INIT(snd_ump, tread) LIBREMIDI_SYMBOL_INIT(snd_ump, write) + LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors) + LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_count) + LIBREMIDI_SYMBOL_INIT(snd_ump, poll_descriptors_revents) + + // Endpoint info functions + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_sizeof) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_card) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_device) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_flags) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_protocol_caps) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_protocol) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_num_blocks) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_version) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_manufacturer_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_family_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_model_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_sw_revision) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_name) + LIBREMIDI_SYMBOL_INIT(snd_ump, endpoint_info_get_product_id) + + // Block info functions + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_sizeof) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_set_block_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_block_id) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_active) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_flags) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_direction) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_first_group) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_num_groups) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_midi_ci_version) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_sysex8_streams) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_ui_hint) + LIBREMIDI_SYMBOL_INIT(snd_ump, block_info_get_name) } bool available{true}; - LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name) - LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof) - LIBREMIDI_SYMBOL_DEF(snd_ump, close) - LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name) - LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof) + + // Core UMP functions LIBREMIDI_SYMBOL_DEF(snd_ump, open) - LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors) - LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count) - LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents) + LIBREMIDI_SYMBOL_DEF(snd_ump, close) LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi) LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params) LIBREMIDI_SYMBOL_DEF(snd_ump, rawmidi_params_current) LIBREMIDI_SYMBOL_DEF(snd_ump, read) LIBREMIDI_SYMBOL_DEF(snd_ump, tread) LIBREMIDI_SYMBOL_DEF(snd_ump, write) + LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors) + LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_count) + LIBREMIDI_SYMBOL_DEF(snd_ump, poll_descriptors_revents) + + // Endpoint info functions + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_sizeof) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_card) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_device) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_flags) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_protocol_caps) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_protocol) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_num_blocks) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_version) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_manufacturer_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_family_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_model_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_sw_revision) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_name) + LIBREMIDI_SYMBOL_DEF(snd_ump, endpoint_info_get_product_id) + + // Block info functions + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_sizeof) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_set_block_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_block_id) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_active) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_flags) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_direction) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_first_group) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_num_groups) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_midi_ci_version) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_sysex8_streams) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_ui_hint) + LIBREMIDI_SYMBOL_DEF(snd_ump, block_info_get_name) } ump{library}; #endif }; @@ -447,6 +525,8 @@ struct libasound #define snd_seq_client_info_alloca(ptr) snd_dylib_alloca(ptr, seq, client_info) #undef snd_seq_port_info_alloca #define snd_seq_port_info_alloca(ptr) snd_dylib_alloca(ptr, seq, port_info) +#undef snd_seq_port_subscribe_alloca +#define snd_seq_port_subscribe_alloca(ptr) snd_dylib_alloca(ptr, seq, port_subscribe) #undef snd_seq_queue_tempo_alloca #define snd_seq_queue_tempo_alloca(ptr) snd_dylib_alloca(ptr, seq, queue_tempo) diff --git a/include/libremidi/backends/linux/dylib_loader.hpp b/include/libremidi/backends/linux/dylib_loader.hpp index 808e197..80c2490 100644 --- a/include/libremidi/backends/linux/dylib_loader.hpp +++ b/include/libremidi/backends/linux/dylib_loader.hpp @@ -1,10 +1,11 @@ #pragma once +#include #if __has_include() #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class dylib_loader { diff --git a/include/libremidi/backends/linux/helpers.hpp b/include/libremidi/backends/linux/helpers.hpp index b86b461..ccce4c9 100644 --- a/include/libremidi/backends/linux/helpers.hpp +++ b/include/libremidi/backends/linux/helpers.hpp @@ -7,7 +7,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct eventfd_notifier { diff --git a/include/libremidi/backends/linux/pipewire.hpp b/include/libremidi/backends/linux/pipewire.hpp index c14bd39..171e20c 100644 --- a/include/libremidi/backends/linux/pipewire.hpp +++ b/include/libremidi/backends/linux/pipewire.hpp @@ -4,7 +4,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { #pragma GCC diagnostic push @@ -12,6 +12,8 @@ namespace libremidi class libpipewire { public: + decltype(&::pw_get_library_version) get_library_version{}; + decltype(&::pw_init) init{}; decltype(&::pw_deinit) deinit{}; @@ -67,12 +69,12 @@ public: bool available{true}; private: - dylib_loader library; + dylib_loader m_library; libpipewire() - : library("libpipewire-0.3.so.0") + : m_library("libpipewire-0.3.so.0") { - if (!library) + if (!m_library) { available = false; return; @@ -81,52 +83,59 @@ private: // in terms of regex: // decltype\(&::([a-z_]+)\) [a-z_]+{}; // \1 = library.symbol("\1"); - init = library.symbol("pw_init"); - deinit = library.symbol("pw_deinit"); + get_library_version + = m_library.symbol("pw_get_library_version"); - context_new = library.symbol("pw_context_new"); - context_connect = library.symbol("pw_context_connect"); - context_destroy = library.symbol("pw_context_destroy"); + init = m_library.symbol("pw_init"); + deinit = m_library.symbol("pw_deinit"); - core_disconnect = library.symbol("pw_core_disconnect"); + context_new = m_library.symbol("pw_context_new"); + context_connect = m_library.symbol("pw_context_connect"); + context_destroy = m_library.symbol("pw_context_destroy"); + + core_disconnect = m_library.symbol("pw_core_disconnect"); proxy_add_listener - = library.symbol("pw_proxy_add_listener"); - proxy_destroy = library.symbol("pw_proxy_destroy"); + = m_library.symbol("pw_proxy_add_listener"); + proxy_destroy = m_library.symbol("pw_proxy_destroy"); - main_loop_new = library.symbol("pw_main_loop_new"); - main_loop_destroy = library.symbol("pw_main_loop_destroy"); - main_loop_quit = library.symbol("pw_main_loop_quit"); - main_loop_run = library.symbol("pw_main_loop_run"); + main_loop_new = m_library.symbol("pw_main_loop_new"); + main_loop_destroy + = m_library.symbol("pw_main_loop_destroy"); + main_loop_quit = m_library.symbol("pw_main_loop_quit"); + main_loop_run = m_library.symbol("pw_main_loop_run"); main_loop_get_loop - = library.symbol("pw_main_loop_get_loop"); + = m_library.symbol("pw_main_loop_get_loop"); - properties_new = library.symbol("pw_properties_new"); - properties_free = library.symbol("pw_properties_free"); - properties_get = library.symbol("pw_properties_get"); + properties_new = m_library.symbol("pw_properties_new"); + properties_free = m_library.symbol("pw_properties_free"); + properties_get = m_library.symbol("pw_properties_get"); - filter_new_simple = library.symbol("pw_filter_new_simple"); + filter_new_simple + = m_library.symbol("pw_filter_new_simple"); filter_get_node_id - = library.symbol("pw_filter_get_node_id"); + = m_library.symbol("pw_filter_get_node_id"); filter_get_properties - = library.symbol("pw_filter_get_properties"); - filter_add_port = library.symbol("pw_filter_add_port"); + = m_library.symbol("pw_filter_get_properties"); + filter_add_port = m_library.symbol("pw_filter_add_port"); filter_remove_port - = library.symbol("pw_filter_remove_port"); - filter_update_properties - = library.symbol("pw_filter_update_properties"); + = m_library.symbol("pw_filter_remove_port"); + filter_update_properties = m_library.symbol( + "pw_filter_update_properties"); filter_update_params - = library.symbol("pw_filter_update_params"); - filter_get_time = library.symbol("pw_filter_get_time"); - filter_destroy = library.symbol("pw_filter_destroy"); - filter_connect = library.symbol("pw_filter_connect"); + = m_library.symbol("pw_filter_update_params"); + filter_get_time = m_library.symbol("pw_filter_get_time"); + filter_destroy = m_library.symbol("pw_filter_destroy"); + filter_connect = m_library.symbol("pw_filter_connect"); filter_get_dsp_buffer - = library.symbol("pw_filter_get_dsp_buffer"); + = m_library.symbol("pw_filter_get_dsp_buffer"); filter_dequeue_buffer - = library.symbol("pw_filter_dequeue_buffer"); + = m_library.symbol("pw_filter_dequeue_buffer"); filter_queue_buffer - = library.symbol("pw_filter_queue_buffer"); - filter_flush = library.symbol("pw_filter_flush"); + = m_library.symbol("pw_filter_queue_buffer"); + filter_flush = m_library.symbol("pw_filter_flush"); + + assert(get_library_version); assert(init); assert(deinit); diff --git a/include/libremidi/backends/linux/udev.hpp b/include/libremidi/backends/linux/udev.hpp index 48e26cb..ff33e8d 100644 --- a/include/libremidi/backends/linux/udev.hpp +++ b/include/libremidi/backends/linux/udev.hpp @@ -1,12 +1,14 @@ #pragma once #include #include +#include #include #include +#include -namespace libremidi +NAMESPACE_LIBREMIDI { struct libudev @@ -25,12 +27,28 @@ struct libudev LIBREMIDI_SYMBOL_INIT(udev, device_get_action) LIBREMIDI_SYMBOL_INIT(udev, device_get_subsystem) + LIBREMIDI_SYMBOL_INIT(udev, device_get_property_value) + LIBREMIDI_SYMBOL_INIT(udev, device_new_from_syspath) + LIBREMIDI_SYMBOL_INIT(udev, device_get_parent_with_subsystem_devtype) LIBREMIDI_SYMBOL_INIT(udev, device_unref) + LIBREMIDI_SYMBOL_INIT(udev, monitor_enable_receiving) LIBREMIDI_SYMBOL_INIT(udev, monitor_get_fd) LIBREMIDI_SYMBOL_INIT(udev, monitor_new_from_netlink) LIBREMIDI_SYMBOL_INIT(udev, monitor_receive_device) LIBREMIDI_SYMBOL_INIT(udev, monitor_unref) + + LIBREMIDI_SYMBOL_INIT(udev, enumerate_new) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_unref) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_is_initialized) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_subsystem) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_add_match_sysname) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_scan_devices) + LIBREMIDI_SYMBOL_INIT(udev, enumerate_get_list_entry) + + LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_name) + LIBREMIDI_SYMBOL_INIT(udev, list_entry_get_next) + LIBREMIDI_SYMBOL_INIT2(udev, new, create) LIBREMIDI_SYMBOL_INIT(udev, unref) } @@ -46,12 +64,28 @@ struct libudev LIBREMIDI_SYMBOL_DEF(udev, device_get_action) LIBREMIDI_SYMBOL_DEF(udev, device_get_subsystem) + LIBREMIDI_SYMBOL_DEF(udev, device_new_from_syspath) + LIBREMIDI_SYMBOL_DEF(udev, device_get_property_value) + LIBREMIDI_SYMBOL_DEF(udev, device_get_parent_with_subsystem_devtype) LIBREMIDI_SYMBOL_DEF(udev, device_unref) + LIBREMIDI_SYMBOL_DEF(udev, monitor_enable_receiving) LIBREMIDI_SYMBOL_DEF(udev, monitor_get_fd) LIBREMIDI_SYMBOL_DEF(udev, monitor_new_from_netlink) LIBREMIDI_SYMBOL_DEF(udev, monitor_receive_device) LIBREMIDI_SYMBOL_DEF(udev, monitor_unref) + + LIBREMIDI_SYMBOL_DEF(udev, enumerate_new) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_unref) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_is_initialized) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_subsystem) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_add_match_sysname) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_scan_devices) + LIBREMIDI_SYMBOL_DEF(udev, enumerate_get_list_entry) + + LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_name) + LIBREMIDI_SYMBOL_DEF(udev, list_entry_get_next) + LIBREMIDI_SYMBOL_DEF2(udev, new, create) LIBREMIDI_SYMBOL_DEF(udev, unref) }; @@ -89,4 +123,107 @@ struct udev_helper udev_monitor* monitor{}; }; +struct udev_enumerator +{ + explicit udev_enumerator(const udev_helper& h) + : helper{h} + { + auto& udev = helper.udev; + auto& instance = helper.instance; + enumerator = udev.enumerate_new(instance); + assert(enumerator); + } + + ~udev_enumerator() + { + auto& udev = helper.udev; + udev.enumerate_unref(enumerator); + } + + const udev_helper& helper; + udev_enumerate* enumerator{}; + operator udev_enumerate*() const noexcept { return enumerator; } +}; + +struct udev_soundcard_info +{ + container_identifier container; + device_identifier path; + transport_type type{}; + std::string vendor; + std::string product; + std::string serial; +}; + +inline udev_soundcard_info get_udev_soundcard_info(const udev_helper& helper, int card) +{ + auto& udev = helper.udev; + udev_enumerator e{helper}; + udev.enumerate_add_match_subsystem(e, "sound"); + udev.enumerate_add_match_sysname(e, ("controlC" + std::to_string(card)).c_str()); + if (udev.enumerate_scan_devices(e) != 0) + return {}; + + for (auto entry = udev.enumerate_get_list_entry(e); entry; + entry = udev.list_entry_get_next(entry)) + { + auto path = udev.list_entry_get_name(entry); + if (!path) + continue; + + auto dev = udev.device_new_from_syspath(helper.instance, path); + if (!dev) + continue; + + const auto usb_device + = udev.device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device"); + const auto pci_device = udev.device_get_parent_with_subsystem_devtype(dev, "pci", nullptr); + + transport_type type = transport_type::hardware; + if (usb_device) + type = (transport_type)(type | transport_type::usb); + else if (pci_device) + type = (transport_type)(type | transport_type::pci); + + auto id_path = udev.device_get_property_value(dev, "ID_PATH"); + if (!id_path) + id_path = ""; + + auto read_property = [&](const char* property) { + auto res = udev.device_get_property_value(dev, property); + if (!res) + res = ""; + if (strlen(res) == 0) + { + if (usb_device) + { + res = udev.device_get_property_value(usb_device, property); + if (!res) + res = ""; + } + else if (pci_device) + { + res = udev.device_get_property_value(pci_device, property); + if (!res) + res = ""; + } + } + return res; + }; + + auto id_vendor = read_property("ID_VENDOR_FROM_DATABASE"); + auto id_product = read_property("ID_MODEL_FROM_DATABASE"); + auto id_serial = read_property("ID_SERIAL"); + auto ret = udev_soundcard_info{ + .container = id_path, + .path = path, + .type = type, + .vendor = id_vendor, + .product = id_product, + .serial = id_serial}; + udev.device_unref(dev); + return ret; + } + return {}; +} } diff --git a/include/libremidi/backends/net/config.hpp b/include/libremidi/backends/net/config.hpp index 8365b50..f441557 100644 --- a/include/libremidi/backends/net/config.hpp +++ b/include/libremidi/backends/net/config.hpp @@ -2,12 +2,15 @@ #include #include + +#if !defined(BOOST_ASIO_IO_CONTEXT_HPP) namespace boost::asio { struct io_context; } +#endif -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { enum class protocol { @@ -46,7 +49,7 @@ struct dgram_observer_configuration } -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { enum class protocol { diff --git a/include/libremidi/backends/net/helpers.hpp b/include/libremidi/backends/net/helpers.hpp index 54d4879..ca1251b 100644 --- a/include/libremidi/backends/net/helpers.hpp +++ b/include/libremidi/backends/net/helpers.hpp @@ -23,7 +23,7 @@ public: string_ref message(const error& e) const noexcept override { auto err = e.m_value; - + } [[noreturn]] void throw_exception(const error& e) const override @@ -35,38 +35,41 @@ public: */ } -namespace libremidi +NAMESPACE_LIBREMIDI { template struct optionally_owned { public: explicit optionally_owned(T* maybe_existing) - : storage{.ref = maybe_existing} + : m_storage{.ref = maybe_existing} { - if (storage.ref) + if (m_storage.ref) { - ownership = unowned; + m_ownership = unowned; return; } else { - std::destroy_at(&storage.ref); - std::construct_at(reinterpret_cast(storage.object)); - ownership = owned; + std::destroy_at(&m_storage.ref); + std::construct_at(reinterpret_cast(m_storage.object)); + m_ownership = owned; } } ~optionally_owned() { if (is_owned()) - std::destroy_at(reinterpret_cast(storage.object)); + std::destroy_at(reinterpret_cast(m_storage.object)); } - T& get() noexcept { return *(is_owned() ? reinterpret_cast(&storage.object) : storage.ref); } + T& get() noexcept + { + return *(is_owned() ? reinterpret_cast(&m_storage.object) : m_storage.ref); + } const T& get() const noexcept { - return *(is_owned() ? reinterpret_cast(&storage.object) : storage.ref); + return *(is_owned() ? reinterpret_cast(&m_storage.object) : m_storage.ref); } optionally_owned(const optionally_owned&) = delete; @@ -74,18 +77,18 @@ public: optionally_owned& operator=(const optionally_owned&) = delete; optionally_owned& operator=(optionally_owned&&) noexcept = delete; - bool is_owned() const noexcept { return ownership == owned; } + bool is_owned() const noexcept { return m_ownership == owned; } private: union { alignas(T) unsigned char object[sizeof(T)]; T* ref; - } storage; + } m_storage; enum { owned, unowned - } ownership{}; + } m_ownership{}; }; } diff --git a/include/libremidi/backends/net/midi_in.hpp b/include/libremidi/backends/net/midi_in.hpp index abf5c67..fb1b03a 100644 --- a/include/libremidi/backends/net/midi_in.hpp +++ b/include/libremidi/backends/net/midi_in.hpp @@ -4,7 +4,8 @@ #include #include -namespace libremidi +#include +NAMESPACE_LIBREMIDI { template struct osc_parser @@ -114,7 +115,7 @@ struct osc_parser } -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { struct osc_parser_midi1 @@ -219,8 +220,6 @@ public: return {}; } - stdx::error set_port_name(std::string_view) override { return stdx::error{}; } - timestamp absolute_timestamp() const noexcept override { return std::chrono::steady_clock::now().time_since_epoch().count(); @@ -265,7 +264,7 @@ public: }; } -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { struct osc_parser_midi2 { @@ -394,8 +393,6 @@ public: return {}; } - stdx::error set_port_name(std::string_view) override { return stdx::error{}; } - timestamp absolute_timestamp() const noexcept override { return std::chrono::steady_clock::now().time_since_epoch().count(); diff --git a/include/libremidi/backends/net/midi_out.hpp b/include/libremidi/backends/net/midi_out.hpp index c77f2f1..ccb4e1e 100644 --- a/include/libremidi/backends/net/midi_out.hpp +++ b/include/libremidi/backends/net/midi_out.hpp @@ -8,7 +8,7 @@ #include #include -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { struct osc_midi1_packet @@ -146,7 +146,7 @@ public: } -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { struct osc_midi2_packet { diff --git a/include/libremidi/backends/net/observer.hpp b/include/libremidi/backends/net/observer.hpp index d2b5516..30320b5 100644 --- a/include/libremidi/backends/net/observer.hpp +++ b/include/libremidi/backends/net/observer.hpp @@ -1,13 +1,13 @@ #pragma once #include -namespace libremidi::net +NAMESPACE_LIBREMIDI::net { using net_observer_configuration = libremidi::dummy_configuration; using observer = libremidi::observer_dummy; } -namespace libremidi::net_ump +NAMESPACE_LIBREMIDI::net_ump { using net_observer_configuration = libremidi::dummy_configuration; using observer = libremidi::observer_dummy; diff --git a/include/libremidi/backends/network.hpp b/include/libremidi/backends/network.hpp index 42930e5..f393a99 100644 --- a/include/libremidi/backends/network.hpp +++ b/include/libremidi/backends/network.hpp @@ -5,7 +5,9 @@ #include #include -namespace libremidi::net +#include + +NAMESPACE_LIBREMIDI::net { struct backend { @@ -16,8 +18,8 @@ struct backend using midi_out_configuration = libremidi::net::dgram_output_configuration; using midi_observer_configuration = libremidi::net::net_observer_configuration; static const constexpr auto API = libremidi::API::NETWORK; - static const constexpr auto name = "network"; - static const constexpr auto display_name = "Network"; + static const constexpr std::string_view name = "network"; + static const constexpr std::string_view display_name = "Network"; static inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/network_ump.hpp b/include/libremidi/backends/network_ump.hpp index 7b12562..90842ba 100644 --- a/include/libremidi/backends/network_ump.hpp +++ b/include/libremidi/backends/network_ump.hpp @@ -4,7 +4,9 @@ #include #include -namespace libremidi::net_ump +#include + +NAMESPACE_LIBREMIDI::net_ump { struct backend { @@ -15,8 +17,8 @@ struct backend using midi_out_configuration = libremidi::net_ump::dgram_output_configuration; using midi_observer_configuration = libremidi::net_ump::net_observer_configuration; static const constexpr auto API = libremidi::API::NETWORK_UMP; - static const constexpr auto name = "network (UMP)"; - static const constexpr auto display_name = "Network (UMP)"; + static const constexpr std::string_view name = "network (UMP)"; + static const constexpr std::string_view display_name = "Network (UMP)"; static inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/pipewire.hpp b/include/libremidi/backends/pipewire.hpp index f3a9a96..914a44d 100644 --- a/include/libremidi/backends/pipewire.hpp +++ b/include/libremidi/backends/pipewire.hpp @@ -7,7 +7,9 @@ #include #include -namespace libremidi::pipewire +#include + +NAMESPACE_LIBREMIDI::pipewire { struct backend { @@ -18,8 +20,8 @@ struct backend using midi_out_configuration = pipewire_output_configuration; using midi_observer_configuration = pipewire_observer_configuration; static const constexpr auto API = libremidi::API::PIPEWIRE; - static const constexpr auto name = "pipewire"; - static const constexpr auto display_name = "PipeWire"; + static const constexpr std::string_view name = "pipewire"; + static const constexpr std::string_view display_name = "PipeWire"; static inline bool available() noexcept { diff --git a/include/libremidi/backends/pipewire/config.hpp b/include/libremidi/backends/pipewire/config.hpp index efb5c25..69efb05 100644 --- a/include/libremidi/backends/pipewire/config.hpp +++ b/include/libremidi/backends/pipewire/config.hpp @@ -12,7 +12,7 @@ struct pw_filter; struct spa_io_position; } -namespace libremidi +NAMESPACE_LIBREMIDI { using pipewire_callback_function = std::function; struct pipewire_callback diff --git a/include/libremidi/backends/pipewire/context.hpp b/include/libremidi/backends/pipewire/context.hpp index b9a8635..d13b7af 100644 --- a/include/libremidi/backends/pipewire/context.hpp +++ b/include/libremidi/backends/pipewire/context.hpp @@ -18,13 +18,14 @@ #include #include #include +#include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" -namespace libremidi +NAMESPACE_LIBREMIDI { template using hash_map = std::unordered_map; @@ -79,11 +80,11 @@ struct pipewire_context struct graph { mutable std::mutex mtx; - libremidi::hash_map physical_audio; - libremidi::hash_map physical_midi; - libremidi::hash_map software_audio; - libremidi::hash_map software_midi; - libremidi::hash_map port_cache; + libremidi::hash_map physical_audio; + libremidi::hash_map physical_midi; + libremidi::hash_map software_audio; + libremidi::hash_map software_midi; + libremidi::hash_map port_cache; void for_each_port(auto func) { @@ -123,7 +124,7 @@ struct pipewire_context } explicit pipewire_context(std::shared_ptr inst) - : global_instance{inst} + : global_instance{std::move(inst)} , owns_main_loop{true} { this->main_loop = pw.main_loop_new(nullptr); @@ -187,13 +188,13 @@ struct pipewire_context pipewire_context& self = *(pipewire_context*)object; if (strcmp(type, PW_TYPE_INTERFACE_Port) == 0) self.register_port(id, type); - }, + }, .global_remove = [](void* object, uint32_t id) { pipewire_context& self = *(pipewire_context*)object; self.unregister_port(id); - }, - }; + }, + }; // Start listening pw_registry_add_listener(this->registry, &this->registry_listener, ®istry_events, this); @@ -244,8 +245,8 @@ struct pipewire_context // Remove from the listeners auto it = std::find_if(port_listener.begin(), port_listener.end(), [&](const listened_port& l) { - return l.id == id; - }); + return l.id == id; + }); if (it != port_listener.end()) { pw.proxy_destroy((pw_proxy*)it->port); @@ -266,14 +267,14 @@ struct pipewire_context static constexpr struct pw_core_events core_events = { .version = PW_VERSION_CORE_EVENTS, .done = - [](void* object, uint32_t id, int seq) { + [](void* object, uint32_t id, int seq) { auto& self = *(pipewire_context*)object; - if(id == PW_ID_CORE && seq == self.pending) + if (id == PW_ID_CORE && seq == self.pending) { self.done = 1; libpipewire::instance().main_loop_quit(self.main_loop); } - }, + }, }; spa_zero(core_listener); @@ -287,7 +288,7 @@ struct pipewire_context spa_hook_remove(&core_listener); } - [[nodiscard]] pw_proxy* link_ports(uint32_t out_port, uint32_t in_port) + [[nodiscard]] pw_proxy* link_ports(uint64_t out_port, uint64_t in_port) { auto props = pw.properties_new( PW_KEY_LINK_OUTPUT_PORT, std::to_string(out_port).c_str(), PW_KEY_LINK_INPUT_PORT, @@ -361,6 +362,8 @@ struct pipewire_context return &this->current_graph.physical_audio[nid]; else if (p.format.find("midi") != p.format.npos) return &this->current_graph.physical_midi[nid]; + else if (p.format.find("UMP") != p.format.npos) + return &this->current_graph.physical_midi[nid]; } else { @@ -368,6 +371,8 @@ struct pipewire_context return &this->current_graph.software_audio[nid]; else if (p.format.find("midi") != p.format.npos) return &this->current_graph.software_midi[nid]; + else if (p.format.find("UMP") != p.format.npos) + return &this->current_graph.software_midi[nid]; } return nullptr; }; @@ -453,12 +458,12 @@ struct pipewire_filter }* port{}; explicit pipewire_filter(std::shared_ptr loop) - : loop{loop} + : loop{std::move(loop)} { } explicit pipewire_filter(std::shared_ptr loop, pw_filter* filter) - : loop{loop} + : loop{std::move(loop)} , filter{filter} { } @@ -498,7 +503,8 @@ struct pipewire_filter pw.filter_destroy(this->filter); } - stdx::error create_local_port(std::string_view port_name, spa_direction direction) + stdx::error + create_local_port(std::string_view port_name, spa_direction direction, const char* format) { // clang-format off this->port = (struct port*)pw.filter_add_port( @@ -507,7 +513,7 @@ struct pipewire_filter PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw.properties_new( - PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_FORMAT_DSP, format, PW_KEY_PORT_NAME, port_name.data(), nullptr), nullptr, 0); @@ -517,7 +523,7 @@ struct pipewire_filter return stdx::error{}; } - void set_port_buffer(int bytes) + void set_port_buffer(int64_t bytes) { uint8_t buffer[1024]; struct spa_pod_builder builder; diff --git a/include/libremidi/backends/pipewire/helpers.hpp b/include/libremidi/backends/pipewire/helpers.hpp index 8c2970c..a8dc2be 100644 --- a/include/libremidi/backends/pipewire/helpers.hpp +++ b/include/libremidi/backends/pipewire/helpers.hpp @@ -9,11 +9,14 @@ #include #include -#include -#include #include -namespace libremidi +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wsign-compare" +#endif + +NAMESPACE_LIBREMIDI { struct pipewire_helpers { @@ -66,14 +69,14 @@ struct pipewire_helpers { this->filter = std::make_unique(this->global_context, configuration.filter); - pipewire_callback cbs{ + libremidi::pipewire_callback cbs{ .token = this_instance, .callback = [&self, p = std::weak_ptr{canary}](spa_io_position* nf) -> void { - if (auto pt = p.lock()) - self.process(nf); + if (auto pt = p.lock()) + self.process(nf); - self.thread_lock.check_client_released(); - }}; + self.thread_lock.check_client_released(); + }}; configuration.set_process_func(cbs); } else @@ -84,10 +87,10 @@ struct pipewire_helpers static constexpr struct pw_filter_events filter_events = {.version = PW_VERSION_FILTER_EVENTS, .process = +[](void* _data, struct spa_io_position* position) -> void { - // FIXME likely we need the thread_lock check here too - Self& self = *static_cast(_data); - self.process(position); - }}; + // FIXME likely we need the thread_lock check here too + Self& self = *static_cast(_data); + self.process(position); + }}; #pragma GCC diagnostic pop this->filter->create_filter(self.configuration.client_name, filter_events, &self); @@ -197,7 +200,8 @@ struct pipewire_helpers } template - stdx::error create_local_port(Self& self, std::string_view portName, spa_direction direction) + stdx::error create_local_port( + Self& self, std::string_view portName, spa_direction direction, const char* format) { assert(this->global_context); assert(this->filter); @@ -207,7 +211,7 @@ struct pipewire_helpers if (!this->filter->port) { - auto ret = this->filter->create_local_port(portName.data(), direction); + auto ret = this->filter->create_local_port(portName.data(), direction, format); if (ret != stdx::error{}) { self.libremidi_handle_error(self.configuration, "error creating port"); @@ -218,11 +222,12 @@ struct pipewire_helpers return stdx::error{}; } - void add_callbacks(const observer_configuration& conf) + template + void add_callbacks(std::string format, const observer_configuration& conf) { assert(global_context); - global_context->on_port_added = [&conf](const pipewire_context::port_info& port) { - if (port.format.find("midi") == std::string::npos) + global_context->on_port_added = [format, &conf](const pipewire_context::port_info& port) { + if (port.format.find(format) == std::string::npos) return; bool unfiltered = conf.track_any; @@ -233,18 +238,18 @@ struct pipewire_helpers if (port.direction == SPA_DIRECTION_INPUT) { if (conf.output_added) - conf.output_added(to_port_info(port)); + conf.output_added(to_port_info(port)); } else { if (conf.input_added) - conf.input_added(to_port_info(port)); + conf.input_added(to_port_info(port)); } } }; - global_context->on_port_removed = [&conf](const pipewire_context::port_info& port) { - if (port.format.find("midi") == std::string::npos) + global_context->on_port_removed = [format, &conf](const pipewire_context::port_info& port) { + if (port.format.find(format) == std::string::npos) return; bool unfiltered = conf.track_any; @@ -255,12 +260,12 @@ struct pipewire_helpers if (port.direction == SPA_DIRECTION_INPUT) { if (conf.output_removed) - conf.output_removed(to_port_info(port)); + conf.output_removed(to_port_info(port)); } else { if (conf.input_removed) - conf.input_removed(to_port_info(port)); + conf.input_removed(to_port_info(port)); } } }; @@ -408,7 +413,7 @@ struct pipewire_helpers return stdx::error{}; } - template + template static auto to_port_info(const pipewire_context::port_info& port) -> std::conditional_t { @@ -425,6 +430,7 @@ struct pipewire_helpers } return {{ + .api = Api, .client = 0, .port = port.id, .manufacturer = "", @@ -436,8 +442,10 @@ struct pipewire_helpers // Note: keep in mind that an "input" port for us (e.g. a keyboard that goes to the computer) // is an "output" port from the point of view of pipewire as data will come out of it - template - static auto get_ports(const observer_configuration& conf, const pipewire_context& ctx) noexcept + template + static auto get_ports( + std::string_view format, const observer_configuration& conf, + const pipewire_context& ctx) noexcept -> std::vector< std::conditional_t> { @@ -452,7 +460,8 @@ struct pipewire_helpers for (auto& port : (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) { - ret.push_back(to_port_info(port)); + if (port.format.find(format) != std::string::npos) + ret.push_back(to_port_info(port)); } } @@ -462,7 +471,8 @@ struct pipewire_helpers for (auto& port : (Direction == SPA_DIRECTION_INPUT ? node.second.inputs : node.second.outputs)) { - ret.push_back(to_port_info(port)); + if (port.format.find(format) != std::string::npos) + ret.push_back(to_port_info(port)); } } } @@ -471,3 +481,7 @@ struct pipewire_helpers } }; } + +#if defined(__clang__) + #pragma clang diagnostic pop +#endif diff --git a/include/libremidi/backends/pipewire/midi_in.hpp b/include/libremidi/backends/pipewire/midi_in.hpp index a584b2d..6d0ae97 100644 --- a/include/libremidi/backends/pipewire/midi_in.hpp +++ b/include/libremidi/backends/pipewire/midi_in.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_pipewire final : public midi1::in_api @@ -47,7 +47,8 @@ public: stdx::error open_port(const input_port& in_port, std::string_view name) override { - if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{}) + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "8 bit raw midi"); + err != stdx::error{}) return err; if (auto err = link_ports(*this, in_port); err != stdx::error{}) @@ -59,7 +60,8 @@ public: stdx::error open_virtual_port(std::string_view name) override { - if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT); err != stdx::error{}) + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "8 bit raw midi"); + err != stdx::error{}) return err; start_thread(); @@ -72,10 +74,7 @@ public: return do_close_port(); } - stdx::error set_port_name(std::string_view port_name) override - { - return rename_port(port_name); - } + stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); } timestamp absolute_timestamp() const noexcept override { return system_ns(); } diff --git a/include/libremidi/backends/pipewire/midi_out.hpp b/include/libremidi/backends/pipewire/midi_out.hpp index e8ae5c5..38bf1c2 100644 --- a/include/libremidi/backends/pipewire/midi_out.hpp +++ b/include/libremidi/backends/pipewire/midi_out.hpp @@ -5,9 +5,7 @@ #include -#include - -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_pipewire : public midi1::out_api @@ -50,7 +48,8 @@ public: stdx::error open_port(const output_port& out_port, std::string_view name) override { - if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{}) + if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "8 bit raw midi"); + err != stdx::error{}) return err; this->filter->set_port_buffer(configuration.output_buffer_size); @@ -64,7 +63,8 @@ public: stdx::error open_virtual_port(std::string_view name) override { - if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT); err != stdx::error{}) + if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "8 bit raw midi"); + err != stdx::error{}) return err; this->filter->set_port_buffer(configuration.output_buffer_size); @@ -79,10 +79,7 @@ public: return do_close_port(); } - stdx::error set_port_name(std::string_view port_name) override - { - return rename_port(port_name); - } + stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); } int process(spa_io_position* pos) { @@ -121,8 +118,9 @@ public: continue; } - spa_pod_builder_control(&build, m.timestamp, SPA_CONTROL_Midi); - int res = spa_pod_builder_bytes(&build, m.bytes.data(), m.bytes.size()); + spa_pod_builder_control(&build, static_cast(m.timestamp), SPA_CONTROL_Midi); + int res + = spa_pod_builder_bytes(&build, m.bytes.data(), static_cast(m.bytes.size())); // Try again next buffer if (res == -ENOSPC) diff --git a/include/libremidi/backends/pipewire/observer.hpp b/include/libremidi/backends/pipewire/observer.hpp index 66eba74..acc87a3 100644 --- a/include/libremidi/backends/pipewire/observer.hpp +++ b/include/libremidi/backends/pipewire/observer.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_pipewire final : public observer_api @@ -19,24 +19,15 @@ public: { } configuration; - explicit observer_pipewire(observer_configuration&& conf, pipewire_observer_configuration&& apiconf) + explicit observer_pipewire( + observer_configuration&& conf, pipewire_observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} { create_context(*this); - // FIXME notify_in_constructor // FIXME port rename callback -#if 0 - // Initialize PipeWire client - if (configuration.context) { - this->client = configuration.context; - set_callbacks(); - } - else -#endif - { - this->add_callbacks(configuration); + this->add_callbacks("midi", configuration); this->start_thread(); } @@ -56,27 +47,20 @@ public: std::vector get_input_ports() const noexcept override { - return get_ports(this->configuration, *this->global_context); + return get_ports( + "midi", this->configuration, *this->global_context); } std::vector get_output_ports() const noexcept override { - return get_ports(this->configuration, *this->global_context); + return get_ports( + "midi", this->configuration, *this->global_context); } ~observer_pipewire() { stop_thread(); destroy_context(); -#if 0 - if (client && !configuration.context) - { - // If we own the client, deactivate it - pipewire_deactivate(this->client); - pipewire_client_close(this->client); - this->client = nullptr; - } -#endif } std::unordered_set seen_input_ports; diff --git a/include/libremidi/backends/pipewire/shared_handler.hpp b/include/libremidi/backends/pipewire/shared_handler.hpp index 5dab246..a6e531a 100644 --- a/include/libremidi/backends/pipewire/shared_handler.hpp +++ b/include/libremidi/backends/pipewire/shared_handler.hpp @@ -8,7 +8,7 @@ #include -namespace libremidi::pipewire +NAMESPACE_LIBREMIDI::pipewire { // Create a PipeWire client which will be shared across objects @@ -23,13 +23,10 @@ struct shared_handler : public libremidi::shared_context client = pipewire_client_open(v.data(), PipewireNoStartServer, &status); assert(client); assert(status == 0); - pipewire_set_process_callback( - client, - +[](pipewire_nframes_t cnt, void* ctx) -> int { - ((shared_handler*)ctx)->pipewire_callback(cnt); - return 0; - }, - this); + pipewire_set_process_callback(client, +[](pipewire_nframes_t cnt, void* ctx) -> int { + ((shared_handler*)ctx)->pipewire_callback(cnt); + return 0; + }, this); } virtual void start_processing() override { pipewire_activate(client); } @@ -74,10 +71,10 @@ struct shared_handler : public libremidi::shared_context { case in_callback_added: midiin_callbacks.push_back( - std::move(*std::get_if(&ev.payload))); + std::move(*get_if(&ev.payload))); break; case in_callback_removed: { - auto idx = *std::get_if(&ev.payload); + auto idx = *get_if(&ev.payload); for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();) { if (it->token == idx) @@ -94,10 +91,10 @@ struct shared_handler : public libremidi::shared_context } case out_callback_added: midiout_callbacks.push_back( - std::move(*std::get_if(&ev.payload))); + std::move(*get_if(&ev.payload))); break; case out_callback_removed: - auto idx = *std::get_if(&ev.payload); + auto idx = *get_if(&ev.payload); for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();) { if (it->token == idx) @@ -141,7 +138,7 @@ struct shared_handler : public libremidi::shared_context struct event { event_type type; - std::variant payload; + libremidi_variant_alias::variant payload; }; boost::lockfree::spsc_queue events{16}; diff --git a/include/libremidi/backends/pipewire_ump.hpp b/include/libremidi/backends/pipewire_ump.hpp new file mode 100644 index 0000000..eed0a7e --- /dev/null +++ b/include/libremidi/backends/pipewire_ump.hpp @@ -0,0 +1,39 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include + +NAMESPACE_LIBREMIDI::pipewire_ump +{ +struct backend +{ + using midi_in = libremidi::pipewire_ump::midi_in_pipewire; + using midi_out = libremidi::pipewire_ump::midi_out_pipewire; + using midi_observer = libremidi::pipewire_ump::observer_pipewire; + using midi_in_configuration = libremidi::pipewire_ump::input_configuration; + using midi_out_configuration = libremidi::pipewire_ump::output_configuration; + using midi_observer_configuration = libremidi::pipewire_ump::observer_configuration; + static const constexpr auto API = libremidi::API::PIPEWIRE_UMP; + static const constexpr std::string_view name = "pipewire_ump"; + static const constexpr std::string_view display_name = "PipeWire (UMP)"; + + static inline bool available() noexcept + { + static const libpipewire& pw = libpipewire::instance(); + if (!pw.available) + return false; + const std::string_view version = pw.get_library_version(); + if (version.size() >= 3) + { + if (version[0] > '1' || version[2] >= '4') + return true; + } + return false; + } +}; +} diff --git a/include/libremidi/backends/pipewire_ump/config.hpp b/include/libremidi/backends/pipewire_ump/config.hpp new file mode 100644 index 0000000..ab59684 --- /dev/null +++ b/include/libremidi/backends/pipewire_ump/config.hpp @@ -0,0 +1,36 @@ +#pragma once +#include +#include + +NAMESPACE_LIBREMIDI::pipewire_ump +{ +struct input_configuration +{ + std::string client_name = "libremidi client"; + + pw_main_loop* context{}; + pw_filter* filter{}; + std::function set_process_func{}; + std::function clear_process_func{}; +}; + +struct output_configuration +{ + std::string client_name = "libremidi client"; + + pw_main_loop* context{}; + pw_filter* filter{}; + std::function set_process_func{}; + std::function clear_process_func{}; + + int64_t output_buffer_size{65536}; +}; + +struct observer_configuration +{ + std::string client_name = "libremidi client"; + + pw_main_loop* context{}; +}; + +} diff --git a/include/libremidi/backends/pipewire_ump/midi_in.hpp b/include/libremidi/backends/pipewire_ump/midi_in.hpp new file mode 100644 index 0000000..05b112b --- /dev/null +++ b/include/libremidi/backends/pipewire_ump/midi_in.hpp @@ -0,0 +1,132 @@ +#pragma once +#include +#include +#include +#include + +NAMESPACE_LIBREMIDI::pipewire_ump +{ +class midi_in_pipewire final + : public midi2::in_api + , public pipewire_helpers + , public error_handler +{ +public: + struct + : ump_input_configuration + , libremidi::pipewire_ump::input_configuration + { + } configuration; + + explicit midi_in_pipewire( + ump_input_configuration&& conf, libremidi::pipewire_ump::input_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + if (auto ret = create_context(*this); ret != stdx::error{}) + { + client_open_ = ret; + return; + } + if (auto ret = create_filter(*this); ret != stdx::error{}) + { + client_open_ = ret; + return; + } + client_open_ = stdx::error{}; + } + + ~midi_in_pipewire() override + { + stop_thread(); + do_close_port(); + destroy_filter(*this); + destroy_context(); + client_open_ = std::errc::not_connected; + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; } + + stdx::error open_port(const input_port& in_port, std::string_view name) override + { + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "32 bit raw UMP"); + err != stdx::error{}) + return err; + + if (auto err = link_ports(*this, in_port); err != stdx::error{}) + return err; + + start_thread(); + return stdx::error{}; + } + + stdx::error open_virtual_port(std::string_view name) override + { + if (auto err = create_local_port(*this, name, SPA_DIRECTION_INPUT, "32 bit raw UMP"); + err != stdx::error{}) + return err; + + start_thread(); + return stdx::error{}; + } + + stdx::error close_port() override + { + stop_thread(); + return do_close_port(); + } + + stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); } + + timestamp absolute_timestamp() const noexcept override { return system_ns(); } + + void process(struct spa_io_position* position) + { + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = true, + .absolute_is_monotonic = true, + .has_samples = true, + }; + + assert(this->filter); + assert(this->filter->port); + const auto b = pw.filter_dequeue_buffer(this->filter->port); + if (!b) + return; + + const auto buf = b->buffer; + const auto d = &buf->datas[0]; + + if (d->data == nullptr) + return; + + const auto pod + = (spa_pod*)spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size); + if (!pod) + return; + if (!spa_pod_is_sequence(pod)) + return; + + struct spa_pod_control* c{}; + SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) + { + if (c->type != SPA_CONTROL_UMP) + continue; + + auto data = (uint8_t*)SPA_POD_BODY(&c->value); + auto size = SPA_POD_BODY_SIZE(&c->value); + + const auto to_ns = [=, clk = position->clock] { + return 1e9 * ((clk.position + c->offset) / (double)clk.rate.denom); + }; + + m_processing.on_bytes( + {(uint32_t*)data, (uint32_t*)(data + size)}, + m_processing.timestamp(to_ns, c->offset)); + } + + pw.filter_queue_buffer(this->filter->port, b); + } + + midi2::input_state_machine m_processing{this->configuration}; +}; +} diff --git a/include/libremidi/backends/pipewire_ump/midi_out.hpp b/include/libremidi/backends/pipewire_ump/midi_out.hpp new file mode 100644 index 0000000..1c2bbff --- /dev/null +++ b/include/libremidi/backends/pipewire_ump/midi_out.hpp @@ -0,0 +1,182 @@ +#pragma once +#include +#include +#include + +#include + +NAMESPACE_LIBREMIDI::pipewire_ump +{ +class midi_out_pipewire + : public midi2::out_api + , public pipewire_helpers + , public error_handler +{ +public: + struct + : libremidi::output_configuration + , libremidi::pipewire_ump::output_configuration + { + } configuration; + + midi_out_pipewire( + libremidi::output_configuration&& conf, + libremidi::pipewire_ump::output_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + if (auto ret = create_context(*this); ret != stdx::error{}) + { + client_open_ = ret; + return; + } + if (auto ret = create_filter(*this); ret != stdx::error{}) + { + client_open_ = ret; + return; + } + client_open_ = stdx::error{}; + } + + ~midi_out_pipewire() override + { + stop_thread(); + do_close_port(); + destroy_filter(*this); + destroy_context(); + client_open_ = std::errc::not_connected; + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; } + + stdx::error open_port(const output_port& out_port, std::string_view name) override + { + if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "32 bit raw UMP"); + err != stdx::error{}) + return err; + + this->filter->set_port_buffer(configuration.output_buffer_size); + + if (auto err = link_ports(*this, out_port); err != stdx::error{}) + return err; + + start_thread(); + return stdx::error{}; + } + + stdx::error open_virtual_port(std::string_view name) override + { + if (auto err = create_local_port(*this, name, SPA_DIRECTION_OUTPUT, "32 bit raw UMP"); + err != stdx::error{}) + return err; + + this->filter->set_port_buffer(configuration.output_buffer_size); + + start_thread(); + return stdx::error{}; + } + + stdx::error close_port() override + { + stop_thread(); + return do_close_port(); + } + + stdx::error set_port_name(std::string_view port_name) override { return rename_port(port_name); } + + int process(spa_io_position* pos) + { + m_process_clock.store(pos->clock.nsec, std::memory_order_relaxed); + const auto b = pw.filter_dequeue_buffer(this->filter->port); + if (!b) + return 1; + + const auto buf = b->buffer; + const auto d = &buf->datas[0]; + + if (d->data == nullptr) + return 1; + + spa_pod_builder build; + spa_zero(build); + spa_pod_builder_init(&build, d->data, d->maxsize); + + spa_pod_frame f; + spa_pod_builder_push_sequence(&build, &f, 0); + + // for all events + while (auto m_ptr = m_queue.peek()) + { + auto& m = *m_ptr; + + spa_pod_builder_control(&build, static_cast(m.timestamp), SPA_CONTROL_UMP); + int res = spa_pod_builder_bytes(&build, m.data, cmidi2_ump_get_message_size_bytes(m.data)); + + // Try again next buffer + if (res == -ENOSPC) + break; + + // Recycle the memory + m_queue.pop(); + } + spa_pod_builder_pop(&build, &f); + + int n_fill_frames = build.state.offset; + if (n_fill_frames > 0) + { + d->chunk->offset = 0; + d->chunk->stride = 1; + d->chunk->size = n_fill_frames; + b->size = n_fill_frames; + + pw.filter_queue_buffer(this->filter->port, b); + return 0; + } + + pw.filter_flush(this->filter->filter, true); + + return 0; + } + + stdx::error send_ump(const uint32_t* message, size_t size) override + { + // FIXME + if (size > 4) + return std::errc::message_size; + + libremidi::ump m; + std::copy_n(message, size, m.data); + m.timestamp = 0; + m_queue.enqueue(std::move(m)); + return stdx::error{}; + } + + int convert_timestamp(int64_t user) const noexcept + { + switch (configuration.timestamps) + { + case timestamp_mode::AudioFrame: + return static_cast(user); + + default: + // TODO + return 0; + } + } + + stdx::error schedule_ump(int64_t ts, const uint32_t* message, size_t size) override + { + // FIXME + if (size > 4) + return std::errc::message_size; + + libremidi::ump m; + std::copy_n(message, size, m.data); + m.timestamp = convert_timestamp(ts); + m_queue.enqueue(std::move(m)); // FIXME actually send them scheudled + return stdx::error{}; + } + + moodycamel::ReaderWriterQueue m_queue; + std::atomic_int64_t m_process_clock = 0; +}; +} diff --git a/include/libremidi/backends/pipewire_ump/observer.hpp b/include/libremidi/backends/pipewire_ump/observer.hpp new file mode 100644 index 0000000..e3b1bd8 --- /dev/null +++ b/include/libremidi/backends/pipewire_ump/observer.hpp @@ -0,0 +1,70 @@ +#pragma once +#include +#include +#include + +#include + +NAMESPACE_LIBREMIDI::pipewire_ump +{ +class observer_pipewire final + : public observer_api + , private pipewire_helpers + , private error_handler +{ +public: + struct + : libremidi::observer_configuration + , libremidi::pipewire_ump::observer_configuration + { + } configuration; + + explicit observer_pipewire( + libremidi::observer_configuration&& conf, + libremidi::pipewire_ump::observer_configuration&& apiconf) + : configuration{std::move(conf), std::move(apiconf)} + { + create_context(*this); + + // FIXME port rename callback + { + this->add_callbacks("UMP", configuration); + this->start_thread(); + } + + if (configuration.notify_in_constructor) + { + if (configuration.input_added) + for (const auto& p : get_input_ports()) + configuration.input_added(p); + + if (configuration.output_added) + for (const auto& p : get_output_ports()) + configuration.output_added(p); + } + } + + libremidi::API get_current_api() const noexcept override { return libremidi::API::PIPEWIRE_UMP; } + + std::vector get_input_ports() const noexcept override + { + return get_ports( + "UMP", this->configuration, *this->global_context); + } + + std::vector get_output_ports() const noexcept override + { + return get_ports( + "UMP", this->configuration, *this->global_context); + } + + ~observer_pipewire() + { + stop_thread(); + destroy_context(); + } + + std::unordered_set seen_input_ports; + std::unordered_set seen_output_ports; +}; +} diff --git a/include/libremidi/backends/winmidi.hpp b/include/libremidi/backends/winmidi.hpp index be7788c..a641ab2 100644 --- a/include/libremidi/backends/winmidi.hpp +++ b/include/libremidi/backends/winmidi.hpp @@ -3,7 +3,9 @@ #include #include -namespace libremidi::winmidi +#include + +NAMESPACE_LIBREMIDI::winmidi { struct backend { @@ -14,9 +16,11 @@ struct backend using midi_out_configuration = winmidi::output_configuration; using midi_observer_configuration = winmidi::observer_configuration; static const constexpr auto API = libremidi::API::WINDOWS_MIDI_SERVICES; - static const constexpr auto name = "winmidi"; - static const constexpr auto display_name = "Windows MIDI Services"; + static const constexpr std::string_view name = "winmidi"; + static const constexpr std::string_view display_name = "Windows MIDI Services"; - static constexpr inline bool available() noexcept { return true; } + static inline bool available() noexcept { + return libremidi::instance()->ready; + } }; -} // namespace libremidi +} // NAMESPACE_LIBREMIDI diff --git a/include/libremidi/backends/winmidi/config.hpp b/include/libremidi/backends/winmidi/config.hpp index 276b616..c1ef02f 100644 --- a/include/libremidi/backends/winmidi/config.hpp +++ b/include/libremidi/backends/winmidi/config.hpp @@ -1,19 +1,36 @@ #pragma once #include -namespace libremidi::winmidi +#include + +#if !defined(LIBREMIDI_MODULE_BUILD) || !defined(_WIN32) +namespace winrt::Microsoft::Windows::Devices::Midi2 +{ +struct MidiSession; +struct MidiEndpointDeviceInformation; +struct MidiEndpointConnection; +} +#endif + +// TODO allow to share midi session and endpoints +NAMESPACE_LIBREMIDI::winmidi { struct input_configuration { + std::string client_name = "libremidi input"; + winrt::Microsoft::Windows::Devices::Midi2::MidiSession* context{}; }; struct output_configuration { + std::string client_name = "libremidi output"; + winrt::Microsoft::Windows::Devices::Midi2::MidiSession* context{}; }; struct observer_configuration { + std::string client_name = "libremidi observer"; }; } diff --git a/include/libremidi/backends/winmidi/helpers.hpp b/include/libremidi/backends/winmidi/helpers.hpp index f8218a5..0a586bd 100644 --- a/include/libremidi/backends/winmidi/helpers.hpp +++ b/include/libremidi/backends/winmidi/helpers.hpp @@ -3,58 +3,216 @@ #define NOMINMAX 1 #define WIN32_LEAN_AND_MEAN 1 #include +#include -#include #include #include -#include -#include #include +#include #include #include #include -#include +#include +#include +#if __has_include() +#include + #define LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS 1 -namespace midi2 = winrt::Windows::Devices::Midi2; -namespace foundation = winrt::Windows::Foundation; -namespace collections = winrt::Windows::Foundation::Collections; +#define LIBREMIDI_DEFINE_GUID_CONSTEXPR(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ + LIBREMIDI_STATIC constexpr const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} +NAMESPACE_LIBREMIDI { + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_IMidiEndpointConnectionMessagesReceivedCallback, 0x8087b303, 0x0519, 0x31d1, 0x31, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10); + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_IMidiEndpointConnectionRaw, 0x8087b303, 0x0519, 0x31d1, 0x31, 0xd1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20); + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_IMidiClientInitializer, 0x8087b303, 0xd551, 0xbce2, 0x1e, 0xad, 0xa2, 0x50, 0x0d, 0x50, 0xc5, 0x80); + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_MidiClientInitializerUuid, 0xc3263827, 0xc3b0, 0xbdbd, 0x25, 0x00, 0xce, 0x63, 0xa3, 0xf3, 0xf2, 0xc3); + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_MidiSrvTransportUuid, 0x2ba15e4e, 0x5417, 0x4a66, 0x85, 0xb8, 0x2b, 0x22, 0x60, 0xef, 0xbc, 0x84); +} +#endif // clang-format on -namespace libremidi::winmidi +namespace midi2 = winrt::Microsoft::Windows::Devices::Midi2; +namespace foundation = winrt::Windows::Foundation; + +NAMESPACE_LIBREMIDI::winmidi { using namespace winrt; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Devices::Enumeration; using namespace winrt::Windows::Storage::Streams; -using namespace winrt::Windows::Devices::Midi2; -using namespace Windows::Devices::Enumeration; +using namespace winrt::Microsoft::Windows::Devices::Midi2; inline bool ichar_equals(char a, char b) { - return std::tolower(static_cast(a)) == - std::tolower(static_cast(b)); + return std::tolower(static_cast(a)) + == std::tolower(static_cast(b)); } + inline bool iequals(std::string_view lhs, std::string_view rhs) { return std::ranges::equal(lhs, rhs, ichar_equals); } -inline winrt::Windows::Devices::Midi2::MidiEndpointDeviceInformation -get_port_by_name(const std::string& port) { +inline std::pair< + winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointDeviceInformation, + winrt::Microsoft::Windows::Devices::Midi2::MidiGroupTerminalBlock> +get_port(const std::string& device_name, int group_terminal_block) +{ auto eps = MidiEndpointDeviceInformation::FindAll(); for (const auto& ep : eps) { - auto str = to_string(ep.Id()); + auto str = to_string(ep.EndpointDeviceId()); if (str.empty()) continue; - if (iequals(str, port)) - return ep; + if (iequals(str, device_name)) + { + for (const auto& gp : ep.GetGroupTerminalBlocks()) + { + if (gp.Number() == group_terminal_block) + { + return std::make_pair(ep, gp); + } + } + } } - return {nullptr}; + return {nullptr, nullptr}; } + +// From Microsoft.Windows.Devices.Midi2.Initialization.hpp +typedef enum +{ + Platform_x64 = 1, + // Platform_Arm64 = 2, + // Platform_Arm64EC = 3, + Platform_Arm64X = 4, +} MidiAppSDKPlatform; + +struct IMidiClientInitializer : ::IUnknown +{ + // returns the SDK version info. Supply nullptr for arguments you don't care about + STDMETHOD(GetInstalledWindowsMidiServicesSdkVersion)( + MidiAppSDKPlatform* buildPlatform, + USHORT* versionMajor, + USHORT* versionMinor, + USHORT* versionPatch, + + LPWSTR* buildSource, + LPWSTR* versionName, + LPWSTR* versionFullString + ) = 0; + + // demand-starts the service if present + STDMETHOD(EnsureServiceAvailable)() = 0; +}; +struct winmidi_shared_data_instance +{ + IMidiClientInitializer* initializer{nullptr}; + bool ready{false}; + winmidi_shared_data_instance() + { + // 1. Check if MIDI Services are available + { + ::IUnknown* servicePointer{ nullptr }; + auto hr = CoCreateInstance( + libremidi::IID_MidiSrvTransportUuid, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&servicePointer) + ); + + if (SUCCEEDED(hr)) + { + if (servicePointer == nullptr) + { + ready = false; + return; + } + + // Here is the good case: + servicePointer->Release(); + servicePointer = nullptr; + ready = true; + } + else if (hr == REGDB_E_CLASSNOTREG) + { + servicePointer = nullptr; + ready = false; + return; + } + else + { + servicePointer = nullptr; + ready = false; + return; + } + } + + // 2. Check if MIDI services can be created + { + if (SUCCEEDED(CoCreateInstance( + libremidi::IID_MidiClientInitializerUuid, + NULL, + CLSCTX::CLSCTX_INPROC_SERVER | CLSCTX::CLSCTX_FROM_DEFAULT_CONTEXT, + libremidi::IID_IMidiClientInitializer, + reinterpret_cast(&initializer) + ))) + { + if (initializer != nullptr) + { + ready = true; + } + else + { + ready = false; + return; + } + } + else + { + ready = false; + return; + } + } + + // 3. Check if MIDI services can be used + if (SUCCEEDED(initializer->EnsureServiceAvailable())) + { + ready = true; + } + else + { + ready = false; + initializer->Release(); + initializer = nullptr; + return; + } + } + + ~winmidi_shared_data_instance() + { + if (initializer != nullptr) + { + initializer->Release(); + initializer = nullptr; + } + } +}; + +struct winmidi_shared_data +{ + std::shared_ptr self = libremidi::instance(); + + winmidi_shared_data() + { + } + + ~winmidi_shared_data() + { + } +}; + } diff --git a/include/libremidi/backends/winmidi/midi_in.hpp b/include/libremidi/backends/winmidi/midi_in.hpp index 041060e..b8d0fc4 100644 --- a/include/libremidi/backends/winmidi/midi_in.hpp +++ b/include/libremidi/backends/winmidi/midi_in.hpp @@ -5,14 +5,12 @@ #include #include -#include - -namespace libremidi::winmidi +NAMESPACE_LIBREMIDI::winmidi { - class midi_in_impl final : public midi2::in_api , public error_handler + , public winmidi_shared_data { public: struct @@ -21,10 +19,62 @@ public: { } configuration; +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + struct raw_callback_type final : IMidiEndpointConnectionMessagesReceivedCallback + { + explicit raw_callback_type(midi_in_impl& self) + : self{self} + { + + } + + midi_in_impl& self; + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override + { + if (!ppvObject) + return E_POINTER; + + if (riid == __uuidof(IUnknown) || + riid == libremidi::IID_IMidiEndpointConnectionMessagesReceivedCallback) + { + *ppvObject = static_cast(this); + AddRef(); + return S_OK; + } + + *ppvObject = nullptr; + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() override + { + return 1; + } + + ULONG STDMETHODCALLTYPE Release() override + { + return 1; + } + + HRESULT STDMETHODCALLTYPE MessagesReceived( + GUID sessionId, + GUID connectionId, + UINT64 timestamp, + UINT32 wordCount, + UINT32 *messages) override { + HRESULT res{}; + self.process_message(sessionId, connectionId, timestamp, wordCount, messages); + return res; + } + } raw_callback{*this}; +#endif + explicit midi_in_impl( libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} - , m_session{MidiSession::CreateSession(L"libremidi session")} + , m_session{ + configuration.context ? *configuration.context + : MidiSession::Create(to_hstring(configuration.client_name))} { this->client_open_ = stdx::error{}; } @@ -42,24 +92,41 @@ public: stdx::error open_port(const input_port& port, std::string_view) override { - auto ep = get_port_by_name(port.port_name); - if (!ep) + auto device_id = get_if(&port.device); + if (!device_id) + return std::errc::invalid_argument; + + auto [ep, gp] = get_port(*device_id, port.port); + if (!ep || !gp) return std::errc::address_not_available; - m_endpoint = m_session.CreateEndpointConnection(ep.Id()); + m_group_filter = port.port - 1; + // TODO use a MidiGroupEndpointListener for the filtering + m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId()); + +#if !LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS m_revoke_token = m_endpoint.MessageReceived( - [&](const foundation::IInspectable& sender, - const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) { + [this]( + const winrt::Microsoft::Windows::Devices::Midi2::IMidiMessageReceivedEventSource&, + const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) { process_message(args); }); +#else + m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void()); + + m_raw_endpoint->SetMessagesReceivedCallback( + &raw_callback + ); +#endif m_endpoint.Open(); return stdx::error{}; } - void process_message(const winrt::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg) + void process_message( + const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg) { static constexpr timestamp_backend_info timestamp_info{ .has_absolute_timestamps = true, @@ -68,6 +135,14 @@ public: }; const auto& ump = msg.GetMessagePacket(); + auto pk = msg.PeekFirstWord(); + if (m_group_filter >= 0) + { + int group = cmidi2_ump_get_group(&pk); + if (group != m_group_filter) + return; + } + const auto& b = ump.GetAllWords(); uint32_t ump_space[64]; @@ -79,9 +154,49 @@ public: {ump_space, ump_space + b.Size()}, m_processing.timestamp(to_ns, 0)); } +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + void process_message( + const GUID& /* sessionId */, + const GUID& /* connectionId */, + UINT64 timestamp, + UINT32 wordCount, + UINT32 *ump) + { + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = true, + .absolute_is_monotonic = false, + .has_samples = false, + }; + + if(wordCount == 0) + return; + + if (m_group_filter >= 0) + { + int group = cmidi2_ump_get_group(ump); + if (group != m_group_filter) + return; + } + + auto to_ns = [t = timestamp] { return t; }; + m_processing.on_bytes( + {ump, ump + wordCount}, m_processing.timestamp(to_ns, 0)); + } +#endif + stdx::error close_port() override { + if(!m_endpoint) + return std::errc::not_connected; + +#if !LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS m_endpoint.MessageReceived(m_revoke_token); +#else + if(m_raw_endpoint) { + m_raw_endpoint->RemoveMessagesReceivedCallback(); + m_raw_endpoint = nullptr; + } +#endif m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId()); return stdx::error{}; } @@ -91,7 +206,11 @@ public: private: MidiSession m_session; winrt::event_token m_revoke_token{}; - winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; + winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + winrt::impl::com_ref m_raw_endpoint{}; +#endif midi2::input_state_machine m_processing{this->configuration}; + int m_group_filter = -1; }; } diff --git a/include/libremidi/backends/winmidi/midi_out.hpp b/include/libremidi/backends/winmidi/midi_out.hpp index 3a95546..29b334c 100644 --- a/include/libremidi/backends/winmidi/midi_out.hpp +++ b/include/libremidi/backends/winmidi/midi_out.hpp @@ -1,16 +1,17 @@ #pragma once -#include #include #include #include #include +#include -namespace libremidi::winmidi +NAMESPACE_LIBREMIDI::winmidi { class midi_out_impl final : public midi2::out_api , public error_handler + , public winmidi_shared_data { public: struct @@ -21,7 +22,9 @@ public: midi_out_impl(libremidi::output_configuration&& conf, winmidi::output_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} - , m_session{MidiSession::CreateSession(L"libremidi session")} + , m_session{ + configuration.context ? *configuration.context + : MidiSession::Create(to_hstring(configuration.client_name))} { this->client_open_ = stdx::error{}; } @@ -35,11 +38,18 @@ public: stdx::error open_port(const output_port& port, std::string_view) override { - auto ep = get_port_by_name(port.port_name); - if (!ep) + auto device_id = get_if(&port.device); + if (!device_id) + return std::errc::invalid_argument; + + auto [ep, gp] = get_port(*device_id, port.port); + if (!ep || !gp) return std::errc::address_not_available; - m_endpoint = m_session.CreateEndpointConnection(ep.Id()); + m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId()); +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void()); + #endif m_endpoint.Open(); return stdx::error{}; @@ -47,6 +57,9 @@ public: stdx::error close_port() override { + if(!m_endpoint) + return std::errc::not_connected; + m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId()); return stdx::error{}; } @@ -54,8 +67,10 @@ public: stdx::error send_ump(const uint32_t* message, size_t size) override { auto write_func = [this](const uint32_t* ump, int64_t bytes) -> std::errc { + +#if !LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS MidiSendMessageResults ret{}; - switch(bytes / 4) + switch (bytes / 4) { case 1: ret = m_endpoint.SendSingleMessagePacket(MidiMessage32(0, ump[0])); @@ -67,23 +82,52 @@ public: ret = m_endpoint.SendSingleMessagePacket(MidiMessage96(0, ump[0], ump[1], ump[2])); break; case 4: - ret = m_endpoint.SendSingleMessagePacket(MidiMessage128(0, ump[0], ump[1], ump[2], ump[3])); + ret = m_endpoint.SendSingleMessagePacket( + MidiMessage128(0, ump[0], ump[1], ump[2], ump[3])); break; default: return std::errc::bad_message; } - - if(ret != MidiSendMessageResults::Succeeded) + if (ret != MidiSendMessageResults::Succeeded) return std::errc::bad_message; + #else + HRESULT ret{}; + switch (bytes / 4) + { + case 1: + assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(1, (UINT32*)ump)); + ret = m_raw_endpoint->SendMidiMessagesRaw(0, 1, (UINT32*)ump); + break; + case 2: + assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(2, (UINT32*)ump)); + ret = m_raw_endpoint->SendMidiMessagesRaw(0, 2, (UINT32*)ump); + break; + case 3: + assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(3, (UINT32*)ump)); + ret = m_raw_endpoint->SendMidiMessagesRaw(0, 3, (UINT32*)ump); + break; + case 4: + assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(4, (UINT32*)ump)); + ret = m_raw_endpoint->SendMidiMessagesRaw(0, 4, (UINT32*)ump); + break; + default: + return std::errc::bad_message; + } + #endif + if(ret < 0) + return std::errc::io_error; return std::errc{0}; }; - return segment_ump_stream(message, size, write_func, []() {}); + return segment_ump_stream(message, size, write_func, []() { }); } private: MidiSession m_session; - winrt::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; + winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + winrt::impl::com_ref m_raw_endpoint{}; +#endif }; } diff --git a/include/libremidi/backends/winmidi/observer.hpp b/include/libremidi/backends/winmidi/observer.hpp index 3642324..b26f180 100644 --- a/include/libremidi/backends/winmidi/observer.hpp +++ b/include/libremidi/backends/winmidi/observer.hpp @@ -2,16 +2,18 @@ #include #include #include -; -namespace libremidi::winmidi + +NAMESPACE_LIBREMIDI::winmidi { struct port_info { - hstring id; - hstring name; + winrt::hstring id; + winrt::hstring name; }; -class observer_impl final : public observer_api +class observer_impl final + : public observer_api + , public winmidi_shared_data { public: struct @@ -20,46 +22,55 @@ public: { } configuration; + MidiEndpointDeviceWatcher watcher = nullptr; + MidiEndpointDeviceWatcher::Added_revoker m_addHandler; + MidiEndpointDeviceWatcher::Updated_revoker m_updHandler; + MidiEndpointDeviceWatcher::Removed_revoker m_delHandler; + std::map> m_known_input_devices; + std::map> m_known_output_devices; + std::mutex m_devices_mtx; + std::atomic_bool m_in_constructor{true}; explicit observer_impl( libremidi::observer_configuration&& conf, winmidi::observer_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} - , session{MidiSession::CreateSession(L"libremidi session")} { + struct on_delete { observer_impl& self; ~on_delete() { self.m_in_constructor = false; } } on_delete{*this}; + if (!configuration.has_callbacks()) return; - if (configuration.notify_in_constructor) + // Note: winmidi also notifies for existing devices, so the "notify in constructor" + // is handled in the callbacks + if ((watcher = MidiEndpointDeviceWatcher::Create())) { - if (configuration.input_added) - for (const auto& p : get_input_ports()) - configuration.input_added(p); + namespace enumeration = winrt::Windows::Devices::Enumeration; + auto addHandler = foundation::TypedEventHandler< + MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationAddedEventArgs>( + this, &observer_impl::on_device_added); + auto updHandler = foundation::TypedEventHandler< + MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationUpdatedEventArgs>( + this, &observer_impl::on_device_updated); + auto delHandler = foundation::TypedEventHandler< + MidiEndpointDeviceWatcher, MidiEndpointDeviceInformationRemovedEventArgs>( + this, &observer_impl::on_device_removed); - if (configuration.output_added) - for (const auto& p : get_output_ports()) - configuration.output_added(p); + m_addHandler = watcher.Added(winrt::auto_revoke, addHandler); + m_updHandler = watcher.Updated(winrt::auto_revoke, updHandler); + m_delHandler = watcher.Removed(winrt::auto_revoke, delHandler); + + watcher.Start(); } - - /* - evTokenOnInputAdded_ - = internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); }); - evTokenOnInputRemoved_ - = internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); }); - evTokenOnOutputAdded_ - = internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); }); - evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved( - [this](const port_info& p) { on_output_removed(p); }); -*/ } ~observer_impl() { if (!configuration.has_callbacks()) return; - // internalInPortObserver_.PortAdded(evTokenOnInputAdded_); - // internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_); - // internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_); - // internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_); + + m_addHandler.revoke(); + m_updHandler.revoke(); + m_delHandler.revoke(); } libremidi::API get_current_api() const noexcept override @@ -67,33 +78,60 @@ public: return libremidi::API::WINDOWS_MIDI_SERVICES; } - template - auto to_port_info(const DeviceInformation& p) const noexcept - -> std::conditional_t + static transport_type code_to_type(std::string_view str) noexcept { + using enum transport_type; + + if (str.starts_with("KS")) + return hardware; + if (str == "BLE") + return transport_type(hardware | bluetooth); + if (str == "VPB" || str == "APP") + return software; + if (str == "LOOP") + return transport_type(software | loopback); + if (str.starts_with("NET")) + return network; + return unknown; + } + + template + auto to_port_info(const MidiEndpointDeviceInformation& p, const MidiGroupTerminalBlock& gp) + const noexcept -> std::conditional_t + { + const auto& tinfo = p.GetTransportSuppliedInfo(); + return { - {.client = 0, - .port = 0, - .manufacturer = "", - .device_name = "", - .port_name = to_string(p.Id()), - .display_name = to_string(p.Name())}}; + {.api = libremidi::API::WINDOWS_MIDI_SERVICES, + .client = 0, + .container = std::bit_cast(p.ContainerId()), + .device = to_string(p.EndpointDeviceId()), + .port = gp.Number(), + .manufacturer = to_string(tinfo.ManufacturerName), + .product = to_string(tinfo.Name), + .serial = to_string(tinfo.SerialNumber), + .device_name = to_string(p.Name()), + .port_name = to_string(gp.Name()), + .display_name = to_string(gp.Name()) + " " + std::to_string(gp.Number()), + .type = code_to_type(to_string(p.GetTransportSuppliedInfo().TransportCode))}}; } std::vector get_input_ports() const noexcept override { std::vector ret; - auto deviceSelector = MidiEndpointConnection::GetDeviceSelector(); - auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get(); - for (const auto& ep : endpointDevices) + for (const auto& ep : MidiEndpointDeviceInformation::FindAll()) { - if(ep.Name().starts_with(L"Diagnostics")) { + if (ep.Name().starts_with(L"Diagnostics")) + { continue; } - // FIXME if(has input...) - ret.emplace_back(to_port_info(ep)); + for (const auto& gp : ep.GetGroupTerminalBlocks()) + { + if (gp.Direction() != MidiGroupTerminalBlockDirection::BlockOutput) + ret.emplace_back(to_port_info(ep, gp)); + } } return ret; @@ -103,46 +141,142 @@ public: { std::vector ret; - auto deviceSelector = MidiEndpointConnection::GetDeviceSelector(); - auto endpointDevices = DeviceInformation::FindAllAsync(deviceSelector).get(); - for (const auto& ep : endpointDevices) + for (const auto& ep : MidiEndpointDeviceInformation::FindAll()) { - if(ep.Name().starts_with(L"Diagnostics")) { + if (ep.Name().starts_with(L"Diagnostics")) + { continue; } - // FIXME if(has output...) - ret.emplace_back(to_port_info(ep)); + + for (const auto& gp : ep.GetGroupTerminalBlocks()) + { + if (gp.Direction() != MidiGroupTerminalBlockDirection::BlockInput) + ret.emplace_back(to_port_info(ep, gp)); + } } return ret; } - void on_input_added(const DeviceInformation& name) + // Note: these callbacks are called from some random thread! + void on_device_added( + const MidiEndpointDeviceWatcher&, const MidiEndpointDeviceInformationAddedEventArgs& result) { - if (configuration.input_added) - configuration.input_added(to_port_info(name)); + add_device(result.AddedDevice()); } - void on_input_removed(const DeviceInformation& name) + void on_device_updated( + const MidiEndpointDeviceWatcher&, + const MidiEndpointDeviceInformationUpdatedEventArgs& result) { + // OPTIMIZEME + remove_device(result.EndpointDeviceId()); + + add_device( + MidiEndpointDeviceInformation::CreateFromEndpointDeviceId(result.EndpointDeviceId())); + } + + void on_device_removed( + const MidiEndpointDeviceWatcher&, + const MidiEndpointDeviceInformationRemovedEventArgs& result) + { + remove_device(result.EndpointDeviceId()); + } + + void add_device(const MidiEndpointDeviceInformation& ep) + { + for (const auto& gp : ep.GetGroupTerminalBlocks()) + { + MidiGroupTerminalBlockDirection direction = gp.Direction(); + switch (direction) + { + case MidiGroupTerminalBlockDirection::Bidirectional: { + if (configuration.input_added) + { + auto ip = to_port_info(ep, gp); + { + std::lock_guard _{m_devices_mtx}; + m_known_input_devices[ep.EndpointDeviceId()].push_back(ip); + } + + if(!m_in_constructor || configuration.notify_in_constructor) + configuration.input_added(std::move(ip)); + } + if (configuration.output_added) + { + auto op = to_port_info(ep, gp); + { + std::lock_guard _{m_devices_mtx}; + m_known_output_devices[ep.EndpointDeviceId()].push_back(op); + } + + if(!m_in_constructor || configuration.notify_in_constructor) + configuration.output_added(std::move(op)); + } + break; + } + case MidiGroupTerminalBlockDirection::BlockInput: + if (configuration.input_added) + { + auto ip = to_port_info(ep, gp); + { + std::lock_guard _{m_devices_mtx}; + m_known_input_devices[ep.EndpointDeviceId()].push_back(ip); + } + + if(!m_in_constructor || configuration.notify_in_constructor) + configuration.input_added(std::move(ip)); + } + break; + case MidiGroupTerminalBlockDirection::BlockOutput: + if (configuration.output_added) + { + auto op = to_port_info(ep, gp); + { + std::lock_guard _{m_devices_mtx}; + m_known_output_devices[ep.EndpointDeviceId()].push_back(op); + } + + if(!m_in_constructor || configuration.notify_in_constructor) + configuration.output_added(std::move(op)); + } + break; + } + } + } + + void remove_device(winrt::hstring eid) + { + std::vector to_remove_in; + std::vector to_remove_out; + + { + std::lock_guard _{m_devices_mtx}; + if (auto it = m_known_input_devices.find(eid); it != m_known_input_devices.end()) + { + for (auto& ip : it->second) + { + to_remove_in.push_back(ip); + } + m_known_input_devices.erase(it); + } + if (auto it = m_known_output_devices.find(eid); it != m_known_output_devices.end()) + { + for (auto& op : it->second) + { + to_remove_out.push_back(op); + } + m_known_output_devices.erase(it); + } + } + if (configuration.input_removed) - configuration.input_removed(to_port_info(name)); - } - - void on_output_added(const DeviceInformation& name) - { - if (configuration.output_added) - configuration.output_added(to_port_info(name)); - } - - void on_output_removed(const DeviceInformation& name) - { + for (auto& port : to_remove_in) + configuration.input_removed(port); if (configuration.output_removed) - configuration.output_removed(to_port_info(name)); + for (auto& port : to_remove_out) + configuration.output_removed(port); } - -private: - MidiSession session; }; } diff --git a/include/libremidi/backends/winmm.hpp b/include/libremidi/backends/winmm.hpp index a45692f..04b5baf 100644 --- a/include/libremidi/backends/winmm.hpp +++ b/include/libremidi/backends/winmm.hpp @@ -3,6 +3,8 @@ #include #include +#include + // Default for Windows is to add an identifier to the port names; this // flag can be defined (e.g. in your project file) to disable this behaviour. // #define LIBREMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES @@ -16,7 +18,7 @@ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp // Thanks to Jean-Baptiste Berruchon for the sysex code. -namespace libremidi +NAMESPACE_LIBREMIDI { struct winmm_backend @@ -28,8 +30,8 @@ struct winmm_backend using midi_out_configuration = winmm_output_configuration; using midi_observer_configuration = winmm_observer_configuration; static const constexpr auto API = libremidi::API::WINDOWS_MM; - static const constexpr auto name = "winmm"; - static const constexpr auto display_name = "Windows Multimedia"; + static const constexpr std::string_view name = "winmm"; + static const constexpr std::string_view display_name = "Windows Multimedia"; static constexpr inline bool available() noexcept { return true; } }; diff --git a/include/libremidi/backends/winmm/config.hpp b/include/libremidi/backends/winmm/config.hpp index 61863e0..f681e45 100644 --- a/include/libremidi/backends/winmm/config.hpp +++ b/include/libremidi/backends/winmm/config.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct winmm_input_configuration diff --git a/include/libremidi/backends/winmm/error_domain.hpp b/include/libremidi/backends/winmm/error_domain.hpp index f1bf49c..fb4d04c 100644 --- a/include/libremidi/backends/winmm/error_domain.hpp +++ b/include/libremidi/backends/winmm/error_domain.hpp @@ -9,7 +9,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct winmm_error_domain : public stdx::error_domain @@ -34,28 +34,50 @@ public: { switch (error_cast(e)) { - case MMSYSERR_NOERROR: return "No error"; - case MMSYSERR_ERROR: return "Error"; - case MMSYSERR_BADDEVICEID: return "Bad device ID"; - case MMSYSERR_NOTENABLED: return "Not enabled"; - case MMSYSERR_ALLOCATED: return "Allocated"; - case MMSYSERR_INVALHANDLE: return "Invalid handle"; - case MMSYSERR_NODRIVER: return "No driver"; - case MMSYSERR_NOMEM: return "No memory"; - case MMSYSERR_NOTSUPPORTED: return "Not supported"; - case MMSYSERR_BADERRNUM: return "Bad errnum"; - case MMSYSERR_INVALFLAG: return "Invalid flag"; - case MMSYSERR_INVALPARAM: return "Invalid parameter"; - case MMSYSERR_HANDLEBUSY: return "Handle busy"; - case MMSYSERR_INVALIDALIAS: return "Invalid alias"; - case MMSYSERR_BADDB: return "Bad database"; - case MMSYSERR_KEYNOTFOUND: return "Key not found"; - case MMSYSERR_READERROR: return "Read error"; - case MMSYSERR_WRITEERROR: return "Write error"; - case MMSYSERR_DELETEERROR: return "Delete error"; - case MMSYSERR_VALNOTFOUND: return "Value not found"; - case MMSYSERR_NODRIVERCB: return "No driver callback"; - case MMSYSERR_MOREDATA: return "More data"; + case MMSYSERR_NOERROR: + return "No error"; + case MMSYSERR_ERROR: + return "Error"; + case MMSYSERR_BADDEVICEID: + return "Bad device ID"; + case MMSYSERR_NOTENABLED: + return "Not enabled"; + case MMSYSERR_ALLOCATED: + return "Allocated"; + case MMSYSERR_INVALHANDLE: + return "Invalid handle"; + case MMSYSERR_NODRIVER: + return "No driver"; + case MMSYSERR_NOMEM: + return "No memory"; + case MMSYSERR_NOTSUPPORTED: + return "Not supported"; + case MMSYSERR_BADERRNUM: + return "Bad errnum"; + case MMSYSERR_INVALFLAG: + return "Invalid flag"; + case MMSYSERR_INVALPARAM: + return "Invalid parameter"; + case MMSYSERR_HANDLEBUSY: + return "Handle busy"; + case MMSYSERR_INVALIDALIAS: + return "Invalid alias"; + case MMSYSERR_BADDB: + return "Bad database"; + case MMSYSERR_KEYNOTFOUND: + return "Key not found"; + case MMSYSERR_READERROR: + return "Read error"; + case MMSYSERR_WRITEERROR: + return "Write error"; + case MMSYSERR_DELETEERROR: + return "Delete error"; + case MMSYSERR_VALNOTFOUND: + return "Value not found"; + case MMSYSERR_NODRIVERCB: + return "No driver callback"; + case MMSYSERR_MOREDATA: + return "More data"; } return "Unknown error code"; } diff --git a/include/libremidi/backends/winmm/helpers.hpp b/include/libremidi/backends/winmm/helpers.hpp index ee75550..c116c75 100644 --- a/include/libremidi/backends/winmm/helpers.hpp +++ b/include/libremidi/backends/winmm/helpers.hpp @@ -1,9 +1,10 @@ #pragma once #include #include -#include -namespace libremidi +#include + +NAMESPACE_LIBREMIDI { // Convert a nullptr-terminated wide string or ANSI-encoded string to UTF-8. @@ -70,5 +71,4 @@ inline void MakeUniqueOutPortName(std::string& deviceName, std::size_t portNumbe deviceName += " "; deviceName += std::to_string(x); } - } diff --git a/include/libremidi/backends/winmm/midi_in.hpp b/include/libremidi/backends/winmm/midi_in.hpp index f04d57e..2ada660 100644 --- a/include/libremidi/backends/winmm/midi_in.hpp +++ b/include/libremidi/backends/winmm/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_winmm final @@ -24,9 +24,7 @@ public: { if (!InitializeCriticalSectionAndSpinCount(&(this->_mutex), 0x00000400)) { - libremidi_handle_error( - configuration, - "InitializeCriticalSectionAndSpinCount failed."); + libremidi_handle_error(configuration, "InitializeCriticalSectionAndSpinCount failed."); this->client_open_ = std::errc::too_many_files_open; return; @@ -40,7 +38,7 @@ public: // Close a connection if it exists. midi_in_winmm::close_port(); - if(this->client_open_ == stdx::error{}) + if (this->client_open_ == stdx::error{}) DeleteCriticalSection(&(this->_mutex)); } @@ -53,8 +51,7 @@ public: std::bit_cast(this), CALLBACK_FUNCTION); if (result != MMSYSERR_NOERROR) { - libremidi_handle_error( - configuration, "error creating Windows MM MIDI input port."); + libremidi_handle_error(configuration, "error creating Windows MM MIDI input port."); return from_mmerr(result); } @@ -101,8 +98,7 @@ public: { midiInClose(this->inHandle); this->inHandle = nullptr; - libremidi_handle_error( - configuration, "error starting Windows MM MIDI input port."); + libremidi_handle_error(configuration, "error starting Windows MM MIDI input port."); return from_mmerr(result); } @@ -126,8 +122,7 @@ public: if (p.port_name == port.port_name) return do_open(port.port); } - libremidi_handle_error( - configuration, "port not found: " + p.port_name); + libremidi_handle_error(configuration, "port not found: " + p.port_name); return std::errc::invalid_argument; } @@ -238,13 +233,13 @@ private: { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR ) const auto* sysex = reinterpret_cast(midiMessage); - if(inputStatus == MIM_LONGERROR) + if (inputStatus == MIM_LONGERROR) { self.m_processing.reset(); } else if (!self.configuration.ignore_sysex) { - if(sysex->dwBytesRecorded > 0) + if (sysex->dwBytesRecorded > 0) { const auto sysex_bytes = reinterpret_cast(sysex->lpData); diff --git a/include/libremidi/backends/winmm/midi_out.hpp b/include/libremidi/backends/winmm/midi_out.hpp index 3f8b596..3a854f7 100644 --- a/include/libremidi/backends/winmm/midi_out.hpp +++ b/include/libremidi/backends/winmm/midi_out.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_winmm final @@ -65,8 +65,7 @@ public: if (p.port_name == port.port_name) return do_open(port.port); } - libremidi_handle_error( - configuration, "port not found: " + p.port_name); + libremidi_handle_error(configuration, "port not found: " + p.port_name); return std::errc::invalid_argument; } @@ -107,8 +106,7 @@ public: auto result = midiOutPrepareHeader(this->outHandle, &sysex, sizeof(MIDIHDR)); if (result != MMSYSERR_NOERROR) { - libremidi_handle_error( - configuration, "error preparing sysex header."); + libremidi_handle_error(configuration, "error preparing sysex header."); return from_mmerr(result); } @@ -116,8 +114,7 @@ public: result = midiOutLongMsg(this->outHandle, &sysex, sizeof(MIDIHDR)); if (result != MMSYSERR_NOERROR) { - libremidi_handle_error( - configuration, "error sending sysex message."); + libremidi_handle_error(configuration, "error sending sysex message."); return from_mmerr(result); } @@ -148,8 +145,7 @@ public: auto result = midiOutShortMsg(this->outHandle, packet); if (result != MMSYSERR_NOERROR) { - libremidi_handle_error( - configuration, "error sending MIDI message."); + libremidi_handle_error(configuration, "error sending MIDI message."); return from_mmerr(result); } } diff --git a/include/libremidi/backends/winmm/observer.hpp b/include/libremidi/backends/winmm/observer.hpp index c243bd3..57948ed 100644 --- a/include/libremidi/backends/winmm/observer.hpp +++ b/include/libremidi/backends/winmm/observer.hpp @@ -3,12 +3,9 @@ #include #include -#include -#include -#include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_winmm : public observer_api @@ -106,8 +103,12 @@ protected: auto rawName = ConvertToUTF8(deviceCaps.szPname); auto portName = rawName; MakeUniqueInPortName(portName, index); + return { - {.client = 0, + {.api = libremidi::API::WINDOWS_MM, + .client = 0, + .device + = usb_device_identifier{.vendor_id = deviceCaps.wMid, .product_id = deviceCaps.wPid}, .port = index, .manufacturer = "", .device_name = "", @@ -123,8 +124,12 @@ protected: auto rawName = ConvertToUTF8(deviceCaps.szPname); auto portName = rawName; MakeUniqueOutPortName(portName, index); + return { - {.client = 0, + {.api = libremidi::API::WINDOWS_MM, + .client = 0, + .device + = usb_device_identifier{.vendor_id = deviceCaps.wMid, .product_id = deviceCaps.wPid}, .port = index, .manufacturer = "", .device_name = "", @@ -167,7 +172,7 @@ protected: #if __has_include() && __cpp_lib_jthread >= 201911L #include -namespace libremidi::winmm +NAMESPACE_LIBREMIDI::winmm { class observer_threaded final : public observer_winmm { @@ -197,7 +202,7 @@ private: #else #include #include -namespace libremidi::winmm +NAMESPACE_LIBREMIDI::winmm { class observer_threaded final : public observer_winmm { @@ -237,7 +242,7 @@ private: } #endif -namespace libremidi::winmm +NAMESPACE_LIBREMIDI::winmm { class observer_manual final : public observer_winmm { @@ -257,7 +262,7 @@ public: ~observer_manual() { } }; } -namespace libremidi +NAMESPACE_LIBREMIDI { template <> inline std::unique_ptr make( diff --git a/include/libremidi/backends/winuwp.hpp b/include/libremidi/backends/winuwp.hpp index 48e91b3..898344d 100644 --- a/include/libremidi/backends/winuwp.hpp +++ b/include/libremidi/backends/winuwp.hpp @@ -3,7 +3,9 @@ #include #include -namespace libremidi +#include + +NAMESPACE_LIBREMIDI { struct winuwp_backend { @@ -14,9 +16,9 @@ struct winuwp_backend using midi_out_configuration = winuwp_output_configuration; using midi_observer_configuration = winuwp_observer_configuration; static const constexpr auto API = libremidi::API::WINDOWS_UWP; - static const constexpr auto name = "winuwp"; - static const constexpr auto display_name = "Windows UWP"; + static const constexpr std::string_view name = "winuwp"; + static const constexpr std::string_view display_name = "Windows UWP"; static constexpr inline bool available() noexcept { return true; } }; -} // namespace libremidi +} // NAMESPACE_LIBREMIDI diff --git a/include/libremidi/backends/winuwp/config.hpp b/include/libremidi/backends/winuwp/config.hpp index 27bb1a1..a123b57 100644 --- a/include/libremidi/backends/winuwp/config.hpp +++ b/include/libremidi/backends/winuwp/config.hpp @@ -1,7 +1,7 @@ #pragma once #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct winuwp_input_configuration diff --git a/include/libremidi/backends/winuwp/helpers.hpp b/include/libremidi/backends/winuwp/helpers.hpp index 380e458..7b876f4 100644 --- a/include/libremidi/backends/winuwp/helpers.hpp +++ b/include/libremidi/backends/winuwp/helpers.hpp @@ -7,13 +7,14 @@ #include #include #include +#include #include #include #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { inline void winrt_init() { @@ -23,8 +24,6 @@ inline void winrt_init() // winrt::init_apartment(); } -namespace -{ using namespace winrt; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Devices::Midi; @@ -36,7 +35,7 @@ using namespace winrt::Windows::Storage::Streams; // get() function from winrt is that we avoid an assertion if waiting // from the STA thread. template -auto get(T const& async) +LIBREMIDI_STATIC auto get(T const& async) { if (async.Status() != AsyncStatus::Completed) { @@ -60,4 +59,3 @@ auto get(T const& async) return async.GetResults(); } } -} diff --git a/include/libremidi/backends/winuwp/midi_in.hpp b/include/libremidi/backends/winuwp/midi_in.hpp index 0ccfbc5..3d70613 100644 --- a/include/libremidi/backends/winuwp/midi_in.hpp +++ b/include/libremidi/backends/winuwp/midi_in.hpp @@ -5,7 +5,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_winuwp final @@ -47,7 +47,8 @@ public: midi_start_timestamp = std::chrono::steady_clock::now(); port_.MessageReceived( - [=](const winrt::Windows::Devices::Midi::IMidiInPort& inputPort, + [this]( + const winrt::Windows::Devices::Midi::IMidiInPort&, const winrt::Windows::Devices::Midi::MidiMessageReceivedEventArgs& args) { this->process_message(args.Message()); }); diff --git a/include/libremidi/backends/winuwp/midi_out.hpp b/include/libremidi/backends/winuwp/midi_out.hpp index af7d2e7..48893f0 100644 --- a/include/libremidi/backends/winuwp/midi_out.hpp +++ b/include/libremidi/backends/winuwp/midi_out.hpp @@ -4,7 +4,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_winuwp final diff --git a/include/libremidi/backends/winuwp/observer.hpp b/include/libremidi/backends/winuwp/observer.hpp index 86eeb59..1921720 100644 --- a/include/libremidi/backends/winuwp/observer.hpp +++ b/include/libremidi/backends/winuwp/observer.hpp @@ -1,16 +1,17 @@ #pragma once #include +#include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_winuwp_internal { public: struct port_info { - hstring id; - hstring name; + winrt::hstring id; + winrt::hstring name; }; struct callback { @@ -33,16 +34,16 @@ public: cbs.emplace_back(tk, f); return tk; } + void remove(int tk) { auto it = std::remove_if( cbs.begin(), cbs.end(), [tk](const callback& c) { return c.token == tk; }); - auto r = std::distance(it, cbs.end()); cbs.erase(it, cbs.end()); } }; - explicit observer_winuwp_internal(hstring deviceSelector) { initialize(deviceSelector); } + explicit observer_winuwp_internal(winrt::hstring deviceSelector) { initialize(deviceSelector); } ~observer_winuwp_internal() { terminate(); } std::vector get_ports() const @@ -66,10 +67,10 @@ public: return true; } - hstring get_port_id(unsigned int portNumber) const + winrt::hstring get_port_id(unsigned int portNumber) const { std::lock_guard lock(portListMutex_); - return portNumber < portList_.size() ? portList_[portNumber].id : hstring{}; + return portNumber < portList_.size() ? portList_[portNumber].id : winrt::hstring{}; } std::string get_port_name(unsigned int portNumber) const @@ -97,7 +98,7 @@ private: observer_winuwp_internal& operator=(const observer_winuwp_internal&) = delete; private: - void initialize(hstring deviceSelector) + void initialize(winrt::hstring deviceSelector) { deviceWatcher_ = DeviceInformation::CreateWatcher(deviceSelector); @@ -122,7 +123,7 @@ private: deviceWatcher_.Added(evTokenOnDeviceAdded_); } - void on_device_added(DeviceWatcher sender, DeviceInformation deviceInfo) + void on_device_added(const DeviceWatcher&, const DeviceInformation& deviceInfo) { port_info p; { @@ -133,12 +134,12 @@ private: portAddedEvent_(p); } - void on_device_removed(DeviceWatcher sender, DeviceInformationUpdate deviceUpdate) + void on_device_removed(const DeviceWatcher&, const DeviceInformationUpdate& deviceUpdate) { const auto id = deviceUpdate.Id(); auto pred = [&id](const port_info& portInfo) { return portInfo.id == id; }; std::optional p; - hstring name; + winrt::hstring name; { std::lock_guard lock(portListMutex_); auto iter = std::find_if(portList_.begin(), portList_.end(), pred); @@ -152,9 +153,9 @@ private: portRemovedEvent_(*p); } - void on_device_updated(DeviceWatcher sender, DeviceInformationUpdate deviceUpdate) { } + void on_device_updated(const DeviceWatcher&, const DeviceInformationUpdate&) { } - void on_device_enumeration_completed(DeviceWatcher sender, IInspectable const&) { } + void on_device_enumeration_completed(const DeviceWatcher&, const IInspectable&) { } private: std::vector portList_; @@ -197,13 +198,13 @@ public: configuration.output_added(p); } - evTokenOnInputAdded_ - = internalInPortObserver_.PortAdded([this](const port_info& p) { on_input_added(p); }); - evTokenOnInputRemoved_ - = internalInPortObserver_.PortRemoved([this](const port_info& p) { on_input_removed(p); }); - evTokenOnOutputAdded_ - = internalOutPortObserver_.PortAdded([this](const port_info& p) { on_output_added(p); }); - evTokenOnOutputRemoved_ = internalOutPortObserver_.PortRemoved( + evTokenOnInputAdded_ = get_internal_in_port_observer().PortAdded( + [this](const port_info& p) { on_input_added(p); }); + evTokenOnInputRemoved_ = get_internal_in_port_observer().PortRemoved( + [this](const port_info& p) { on_input_removed(p); }); + evTokenOnOutputAdded_ = get_internal_out_port_observer().PortAdded( + [this](const port_info& p) { on_output_added(p); }); + evTokenOnOutputRemoved_ = get_internal_out_port_observer().PortRemoved( [this](const port_info& p) { on_output_removed(p); }); } @@ -211,10 +212,10 @@ public: { if (!configuration.has_callbacks()) return; - internalInPortObserver_.PortAdded(evTokenOnInputAdded_); - internalInPortObserver_.PortRemoved(evTokenOnInputRemoved_); - internalOutPortObserver_.PortAdded(evTokenOnOutputAdded_); - internalOutPortObserver_.PortRemoved(evTokenOnOutputRemoved_); + get_internal_in_port_observer().PortAdded(evTokenOnInputAdded_); + get_internal_in_port_observer().PortRemoved(evTokenOnInputRemoved_); + get_internal_out_port_observer().PortAdded(evTokenOnOutputAdded_); + get_internal_out_port_observer().PortRemoved(evTokenOnOutputRemoved_); } libremidi::API get_current_api() const noexcept override { return libremidi::API::WINDOWS_UWP; } @@ -224,7 +225,8 @@ public: -> std::conditional_t { return { - {.client = 0, + {.api = libremidi::API::WINDOWS_UWP, + .client = 0, .port = 0, .manufacturer = "", .device_name = "", @@ -235,7 +237,7 @@ public: std::vector get_input_ports() const noexcept override { std::vector ret; - for (auto& port : internalInPortObserver_.get_ports()) + for (auto& port : get_internal_in_port_observer().get_ports()) ret.push_back(to_port_info(port)); return ret; } @@ -243,18 +245,20 @@ public: std::vector get_output_ports() const noexcept override { std::vector ret; - for (auto& port : internalOutPortObserver_.get_ports()) + for (auto& port : get_internal_out_port_observer().get_ports()) ret.push_back(to_port_info(port)); return ret; } static observer_winuwp_internal& get_internal_in_port_observer() { + static observer_winuwp_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()}; return internalInPortObserver_; } static observer_winuwp_internal& get_internal_out_port_observer() { + static observer_winuwp_internal internalOutPortObserver_{MidiOutPort::GetDeviceSelector()}; return internalOutPortObserver_; } @@ -283,10 +287,6 @@ public: } private: - static inline observer_winuwp_internal internalInPortObserver_{MidiInPort::GetDeviceSelector()}; - static inline observer_winuwp_internal internalOutPortObserver_{ - MidiOutPort::GetDeviceSelector()}; - int evTokenOnInputAdded_{-1}; int evTokenOnInputRemoved_{-1}; int evTokenOnOutputAdded_{-1}; diff --git a/include/libremidi/client.cpp b/include/libremidi/client.cpp index 4d2c2d2..1e22509 100644 --- a/include/libremidi/client.cpp +++ b/include/libremidi/client.cpp @@ -10,7 +10,7 @@ #ifdef LIBREMIDI_JACK #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE shared_configurations diff --git a/include/libremidi/client.hpp b/include/libremidi/client.hpp index 068c496..1a913c2 100644 --- a/include/libremidi/client.hpp +++ b/include/libremidi/client.hpp @@ -4,8 +4,9 @@ #include #include +#include -namespace libremidi::midi1 +NAMESPACE_LIBREMIDI::midi1 { struct client_configuration { @@ -17,7 +18,7 @@ struct client_configuration //! Set a callback function to be invoked for incoming MIDI messages. //! Mandatory! std::function on_message - = [](const libremidi::input_port& /*port*/, libremidi::message&&) {}; + = [](const libremidi::input_port& /*port*/, libremidi::message&&) { }; //! Observation callbacks for when ports are added or removed input_port_callback input_added; @@ -66,8 +67,8 @@ public: } explicit client(const client_configuration& conf, shared_configurations ctx) - : configuration{conf} - , context{ctx} + : m_configuration{conf} + , m_context{std::move(ctx)} , m_observer{ observer_configuration{ .on_error = conf.on_error, @@ -81,10 +82,10 @@ public: .track_hardware = conf.track_hardware, .track_virtual = conf.track_virtual, .notify_in_constructor = false}, - context.observer} + m_context.observer} { - if (context.context) - context.context->start_processing(); + if (m_context.context) + m_context.context->start_processing(); } ~client() @@ -92,8 +93,8 @@ public: m_inputs.clear(); m_outputs.clear(); - if (context.context) - context.context->stop_processing(); + if (m_context.context) + m_context.context->stop_processing(); } std::vector get_input_ports() const noexcept @@ -116,19 +117,19 @@ public: input_configuration{ .on_message = [this, - port](libremidi::message&& m) { configuration.on_message(port, std::move(m)); }, + port](libremidi::message&& m) { m_configuration.on_message(port, std::move(m)); }, .on_raw_data = {}, .get_timestamp = {}, - .on_error = configuration.on_error, - .on_warning = configuration.on_warning, + .on_error = m_configuration.on_error, + .on_warning = m_configuration.on_warning, - .ignore_sysex = configuration.ignore_sysex, - .ignore_timing = configuration.ignore_timing, - .ignore_sensing = configuration.ignore_sensing, + .ignore_sysex = m_configuration.ignore_sysex, + .ignore_timing = m_configuration.ignore_timing, + .ignore_sensing = m_configuration.ignore_sensing, - .timestamps = configuration.timestamps}, - context.in); + .timestamps = m_configuration.timestamps}, + m_context.in); res.first->second.open_port(port, name); } @@ -141,11 +142,11 @@ public: auto res = m_outputs.try_emplace( port, output_configuration{ - .on_error = configuration.on_error, - .on_warning = configuration.on_warning, + .on_error = m_configuration.on_error, + .on_warning = m_configuration.on_warning, - .timestamps = configuration.timestamps}, - context.out); + .timestamps = m_configuration.timestamps}, + m_context.out); res.first->second.open_port(port, name); } @@ -190,8 +191,8 @@ public: } private: - client_configuration configuration; - shared_configurations context; + client_configuration m_configuration; + shared_configurations m_context; std::map m_inputs; std::map m_outputs; diff --git a/include/libremidi/cmidi2.hpp b/include/libremidi/cmidi2.hpp index 534e2b6..57c80c7 100644 --- a/include/libremidi/cmidi2.hpp +++ b/include/libremidi/cmidi2.hpp @@ -1,15 +1,25 @@ + #ifndef CMIDI2_H_INCLUDED #define CMIDI2_H_INCLUDED +#include + #include #include #include -#define MIDI_2_0_RESERVED 0 -#define JR_TIMESTAMP_TICKS_PER_SECOND 31250 +#include +#if !defined(_MSC_VER) #pragma GCC system_header #pragma clang system_header +#endif + +#define CMIDI2_MIDI_2_0_RESERVED 0 +#define CMIDI2_JR_TIMESTAMP_TICKS_PER_SECOND 31250 +// FIXME: remove those global defs that are quite harmful (conflicts with other libraries) +#define MIDI_2_0_RESERVED 0 +#define JR_TIMESTAMP_TICKS_PER_SECOND 31250 #ifdef __cplusplus extern "C" { @@ -235,13 +245,6 @@ enum cmidi2_system_message_status CMIDI2_SYSTEM_STATUS_RESET = 0xFF, }; -// TODO: remove this. Use cmidi2_utility_message_status instead -enum cmidi2_jr_timestamp_status -{ - CMIDI2_JR_CLOCK = 0x10, - CMIDI2_JR_TIMESTAMP = 0x20, -}; - enum cmidi2_utility_message_status { CMIDI2_UTILITY_STATUS_NOOP = 0, @@ -277,7 +280,7 @@ enum cmidi2_flex_data_metadata_text_status CMIDI2_FLEX_DATA_STATUS_ARRANGER_NAME = 7, CMIDI2_FLEX_DATA_STATUS_PUBLISHER_NAME = 8, CMIDI2_FLEX_DATA_STATUS_PRIMARY_PERFORMER_NAME = 9, - CMIDI2_FLEX_DATA_STATUS_ACCOMPANYING_PERFORMAER_NAME = 10, + CMIDI2_FLEX_DATA_STATUS_ACCOMPANYING_PERFORMER_NAME = 10, CMIDI2_FLEX_DATA_STATUS_RECORDING_DATE = 11, CMIDI2_FLEX_DATA_STATUS_RECORDING_LOCATION = 12 }; @@ -311,7 +314,7 @@ enum cmidi2_ump_chord_name_tonic_note CMIDI2_UMP_CHORD_NAME_G = 7, }; -enum cmidi2_ump_chor_name_chord_type +enum cmidi2_ump_chord_name_chord_type { CMIDI2_UMP_CHORD_TYPE_UNKNOWN = 0, CMIDI2_UMP_CHORD_TYPE_MAJOR = 1, @@ -344,7 +347,7 @@ enum cmidi2_ump_chor_name_chord_type CMIDI2_UMP_CHORD_TYPE_7_SUSPENDED_4TH = 27, }; -static inline uint8_t cmidi2_ump_get_num_bytes(uint32_t data) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_num_bytes(uint32_t data) { switch (((data & 0xF0000000) >> 28) & 0xF) { @@ -411,7 +414,7 @@ enum cmidi2_ump_function_block_discovery_flags // 7.1 UMP Stream Messages -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_endpoint_discovery(cmidi2_ump_version_t version, uint8_t filterBitmap) { cmidi2_ump128_t ret @@ -422,7 +425,7 @@ cmidi2_ump_endpoint_discovery(cmidi2_ump_version_t version, uint8_t filterBitmap return ret; } -static inline cmidi2_ump128_t cmidi2_ump_endpoint_info_notification( +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_endpoint_info_notification( cmidi2_ump_version_t version, bool isStaticFunctionBlock, uint8_t numFunctionBlocks, bool midi2Capable, bool midi1Capable, bool rxJR, bool txJR) { @@ -436,7 +439,7 @@ static inline cmidi2_ump128_t cmidi2_ump_endpoint_info_notification( return ret; } -static inline cmidi2_ump128_t cmidi2_ump_device_identity_notification( +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_device_identity_notification( uint32_t manufacturerIdIn7bitArray, uint8_t deviceFamilyLSB, uint8_t deviceFamilyMSB, uint8_t deviceFamilyModelLSB, uint8_t deviceFamilyModelMSB, uint32_t softwareRevisionIn7bitArray) @@ -452,7 +455,7 @@ static inline cmidi2_ump128_t cmidi2_ump_device_identity_notification( return ret; } -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_internal_name_notification(uint8_t statusCode, const char name[14]) { cmidi2_ump128_t ret @@ -464,17 +467,17 @@ cmidi2_ump_internal_name_notification(uint8_t statusCode, const char name[14]) return ret; } -static inline cmidi2_ump128_t cmidi2_ump_endpoint_name_notification(const char name[14]) +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_endpoint_name_notification(const char name[14]) { return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_ENDPOINT_NAME, name); } -static inline cmidi2_ump128_t cmidi2_ump_product_instance_id_notification(const char id[14]) +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_product_instance_id_notification(const char id[14]) { return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_PRODUCT_INSTANCE_ID, id); } -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_stream_configuration_request(uint8_t protocol, bool rxJR, bool txJR) { cmidi2_ump128_t ret @@ -485,7 +488,7 @@ cmidi2_ump_stream_configuration_request(uint8_t protocol, bool rxJR, bool txJR) return ret; } -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_stream_configuration_notification(uint8_t protocol, bool rxJR, bool txJR) { cmidi2_ump128_t ret @@ -496,7 +499,7 @@ cmidi2_ump_stream_configuration_notification(uint8_t protocol, bool rxJR, bool t return ret; } -static inline cmidi2_ump128_t +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_function_block_discovery(uint8_t numFunctionBlocks, uint8_t filter) { cmidi2_ump128_t ret @@ -507,7 +510,7 @@ cmidi2_ump_function_block_discovery(uint8_t numFunctionBlocks, uint8_t filter) return ret; } -static inline cmidi2_ump128_t cmidi2_ump_function_block_info_notification( +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_function_block_info_notification( bool active, uint8_t numFunctionBlocks, uint8_t uiHint, uint8_t midi1, uint8_t direction, uint8_t firstGroup, uint8_t numSpannedGroup, uint8_t midiCIVersionFormat, uint8_t maxNumSysEx8Streams) @@ -522,12 +525,12 @@ static inline cmidi2_ump128_t cmidi2_ump_function_block_info_notification( return ret; } -static inline cmidi2_ump128_t cmidi2_ump_function_block_name_notification(const char name[14]) +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_function_block_name_notification(const char name[14]) { return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_NAME, name); } -static inline cmidi2_ump128_t cmidi2_ump_start_of_clip() +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_start_of_clip() { cmidi2_ump128_t ret = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28) @@ -536,7 +539,7 @@ static inline cmidi2_ump128_t cmidi2_ump_start_of_clip() return ret; } -static inline cmidi2_ump128_t cmidi2_ump_end_of_clip() +LIBREMIDI_STATIC cmidi2_ump128_t cmidi2_ump_end_of_clip() { cmidi2_ump128_t ret = {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28) @@ -546,48 +549,46 @@ static inline cmidi2_ump128_t cmidi2_ump_end_of_clip() } // 7.2 Utility Messages -static inline uint32_t cmidi2_ump_noop(uint8_t group) +LIBREMIDI_STATIC uint32_t cmidi2_ump_noop() { - return (group & 0xF) << 24; + return 0; } -static inline uint32_t cmidi2_ump_jr_clock_direct(uint8_t group, uint32_t senderClockTime) +LIBREMIDI_STATIC uint32_t cmidi2_ump_jr_clock_direct(uint16_t senderClockTime) { - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + senderClockTime; + return (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + senderClockTime; } -static inline uint32_t cmidi2_ump_jr_clock(uint8_t group, double senderClockTime) +LIBREMIDI_STATIC uint32_t cmidi2_ump_jr_clock(double senderClockTime) { uint16_t value = (uint16_t)(senderClockTime * JR_TIMESTAMP_TICKS_PER_SECOND); - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + value; + return (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + value; } -static inline uint32_t cmidi2_ump_jr_timestamp_direct(uint8_t group, uint16_t senderClockTimestamp) +LIBREMIDI_STATIC uint32_t cmidi2_ump_jr_timestamp_direct(uint16_t senderClockTimestamp) { - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) - + senderClockTimestamp; + return (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) + senderClockTimestamp; } -static inline uint32_t cmidi2_ump_jr_timestamp(uint8_t group, double senderClockTimestamp) +LIBREMIDI_STATIC uint32_t cmidi2_ump_jr_timestamp(double senderClockTimestamp) { uint16_t value = (uint16_t)(senderClockTimestamp * JR_TIMESTAMP_TICKS_PER_SECOND); - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) + value; + return (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) + value; } -static inline uint32_t cmidi2_ump_dctpq(uint8_t group, uint32_t dctpq) +LIBREMIDI_STATIC uint32_t cmidi2_ump_dctpq(uint32_t dctpq) { - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_DCTPQ << 16) + dctpq; + return (CMIDI2_UTILITY_STATUS_DCTPQ << 16) + dctpq; } -static inline uint32_t cmidi2_ump_dcs(uint8_t group, uint32_t ticks) +LIBREMIDI_STATIC uint32_t cmidi2_ump_dcs(uint32_t ticks) { // Note that unlike JR timestamp delta clockstamps accepts ticks up to 20 bits. - return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_DELTA_CLOCKSTAMP << 16) - + (ticks & 0xFFFFF); + return (CMIDI2_UTILITY_STATUS_DELTA_CLOCKSTAMP << 16) + (ticks & 0xFFFFF); } // 7.6 System Common and System Real Time Messages -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_system_message(uint8_t group, uint8_t status, uint8_t midi1Byte2, uint8_t midi1Byte3) { return (CMIDI2_MESSAGE_TYPE_SYSTEM << 28) + ((group & 0xF) << 24) + (status << 16) @@ -595,66 +596,66 @@ cmidi2_ump_system_message(uint8_t group, uint8_t status, uint8_t midi1Byte2, uin } // 7.3 MIDI 1.0 Channel Voice Messages -static inline int32_t cmidi2_ump_midi1_message( +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_message( uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4) { return (CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL << 28) + ((group & 0xF) << 24) + (((code & 0xF0) + (channel & 0xF)) << 16) + ((byte3 & 0x7F) << 8) + (byte4 & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_note_off(uint8_t group, uint8_t channel, uint8_t note, uint8_t velocity) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_NOTE_OFF, channel, note & 0x7F, velocity & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_note_on(uint8_t group, uint8_t channel, uint8_t note, uint8_t velocity) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_NOTE_ON, channel, note & 0x7F, velocity & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_paf(uint8_t group, uint8_t channel, uint8_t note, uint8_t data) { return cmidi2_ump_midi1_message(group, CMIDI2_STATUS_PAF, channel, note & 0x7F, data & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_cc(uint8_t group, uint8_t channel, uint8_t index, uint8_t data) { return cmidi2_ump_midi1_message(group, CMIDI2_STATUS_CC, channel, index & 0x7F, data & 0x7F); } -static inline int32_t cmidi2_ump_midi1_program(uint8_t group, uint8_t channel, uint8_t program) +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_program(uint8_t group, uint8_t channel, uint8_t program) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_PROGRAM, channel, program & 0x7F, MIDI_2_0_RESERVED); } -static inline int32_t cmidi2_ump_midi1_caf(uint8_t group, uint8_t channel, uint8_t data) +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_caf(uint8_t group, uint8_t channel, uint8_t data) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_CAF, channel, data & 0x7F, MIDI_2_0_RESERVED); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_pitch_bend_direct(uint8_t group, uint8_t channel, uint16_t data) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_PITCH_BEND, channel, data & 0x7F, (data >> 7) & 0x7F); } -static inline int32_t +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_pitch_bend_split(uint8_t group, uint8_t channel, uint8_t dataLSB, uint8_t dataMSB) { return cmidi2_ump_midi1_message( group, CMIDI2_STATUS_PITCH_BEND, channel, dataLSB & 0x7F, dataMSB & 0x7F); } -static inline int32_t cmidi2_ump_midi1_pitch_bend(uint8_t group, uint8_t channel, int16_t data) +LIBREMIDI_STATIC int32_t cmidi2_ump_midi1_pitch_bend(uint8_t group, uint8_t channel, int16_t data) { data += 8192; return cmidi2_ump_midi1_message( @@ -662,7 +663,7 @@ static inline int32_t cmidi2_ump_midi1_pitch_bend(uint8_t group, uint8_t channel } // 7.4 MIDI 2.0 Channel Voice Messages -static inline int64_t cmidi2_ump_midi2_channel_message_8_8_16_16( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_channel_message_8_8_16_16( uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4, uint16_t short1, uint16_t short2) { @@ -672,7 +673,7 @@ static inline int64_t cmidi2_ump_midi2_channel_message_8_8_16_16( + ((uint64_t)short1 << 16) + short2; } -static inline int64_t cmidi2_ump_midi2_channel_message_8_8_32( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_channel_message_8_8_32( uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4, uint32_t rest) { return (((uint64_t)(CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL << 28) + ((group & 0xF) << 24) @@ -681,7 +682,7 @@ static inline int64_t cmidi2_ump_midi2_channel_message_8_8_32( + rest; } -static inline uint16_t cmidi2_ump_pitch_7_9(double semitone) +LIBREMIDI_STATIC uint16_t cmidi2_ump_pitch_7_9(double semitone) { double actual = semitone < 0.0 ? 0.0 : semitone >= 128.0 ? 128.0 : semitone; uint16_t dec = (uint16_t)actual; @@ -689,7 +690,7 @@ static inline uint16_t cmidi2_ump_pitch_7_9(double semitone) return (dec << 9) + (int)(microtone * 512.0); } -static inline uint16_t cmidi2_ump_pitch_7_9_split(uint8_t semitone, double microtone) +LIBREMIDI_STATIC uint16_t cmidi2_ump_pitch_7_9_split(uint8_t semitone, double microtone) { uint16_t ret = (uint16_t)(semitone & 0x7F) << 9; double actual = microtone < 0.0 ? 0.0 : microtone > 1.0 ? 1.0 : microtone; @@ -697,7 +698,7 @@ static inline uint16_t cmidi2_ump_pitch_7_9_split(uint8_t semitone, double micro return ret; } -static inline int64_t cmidi2_ump_midi2_note_off( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_note_off( uint8_t group, uint8_t channel, uint8_t note, uint8_t attributeType, uint16_t velocity, uint16_t attributeData) { @@ -705,7 +706,7 @@ static inline int64_t cmidi2_ump_midi2_note_off( group, CMIDI2_STATUS_NOTE_OFF, channel, note & 0x7F, attributeType, velocity, attributeData); } -static inline int64_t cmidi2_ump_midi2_note_on( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_note_on( uint8_t group, uint8_t channel, uint8_t note, uint8_t attributeType, uint16_t velocity, uint16_t attributeData) { @@ -713,56 +714,56 @@ static inline int64_t cmidi2_ump_midi2_note_on( group, CMIDI2_STATUS_NOTE_ON, channel, note & 0x7F, attributeType, velocity, attributeData); } -static inline int64_t +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_paf(uint8_t group, uint8_t channel, uint8_t note, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PAF, channel, note & 0x7F, MIDI_2_0_RESERVED, data); } -static inline int64_t cmidi2_ump_midi2_per_note_rcc( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_rcc( uint8_t group, uint8_t channel, uint8_t note, uint8_t index, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PER_NOTE_RCC, channel, note & 0x7F, index, data); } -static inline int64_t cmidi2_ump_midi2_per_note_acc( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_acc( uint8_t group, uint8_t channel, uint8_t note, uint8_t index, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PER_NOTE_ACC, channel, note & 0x7F, index, data); } -static inline int64_t cmidi2_ump_midi2_per_note_management( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_management( uint8_t group, uint8_t channel, uint8_t note, uint8_t optionFlags) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PER_NOTE_MANAGEMENT, channel, note & 0x7F, optionFlags & 3, 0); } -static inline int64_t +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_cc(uint8_t group, uint8_t channel, uint8_t index, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_CC, channel, index & 0x7F, MIDI_2_0_RESERVED, data); } -static inline int64_t cmidi2_ump_midi2_rpn( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_rpn( uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_RPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F, dataAkaDTE); } -static inline int64_t cmidi2_ump_midi2_nrpn( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_nrpn( uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_NRPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F, dataAkaDTE); } -static inline int64_t cmidi2_ump_midi2_relative_rpn( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_relative_rpn( uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE) { return cmidi2_ump_midi2_channel_message_8_8_32( @@ -770,7 +771,7 @@ static inline int64_t cmidi2_ump_midi2_relative_rpn( dataAkaDTE); } -static inline int64_t cmidi2_ump_midi2_relative_nrpn( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_relative_nrpn( uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE) { return cmidi2_ump_midi2_channel_message_8_8_32( @@ -778,7 +779,7 @@ static inline int64_t cmidi2_ump_midi2_relative_nrpn( dataAkaDTE); } -static inline int64_t cmidi2_ump_midi2_program( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_program( uint8_t group, uint8_t channel, uint8_t optionFlags, uint8_t program, uint8_t bankMSB, uint8_t bankLSB) { @@ -787,13 +788,13 @@ static inline int64_t cmidi2_ump_midi2_program( ((program & 0x7F) << 24) + (bankMSB << 8) + bankLSB); } -static inline int64_t cmidi2_ump_midi2_caf(uint8_t group, uint8_t channel, uint32_t data) +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_caf(uint8_t group, uint8_t channel, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_CAF, channel, MIDI_2_0_RESERVED, MIDI_2_0_RESERVED, data); } -static inline int64_t +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_pitch_bend_direct(uint8_t group, uint8_t channel, uint32_t unsignedData) { return cmidi2_ump_midi2_channel_message_8_8_32( @@ -801,19 +802,19 @@ cmidi2_ump_midi2_pitch_bend_direct(uint8_t group, uint8_t channel, uint32_t unsi unsignedData); } -static inline int64_t cmidi2_ump_midi2_pitch_bend(uint8_t group, uint8_t channel, int32_t data) +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_pitch_bend(uint8_t group, uint8_t channel, int32_t data) { return cmidi2_ump_midi2_pitch_bend_direct(group, channel, 0x80000000 + data); } -static inline int64_t cmidi2_ump_midi2_per_note_pitch_bend_direct( +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_pitch_bend_direct( uint8_t group, uint8_t channel, uint8_t note, uint32_t data) { return cmidi2_ump_midi2_channel_message_8_8_32( group, CMIDI2_STATUS_PER_NOTE_PITCH_BEND, channel, note & 0x7F, MIDI_2_0_RESERVED, data); } -static inline int64_t +LIBREMIDI_STATIC int64_t cmidi2_ump_midi2_per_note_pitch_bend(uint8_t group, uint8_t channel, uint8_t note, uint32_t data) { return cmidi2_ump_midi2_per_note_pitch_bend_direct(group, channel, note, 0x80000000 + data); @@ -821,22 +822,22 @@ cmidi2_ump_midi2_per_note_pitch_bend(uint8_t group, uint8_t channel, uint8_t not // Common utility functions for sysex support -static inline uint8_t cmidi2_ump_get_byte_from_uint32(uint32_t src, uint8_t index) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_byte_from_uint32(uint32_t src, uint8_t index) { return (uint8_t)(src >> ((7 - index) * 8) & 0xFF); } -static inline uint8_t cmidi2_ump_get_byte_from_uint64(uint64_t src, uint8_t index) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_byte_from_uint64(uint64_t src, uint8_t index) { return (uint8_t)(src >> ((7 - index) * 8) & 0xFF); } -static inline uint8_t cmidi2_ump_sysex_get_num_packets(uint8_t numBytes, uint8_t radix) +LIBREMIDI_STATIC size_t cmidi2_ump_sysex_get_num_packets(size_t numBytes, uint8_t radix) { - return numBytes <= radix ? 1 : numBytes / radix + (numBytes % radix ? 1 : 0); + return numBytes <= radix ? 1 : (numBytes / radix + (numBytes % radix ? 1 : 0)); } -static inline uint32_t cmidi2_ump_read_uint32_bytes_le(const void* sequence) +LIBREMIDI_STATIC uint32_t cmidi2_ump_read_uint32_bytes_le(const void* sequence) { const uint8_t* bytes = (const uint8_t*)sequence; uint32_t ret = 0; @@ -845,7 +846,7 @@ static inline uint32_t cmidi2_ump_read_uint32_bytes_le(const void* sequence) return ret; } -static inline uint32_t cmidi2_ump_read_uint32_bytes_be(const void* sequence) +LIBREMIDI_STATIC uint32_t cmidi2_ump_read_uint32_bytes_be(const void* sequence) { const uint8_t* bytes = (const uint8_t*)sequence; uint32_t ret = 0; @@ -854,38 +855,38 @@ static inline uint32_t cmidi2_ump_read_uint32_bytes_be(const void* sequence) return ret; } -static inline bool cmidi2_util_is_platform_little_endian() +LIBREMIDI_STATIC bool cmidi2_util_is_platform_little_endian() { int i = 1; return *(char*)&i; } -static inline uint32_t cmidi2_ump_read_uint32_bytes(const void* sequence) +LIBREMIDI_STATIC uint32_t cmidi2_ump_read_uint32_bytes(const void* sequence) { return cmidi2_util_is_platform_little_endian() ? cmidi2_ump_read_uint32_bytes_le(sequence) : cmidi2_ump_read_uint32_bytes_be(sequence); } -static inline uint64_t cmidi2_ump_read_uint64_bytes_le(const void* sequence) +LIBREMIDI_STATIC uint64_t cmidi2_ump_read_uint64_bytes_le(const void* sequence) { return ((uint64_t)cmidi2_ump_read_uint32_bytes_le(sequence) << 32) + cmidi2_ump_read_uint32_bytes_le((const uint8_t*)sequence + 4); } -static inline uint64_t cmidi2_ump_read_uint64_bytes_be(const void* sequence) +LIBREMIDI_STATIC uint64_t cmidi2_ump_read_uint64_bytes_be(const void* sequence) { return ((uint64_t)cmidi2_ump_read_uint32_bytes_be(sequence) << 32) + cmidi2_ump_read_uint32_bytes_be((const uint8_t*)sequence + 4); } -static inline uint64_t cmidi2_ump_read_uint64_bytes(const void* sequence) +LIBREMIDI_STATIC uint64_t cmidi2_ump_read_uint64_bytes(const void* sequence) { return cmidi2_util_is_platform_little_endian() ? cmidi2_ump_read_uint64_bytes_le(sequence) : cmidi2_ump_read_uint64_bytes_be(sequence); } -static inline void cmidi2_ump_sysex_get_packet_of( - uint64_t* result1, uint64_t* result2, uint8_t group, uint8_t numBytes, const void* srcData, +LIBREMIDI_STATIC void cmidi2_ump_sysex_get_packet_of( + uint64_t* result1, uint64_t* result2, uint8_t group, size_t numBytes, const void* srcData, int32_t index, enum cmidi2_message_type messageType, int radix, bool hasStreamId, uint8_t streamId) { @@ -897,7 +898,7 @@ static inline void cmidi2_ump_sysex_get_packet_of( enum cmidi2_sysex_status status; uint8_t size; - if (numBytes <= radix) + if (numBytes <= (size_t)radix) { status = CMIDI2_SYSEX_IN_ONE_UMP; size = numBytes; // single packet message @@ -909,7 +910,7 @@ static inline void cmidi2_ump_sysex_get_packet_of( } else { - uint8_t isEnd = index == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1; + uint8_t isEnd = (size_t)index == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1; if (isEnd) { size = numBytes % radix ? numBytes % radix : radix; @@ -927,7 +928,7 @@ static inline void cmidi2_ump_sysex_get_packet_of( dst8[2] = streamId; uint8_t dstOffset = hasStreamId ? 3 : 2; - for (uint8_t i = 0, j = index * radix; i < size; i++, j++) + for (size_t i = 0, j = index * radix; i < size; i++, j++) dst8[i + dstOffset] = src8[j]; *result1 = cmidi2_ump_read_uint64_bytes_be(dst8); @@ -936,7 +937,7 @@ static inline void cmidi2_ump_sysex_get_packet_of( } // 7.7 System Exclusive 7-Bit Messages -static inline uint64_t cmidi2_ump_sysex7_direct( +LIBREMIDI_STATIC uint64_t cmidi2_ump_sysex7_direct( uint8_t group, uint8_t status, uint8_t numBytes, uint8_t data1, uint8_t data2, uint8_t data3, uint8_t data4, uint8_t data5, uint8_t data6) { @@ -947,7 +948,7 @@ static inline uint64_t cmidi2_ump_sysex7_direct( + (data5 << 8) + data6; } -static inline uint32_t cmidi2_ump_sysex7_get_sysex_length(const void* srcData) +LIBREMIDI_STATIC uint32_t cmidi2_ump_sysex7_get_sysex_length(const void* srcData) { int i = 0; const uint8_t* csrc = (const uint8_t*)srcData; @@ -957,13 +958,13 @@ static inline uint32_t cmidi2_ump_sysex7_get_sysex_length(const void* srcData) return i - (csrc[0] == 0xF0 ? 1 : 0); } -static inline uint8_t cmidi2_ump_sysex7_get_num_packets(uint8_t numSysex7Bytes) +LIBREMIDI_STATIC size_t cmidi2_ump_sysex7_get_num_packets(size_t numSysex7Bytes) { return cmidi2_ump_sysex_get_num_packets(numSysex7Bytes, 6); } -static inline uint64_t cmidi2_ump_sysex7_get_packet_of( - uint8_t group, uint8_t numBytes, const void* srcData, int32_t index) +LIBREMIDI_STATIC uint64_t +cmidi2_ump_sysex7_get_packet_of(uint8_t group, size_t numBytes, const void* srcData, int32_t index) { uint64_t result; int srcOffset = numBytes > 0 && ((const uint8_t*)srcData)[0] == 0xF0 ? 1 : 0; @@ -978,11 +979,11 @@ static inline uint64_t cmidi2_ump_sysex7_get_packet_of( // This returns NULL for success, or anything else for failure. typedef void* (*cmidi2_ump_handler_u64)(uint64_t data, void* context); +// Processes sysex7 inputs where we do not always end at F7 and thus takes length as the argument. // This returns NULL for success, or anything else that `sendUMP` returns for failure. -static inline void* cmidi2_ump_sysex7_process( - uint8_t group, void* sysex, cmidi2_ump_handler_u64 sendUMP, void* context) +LIBREMIDI_STATIC void* cmidi2_ump_sysex7_process_n( + uint8_t group, void* sysex, uint32_t length, cmidi2_ump_handler_u64 sendUMP, void* context) { - int32_t length = cmidi2_ump_sysex7_get_sysex_length(sysex); int32_t numPackets = cmidi2_ump_sysex7_get_num_packets(length); for (int p = 0; p < numPackets; p++) { @@ -994,15 +995,23 @@ static inline void* cmidi2_ump_sysex7_process( return NULL; } +// This returns NULL for success, or anything else that `sendUMP` returns for failure. +LIBREMIDI_STATIC void* cmidi2_ump_sysex7_process( + uint8_t group, void* sysex, cmidi2_ump_handler_u64 sendUMP, void* context) +{ + uint32_t length = cmidi2_ump_sysex7_get_sysex_length(sysex); + return cmidi2_ump_sysex7_process_n(group, sysex, length, sendUMP, context); +} + // 7.8 System Exclusive 8-Bit Messages -static inline int8_t cmidi2_ump_sysex8_get_num_packets(uint8_t numBytes) +LIBREMIDI_STATIC size_t cmidi2_ump_sysex8_get_num_packets(size_t numBytes) { return cmidi2_ump_sysex_get_num_packets(numBytes, 13); } -static inline void cmidi2_ump_sysex8_get_packet_of( - uint8_t group, uint8_t streamId, uint8_t numBytes, const void* srcData, int32_t index, +LIBREMIDI_STATIC void cmidi2_ump_sysex8_get_packet_of( + uint8_t group, uint8_t streamId, size_t numBytes, const void* srcData, size_t index, uint64_t* result1, uint64_t* result2) { cmidi2_ump_sysex_get_packet_of( @@ -1017,7 +1026,7 @@ typedef void* (*cmidi2_ump_handler_u128)( uint64_t data1, uint64_t data2, size_t index, void* context); // This returns NULL for success, or anything else that `sendUMP` returns for failure. -static inline void* cmidi2_ump_sysex8_process( +LIBREMIDI_STATIC void* cmidi2_ump_sysex8_process( uint8_t group, void* sysex, uint32_t length, uint8_t streamId, cmidi2_ump_handler_u128 sendUMP, void* context) { @@ -1035,21 +1044,21 @@ static inline void* cmidi2_ump_sysex8_process( // 7.9 Mixed Data Set Message -static inline uint16_t cmidi2_ump_mds_get_num_chunks(uint32_t numTotalBytesInMDS) +LIBREMIDI_STATIC uint16_t cmidi2_ump_mds_get_num_chunks(uint32_t numTotalBytesInMDS) { uint32_t radix = 14 * 0x10000; return numTotalBytesInMDS / radix + (numTotalBytesInMDS % radix ? 1 : 0); } // Returns -1 if input is out of range -static inline int32_t cmidi2_ump_mds_get_num_payloads(uint32_t numTotalBytesinChunk) +LIBREMIDI_STATIC int32_t cmidi2_ump_mds_get_num_payloads(uint32_t numTotalBytesinChunk) { if (numTotalBytesinChunk > 14 * 65535) return -1; return numTotalBytesinChunk / 14 + (numTotalBytesinChunk % 14 ? 1 : 0); } -static inline void cmidi2_ump_mds_get_header( +LIBREMIDI_STATIC void cmidi2_ump_mds_get_header( uint8_t group, uint8_t mdsId, uint16_t numBytesInChunk, uint16_t numChunks, uint16_t chunkIndex, uint16_t manufacturerId, uint16_t deviceId, uint16_t subId, uint16_t subId2, uint64_t* result1, uint64_t* result2) @@ -1073,7 +1082,7 @@ static inline void cmidi2_ump_mds_get_header( } // srcData points to exact start of the source data. -static inline void cmidi2_ump_mds_get_payload_of( +LIBREMIDI_STATIC void cmidi2_ump_mds_get_payload_of( uint8_t group, uint8_t mdsId, uint16_t numBytes, const void* srcData, uint64_t* result1, uint64_t* result2) { @@ -1101,7 +1110,7 @@ typedef void* (*cmidi2_mds_handler)( uint64_t data1, uint64_t data2, size_t chunkId, size_t payloadId, void* context); // This returns NULL for success, or anything else that `sendUMP` returns for failure. -static inline void* cmidi2_ump_mds_process( +LIBREMIDI_STATIC void* cmidi2_ump_mds_process( uint8_t group, uint8_t mdsId, void* data, uint32_t length, cmidi2_mds_handler sendUMP, void* context) { @@ -1127,12 +1136,12 @@ static inline void* cmidi2_ump_mds_process( // 7.5 Flex Data Message -static inline uint16_t cmidi2_ump_flex_data_get_num_packets(uint32_t numTotalBytesInFlexData) +LIBREMIDI_STATIC uint16_t cmidi2_ump_flex_data_get_num_packets(uint32_t numTotalBytesInFlexData) { return numTotalBytesInFlexData / 12 + (numTotalBytesInFlexData % 12 ? 1 : 0); } -static inline void cmidi2_ump_flex_data_complete_packet( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_complete_packet( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode, uint32_t data1, uint32_t data2, uint32_t data3, uint64_t* result1, uint64_t* result2) { @@ -1147,7 +1156,7 @@ static inline void cmidi2_ump_flex_data_complete_packet( *result2 = ((uint64_t)data2 << 32) + data3; } -static inline void cmidi2_ump_flex_data_get_packet_of( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_get_packet_of( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode, uint16_t numBytes, const void* srcData, int32_t currentPacket, uint64_t* result1, uint64_t* result2) @@ -1173,7 +1182,7 @@ static inline void cmidi2_ump_flex_data_get_packet_of( } else { - uint8_t isEnd = currentPacket == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1; + uint8_t isEnd = (size_t)currentPacket == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1; if (isEnd) { size = numBytes % radix ? numBytes % radix : radix; @@ -1202,7 +1211,7 @@ typedef void* (*cmidi2_flex_data_handler)(uint64_t data1, uint64_t data2, void* // This returns NULL for success, or anything else that `sendUMP` returns for failure. // `text` is usually a null-terminated text string, but it may contain `\0` as Melisma in lyricText. Hence we still need `length`. -static inline void* cmidi2_ump_flex_data_process( +LIBREMIDI_STATIC void* cmidi2_ump_flex_data_process( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode, const char* text, uint32_t length, cmidi2_flex_data_handler sendUMP, void* context) { @@ -1221,7 +1230,7 @@ static inline void* cmidi2_ump_flex_data_process( // individual flex data message generators -static inline void cmidi2_ump_flex_data_set_tempo_direct( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_tempo_direct( uint8_t group, uint8_t channel, uint32_t tempoIn10NanosecondsPerQN, uint64_t* result1, uint64_t* result2) { @@ -1230,7 +1239,7 @@ static inline void cmidi2_ump_flex_data_set_tempo_direct( CMIDI2_FLEX_DATA_STATUS_SET_TEMPO, tempoIn10NanosecondsPerQN, 0, 0, result1, result2); } -static inline void cmidi2_ump_flex_data_set_time_signature( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_time_signature( uint8_t group, uint8_t channel, uint8_t numerator, uint8_t denominator, uint8_t numberOf32thNotes, uint64_t* result1, uint64_t* result2) { @@ -1240,7 +1249,7 @@ static inline void cmidi2_ump_flex_data_set_time_signature( (numerator << 24) + (denominator << 16) + (numberOf32thNotes << 8), 0, 0, result1, result2); } -static inline void cmidi2_ump_flex_data_set_metronome( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_metronome( uint8_t group, uint8_t channel, uint8_t clocksPerPrimaryClick, uint8_t barAccent1, uint8_t barAccent2, uint8_t barAccent3, uint8_t subDivisionClicks1, uint8_t subDivisionClicks2, uint64_t* result1, uint64_t* result2) @@ -1252,7 +1261,7 @@ static inline void cmidi2_ump_flex_data_set_metronome( (subDivisionClicks1 << 24) + (subDivisionClicks2 << 16), 0, result1, result2); } -static inline void cmidi2_ump_flex_data_set_key_signature( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_key_signature( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t sharpsFlats, uint8_t tonicNote, uint64_t* result1, uint64_t* result2) { @@ -1262,7 +1271,7 @@ static inline void cmidi2_ump_flex_data_set_key_signature( result1, result2); } -static inline void cmidi2_ump_flex_data_set_chord_name( +LIBREMIDI_STATIC void cmidi2_ump_flex_data_set_chord_name( uint8_t group, uint8_t addressing, uint8_t channel, uint8_t sharpsFlats, uint8_t chordTonic, uint8_t chordType, uint8_t alter1Type, uint8_t alter1Degree, uint8_t alter2Type, uint8_t alter2Degree, uint8_t alter3Type, uint8_t alter3Degree, uint8_t alter4Type, @@ -1288,18 +1297,18 @@ static inline void cmidi2_ump_flex_data_set_chord_name( typedef uint32_t cmidi2_ump; -static inline void cmidi2_ump_write32(cmidi2_ump* dst, uint32_t value) +LIBREMIDI_STATIC void cmidi2_ump_write32(cmidi2_ump* dst, uint32_t value) { dst[0] = value; } -static inline void cmidi2_ump_write64(cmidi2_ump* dst, uint64_t value) +LIBREMIDI_STATIC void cmidi2_ump_write64(cmidi2_ump* dst, uint64_t value) { dst[0] = value >> 32; dst[1] = value & 0xFFFFFFFF; } -static inline void cmidi2_ump_write128(cmidi2_ump* dst, uint64_t value1, uint64_t value2) +LIBREMIDI_STATIC void cmidi2_ump_write128(cmidi2_ump* dst, uint64_t value1, uint64_t value2) { dst[0] = value1 >> 32; dst[1] = value1 & 0xFFFFFFFF; @@ -1307,7 +1316,7 @@ static inline void cmidi2_ump_write128(cmidi2_ump* dst, uint64_t value1, uint64_ dst[3] = value2 & 0xFFFFFFFF; } -static inline uint8_t cmidi2_ump_get_byte_at(const cmidi2_ump* ump, uint8_t at) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_byte_at(const cmidi2_ump* ump, uint8_t at) { ump += at / 4; switch (at % 4) @@ -1324,12 +1333,12 @@ static inline uint8_t cmidi2_ump_get_byte_at(const cmidi2_ump* ump, uint8_t at) return 0; // This is unexpected. } -static inline uint8_t cmidi2_ump_get_message_type(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_message_type(const cmidi2_ump* ump) { return *ump >> 28; } -static inline uint8_t cmidi2_ump_get_message_size_bytes(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_message_size_bytes(const cmidi2_ump* ump) { switch (cmidi2_ump_get_message_type(ump)) { @@ -1346,267 +1355,272 @@ static inline uint8_t cmidi2_ump_get_message_size_bytes(const cmidi2_ump* ump) return 0; // invalid } -static inline uint8_t cmidi2_ump_get_group(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_group(const cmidi2_ump* ump) { return (*ump >> 24) & 0xF; } -static inline uint8_t cmidi2_ump_get_status_code(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_status_byte(const cmidi2_ump* ump) +{ + return (*ump >> 16) & 0xFF; +} + +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_status_code(const cmidi2_ump* ump) { return (*ump >> 16) & 0xF0; } -static inline uint8_t cmidi2_ump_get_channel(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_channel(const cmidi2_ump* ump) { return (*ump >> 16) & 0xF; } -static inline uint32_t cmidi2_ump_get_32_to_64(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_32_to_64(const cmidi2_ump* ump) { return *(ump + 1); } -static inline uint16_t cmidi2_ump_get_jr_clock_time(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_jr_clock_time(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_jr_timestamp_timestamp(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_jr_timestamp_timestamp(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_dctpq(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_dctpq(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_dcs(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_dcs(const cmidi2_ump* ump) { return ((cmidi2_ump_get_byte_at(ump, 1) & 0xF) << 16) + (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_system_message_byte2(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_system_message_byte2(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_system_message_byte3(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_system_message_byte3(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_byte2(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_byte2(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_byte3(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_byte3(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_note_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_note_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_note_velocity(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_note_velocity(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_paf_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_paf_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_paf_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_paf_data(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_cc_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_cc_index(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_cc_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_cc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi1_program_program(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_program_program(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi1_caf_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi1_caf_data(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint16_t cmidi2_ump_get_midi1_pitch_bend_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_midi1_pitch_bend_data(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2) + cmidi2_ump_get_byte_at(ump, 3) * 0x80; } -static inline uint8_t cmidi2_ump_get_sysex7_num_bytes(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_sysex7_num_bytes(const cmidi2_ump* ump) { return cmidi2_ump_get_channel(ump); // same bits } -static inline uint8_t cmidi2_ump_get_midi2_note_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_note_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi2_note_attribute_type(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_note_attribute_type(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_midi2_note_velocity(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_midi2_note_velocity(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 4) << 8) + (cmidi2_ump_get_byte_at(ump, 5)); } -static inline uint16_t cmidi2_ump_get_midi2_note_attribute_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_midi2_note_attribute_data(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 6) << 8) + (cmidi2_ump_get_byte_at(ump, 7)); } -static inline uint8_t cmidi2_ump_get_midi2_paf_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_paf_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint32_t cmidi2_ump_get_midi2_paf_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_paf_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_pnrcc_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pnrcc_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi2_pnrcc_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pnrcc_index(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint32_t cmidi2_ump_get_midi2_pnrcc_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_pnrcc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_pnacc_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pnacc_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_midi2_pnacc_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pnacc_index(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint32_t cmidi2_ump_get_midi2_pnacc_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_pnacc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint32_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint32_t cmidi2_ump_get_midi2_pn_management_options(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_pn_management_options(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi2_cc_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_cc_index(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint32_t cmidi2_ump_get_midi2_cc_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_cc_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } // absolute RPN or relative RPN -static inline uint8_t cmidi2_ump_get_midi2_rpn_msb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_rpn_msb(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } // absolute RPN or relative RPN -static inline uint8_t cmidi2_ump_get_midi2_rpn_lsb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_rpn_lsb(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } // absolute RPN or relative RPN -static inline uint32_t cmidi2_ump_get_midi2_rpn_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_rpn_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } // absolute NRPN or relative NRPN -static inline uint8_t cmidi2_ump_get_midi2_nrpn_msb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_nrpn_msb(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } // absolute NRPN or relative NRPN -static inline uint8_t cmidi2_ump_get_midi2_nrpn_lsb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_nrpn_lsb(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } // absolute NRPN or relative NRPN -static inline uint32_t cmidi2_ump_get_midi2_nrpn_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_nrpn_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_program_options(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_program_options(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 3); } -static inline uint8_t cmidi2_ump_get_midi2_program_program(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_program_program(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 4); } -static inline uint8_t cmidi2_ump_get_midi2_program_bank_msb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_program_bank_msb(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 6)); } -static inline uint8_t cmidi2_ump_get_midi2_program_bank_lsb(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_program_bank_lsb(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 7)); } -static inline uint32_t cmidi2_ump_get_midi2_caf_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_caf_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } // either per-note or channel -static inline uint32_t cmidi2_ump_get_midi2_pitch_bend_data(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint32_t cmidi2_ump_get_midi2_pitch_bend_data(const cmidi2_ump* ump) { return cmidi2_ump_get_32_to_64(ump); } -static inline uint8_t cmidi2_ump_get_midi2_pn_pitch_bend_note(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_midi2_pn_pitch_bend_note(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_sysex8_num_bytes(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_sysex8_num_bytes(const cmidi2_ump* ump) { return cmidi2_ump_get_channel(ump); // same bits } -static inline uint8_t cmidi2_ump_get_sysex8_stream_id(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_sysex8_stream_id(const cmidi2_ump* ump) { return cmidi2_ump_get_byte_at(ump, 2); } -static inline uint8_t cmidi2_ump_get_mds_mds_id(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint8_t cmidi2_ump_get_mds_mds_id(const cmidi2_ump* ump) { return cmidi2_ump_get_channel(ump); // same bits } -static inline uint16_t cmidi2_ump_get_mds_num_chunk_bytes(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_num_chunk_bytes(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3); } -static inline uint16_t cmidi2_ump_get_mds_num_chunks(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_num_chunks(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 4) << 8) + cmidi2_ump_get_byte_at(ump, 5); } -static inline uint16_t cmidi2_ump_get_mds_chunk_index(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_chunk_index(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 6) << 8) + cmidi2_ump_get_byte_at(ump, 7); } -static inline uint16_t cmidi2_ump_get_mds_manufacturer_id(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_manufacturer_id(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 8) << 8) + cmidi2_ump_get_byte_at(ump, 9); } -static inline uint16_t cmidi2_ump_get_mds_device_id(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_device_id(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 10) << 8) + cmidi2_ump_get_byte_at(ump, 11); } -static inline uint16_t cmidi2_ump_get_mds_sub_id_1(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_sub_id_1(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 12) << 8) + cmidi2_ump_get_byte_at(ump, 13); } -static inline uint16_t cmidi2_ump_get_mds_sub_id_2(const cmidi2_ump* ump) +LIBREMIDI_STATIC uint16_t cmidi2_ump_get_mds_sub_id_2(const cmidi2_ump* ump) { return (cmidi2_ump_get_byte_at(ump, 14) << 8) + cmidi2_ump_get_byte_at(ump, 15); } @@ -1662,13 +1676,13 @@ typedef struct cmidi2_ump_binary_read_state enum cmidi2_ump_binary_reader_result_code resultCode; } cmidi2_ump_binary_read_state; -static inline void cmidi2_ump_binary_read_state_reset(cmidi2_ump_binary_read_state* state) +LIBREMIDI_STATIC void cmidi2_ump_binary_read_state_reset(cmidi2_ump_binary_read_state* state) { state->dataSize = 0; state->resultCode = CMIDI2_BINARY_READER_RESULT_INCOMPLETE; } -static inline void cmidi2_ump_binary_read_state_init( +LIBREMIDI_STATIC void cmidi2_ump_binary_read_state_init( cmidi2_ump_binary_read_state* state, void* context, uint8_t* dataBuffer, size_t dataCapacity, bool continueOnCompletion) { @@ -1699,7 +1713,7 @@ typedef bool (*cmidi2_ump_binary_read_continuity_checker_func)( cmidi2_ump_binary_read_state* stream, cmidi2_ump* ump); // it is a special copy function that only works with sysex8 memory state at `src` that can access beyond `sizeInBytes`. -static inline void cmidi2_internal_sysex8_copy_data_byte_swapping( +LIBREMIDI_STATIC void cmidi2_internal_sysex8_copy_data_byte_swapping( uint8_t* dst, uint8_t srcHead, uint32_t* srcTail, size_t sizeInBytes) { if (sizeInBytes == 0) @@ -1728,7 +1742,7 @@ static inline void cmidi2_internal_sysex8_copy_data_byte_swapping( /// Parse and store sysex8 binary, using some fine-tuned behavioral functions. /// Return the number of 32-bit ints (number of `cmidi2_ump`s) -static inline size_t cmidi2_ump_get_sysex8_data( +LIBREMIDI_STATIC size_t cmidi2_ump_get_sysex8_data( cmidi2_ump_stream_selector_func streamSelector, void* streamSelectorContext, cmidi2_ump_binary_read_continuity_checker_func continuityChecker, const cmidi2_ump* ump, const size_t umpCapacityInInt) @@ -1798,32 +1812,32 @@ static inline size_t cmidi2_ump_get_sysex8_data( /* byte stream splitter */ -static inline void* cmidi2_ump_sequence_next(const void* ptr) +LIBREMIDI_STATIC void* cmidi2_ump_sequence_next(const void* ptr) { return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes(ptr)); } // similar to LV2_ATOM Utilities API... -#define CMIDI2_UMP_SEQUENCE_FOREACH(ptr, numBytes, iter) \ - for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ +#define CMIDI2_UMP_SEQUENCE_FOREACH(ptr, numBytes, iter) \ + for (uint8_t*(iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ (iter) = (uint8_t*)cmidi2_ump_sequence_next(iter)) -static inline void* cmidi2_ump_sequence_next_le(const void* ptr) +LIBREMIDI_STATIC void* cmidi2_ump_sequence_next_le(const void* ptr) { return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes_le(ptr)); } -#define CMIDI2_UMP_SEQUENCE_FOREACH_LE(ptr, numBytes, iter) \ - for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ +#define CMIDI2_UMP_SEQUENCE_FOREACH_LE(ptr, numBytes, iter) \ + for (uint8_t*(iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ (iter) = (uint8_t*)cmidi2_ump_sequence_next_le(iter)) -static inline void* cmidi2_ump_sequence_next_be(const void* ptr) +LIBREMIDI_STATIC void* cmidi2_ump_sequence_next_be(const void* ptr) { return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes_be(ptr)); } -#define CMIDI2_UMP_SEQUENCE_FOREACH_BE(ptr, numBytes, iter) \ - for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ +#define CMIDI2_UMP_SEQUENCE_FOREACH_BE(ptr, numBytes, iter) \ + for (uint8_t*(iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \ (iter) = (uint8_t*)cmidi2_ump_sequence_next_be(iter)) // -------- @@ -1890,14 +1904,14 @@ typedef struct } cmidi2_profile_id; // Assumes the input value is already 7-bit encoded if required. -static inline void cmidi2_ci_direct_uint16_at(uint8_t* buf, uint16_t v) +LIBREMIDI_STATIC void cmidi2_ci_direct_uint16_at(uint8_t* buf, uint16_t v) { buf[0] = v & 0xFF; buf[1] = (v >> 8) & 0xFF; } // Assumes the input value is already 7-bit encoded if required. -static inline void cmidi2_ci_direct_uint32_at(uint8_t* buf, uint32_t v) +LIBREMIDI_STATIC void cmidi2_ci_direct_uint32_at(uint8_t* buf, uint32_t v) { buf[0] = v & 0xFF; buf[1] = (v >> 8) & 0xFF; @@ -1905,20 +1919,20 @@ static inline void cmidi2_ci_direct_uint32_at(uint8_t* buf, uint32_t v) buf[3] = (v >> 24) & 0xFF; } -static inline void cmidi2_ci_7bit_int14_at(uint8_t* buf, uint16_t v) +LIBREMIDI_STATIC void cmidi2_ci_7bit_int14_at(uint8_t* buf, uint16_t v) { buf[0] = v & 0x7F; buf[1] = (v >> 7) & 0x7F; } -static inline void cmidi2_ci_7bit_int21_at(uint8_t* buf, uint32_t v) +LIBREMIDI_STATIC void cmidi2_ci_7bit_int21_at(uint8_t* buf, uint32_t v) { buf[0] = v & 0x7F; buf[1] = (v >> 7) & 0x7F; buf[2] = (v >> 14) & 0x7F; } -static inline void cmidi2_ci_7bit_int28_at(uint8_t* buf, uint32_t v) +LIBREMIDI_STATIC void cmidi2_ci_7bit_int28_at(uint8_t* buf, uint32_t v) { buf[0] = v & 0x7F; buf[1] = (v >> 7) & 0x7F; @@ -1926,7 +1940,7 @@ static inline void cmidi2_ci_7bit_int28_at(uint8_t* buf, uint32_t v) buf[3] = (v >> 21) & 0x7F; } -static inline void cmidi2_ci_message_common( +LIBREMIDI_STATIC void cmidi2_ci_message_common( uint8_t* buf, uint8_t destination, uint8_t sysexSubId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID) { @@ -1941,7 +1955,7 @@ static inline void cmidi2_ci_message_common( // Discovery -static inline void cmidi2_ci_discovery_common( +LIBREMIDI_STATIC void cmidi2_ci_discovery_common( uint8_t* buf, uint8_t sysexSubId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint32_t deviceManufacturer3Bytes, uint16_t deviceFamily, uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel, uint8_t ciCategorySupported, @@ -1962,7 +1976,7 @@ static inline void cmidi2_ci_discovery_common( buf[29] = initiatorOutputPathId; } -static inline void cmidi2_ci_discovery( +LIBREMIDI_STATIC void cmidi2_ci_discovery( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t deviceManufacturer, uint16_t deviceFamily, uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel, uint8_t ciCategorySupported, uint32_t receivableMaxSysExSize, uint8_t initiatorOutputPathId) @@ -1973,7 +1987,7 @@ static inline void cmidi2_ci_discovery( ciCategorySupported, receivableMaxSysExSize, initiatorOutputPathId); } -static inline void cmidi2_ci_discovery_reply( +LIBREMIDI_STATIC void cmidi2_ci_discovery_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint32_t deviceManufacturer, uint16_t deviceFamily, uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel, uint8_t ciCategorySupported, uint32_t receivableMaxSysExSize, @@ -1986,7 +2000,7 @@ static inline void cmidi2_ci_discovery_reply( buf[30] = functionBlockOr7Fh; } -static inline void cmidi2_ci_discovery_invalidate_muid( +LIBREMIDI_STATIC void cmidi2_ci_discovery_invalidate_muid( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t targetMUID) { cmidi2_ci_message_common( @@ -1994,7 +2008,7 @@ static inline void cmidi2_ci_discovery_invalidate_muid( cmidi2_ci_direct_uint32_at(buf + 13, targetMUID); } -static inline void cmidi2_ci_ack_nak_common( +LIBREMIDI_STATIC void cmidi2_ci_ack_nak_common( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t statusCode, uint8_t statusData, uint8_t* details5Bytes, uint16_t messageLength, const char* messageText) @@ -2009,7 +2023,7 @@ static inline void cmidi2_ci_ack_nak_common( memcpy(buf + 23, messageText, messageLength); } -static inline void cmidi2_ci_ack( +LIBREMIDI_STATIC void cmidi2_ci_ack( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t ackStatusCode, uint8_t ackStatusData, uint8_t* ackDetails5Bytes, uint16_t messageLength, @@ -2020,7 +2034,7 @@ static inline void cmidi2_ci_ack( ackStatusCode, ackStatusData, ackDetails5Bytes, messageLength, messageText); } -static inline void cmidi2_ci_nak( +LIBREMIDI_STATIC void cmidi2_ci_nak( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t nakStatusCode, uint8_t nakStatusData, uint8_t* nakDetails5Bytes, uint16_t messageLength, @@ -2034,7 +2048,7 @@ static inline void cmidi2_ci_nak( // Protocol Negotiation // Note that it was removed in MIDI 2.0 specification June 2023 Updates (MIDI-CI 1.2). -static inline void cmidi2_ci_protocol_info(uint8_t* buf, cmidi2_ci_protocol_type_info info) +LIBREMIDI_STATIC void cmidi2_ci_protocol_info(uint8_t* buf, cmidi2_ci_protocol_type_info info) { buf[0] = info.type; buf[1] = info.version; @@ -2043,7 +2057,7 @@ static inline void cmidi2_ci_protocol_info(uint8_t* buf, cmidi2_ci_protocol_type buf[4] = info.reserved2; } -static inline void cmidi2_ci_protocols( +LIBREMIDI_STATIC void cmidi2_ci_protocols( uint8_t* buf, uint8_t numSupportedProtocols, cmidi2_ci_protocol_type_info* protocolTypes) { buf[0] = numSupportedProtocols; @@ -2051,7 +2065,7 @@ static inline void cmidi2_ci_protocols( cmidi2_ci_protocol_info(buf + 1 + i * 5, protocolTypes[i]); } -static inline void cmidi2_ci_protocol_negotiation( +LIBREMIDI_STATIC void cmidi2_ci_protocol_negotiation( uint8_t* buf, bool isReply, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel, uint8_t numSupportedProtocols, cmidi2_ci_protocol_type_info* protocolTypes) @@ -2065,7 +2079,7 @@ static inline void cmidi2_ci_protocol_negotiation( cmidi2_ci_protocols(buf + 14, numSupportedProtocols, protocolTypes); } -static inline void cmidi2_ci_protocol_set( +LIBREMIDI_STATIC void cmidi2_ci_protocol_set( uint8_t* buf, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel, cmidi2_ci_protocol_type_info newProtocolType) { @@ -2075,7 +2089,7 @@ static inline void cmidi2_ci_protocol_set( cmidi2_ci_protocol_info(buf + 14, newProtocolType); } -static inline void cmidi2_ci_protocol_test( +LIBREMIDI_STATIC void cmidi2_ci_protocol_test( uint8_t* buf, bool isInitiatorToResponder, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel, uint8_t* testData48Bytes) { @@ -2088,7 +2102,7 @@ static inline void cmidi2_ci_protocol_test( memcpy(buf + 14, testData48Bytes, 48); } -static inline void cmidi2_ci_protocol_confirm_established( +LIBREMIDI_STATIC void cmidi2_ci_protocol_confirm_established( uint8_t* buf, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel) { cmidi2_ci_message_common( @@ -2097,7 +2111,7 @@ static inline void cmidi2_ci_protocol_confirm_established( buf[13] = authorityLevel; } -static inline int32_t cmidi2_ci_try_parse_new_protocol(uint8_t* buf, size_t length) +LIBREMIDI_STATIC int32_t cmidi2_ci_try_parse_new_protocol(uint8_t* buf, size_t length) { return (length != 19 || buf[0] != 0x7E || buf[1] != 0x7F || buf[2] != CMIDI2_CI_SUB_ID || buf[3] != CMIDI2_CI_SUB_ID_2_SET_NEW_PROTOCOL || buf[4] != 1) @@ -2107,7 +2121,7 @@ static inline int32_t cmidi2_ci_try_parse_new_protocol(uint8_t* buf, size_t leng // Profile Configuration -static inline void cmidi2_ci_profile(uint8_t* buf, cmidi2_profile_id info) +LIBREMIDI_STATIC void cmidi2_ci_profile(uint8_t* buf, cmidi2_profile_id info) { buf[0] = info.fixed_7e; buf[1] = info.bank; @@ -2116,14 +2130,14 @@ static inline void cmidi2_ci_profile(uint8_t* buf, cmidi2_profile_id info) buf[4] = info.level; } -static inline void cmidi2_ci_profile_inquiry( +LIBREMIDI_STATIC void cmidi2_ci_profile_inquiry( uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID) { cmidi2_ci_message_common( buf, source, CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY, 1, sourceMUID, destinationMUID); } -static inline void cmidi2_ci_profile_inquiry_reply( +LIBREMIDI_STATIC void cmidi2_ci_profile_inquiry_reply( uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t numEnabledProfiles, cmidi2_profile_id* enabledProfiles, uint8_t numDisabledProfiles, cmidi2_profile_id* disabledProfiles) @@ -2139,7 +2153,7 @@ static inline void cmidi2_ci_profile_inquiry_reply( cmidi2_ci_profile(buf + pos + i * 5, disabledProfiles[i]); } -static inline void cmidi2_ci_profile_set( +LIBREMIDI_STATIC void cmidi2_ci_profile_set( uint8_t* buf, uint8_t destination, bool turnOn, uint32_t sourceMUID, uint32_t destinationMUID, cmidi2_profile_id profile) { @@ -2150,7 +2164,7 @@ static inline void cmidi2_ci_profile_set( cmidi2_ci_profile(buf + 13, profile); } -static inline void cmidi2_ci_profile_report( +LIBREMIDI_STATIC void cmidi2_ci_profile_report( uint8_t* buf, uint8_t source, bool isEnabledReport, uint32_t sourceMUID, cmidi2_profile_id profile) { @@ -2162,7 +2176,7 @@ static inline void cmidi2_ci_profile_report( cmidi2_ci_profile(buf + 13, profile); } -static inline void cmidi2_ci_profile_specific_data( +LIBREMIDI_STATIC void cmidi2_ci_profile_specific_data( uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID, cmidi2_profile_id profile, uint32_t dataSize, void* data) { @@ -2175,7 +2189,7 @@ static inline void cmidi2_ci_profile_specific_data( // Property Exchange -static inline void cmidi2_ci_property_get_capabilities( +LIBREMIDI_STATIC void cmidi2_ci_property_get_capabilities( uint8_t* buf, uint8_t destination, bool isReply, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t maxSupportedRequests) { @@ -2188,7 +2202,7 @@ static inline void cmidi2_ci_property_get_capabilities( } // common to all of: has data & reply, get data & reply, set data & reply, subscribe & reply, notify -static inline void cmidi2_ci_property_common( +LIBREMIDI_STATIC void cmidi2_ci_property_common( uint8_t* buf, uint8_t destination, uint8_t messageTypeSubId2, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, uint16_t headerSize, void* header, uint16_t numChunks, uint16_t chunkIndex, uint16_t dataSize, void* data) @@ -2203,7 +2217,7 @@ static inline void cmidi2_ci_property_common( memcpy(buf + 22 + headerSize, data, dataSize); } -static inline void cmidi2_ci_property_get_capabilities_reply( +LIBREMIDI_STATIC void cmidi2_ci_property_get_capabilities_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t maxSupportedRequests, uint8_t peMajorVersion, uint8_t peMinorVersion) { @@ -2216,7 +2230,7 @@ static inline void cmidi2_ci_property_get_capabilities_reply( buf[15] = peMinorVersion; } -static inline void cmidi2_ci_property_data_common( +LIBREMIDI_STATIC void cmidi2_ci_property_data_common( uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, @@ -2236,7 +2250,7 @@ static inline void cmidi2_ci_property_data_common( memcpy(buf + headerSize + 22, propertyData, propertyDataLength); } -static inline void cmidi2_ci_property_get_data( +LIBREMIDI_STATIC void cmidi2_ci_property_get_data( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData) { @@ -2245,7 +2259,7 @@ static inline void cmidi2_ci_property_get_data( requestId, headerSize, headerData, 1, 1, 0, NULL); } -static inline void cmidi2_ci_property_get_data_reply( +LIBREMIDI_STATIC void cmidi2_ci_property_get_data_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2256,7 +2270,7 @@ static inline void cmidi2_ci_property_get_data_reply( propertyDataLength, propertyData); } -static inline void cmidi2_ci_property_set_data( +LIBREMIDI_STATIC void cmidi2_ci_property_set_data( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2267,7 +2281,7 @@ static inline void cmidi2_ci_property_set_data( propertyData); } -static inline void cmidi2_ci_property_set_data_reply( +LIBREMIDI_STATIC void cmidi2_ci_property_set_data_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData) { @@ -2276,7 +2290,7 @@ static inline void cmidi2_ci_property_set_data_reply( destinationMUID, requestId, headerSize, headerData, 1, 1, 0, NULL); } -static inline void cmidi2_ci_property_subscribe( +LIBREMIDI_STATIC void cmidi2_ci_property_subscribe( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2287,7 +2301,7 @@ static inline void cmidi2_ci_property_subscribe( propertyData); } -static inline void cmidi2_ci_property_subscribe_reply( +LIBREMIDI_STATIC void cmidi2_ci_property_subscribe_reply( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2298,7 +2312,7 @@ static inline void cmidi2_ci_property_subscribe_reply( propertyDataLength, propertyData); } -static inline void cmidi2_ci_property_notify( +LIBREMIDI_STATIC void cmidi2_ci_property_notify( uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData) @@ -2311,7 +2325,7 @@ static inline void cmidi2_ci_property_notify( // Process Inquiry -static inline void cmidi2_ci_process_get_capabilities( +LIBREMIDI_STATIC void cmidi2_ci_process_get_capabilities( uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID) { @@ -2321,7 +2335,7 @@ static inline void cmidi2_ci_process_get_capabilities( versionAndFormat, sourceMUID, destinationMUID); } -static inline void cmidi2_ci_process_get_capabilities_reply( +LIBREMIDI_STATIC void cmidi2_ci_process_get_capabilities_reply( uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t processInquirySupportedFeatures) { @@ -2333,7 +2347,7 @@ static inline void cmidi2_ci_process_get_capabilities_reply( buf[13] = processInquirySupportedFeatures; } -static inline void cmidi2_ci_process_midi_report_common( +LIBREMIDI_STATIC void cmidi2_ci_process_midi_report_common( uint8_t* buf, uint8_t deviceId, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages, uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages) @@ -2349,7 +2363,7 @@ static inline void cmidi2_ci_process_midi_report_common( buf[17] = requestedNoteDataMessages; } -static inline void cmidi2_ci_process_get_midi_report( +LIBREMIDI_STATIC void cmidi2_ci_process_get_midi_report( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages, uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages) @@ -2360,7 +2374,7 @@ static inline void cmidi2_ci_process_get_midi_report( requestedChannelControllerMessages, requestedNoteDataMessages); } -static inline void cmidi2_ci_process_get_midi_report_reply( +LIBREMIDI_STATIC void cmidi2_ci_process_get_midi_report_reply( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages, uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages) @@ -2371,7 +2385,7 @@ static inline void cmidi2_ci_process_get_midi_report_reply( requestedChannelControllerMessages, requestedNoteDataMessages); } -static inline void cmidi2_ci_process_get_midi_report_end( +LIBREMIDI_STATIC void cmidi2_ci_process_get_midi_report_end( uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID) { @@ -2383,7 +2397,7 @@ static inline void cmidi2_ci_process_get_midi_report_end( // Miscellaneous MIDI Utilities /** Encodes `value` into `bytes` stream. Returns the length of the encoded value in bytes. */ -static inline uint8_t cmidi2_midi1_write_7bit_encoded_int(uint8_t* bytes, uint32_t value) +LIBREMIDI_STATIC uint8_t cmidi2_midi1_write_7bit_encoded_int(uint8_t* bytes, uint32_t value) { uint8_t pos = 0; for (;; pos++) @@ -2400,7 +2414,7 @@ static inline uint8_t cmidi2_midi1_write_7bit_encoded_int(uint8_t* bytes, uint32 } /** Returns the length of `value` when it would be encoded into a byte stream. */ -static inline uint8_t cmidi2_midi1_get_7bit_encoded_int_length(uint32_t value) +LIBREMIDI_STATIC uint8_t cmidi2_midi1_get_7bit_encoded_int_length(uint32_t value) { for (uint8_t ret = 1;; ret++) { @@ -2413,7 +2427,7 @@ static inline uint8_t cmidi2_midi1_get_7bit_encoded_int_length(uint32_t value) } /** Returns the 7-bit encoded value in `bytes` stream of `length` bytes. */ -static inline uint32_t cmidi2_midi1_get_7bit_encoded_int(uint8_t* bytes, uint32_t length) +LIBREMIDI_STATIC uint32_t cmidi2_midi1_get_7bit_encoded_int(uint8_t* bytes, uint32_t length) { uint8_t* start = bytes; uint32_t value = 0; @@ -2430,38 +2444,71 @@ static inline uint32_t cmidi2_midi1_get_7bit_encoded_int(uint8_t* bytes, uint32_ return value; } -static inline uint32_t cmidi2_midi1_get_message_size(uint8_t* bytes, uint32_t length) +LIBREMIDI_STATIC uint32_t cmidi2_midi1_get_message_size(uint8_t* bytes, uint32_t length) { - uint32_t metaLength; - uint8_t* start = bytes; - uint8_t* end = bytes + length; switch (bytes[0]) { - case 0xF0: + case 0xF0: { + uint8_t* start = bytes; + uint8_t* end = bytes + length; for (bytes++; bytes < end; bytes++) if (*bytes == 0xF7) break; bytes++; - break; - case 0xFF: - bytes++; - metaLength = cmidi2_midi1_get_7bit_encoded_int(bytes, end - bytes); - bytes += metaLength + cmidi2_midi1_get_7bit_encoded_int_length(metaLength); - break; + return bytes - start; + } + case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: + case CMIDI2_SYSTEM_STATUS_SONG_SELECT: + return 2; + case CMIDI2_SYSTEM_STATUS_SONG_POSITION: + return 3; + case CMIDI2_SYSTEM_STATUS_TUNE_REQUEST: + case CMIDI2_SYSTEM_STATUS_TIMING_CLOCK: + case CMIDI2_SYSTEM_STATUS_START: + case CMIDI2_SYSTEM_STATUS_CONTINUE: + case CMIDI2_SYSTEM_STATUS_STOP: + case CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING: + case CMIDI2_SYSTEM_STATUS_RESET: // Use cmidi2_midi1_get_message_size_smf to handle meta-events + return 1; default: switch (bytes[0] & 0xF0) { case 0xC0: case 0xD0: - bytes += 2; - break; + return 2; default: - bytes += 3; - break; + return 3; } break; } - return bytes - start; + return 0; +} + +LIBREMIDI_STATIC uint32_t cmidi2_midi1_get_message_size_live(uint8_t* bytes, uint32_t length) +{ + // 0xFF in live MIDI stream indicates reset. We handle it there. + return cmidi2_midi1_get_message_size(bytes, length); +} + +LIBREMIDI_STATIC uint32_t cmidi2_midi1_get_message_size_smf(uint8_t* bytes, uint32_t length) +{ + switch (bytes[0]) + { + case 0xFF: // 0xFF in standard MIDI file indicates Meta-events, not RESET + { + uint32_t metaLength; + uint8_t* start = bytes; + uint8_t* end = bytes + length; + + bytes++; + metaLength = cmidi2_midi1_get_7bit_encoded_int(bytes, end - bytes); + bytes += metaLength + cmidi2_midi1_get_7bit_encoded_int_length(metaLength); + + return bytes - start; + } + default: + return cmidi2_midi1_get_message_size(bytes, length); + } } // MIDI1 to UMP Translator @@ -2531,7 +2578,7 @@ typedef struct cmidi2_midi_conversion_context int32_t context_rpn; int32_t context_nrpn; int32_t context_dte; - // MIDI 2.0 Defalult Translation (UMP specification Appendix D.3) accepts only DTE LSB + // MIDI 2.0 Default Translation (UMP specification Appendix D.3) accepts only DTE LSB // as the conversion terminator, but cmidi2 allows DTE LSB to come first, // if this flag is enabled. bool allow_reordered_dte; @@ -2562,7 +2609,7 @@ enum cmidi2_midi_conversion_result CMIDI2_CONVERSION_RESULT_INVALID_INPUT = 0x40, }; -static inline void +LIBREMIDI_STATIC void cmidi2_midi_conversion_context_initialize(cmidi2_midi_conversion_context* context) { context->is_midi1_smf = false; @@ -2591,17 +2638,18 @@ typedef struct cmidi2_convert_sysex_context size_t dst_offset; } cmidi2_convert_sysex_context; -static inline void* +LIBREMIDI_STATIC void* cmidi2_internal_convert_add_midi1_sysex7_ump_to_list(uint64_t data, void* context) { cmidi2_convert_sysex_context* s7ctx = (cmidi2_convert_sysex_context*)context; s7ctx->conversion_context->ump[s7ctx->dst_offset] = data >> 32; s7ctx->conversion_context->ump[s7ctx->dst_offset + 1] = data & 0xFFFFFFFF; - s7ctx->conversion_context->ump_proceeded_bytes += 2; + s7ctx->conversion_context->ump_proceeded_bytes += 2 * sizeof(cmidi2_ump); + s7ctx->dst_offset += 2; return NULL; } -static inline void* cmidi2_internal_convert_add_midi1_sysex8_ump_to_list( +LIBREMIDI_STATIC void* cmidi2_internal_convert_add_midi1_sysex8_ump_to_list( uint64_t data1, uint64_t data2, size_t index, void* context) { (void)index; @@ -2610,11 +2658,12 @@ static inline void* cmidi2_internal_convert_add_midi1_sysex8_ump_to_list( s8ctx->conversion_context->ump[s8ctx->dst_offset + 1] = data1 & 0xFFFFFFFF; s8ctx->conversion_context->ump[s8ctx->dst_offset + 2] = data2 >> 32; s8ctx->conversion_context->ump[s8ctx->dst_offset + 3] = data2 & 0xFFFFFFFF; - s8ctx->conversion_context->ump_proceeded_bytes += 4; + s8ctx->conversion_context->ump_proceeded_bytes += 4 * sizeof(cmidi2_ump); + s8ctx->dst_offset += 4; return NULL; } -static inline uint64_t +LIBREMIDI_STATIC uint64_t cmidi2_internal_convert_midi1_dte_to_ump(cmidi2_midi_conversion_context* context, uint8_t channel) { bool isRpn = (context->context_rpn & 0x8080) == 0; @@ -2629,7 +2678,7 @@ cmidi2_internal_convert_midi1_dte_to_ump(cmidi2_midi_conversion_context* context : cmidi2_ump_midi2_nrpn(context->group, channel, msb, lsb, data); } -static inline uint32_t cmidi2_internal_swap_endian(uint32_t v) +LIBREMIDI_STATIC uint32_t cmidi2_internal_swap_endian(uint32_t v) { return ((v & 0xFF) << 24) + (((v >> 8) & 0xFF) << 16) + (((v >> 16) & 0xFF) << 8) + ((v >> 24) & 0xFF); @@ -2651,7 +2700,8 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) { // FIXME: implement deltaTime to JR Timestamp conversion. - if (context->midi1[*sIdx] == 0xF0) + uint8_t status = context->midi1[*sIdx]; + if (status == 0xF0) { // sysex uint8_t* f7 @@ -2682,26 +2732,38 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) context->group, context->midi1 + *sIdx, cmidi2_internal_convert_add_midi1_sysex7_ump_to_list, &sysExCtx); } - *sIdx += sysexSize + 1; // +1 for 0xF7 + *sIdx += sysexSize + 2; // +1 for 0xF0 and 0xF7 } else { // fixed sized message size_t remaining = sLen - *sIdx; - size_t len = cmidi2_midi1_get_message_size(context->midi1 + *sIdx, remaining); - if (len > remaining) + size_t inputMidi1Len + = context->is_midi1_smf + ? cmidi2_midi1_get_message_size_smf(context->midi1 + *sIdx, remaining) + : cmidi2_midi1_get_message_size_live(context->midi1 + *sIdx, remaining); + if (inputMidi1Len > remaining) return CMIDI2_CONVERSION_RESULT_INVALID_INPUT; - uint8_t byte2 = context->midi1[*sIdx + 1]; - uint8_t byte3 = len > 2 ? context->midi1[*sIdx + 2] : 0; - uint8_t channel = context->midi1[*sIdx] & 0xF; + uint8_t byte2 = inputMidi1Len > 1 ? context->midi1[*sIdx + 1] : 0; + uint8_t byte3 = inputMidi1Len > 2 ? context->midi1[*sIdx + 2] : 0; + uint8_t channel = status & 0xF; if (context->midi_protocol == CMIDI2_PROTOCOL_TYPE_MIDI1) { // generate MIDI1 UMPs - dst[*dIdx] = cmidi2_ump_midi1_message( - context->group, context->midi1[*sIdx] & 0xF0, channel, byte2, byte3); - *sIdx += len; - *dIdx += 4; + if (status > 0xF0) + { + dst[*dIdx] = cmidi2_ump_system_message(context->group, status, byte2, byte3); + *sIdx += inputMidi1Len; + *dIdx += 4; + } + else + { + dst[*dIdx] + = cmidi2_ump_midi1_message(context->group, status & 0xF0, channel, byte2, byte3); + *sIdx += inputMidi1Len; + *dIdx += 4; + } } else { @@ -2711,18 +2773,22 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) const int16_t NO_ATTRIBUTE_DATA = 0; bool bankValid, bankMsbValid, bankLsbValid; bool skipEmitUmp = false; - switch (context->midi1[*sIdx] & 0xF0) + int outputUmpLen = 0; + switch (status & 0xF0) { case CMIDI2_STATUS_NOTE_OFF: m2 = cmidi2_ump_midi2_note_off( context->group, channel, byte2, NO_ATTRIBUTE_TYPE, byte3 << 9, NO_ATTRIBUTE_DATA); + outputUmpLen = 2; break; case CMIDI2_STATUS_NOTE_ON: m2 = cmidi2_ump_midi2_note_on( context->group, channel, byte2, NO_ATTRIBUTE_TYPE, byte3 << 9, NO_ATTRIBUTE_DATA); + outputUmpLen = 2; break; case CMIDI2_STATUS_PAF: m2 = cmidi2_ump_midi2_paf(context->group, channel, byte2, byte3 << 25); + outputUmpLen = 2; break; case CMIDI2_STATUS_CC: switch (byte2) @@ -2747,7 +2813,10 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) context->context_dte = (context->context_dte & 0xFF) | (byte3 << 8); if (context->allow_reordered_dte && (context->context_dte & 0x8080) == 0) + { m2 = cmidi2_internal_convert_midi1_dte_to_ump(context, channel); + outputUmpLen = 2; + } else skipEmitUmp = true; @@ -2760,6 +2829,7 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) if ((context->context_rpn & 0x8080) && (context->context_nrpn & 0x8080)) return CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE; m2 = cmidi2_internal_convert_midi1_dte_to_ump(context, channel); + outputUmpLen = 2; break; case CMIDI2_CC_BANK_SELECT: @@ -2772,6 +2842,7 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) break; default: m2 = cmidi2_ump_midi2_cc(context->group, channel, byte2, byte3 << 25); + outputUmpLen = 2; break; } break; @@ -2785,37 +2856,77 @@ cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context) : CMIDI2_PROGRAM_CHANGE_OPTION_NONE, byte2, bankMsbValid ? context->context_bank >> 8 : 0, bankLsbValid ? context->context_bank & 0x7F : 0); + outputUmpLen = 2; context->context_bank = 0x8080; break; case CMIDI2_STATUS_CAF: m2 = cmidi2_ump_midi2_caf(context->group, channel, byte2 << 25); + outputUmpLen = 2; break; case CMIDI2_STATUS_PITCH_BEND: // Note: Pitch Bend values in the MIDI 1.0 Protocol are presented as Little Endian. m2 = cmidi2_ump_midi2_pitch_bend_direct( context->group, channel, ((byte3 << 7) + byte2) << 18); + outputUmpLen = 2; break; default: - return CMIDI2_CONVERSION_RESULT_INVALID_STATUS; + switch (status) + { + case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: + case CMIDI2_SYSTEM_STATUS_SONG_SELECT: + inputMidi1Len = 2; + m2 = cmidi2_ump_system_message(context->group, status, byte2, byte3); + outputUmpLen = 1; + break; + case CMIDI2_SYSTEM_STATUS_SONG_POSITION: + inputMidi1Len = 3; + m2 = cmidi2_ump_system_message(context->group, status, byte2, byte3); + outputUmpLen = 1; + break; + case CMIDI2_SYSTEM_STATUS_TUNE_REQUEST: + case CMIDI2_SYSTEM_STATUS_TIMING_CLOCK: + case CMIDI2_SYSTEM_STATUS_START: + case CMIDI2_SYSTEM_STATUS_CONTINUE: + case CMIDI2_SYSTEM_STATUS_STOP: + case CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING: + case CMIDI2_SYSTEM_STATUS_RESET: + inputMidi1Len = 1; + m2 = cmidi2_ump_system_message(context->group, status, 0, 0); + outputUmpLen = 1; + break; + default: + return CMIDI2_CONVERSION_RESULT_INVALID_STATUS; + } + break; } if (!skipEmitUmp) { - int platEndian = cmidi2_util_is_platform_little_endian() - ? CMIDI2_TRANSLATOR_LITTLE_ENDIAN - : CMIDI2_TRANSLATOR_BIG_ENDIAN; - int actualEndian - = context->ump_serialization_endianness != CMIDI2_TRANSLATOR_DEFAULT_ENDIAN - ? context->ump_serialization_endianness - : platEndian; - if (platEndian != actualEndian) - m2 = (((uint64_t)cmidi2_internal_swap_endian(m2 >> 32)) << 32) - | cmidi2_internal_swap_endian(m2 & 0xFFFFFFFF); - *(uint32_t*)(dst + *dIdx) = m2 >> 32; - *dIdx += 4; - *(uint32_t*)(dst + *dIdx) = m2 & 0xFFFFFFFF; - *dIdx += 4; + switch (outputUmpLen) + { + case 1: + *(uint32_t*)(dst + *dIdx) = m2; + *dIdx += 4; + break; + case 2: { + int platEndian = cmidi2_util_is_platform_little_endian() + ? CMIDI2_TRANSLATOR_LITTLE_ENDIAN + : CMIDI2_TRANSLATOR_BIG_ENDIAN; + int actualEndian + = context->ump_serialization_endianness != CMIDI2_TRANSLATOR_DEFAULT_ENDIAN + ? context->ump_serialization_endianness + : platEndian; + if (platEndian != actualEndian) + m2 = (((uint64_t)cmidi2_internal_swap_endian(m2 >> 32)) << 32) + | cmidi2_internal_swap_endian(m2 & 0xFFFFFFFF); + *(uint32_t*)(dst + *dIdx) = m2 >> 32; + *dIdx += 4; + *(uint32_t*)(dst + *dIdx) = m2 & 0xFFFFFFFF; + *dIdx += 4; + break; + } + } } - *sIdx += len; + *sIdx += inputMidi1Len; } } } @@ -2852,11 +2963,13 @@ static size_t cmidi2_internal_add_midi1_delta_time( /// Convert one single UMP (without JR Timestamp) to MIDI 1.0 Message (without delta time) /// It is a lengthy function, so it is recommended to wrap it in another non-inline function. -static inline size_t cmidi2_convert_single_ump_to_timed_midi1( +LIBREMIDI_STATIC size_t cmidi2_convert_single_ump_to_timed_midi1( uint8_t* dst, size_t maxBytes, cmidi2_ump* ump, int32_t deltaTime, cmidi2_midi_conversion_context* context, uint8_t* sysex7Buffer, size_t* sysex7BufferIndex) { - (void)maxBytes; + if (maxBytes < 1) + return 0; + size_t midiEventSize = 0; uint64_t sysex7U64; uint8_t sysex7NumBytesInUmp; @@ -2871,29 +2984,47 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( switch (messageType) { case CMIDI2_MESSAGE_TYPE_SYSTEM: + statusCode = cmidi2_ump_get_status_byte(ump); CMIDI2_INTERNAL_ADD_DELTA_TIME - midiEventSize = 1; + switch (statusCode) { - case 0xF1: - case 0xF3: - case 0xF9: - dst[1] = cmidi2_ump_get_system_message_byte2(ump); + case CMIDI2_SYSTEM_STATUS_SONG_POSITION: + midiEventSize = 3; + break; + case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: + case CMIDI2_SYSTEM_STATUS_SONG_SELECT: midiEventSize = 2; break; + default: + midiEventSize = 1; + break; } + if (maxBytes < midiEventSize) + return 0; + dst[0] = statusCode; // no channel filtering + if (midiEventSize >= 2) + dst[1] = cmidi2_ump_get_midi1_byte2(ump); + if (midiEventSize >= 3) + dst[2] = cmidi2_ump_get_midi1_byte3(ump); break; case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL: CMIDI2_INTERNAL_ADD_DELTA_TIME - midiEventSize = 3; - dst[1] = cmidi2_ump_get_midi1_byte2(ump); + switch (statusCode) { case 0xC0: case 0xD0: midiEventSize = 2; + if (maxBytes < midiEventSize) + return 0; + dst[1] = cmidi2_ump_get_midi1_byte2(ump); break; default: + midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; + dst[1] = cmidi2_ump_get_midi1_byte2(ump); dst[2] = cmidi2_ump_get_midi1_byte3(ump); break; } @@ -2905,6 +3036,8 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( case CMIDI2_STATUS_RPN: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 12; + if (maxBytes < midiEventSize) + return 0; dst[0] = cmidi2_ump_get_channel(ump) + CMIDI2_STATUS_CC; dst[1] = CMIDI2_CC_RPN_MSB; dst[2] = cmidi2_ump_get_midi2_rpn_msb(ump); @@ -2921,6 +3054,8 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( case CMIDI2_STATUS_NRPN: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 12; + if (maxBytes < midiEventSize) + return 0; dst[0] = cmidi2_ump_get_channel(ump) + CMIDI2_STATUS_CC; dst[1] = CMIDI2_CC_NRPN_MSB; dst[2] = cmidi2_ump_get_midi2_nrpn_msb(ump); @@ -2938,18 +3073,24 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( case CMIDI2_STATUS_NOTE_ON: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; dst[1] = cmidi2_ump_get_midi2_note_note(ump); dst[2] = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200; break; case CMIDI2_STATUS_PAF: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; dst[1] = cmidi2_ump_get_midi2_paf_note(ump); dst[2] = cmidi2_ump_get_midi2_paf_data(ump) / 0x2000000; break; case CMIDI2_STATUS_CC: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; dst[0] = statusCode | cmidi2_ump_get_channel(ump); dst[1] = cmidi2_ump_get_midi2_cc_index(ump); dst[2] = cmidi2_ump_get_midi2_cc_data(ump) / 0x2000000; @@ -2960,6 +3101,8 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( if (cmidi2_ump_get_midi2_program_options(ump) & CMIDI2_PROGRAM_CHANGE_OPTION_BANK_VALID) { midiEventSize = 8; + if (maxBytes < midiEventSize) + return 0; dst[6] = dst[0]; // copy dst[7] = cmidi2_ump_get_midi2_program_program(ump); dst[0] = (dst[6] & 0xF) + CMIDI2_STATUS_CC; @@ -2972,17 +3115,23 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( else { midiEventSize = 2; + if (maxBytes < midiEventSize) + return 0; dst[1] = cmidi2_ump_get_midi2_program_program(ump); } break; case CMIDI2_STATUS_CAF: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 2; + if (maxBytes < midiEventSize) + return 0; dst[1] = cmidi2_ump_get_midi2_caf_data(ump) / 0x2000000; break; case CMIDI2_STATUS_PITCH_BEND: CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 3; + if (maxBytes < midiEventSize) + return 0; uint32_t pitchBendV1 = cmidi2_ump_get_midi2_pitch_bend_data(ump) / 0x40000; dst[1] = pitchBendV1 % 0x80; dst[2] = pitchBendV1 / 0x80; @@ -3006,6 +3155,9 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( // minimal implementation for single-byte sysex7 CMIDI2_INTERNAL_ADD_DELTA_TIME midiEventSize = 1 + cmidi2_ump_get_sysex7_num_bytes(ump); + if (maxBytes < midiEventSize) + return 0; + sysex7U64 = cmidi2_ump_read_uint64_bytes(ump); for (size_t i = 0; i < midiEventSize - 1; i++) dst[i] = cmidi2_ump_get_byte_from_uint64(sysex7U64, 2 + i); @@ -3020,7 +3172,7 @@ static inline size_t cmidi2_convert_single_ump_to_timed_midi1( } // Left for API backward compatibility. -static inline size_t +LIBREMIDI_STATIC size_t cmidi2_convert_single_ump_to_midi1(uint8_t* dst, size_t maxBytes, cmidi2_ump* ump) { return cmidi2_convert_single_ump_to_timed_midi1(dst, maxBytes, ump, 0, NULL, NULL, 0); @@ -3103,7 +3255,7 @@ typedef struct cmidi2_ump_forge size_t offset; } cmidi2_ump_forge; -static inline void +LIBREMIDI_STATIC void cmidi2_ump_forge_init(cmidi2_ump_forge* forge, cmidi2_ump* buffer, size_t capacityInBytes) { forge->ump = buffer; @@ -3111,7 +3263,7 @@ cmidi2_ump_forge_init(cmidi2_ump_forge* forge, cmidi2_ump* buffer, size_t capaci forge->offset = 0; } -static inline bool cmidi2_ump_forge_add_packet_32(cmidi2_ump_forge* forge, uint32_t ump) +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_packet_32(cmidi2_ump_forge* forge, uint32_t ump) { int size = 4; if (forge->offset + size > forge->capacity) @@ -3122,7 +3274,7 @@ static inline bool cmidi2_ump_forge_add_packet_32(cmidi2_ump_forge* forge, uint3 return true; } -static inline bool cmidi2_ump_forge_add_packet_64(cmidi2_ump_forge* forge, uint64_t ump) +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_packet_64(cmidi2_ump_forge* forge, uint64_t ump) { int size = 8; if (forge->offset + size > forge->capacity) @@ -3133,7 +3285,7 @@ static inline bool cmidi2_ump_forge_add_packet_64(cmidi2_ump_forge* forge, uint6 return true; } -static inline bool +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_packet_128(cmidi2_ump_forge* forge, uint64_t ump1, uint64_t ump2) { int size = 16; @@ -3145,7 +3297,7 @@ cmidi2_ump_forge_add_packet_128(cmidi2_ump_forge* forge, uint64_t ump1, uint64_t return true; } -static inline bool cmidi2_ump_forge_add_single_packet(cmidi2_ump_forge* forge, cmidi2_ump* ump) +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_single_packet(cmidi2_ump_forge* forge, cmidi2_ump* ump) { int size = cmidi2_ump_get_message_size_bytes(ump); if (forge->offset + size > forge->capacity) @@ -3155,7 +3307,7 @@ static inline bool cmidi2_ump_forge_add_single_packet(cmidi2_ump_forge* forge, c return true; } -static inline bool +LIBREMIDI_STATIC bool cmidi2_ump_forge_add_packets(cmidi2_ump_forge* forge, cmidi2_ump* ump, int32_t size) { if (forge->offset + size > forge->capacity) @@ -3167,7 +3319,7 @@ cmidi2_ump_forge_add_packets(cmidi2_ump_forge* forge, cmidi2_ump* ump, int32_t s // UMP sequence merger -static inline bool cmidi2_internal_ump_merge_sequence_write_delta_time( +LIBREMIDI_STATIC bool cmidi2_internal_ump_merge_sequence_write_delta_time( int32_t timestamp1, int32_t timestamp2, int32_t* lastTimestamp, void* dst, int32_t* dIdx, size_t dstSize) { @@ -3179,14 +3331,14 @@ static inline bool cmidi2_internal_ump_merge_sequence_write_delta_time( { cmidi2_ump_write32( (cmidi2_ump*)((uint8_t*)dst + *dIdx), - cmidi2_ump_jr_timestamp_direct(0, deltaTime > 0xFFFF ? 0xFFFF : deltaTime)); + cmidi2_ump_jr_timestamp_direct(deltaTime > 0xFFFF ? 0xFFFF : deltaTime)); *dIdx += 4; } *lastTimestamp += deltaTime; return true; } -static inline size_t cmidi2_ump_merge_sequences( +LIBREMIDI_STATIC size_t cmidi2_ump_merge_sequences( cmidi2_ump* dst, size_t dstCapacity, cmidi2_ump* seq1, size_t seq1Size, cmidi2_ump* seq2, size_t seq2Size) { diff --git a/include/libremidi/config.hpp b/include/libremidi/config.hpp index e845d4b..94367d8 100644 --- a/include/libremidi/config.hpp +++ b/include/libremidi/config.hpp @@ -1,11 +1,51 @@ #pragma once -#if defined(MSC_VER) +#define LIBREMIDI_VERSION "5.4.3" + +// clang-format off +#if !defined(LIBREMIDI_BASE_NAMESPACE) + #define LIBREMIDI_BASE_NAMESPACE libremidi +#endif +#if defined(LIBREMIDI_MODULE_BUILD) + #define LIBREMIDI_MODULE_EXPORT export + #define LIBREMIDI_MODULE_BEGIN_EXPORT export { + #define LIBREMIDI_MODULE_END_EXPORT } + #define NAMESPACE_LIBREMIDI export namespace LIBREMIDI_BASE_NAMESPACE + #define NAMESPACE_STDX export namespace stdx + #define LIBREMIDI_STATIC inline + #define LIBREMIDI_STATIC_IMPLEMENTATION inline + #define LIBREMIDI_STATIC_INLINE_IMPLEMENTATION inline + #define LIBREMIDI_INLINE_IMPLEMENTATION + #define LIBREMIDI_ANONYMOUS_NAMESPACE detail +#else + #define LIBREMIDI_MODULE_EXPORT + #define LIBREMIDI_MODULE_BEGIN_EXPORT + #define LIBREMIDI_MODULE_END_EXPORT + #define NAMESPACE_LIBREMIDI namespace LIBREMIDI_BASE_NAMESPACE + #define NAMESPACE_STDX namespace stdx + #define LIBREMIDI_STATIC static inline + +#if defined(LIBREMIDI_HEADER_ONLY) + #define LIBREMIDI_STATIC_IMPLEMENTATION static + #define LIBREMIDI_STATIC_INLINE_IMPLEMENTATION static inline + #define LIBREMIDI_INLINE_IMPLEMENTATION inline +#else + #define LIBREMIDI_STATIC_IMPLEMENTATION static + #define LIBREMIDI_STATIC_INLINE_IMPLEMENTATION static + #define LIBREMIDI_INLINE_IMPLEMENTATION +#endif + + #define LIBREMIDI_ANONYMOUS_NAMESPACE +#endif +// clang-format on + +#if defined(_MSC_VER) #define NOMINMAX 1 #define WIN32_LEAN_AND_MEAN #endif -#if defined(LIBREMIDI_EXPORTS) +// Dynamic exports +#if defined(LIBREMIDI_EXPORTS) || defined(LIBREMIDI_MODULE_BUILD) #if defined(_MSC_VER) #define LIBREMIDI_EXPORT __declspec(dllexport) #elif defined(__GNUC__) || defined(__clang__) @@ -15,8 +55,7 @@ #define LIBREMIDI_EXPORT #endif -#define LIBREMIDI_VERSION "4.5.0" - +// Boost check to prevent ABI issues #if defined(LIBREMIDI_USE_BOOST) #if !__has_include() #error \ @@ -28,31 +67,77 @@ #endif #endif +// Use boost::small_vector if available #if __has_include() && !defined(LIBREMIDI_NO_BOOST) - #if LIBREMIDI_SLIM_MESSAGE > 0 #include -namespace libremidi +NAMESPACE_LIBREMIDI { using midi_bytes = boost::container::static_vector; } #else #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static constexpr int small_vector_minimum_size +LIBREMIDI_STATIC constexpr int small_vector_minimum_size = sizeof(boost::container::small_vector); using midi_bytes = boost::container::small_vector; } #endif #else #include -namespace libremidi +NAMESPACE_LIBREMIDI { using midi_bytes = std::vector; } #endif +// Use boost::variant2 if available +#define LIBREMIDI_NO_BOOST_VARIANT2 1 +#if __has_include() && !defined(LIBREMIDI_NO_BOOST_VARIANT2) + #include + #define LIBREMIDI_VARIANT_IS_BOOST_VARIANT2 +namespace libremidi_variant_alias = boost::variant2; +#endif + +#if !defined(LIBREMIDI_VARIANT_IS_BOOST_VARIANT2) + #include +namespace libremidi_variant_alias = std; +#endif + +namespace libremidi +{ +using monostate = libremidi_variant_alias::monostate; +template +concept nothrow_move_constructible = std::is_nothrow_move_constructible_v; + +template +using variant = libremidi_variant_alias::variant; + +template +using slow_variant = libremidi_variant_alias::variant; + +template +using variant_element = libremidi_variant_alias::variant_alternative; +template +using variant_element_t = libremidi_variant_alias::variant_alternative_t; + +using libremidi_variant_alias::operator==; +using libremidi_variant_alias::operator!=; +using libremidi_variant_alias::operator<; +using libremidi_variant_alias::operator>; +using libremidi_variant_alias::operator<=; +using libremidi_variant_alias::operator>=; + +// using boost::variant2::in_place; +// using libremidi_variant_alias::in_place; +using libremidi_variant_alias::get; +using libremidi_variant_alias::get_if; +using libremidi_variant_alias::in_place_index; +using libremidi_variant_alias::in_place_type; +using libremidi_variant_alias::visit; +} + #if __has_include() && defined(LIBREMIDI_USE_NI_MIDI2) #define LIBREMIDI_NI_MIDI2_COMPAT 1 #endif @@ -62,3 +147,9 @@ using midi_bytes = std::vector; #else #define LIBREMIDI_INLINE #endif + +#if __cpp_contracts >= 202502L + #define LIBREMIDI_PRECONDITION(...) pre(__VA_ARGS__) +#else + #define LIBREMIDI_PRECONDITION(...) +#endif diff --git a/include/libremidi/configurations.hpp b/include/libremidi/configurations.hpp index b872f41..9af9fba 100644 --- a/include/libremidi/configurations.hpp +++ b/include/libremidi/configurations.hpp @@ -1,24 +1,90 @@ #pragma once +#include #include #include #include #include +#include #include #include #include #include +#include +#include #include #include #include +#include #include #include #include +#include -namespace libremidi +#include +NAMESPACE_LIBREMIDI { - +struct unspecified_configuration +{ +}; struct dummy_configuration { }; + +using input_api_configuration = libremidi_variant_alias::variant< + unspecified_configuration, dummy_configuration, alsa_raw_input_configuration, + alsa_raw_ump::input_configuration, alsa_seq::input_configuration, + alsa_seq_ump::input_configuration, coremidi_input_configuration, + coremidi_ump::input_configuration, emscripten_input_configuration, jack_input_configuration, + kbd_input_configuration, kdmapi::input_configuration, + libremidi::net::dgram_input_configuration, libremidi::net_ump::dgram_input_configuration, + pipewire_input_configuration, winmidi::input_configuration, winmm_input_configuration, + winuwp_input_configuration, jack_ump::input_configuration, pipewire_ump::input_configuration, + android::input_configuration, libremidi::API>; + +using output_api_configuration = libremidi_variant_alias::variant< + unspecified_configuration, dummy_configuration, alsa_raw_output_configuration, + alsa_raw_ump::output_configuration, alsa_seq::output_configuration, + alsa_seq_ump::output_configuration, coremidi_output_configuration, + coremidi_ump::output_configuration, emscripten_output_configuration, jack_output_configuration, + kdmapi::output_configuration, libremidi::net::dgram_output_configuration, + libremidi::net_ump::dgram_output_configuration, pipewire_output_configuration, + winmidi::output_configuration, winmm_output_configuration, winuwp_output_configuration, + jack_ump::output_configuration, pipewire_ump::output_configuration, + android::output_configuration, libremidi::API>; + +using observer_api_configuration = libremidi_variant_alias::variant< + unspecified_configuration, dummy_configuration, alsa_raw_observer_configuration, + alsa_raw_ump::observer_configuration, alsa_seq::observer_configuration, + alsa_seq_ump::observer_configuration, coremidi_observer_configuration, + coremidi_ump::observer_configuration, emscripten_observer_configuration, + jack_observer_configuration, kdmapi::observer_configuration, + libremidi::net::dgram_observer_configuration, libremidi::net_ump::dgram_observer_configuration, + pipewire_observer_configuration, winmidi::observer_configuration, winmm_observer_configuration, + winuwp_observer_configuration, jack_ump::observer_configuration, + pipewire_ump::observer_configuration, android::observer_configuration, libremidi::API>; + +LIBREMIDI_EXPORT +libremidi::API midi_api(const input_api_configuration& conf); +LIBREMIDI_EXPORT +libremidi::API midi_api(const output_api_configuration& conf); +LIBREMIDI_EXPORT +libremidi::API midi_api(const observer_api_configuration& conf); + +void set_client_name(auto&& conf, std::string name) +{ + std::visit([&](auto& impl) { + if constexpr (requires { impl.client_name; }) + impl.client_name = std::move(name); + }, conf); +} + +std::optional client_name(const auto& conf) noexcept +{ + return std::visit([&](auto& impl) -> std::optional { + if constexpr (requires { impl.client_name; }) + return impl.client_name; + return std::nullopt; + }, conf); +} } diff --git a/include/libremidi/defaults.hpp b/include/libremidi/defaults.hpp index 6d380dc..f50e267 100644 --- a/include/libremidi/defaults.hpp +++ b/include/libremidi/defaults.hpp @@ -1,14 +1,14 @@ #pragma once #include #include +#include #include #include #include -#include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in; class midi_out; @@ -16,22 +16,22 @@ class observer; //! Get the default midi in configuration object for a given API LIBREMIDI_EXPORT -std::any midi_in_configuration_for(libremidi::API); +input_api_configuration midi_in_configuration_for(libremidi::API); //! Get the default midi out configuration object for a given API LIBREMIDI_EXPORT -std::any midi_out_configuration_for(libremidi::API); +output_api_configuration midi_out_configuration_for(libremidi::API); //! Get the default observer configuration object for a given API LIBREMIDI_EXPORT -std::any observer_configuration_for(libremidi::API); +observer_api_configuration observer_configuration_for(libremidi::API); //! Get a matching midi in configuration object for a given observer instance. LIBREMIDI_EXPORT -std::any midi_in_configuration_for(const libremidi::observer&); +input_api_configuration midi_in_configuration_for(const libremidi::observer&); //! Get a matching midi out configuration object for a given observer instance. LIBREMIDI_EXPORT -std::any midi_out_configuration_for(const libremidi::observer&); +output_api_configuration midi_out_configuration_for(const libremidi::observer&); //! Get the default port for midi input (if any) in a given API. //! Note that this will only return a hardware / physical port. @@ -47,15 +47,15 @@ namespace midi1 { //! Get a default configuration for creating a MIDI 1 input LIBREMIDI_EXPORT -std::any in_default_configuration(); +input_api_configuration in_default_configuration(); //! Get a default configuration for creating a MIDI 1 output LIBREMIDI_EXPORT -std::any out_default_configuration(); +output_api_configuration out_default_configuration(); //! Get a default configuration for creating a MIDI 1 observer LIBREMIDI_EXPORT -std::any observer_default_configuration(); +observer_api_configuration observer_default_configuration(); //! Get a default MIDI 1 input port for the default API of the system inline std::optional in_default_port() noexcept @@ -74,15 +74,15 @@ namespace midi2 { //! Get a default configuration for creating a MIDI 2 input LIBREMIDI_EXPORT -std::any in_default_configuration(); +input_api_configuration in_default_configuration(); //! Get a default configuration for creating a MIDI 2 output LIBREMIDI_EXPORT -std::any out_default_configuration(); +output_api_configuration out_default_configuration(); //! Get a default configuration for creating a MIDI 2 observer LIBREMIDI_EXPORT -std::any observer_default_configuration(); +observer_api_configuration observer_default_configuration(); //! Get a default MIDI 2 input port for the default API of the system inline std::optional in_default_port() noexcept diff --git a/include/libremidi/detail/conversion.hpp b/include/libremidi/detail/conversion.hpp index d627c30..eab7f13 100644 --- a/include/libremidi/detail/conversion.hpp +++ b/include/libremidi/detail/conversion.hpp @@ -7,8 +7,251 @@ #include #include #include -namespace libremidi + +#include + +NAMESPACE_LIBREMIDI { +struct cmidi2_error_domain : public stdx::error_domain +{ +public: + constexpr cmidi2_error_domain() noexcept + : error_domain{{0x636d696469325f5fULL, 0x636f6e7665727400ULL}} + { + } + + stdx::string_ref name() const noexcept override { return "cmidi2_conversion"; } + + bool equivalent(const stdx::error& lhs, const stdx::error& rhs) const noexcept override + { + if (lhs.domain() == rhs.domain()) + return error_cast(lhs) + == error_cast(rhs); + + return false; + } + + stdx::string_ref message(const stdx::error& e) const noexcept override + { + const auto status = error_cast(e); + switch (status) + { + case CMIDI2_CONVERSION_RESULT_OK: + return "Success"; + case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE: + return "Destination buffer is too small"; + case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX: + return "Invalid System Exclusive message format"; + case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE: + return "Invalid Data Entry, RPN, or NRPN sequence"; + case CMIDI2_CONVERSION_RESULT_INVALID_STATUS: + return "Invalid or unsupported MIDI status byte"; + case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7: + return "Incomplete 7-bit System Exclusive message"; + case CMIDI2_CONVERSION_RESULT_INVALID_INPUT: + return "Invalid input data length or content"; + default: + return "Unknown cmidi2 conversion error"; + } + } +}; + +inline stdx::error from_cmidi2_result(cmidi2_midi_conversion_result ret) noexcept +{ + static constexpr cmidi2_error_domain domain{}; + // We explicitly map OK to success (0 value error) just in case, + // though usually error{} is default constructed for success. + if (ret == CMIDI2_CONVERSION_RESULT_OK) + return stdx::error{}; + + return {ret, domain}; +} +} + +LIBREMIDI_STATIC void cmidi2_reverse(int64_t v, cmidi2_ump* output) +{ + union + { + uint32_t u[2]; + int64_t i; + } e0, e1; + e0.i = v; + e1.u[0] = e0.u[1]; + e1.u[1] = e0.u[0]; + memcpy(output, &e1, 8); +} + +LIBREMIDI_STATIC auto u7_to_u16(uint8_t in) -> uint16_t +{ + static constexpr auto ratio = float(std::numeric_limits::max()) / 127.f; + return std::clamp(std::round(in * ratio), 0.f, float(std::numeric_limits::max())); +}; + +LIBREMIDI_STATIC auto u7_to_u32(uint8_t in) -> uint32_t +{ + static constexpr auto ratio = double(std::numeric_limits::max()) / 127.; + return std::clamp(std::round(in * ratio), 0., double(std::numeric_limits::max())); +}; + +LIBREMIDI_STATIC auto u16_to_u32(uint16_t in) -> uint32_t +{ + static constexpr auto ratio = double(std::numeric_limits::max()) + / double(std::numeric_limits::max()); + return std::clamp(std::round(in * ratio), 0., double(std::numeric_limits::max())); +}; + +LIBREMIDI_STATIC bool +cmidi2_midi1_channel_voice_to_midi2(const uint8_t* bytes, std::size_t sz, cmidi2_ump* output) +{ + if (sz < 2) + return false; + if (sz > 3) + return false; + const auto status = libremidi::message_type(bytes[0] & 0xF0); + const int channel = bytes[0] & 0x0F; + const int group = 0; + switch (status) + { + case libremidi::message_type::NOTE_OFF: { + const int64_t msg + = cmidi2_ump_midi2_note_off(group, channel, bytes[1], 0, u7_to_u16(bytes[2]), 0); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::NOTE_ON: { + const int64_t msg + = cmidi2_ump_midi2_note_on(group, channel, bytes[1], 0, u7_to_u16(bytes[2]), 0); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::POLY_PRESSURE: { + const int64_t msg = cmidi2_ump_midi2_paf(group, channel, bytes[1], u7_to_u32(bytes[2])); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::CONTROL_CHANGE: { + const int64_t msg = cmidi2_ump_midi2_cc(group, channel, bytes[1], u7_to_u32(bytes[2])); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::PROGRAM_CHANGE: { + const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, bytes[1], 0, 0); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::AFTERTOUCH: { + const int64_t msg = cmidi2_ump_midi2_caf(group, channel, u7_to_u32(bytes[1])); + cmidi2_reverse(msg, output); + break; + } + case libremidi::message_type::PITCH_BEND: { + const auto pb = bytes[1] + bytes[2] * 0x80; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, u16_to_u32(pb)); + cmidi2_reverse(msg, output); + break; + } + default: + return false; + } + + return true; +} + +LIBREMIDI_STATIC bool +cmidi2_ump_upgrade_midi1_channel_voice_to_midi2(const cmidi2_ump* input, cmidi2_ump* output) +{ + if (cmidi2_ump_get_message_type(input) != CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL) + return false; + + const auto group = cmidi2_ump_get_group(input); + const auto channel = cmidi2_ump_get_channel(input); + const auto status = cmidi2_ump_get_status_code(input); + const auto b1 = cmidi2_ump_get_midi1_byte2(input); + const auto b2 = cmidi2_ump_get_midi1_byte3(input); + + switch (status) + { + // Not in midi 1: + case CMIDI2_STATUS_PER_NOTE_RCC: + case CMIDI2_STATUS_PER_NOTE_ACC: + case CMIDI2_STATUS_RPN: + case CMIDI2_STATUS_NRPN: + case CMIDI2_STATUS_RELATIVE_RPN: + case CMIDI2_STATUS_RELATIVE_NRPN: + case CMIDI2_STATUS_PER_NOTE_PITCH_BEND: + case CMIDI2_STATUS_PER_NOTE_MANAGEMENT: + return false; + + case CMIDI2_STATUS_NOTE_OFF: { + const int64_t msg = cmidi2_ump_midi2_note_off(group, channel, b1, 0, u7_to_u16(b2), 0); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_NOTE_ON: { + const int64_t msg = cmidi2_ump_midi2_note_on(group, channel, b1, 0, u7_to_u16(b2), 0); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_PAF: { + const int64_t msg = cmidi2_ump_midi2_paf(group, channel, b1, u7_to_u32(b2)); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_CC: { + int64_t msg = cmidi2_ump_midi2_cc(group, channel, b1, u7_to_u32(b2)); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_PROGRAM: { + const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, b1, 0, 0); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_CAF: { + const int64_t msg = cmidi2_ump_midi2_caf(group, channel, u7_to_u32(b1)); + cmidi2_reverse(msg, output); + break; + } + case CMIDI2_STATUS_PITCH_BEND: { + const auto pb = b1 + b2 * 0x80; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, u16_to_u32(pb)); + cmidi2_reverse(msg, output); + break; + } + } + + return true; +} + +NAMESPACE_LIBREMIDI +{ +LIBREMIDI_STATIC libremidi::message midi1_from_ump(libremidi::ump u) +{ + libremidi::message ret; + ret.bytes.resize(4); + if (auto n = cmidi2_convert_single_ump_to_midi1((uint8_t*)ret.bytes.data(), 4, u.data)) + { + ret.timestamp = u.timestamp; + ret.bytes.resize(n); + } + else + { + ret.bytes.clear(); + } + return ret; +} + +LIBREMIDI_STATIC libremidi::ump ump_from_midi1(libremidi::message u) +{ + libremidi::ump ret; + if (!u.bytes.empty()) + { + ret.timestamp = u.timestamp; + cmidi2_midi1_channel_voice_to_midi2(u.bytes.data(), u.bytes.size(), ret.data); + } + return ret; +} + struct midi1_to_midi2 { stdx::error @@ -20,11 +263,27 @@ struct midi1_to_midi2 context.ump = ump; context.ump_num_bytes = sizeof(ump); context.ump_proceeded_bytes = 0; + context.skip_delta_time = true; - if (auto res = cmidi2_convert_midi1_to_ump(&context); res != CMIDI2_CONVERSION_RESULT_OK) - return std::errc::invalid_argument; - - return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp); + switch (auto err = cmidi2_convert_midi1_to_ump(&context)) + { + case CMIDI2_CONVERSION_RESULT_OK: { + // FIXME handle sysex here + if (auto n = context.ump_proceeded_bytes; n > 0) + return on_ump(context.ump, context.ump_proceeded_bytes / 4, timestamp); + else + return std::errc::no_message; + } + case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE: + case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX: + case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE: + case CMIDI2_CONVERSION_RESULT_INVALID_STATUS: + case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7: + case CMIDI2_CONVERSION_RESULT_INVALID_INPUT: + return from_cmidi2_result(err); + default: + return std::errc::operation_not_supported; + } } cmidi2_midi_conversion_context context = [] { @@ -32,20 +291,40 @@ struct midi1_to_midi2 cmidi2_midi_conversion_context_initialize(&tmp); return tmp; }(); + uint32_t ump[65536 / 4]; }; struct midi2_to_midi1 { - stdx::error - convert(const uint32_t* message, std::size_t /* size */, int64_t timestamp, auto on_midi) + stdx::error convert(const uint32_t* message, std::size_t size, int64_t timestamp, auto on_midi) { - auto n - = cmidi2_convert_single_ump_to_midi1(midi, sizeof(midi), const_cast(message)); - if (n > 0) - return on_midi(midi, n, timestamp); - else - return std::errc::no_buffer_space; + context.midi1 = static_cast(midi); + context.midi1_num_bytes = sizeof(midi); + context.midi1_proceeded_bytes = 0; + context.ump = const_cast(message); + context.ump_num_bytes = size * sizeof(uint32_t); + context.ump_proceeded_bytes = 0; + context.skip_delta_time = true; + + switch (auto err = cmidi2_convert_ump_to_midi1(&context)) + { + case CMIDI2_CONVERSION_RESULT_OK: { + if (auto n = context.midi1_proceeded_bytes; n > 0) + return on_midi(midi, n, timestamp); + else + return std::errc::no_message; + } + case CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE: + case CMIDI2_CONVERSION_RESULT_INVALID_SYSEX: + case CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE: + case CMIDI2_CONVERSION_RESULT_INVALID_STATUS: + case CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7: + case CMIDI2_CONVERSION_RESULT_INVALID_INPUT: + return from_cmidi2_result(err); + default: + return std::errc::operation_not_supported; + } } cmidi2_midi_conversion_context context = [] { @@ -53,6 +332,7 @@ struct midi2_to_midi1 cmidi2_midi_conversion_context_initialize(&tmp); return tmp; }(); + uint8_t midi[65536]; }; diff --git a/include/libremidi/detail/memory.hpp b/include/libremidi/detail/memory.hpp index d398c42..02ddc27 100644 --- a/include/libremidi/detail/memory.hpp +++ b/include/libremidi/detail/memory.hpp @@ -1,9 +1,11 @@ #pragma once +#include + #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { template struct deleter diff --git a/include/libremidi/detail/midi_api.hpp b/include/libremidi/detail/midi_api.hpp index 2e5ccab..3225da4 100644 --- a/include/libremidi/detail/midi_api.hpp +++ b/include/libremidi/detail/midi_api.hpp @@ -5,7 +5,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_api { @@ -23,14 +23,7 @@ public: { return std::errc::function_not_supported; } - virtual stdx::error set_client_name(std::string_view) - { - return std::errc::function_not_supported; - } - virtual stdx::error set_port_name(std::string_view) - { - return std::errc::function_not_supported; - } + virtual stdx::error set_port_name(std::string_view) { return std::errc::function_not_supported; } virtual stdx::error close_port() = 0; diff --git a/include/libremidi/detail/midi_in.hpp b/include/libremidi/detail/midi_in.hpp index 29f7307..7f88b87 100644 --- a/include/libremidi/detail/midi_in.hpp +++ b/include/libremidi/detail/midi_in.hpp @@ -1,10 +1,10 @@ #pragma once #include -#include -#include #include +#include +#include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_in_api : public midi_api { diff --git a/include/libremidi/detail/midi_out.hpp b/include/libremidi/detail/midi_out.hpp index 7966a14..74a5b3d 100644 --- a/include/libremidi/detail/midi_out.hpp +++ b/include/libremidi/detail/midi_out.hpp @@ -6,7 +6,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { class midi_out_api : public midi_api diff --git a/include/libremidi/detail/midi_stream_decoder.hpp b/include/libremidi/detail/midi_stream_decoder.hpp index b69fc6b..cea4644 100644 --- a/include/libremidi/detail/midi_stream_decoder.hpp +++ b/include/libremidi/detail/midi_stream_decoder.hpp @@ -1,16 +1,18 @@ #pragma once +#include +#include #include -#include +#include + #include #include -#include #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static inline int64_t system_ns() noexcept +LIBREMIDI_STATIC int64_t system_ns() noexcept { namespace clk = std::chrono; return clk::duration_cast(clk::steady_clock::now().time_since_epoch()).count(); @@ -29,7 +31,7 @@ struct timestamp_backend_info bool has_samples{}; }; -template +template struct input_state_machine_base { const Configuration& configuration; @@ -107,12 +109,12 @@ struct input_state_machine : input_state_machine_base { message.bytes.clear(); message.timestamp = {}; - state = main; + m_state = main; } bool has_finished_sysex(std::span bytes) const noexcept { - return (((bytes.front() == 0xF0) || (state == in_sysex)) && (bytes.back() == 0xF7)); + return (((bytes.front() == 0xF0) || (m_state == in_sysex)) && (bytes.back() == 0xF7)); } // Function to process a byte stream which may contain multiple successive @@ -139,22 +141,22 @@ private: void on_bytes_multi_segmented( const message_callback& cb, std::span bytes, int64_t timestamp) { - int64_t nBytes = bytes.size(); - int64_t iByte = 0; + int64_t n_bytes = bytes.size(); + int64_t i_byte = 0; const bool finished_sysex = has_finished_sysex(bytes); - switch (state) + switch (m_state) { case in_sysex: { return on_continue_sysex(cb, bytes, finished_sysex); } case main: { - while (iByte < nBytes) + while (i_byte < n_bytes) { int64_t size = 1; // We are expecting that the next byte in the packet is a status // byte. - const auto status = bytes[iByte]; + const auto status = bytes[i_byte]; if (!(status & 0x80)) break; @@ -170,17 +172,17 @@ private: if (configuration.ignore_sysex) { size = 0; - iByte = nBytes; + i_byte = n_bytes; } else { - size = nBytes - iByte; + size = n_bytes - i_byte; } - if (bytes[nBytes - 1] != 0xF7) + if (bytes[n_bytes - 1] != 0xF7) { // We know per CoreMIDI API there can't be anything else in this packet - state = in_sysex; + m_state = in_sysex; message.assign(bytes.begin(), bytes.begin() + size); message.timestamp = timestamp; return; @@ -192,7 +194,7 @@ private: if (configuration.ignore_timing) { size = 0; - iByte += 2; + i_byte += 2; } else { @@ -209,7 +211,7 @@ private: if (configuration.ignore_timing) { size = 0; - iByte += 1; + i_byte += 1; } else { @@ -222,7 +224,7 @@ private: if (configuration.ignore_sensing) { size = 0; - iByte += 1; + i_byte += 1; } else { @@ -238,14 +240,14 @@ private: // Now process the actual bytes of the message if (size > 0) { - auto begin = bytes.begin() + iByte; + auto begin = bytes.begin() + i_byte; message.assign(begin, begin + size); message.timestamp = timestamp; cb(std::move(message)); message.clear(); - iByte += size; + i_byte += size; } } } @@ -256,7 +258,7 @@ private: const message_callback& cb, std::span bytes, bool finished_sysex) { if (finished_sysex) - state = main; + m_state = main; if (configuration.ignore_sysex) { @@ -283,7 +285,7 @@ private: // SYSEX start case 0xF0: { if (!finished_sysex) - state = in_sysex; + m_state = in_sysex; if (!this->configuration.ignore_sysex) { @@ -328,7 +330,7 @@ private: return; const bool finished_sysex = has_finished_sysex(bytes); - switch (state) + switch (m_state) { case in_sysex: return on_continue_sysex(cb, bytes, finished_sysex); @@ -346,7 +348,7 @@ private: { main, in_sysex - } state{main}; + } m_state{main}; }; } @@ -413,22 +415,20 @@ private: on_bytes_segmented(const ump_callback& cb, std::span bytes, int64_t timestamp) { // Filter according to message type - switch(cmidi2_ump_get_message_type(bytes.data())) + switch (cmidi2_ump_get_message_type(bytes.data())) { - case CMIDI2_MESSAGE_TYPE_UTILITY: - { + case CMIDI2_MESSAGE_TYPE_UTILITY: { // All the utility messages are about timing if (this->configuration.ignore_timing) return; break; } - case CMIDI2_MESSAGE_TYPE_SYSTEM: - { + case CMIDI2_MESSAGE_TYPE_SYSTEM: { if (this->configuration.ignore_timing) { auto status = cmidi2_ump_get_system_message_byte2(bytes.data()); - switch(status) + switch (status) { case CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE: case CMIDI2_SYSTEM_STATUS_SONG_POSITION: @@ -437,22 +437,33 @@ private: } } - if (this->configuration.ignore_sensing) - { - auto status = cmidi2_ump_get_system_message_byte2(bytes.data()); - if(status == CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING) - return; - } - break; + if (this->configuration.ignore_sensing) + { + auto status = cmidi2_ump_get_system_message_byte2(bytes.data()); + if (status == CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING) + return; + } + break; } case CMIDI2_MESSAGE_TYPE_SYSEX7: - case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS: - { + case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS: { if (this->configuration.ignore_sysex) return; break; } + + case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL: { + if (this->configuration.midi1_channel_events_to_midi2) + { + libremidi::ump msg; + cmidi2_ump_upgrade_midi1_channel_voice_to_midi2(bytes.data(), msg.data); + msg.timestamp = timestamp; + cb(std::move(msg)); + return; + } + break; + } } libremidi::ump msg; diff --git a/include/libremidi/detail/observer.hpp b/include/libremidi/detail/observer.hpp index 3a7f138..f891e0d 100644 --- a/include/libremidi/detail/observer.hpp +++ b/include/libremidi/detail/observer.hpp @@ -1,13 +1,13 @@ #pragma once #include #include -#include #include +#include #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { class observer_api { diff --git a/include/libremidi/detail/semaphore.hpp b/include/libremidi/detail/semaphore.hpp index 255c45f..8cc0c56 100644 --- a/include/libremidi/detail/semaphore.hpp +++ b/include/libremidi/detail/semaphore.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include struct semaphore_pair_lock diff --git a/include/libremidi/detail/ump_stream.hpp b/include/libremidi/detail/ump_stream.hpp index 3dd3f2a..fe58af2 100644 --- a/include/libremidi/detail/ump_stream.hpp +++ b/include/libremidi/detail/ump_stream.hpp @@ -2,10 +2,9 @@ #include #include -#include #include -namespace libremidi +NAMESPACE_LIBREMIDI { enum class segmentation_error { diff --git a/include/libremidi/error.hpp b/include/libremidi/error.hpp index c038d3f..fbc1061 100644 --- a/include/libremidi/error.hpp +++ b/include/libremidi/error.hpp @@ -1,11 +1,15 @@ #pragma once #include +#if !defined(_MSC_VER) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #pragma GCC diagnostic ignored "-Wunused-parameter" +#endif #include +#if !defined(_MSC_VER) #pragma GCC diagnostic pop +#endif #include #include @@ -13,10 +17,15 @@ #if __has_include() && (__cpp_lib_source_location >= 201907L) #include -namespace libremidi { using source_location = std::source_location; } +NAMESPACE_LIBREMIDI +{ +using source_location = std::source_location; +} #else -namespace libremidi { -struct source_location { +NAMESPACE_LIBREMIDI +{ +struct source_location +{ static source_location current() noexcept { return {}; } int line() const noexcept { return -1; } int offset() const noexcept { return -1; } @@ -26,9 +35,9 @@ struct source_location { } #endif -namespace libremidi +NAMESPACE_LIBREMIDI { -inline auto from_errc(int ret) noexcept +inline auto from_errc(int64_t ret) noexcept { return static_cast(-ret); } @@ -40,8 +49,10 @@ inline auto from_errc(int ret) noexcept Note that class behaviour is undefined after a critical error (not a warning) is reported. */ -using midi_error_callback = std::function; -using midi_warning_callback = std::function; +using midi_error_callback + = std::function; +using midi_warning_callback + = std::function; } #if !defined(LIBREMIDI_LOG) diff --git a/include/libremidi/error_handler.hpp b/include/libremidi/error_handler.hpp index 9fb8c4e..d3fc2a4 100644 --- a/include/libremidi/error_handler.hpp +++ b/include/libremidi/error_handler.hpp @@ -2,13 +2,13 @@ #include #include #include -#include #include +#include #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct error_handler { @@ -58,6 +58,8 @@ struct error_handler }; // Needed as apple still doesn't support source_location in xcode 15.3 -#define libremidi_handle_error(config, str) error_impl(config.on_error, str, libremidi::source_location::current()) -#define libremidi_handle_warning(config, str) warning_impl(config.on_warning, str, libremidi::source_location::current()) +#define libremidi_handle_error(config, str) \ + error_impl(config.on_error, str, libremidi::source_location::current()) +#define libremidi_handle_warning(config, str) \ + warning_impl(config.on_warning, str, libremidi::source_location::current()) } diff --git a/include/libremidi/input_configuration.hpp b/include/libremidi/input_configuration.hpp index 574f02f..64666e2 100644 --- a/include/libremidi/input_configuration.hpp +++ b/include/libremidi/input_configuration.hpp @@ -6,7 +6,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { //! Specify how timestamps are handled in the system enum timestamp_mode @@ -123,5 +123,10 @@ struct ump_input_configuration uint32_t ignore_sensing : 1 = true; uint32_t timestamps : 3 = timestamp_mode::Absolute; + + //! Upscale MIDI 1 channel events to MIDI 2 channel events. + //! Note that this only has an effect on Windows with MIDI Services + //! as other platforms already do this by default. + uint32_t midi1_channel_events_to_midi2 : 1 = true; }; } diff --git a/include/libremidi/libremidi-c.cpp b/include/libremidi/libremidi-c.cpp index 1413f70..3062b4e 100644 --- a/include/libremidi/libremidi-c.cpp +++ b/include/libremidi/libremidi-c.cpp @@ -24,10 +24,10 @@ struct libremidi_midi_out_handle libremidi::midi_out self; }; -namespace libremidi +NAMESPACE_LIBREMIDI { -static void assign_error_callback(const auto& src, auto& dst) +LIBREMIDI_STATIC void assign_error_callback(const auto& src, auto& dst) { if (src.callback) { @@ -44,14 +44,14 @@ const char* libremidi_get_version(void) return LIBREMIDI_VERSION; } -void libremidi_available_midi1_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) +void libremidi_midi1_available_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) { if (!cb) return; libremidi::midi1::for_all_backends([=](auto b) { cb(ctx, b.API); }); } -void libremidi_available_midi2_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) +void libremidi_midi2_available_apis(void* ctx, void (*cb)(void* ctx, libremidi_api)) { if (!cb) return; @@ -123,6 +123,16 @@ int libremidi_midi_in_port_name(const libremidi_midi_in_port* port, const char** return 0; } +int libremidi_midi_in_port_handle(const libremidi_midi_in_port* port, uint64_t* handle) +{ + if (!port || !handle) + return -EINVAL; + + auto& p = *reinterpret_cast(port); + *handle = static_cast(p.port); + return 0; +} + int libremidi_midi_out_port_clone( const libremidi_midi_out_port* port, libremidi_midi_out_port** dst) { @@ -152,6 +162,16 @@ int libremidi_midi_out_port_name( return 0; } +int libremidi_midi_out_port_handle(const libremidi_midi_out_port* port, uint64_t* handle) +{ + if (!port || !handle) + return -EINVAL; + + auto& p = *reinterpret_cast(port); + *handle = static_cast(p.port); + return 0; +} + int libremidi_midi_observer_new( const libremidi_observer_configuration* c, libremidi_api_configuration* api, libremidi_midi_observer_handle** out) @@ -276,11 +296,12 @@ int libremidi_midi_in_new( = [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); }; } - if (c->on_midi1_message.callback) + if (c->version == libremidi_midi_configuration::MIDI1 && c->on_midi1_message.callback) conf.on_message = [cb = c->on_midi1_message](const libremidi::message& msg) { cb.callback(cb.context, msg.timestamp, msg.bytes.data(), msg.size()); }; - else if (c->on_midi1_raw_data.callback) + else if ( + c->version == libremidi_midi_configuration::MIDI1_RAW && c->on_midi1_raw_data.callback) { conf.on_raw_data = [cb = c->on_midi1_raw_data](std::span msg, int64_t ts) { cb.callback(cb.context, ts, msg.data(), msg.size()); @@ -319,11 +340,12 @@ int libremidi_midi_in_new( = [cb = c->get_timestamp](int64_t msg) { return cb.callback(cb.context, msg); }; } - if (c->on_midi2_message.callback) + if (c->version == libremidi_midi_configuration::MIDI2 && c->on_midi2_message.callback) conf.on_message = [cb = c->on_midi2_message](const libremidi::ump& msg) { cb.callback(cb.context, msg.timestamp, msg.data, msg.size()); }; - else if (c->on_midi2_raw_data.callback) + else if ( + c->version == libremidi_midi_configuration::MIDI2_RAW && c->on_midi2_raw_data.callback) { conf.on_raw_data = [cb = c->on_midi2_raw_data](std::span msg, int64_t ts) { cb.callback(cb.context, ts, msg.data(), msg.size()); @@ -470,7 +492,8 @@ int libremidi_midi_out_send_message( return res != stdx::error{} ? -EIO : 0; } -int libremidi_midi_out_send_ump(libremidi_midi_out_handle* out, const libremidi_midi2_symbol* msg, size_t sz) +int libremidi_midi_out_send_ump( + libremidi_midi_out_handle* out, const libremidi_midi2_symbol* msg, size_t sz) { if (!out || !msg || sz > std::numeric_limits::max()) return -EINVAL; diff --git a/include/libremidi/libremidi-c.h b/include/libremidi/libremidi-c.h index c43d7e1..bb9e60b 100644 --- a/include/libremidi/libremidi-c.h +++ b/include/libremidi/libremidi-c.h @@ -198,6 +198,10 @@ LIBREMIDI_EXPORT int libremidi_midi_in_port_name( const libremidi_midi_in_port* port, const char** name, size_t* len); +LIBREMIDI_EXPORT +int libremidi_midi_in_port_handle( + const libremidi_midi_in_port* port, uint64_t* handle); + LIBREMIDI_EXPORT int libremidi_midi_out_port_clone( const libremidi_midi_out_port* port, libremidi_midi_out_port** dst); @@ -209,6 +213,10 @@ LIBREMIDI_EXPORT int libremidi_midi_out_port_name( const libremidi_midi_out_port* port, const char** name, size_t* len); +LIBREMIDI_EXPORT +int libremidi_midi_out_port_handle( + const libremidi_midi_out_port* port, uint64_t* handle); + /// Observer API LIBREMIDI_EXPORT int libremidi_midi_observer_new( diff --git a/include/libremidi/libremidi.cpp b/include/libremidi/libremidi.cpp index 3cb79ae..fd1427e 100644 --- a/include/libremidi/libremidi.cpp +++ b/include/libremidi/libremidi.cpp @@ -7,7 +7,7 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { LIBREMIDI_INLINE @@ -53,14 +53,13 @@ LIBREMIDI_INLINE libremidi::API get_compiled_api_by_name(std::string_view name) midi2::for_all_backends([&](auto b) { apis.push_back(b.API); }); return apis; } + LIBREMIDI_INLINE -libremidi::API midi_api(const std::any& conf) +libremidi::API midi_api(const input_api_configuration& conf) { libremidi::API ret = libremidi::API::UNSPECIFIED; midi_any::for_all_backends([&](T) { - if (std::any_cast(&conf) - || std::any_cast(&conf) - || std::any_cast(&conf)) + if (get_if(&conf)) { ret = T::API; } @@ -68,9 +67,34 @@ libremidi::API midi_api(const std::any& conf) return ret; } LIBREMIDI_INLINE -std::any midi_in_configuration_for(libremidi::API api) +libremidi::API midi_api(const output_api_configuration& conf) { - std::any ret; + libremidi::API ret = libremidi::API::UNSPECIFIED; + midi_any::for_all_backends([&](T) { + if (get_if(&conf)) + { + ret = T::API; + } + }); + return ret; +} +LIBREMIDI_INLINE +libremidi::API midi_api(const observer_api_configuration& conf) +{ + libremidi::API ret = libremidi::API::UNSPECIFIED; + midi_any::for_all_backends([&](T) { + if (get_if(&conf)) + { + ret = T::API; + } + }); + return ret; +} + +LIBREMIDI_INLINE +input_api_configuration midi_in_configuration_for(libremidi::API api) +{ + input_api_configuration ret; midi_any::for_backend(api, [&](T) { using conf_type = typename T::midi_in_configuration; ret = conf_type{}; @@ -79,9 +103,9 @@ std::any midi_in_configuration_for(libremidi::API api) } LIBREMIDI_INLINE -std::any midi_out_configuration_for(libremidi::API api) +output_api_configuration midi_out_configuration_for(libremidi::API api) { - std::any ret; + output_api_configuration ret; midi_any::for_backend(api, [&](T) { using conf_type = typename T::midi_out_configuration; ret = conf_type{}; @@ -90,9 +114,9 @@ std::any midi_out_configuration_for(libremidi::API api) } LIBREMIDI_INLINE -std::any observer_configuration_for(libremidi::API api) +observer_api_configuration observer_configuration_for(libremidi::API api) { - std::any ret; + observer_api_configuration ret; midi_any::for_backend(api, [&](T) { using conf_type = typename T::midi_observer_configuration; ret = conf_type{}; @@ -101,13 +125,13 @@ std::any observer_configuration_for(libremidi::API api) } LIBREMIDI_INLINE -std::any midi_in_configuration_for(const libremidi::observer& obs) +input_api_configuration midi_in_configuration_for(const libremidi::observer& obs) { return midi_in_configuration_for(obs.get_current_api()); } LIBREMIDI_INLINE -std::any midi_out_configuration_for(const libremidi::observer& obs) +output_api_configuration midi_out_configuration_for(const libremidi::observer& obs) { // FIXME reuse context when meaningful, e.g. ALSA, JACK... return midi_out_configuration_for(obs.get_current_api()); @@ -144,19 +168,19 @@ catch (const std::exception& e) namespace midi1 { LIBREMIDI_INLINE -std::any in_default_configuration() +input_api_configuration in_default_configuration() { return midi_in_configuration_for(default_api()); } LIBREMIDI_INLINE -std::any out_default_configuration() +output_api_configuration out_default_configuration() { return midi_out_configuration_for(default_api()); } LIBREMIDI_INLINE -std::any observer_default_configuration() +observer_api_configuration observer_default_configuration() { return observer_configuration_for(default_api()); } @@ -165,19 +189,19 @@ std::any observer_default_configuration() namespace midi2 { LIBREMIDI_INLINE -std::any in_default_configuration() +input_api_configuration in_default_configuration() { return midi_in_configuration_for(default_api()); } LIBREMIDI_INLINE -std::any out_default_configuration() +output_api_configuration out_default_configuration() { return midi_out_configuration_for(default_api()); } LIBREMIDI_INLINE -std::any observer_default_configuration() +observer_api_configuration observer_default_configuration() { return observer_configuration_for(default_api()); } diff --git a/include/libremidi/libremidi.hpp b/include/libremidi/libremidi.hpp index 4085980..c354efa 100644 --- a/include/libremidi/libremidi.hpp +++ b/include/libremidi/libremidi.hpp @@ -58,6 +58,7 @@ */ #include +#include #include #include #include @@ -68,9 +69,8 @@ #include #include #endif -#include -namespace libremidi +NAMESPACE_LIBREMIDI { //! Main class for observing hotplug of MIDI 1.0 and 2.0 devices. //! The callbacks will be called whenever a device is added or removed @@ -84,7 +84,7 @@ public: //! such as jack_observer_configuration, winmm_observer_configuration, etc... //! * if no callbacks are passed, no secondary thread will be created unless absolutely necessary explicit observer(const observer_configuration& conf = {}) noexcept; - explicit observer(observer_configuration conf, std::any api_conf); + explicit observer(const observer_configuration& conf, observer_api_configuration api_conf); observer(const observer&) = delete; observer(observer&& other) noexcept; observer& operator=(const observer&) = delete; @@ -98,7 +98,7 @@ public: [[nodiscard]] std::vector get_output_ports() const noexcept; private: - std::unique_ptr impl_; + std::unique_ptr m_impl; }; //! Main class for receiving MIDI 1.0 and 2.0 messages. @@ -111,15 +111,15 @@ public: //! Construct a midi_in object with a configuration object for a specific MIDI 1 back-end //! see configuration.hpp for the available configuration types. //! An exception will be thrown if the requested back-end cannot be opened. - explicit midi_in(input_configuration conf, std::any api_conf); + explicit midi_in(const input_configuration& conf, const input_api_configuration& api_conf); //! Construct a midi_in object with the default MIDI 2 back-end for the platform - explicit midi_in(ump_input_configuration conf) noexcept; + explicit midi_in(const ump_input_configuration& conf) noexcept; //! Construct a midi_in object with a configuration object for a specific MIDI 2 back-end //! see configuration.hpp for the available configuration types. //! An exception will be thrown if the requested back-end cannot be opened. - explicit midi_in(ump_input_configuration conf, std::any api_conf); + explicit midi_in(const ump_input_configuration& conf, const input_api_configuration& api_conf); midi_in(const midi_in&) = delete; midi_in(midi_in&& other) noexcept; @@ -157,7 +157,7 @@ public: timestamp absolute_timestamp() const noexcept; private: - std::unique_ptr impl_; + std::unique_ptr m_impl; }; //! Main class for sending MIDI 1.0 and 2.0 messages. @@ -170,7 +170,7 @@ public: //! Construct a midi_out object with a configuration object for a specific back-end //! see configuration.hpp for the available configuration types. //! An exception will be thrown if the requested back-end cannot be opened. - explicit midi_out(output_configuration conf, std::any api_conf); + explicit midi_out(const output_configuration& conf, const output_api_configuration& api_conf); midi_out(const midi_out&) = delete; midi_out(midi_out&& other) noexcept; @@ -260,7 +260,7 @@ public: stdx::error schedule_ump(int64_t timestamp, const uint32_t* message, size_t size) const; private: - std::unique_ptr impl_; + std::unique_ptr m_impl; }; } diff --git a/include/libremidi/message.hpp b/include/libremidi/message.hpp index da8366e..182935d 100644 --- a/include/libremidi/message.hpp +++ b/include/libremidi/message.hpp @@ -2,13 +2,14 @@ #include #include +#include #include #include #if defined(__cpp_exceptions) #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { enum class message_type : uint8_t { @@ -121,12 +122,8 @@ struct message auto rbegin() noexcept { return bytes.rbegin(); } auto rend() noexcept { return bytes.rend(); } - bool uses_channel(int channel) const + bool uses_channel(int channel) const LIBREMIDI_PRECONDITION(channel > 0 && channel <= 16) { -#if defined(__cpp_exceptions) - if (channel <= 0 || channel > 16) - throw std::range_error("out of range"); -#endif return ((bytes[0] & 0xF) == channel - 1) && ((bytes[0] & 0xF0) != 0xF0); } @@ -250,12 +247,12 @@ struct meta_events int notated_32nd_notes_per_beat = 96) { int n = 1; - int powTwo = 0; + int pow_two = 0; while (n < denominator) { n <<= 1; - ++powTwo; + ++pow_two; } return { @@ -263,7 +260,7 @@ struct meta_events 0x58, 0x04, static_cast(numerator), - static_cast(powTwo), + static_cast(pow_two), static_cast(clocks_per_click), static_cast(notated_32nd_notes_per_beat)}; } diff --git a/include/libremidi/midi_in.cpp b/include/libremidi/midi_in.cpp index 29ba76a..1d4d9a3 100644 --- a/include/libremidi/midi_in.cpp +++ b/include/libremidi/midi_in.cpp @@ -7,10 +7,10 @@ #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static libremidi::ump_input_configuration +LIBREMIDI_STATIC_IMPLEMENTATION libremidi::ump_input_configuration convert_midi1_to_midi2_input_configuration(const input_configuration& base_conf) noexcept { libremidi::ump_input_configuration c2; @@ -32,7 +32,7 @@ convert_midi1_to_midi2_input_configuration(const input_configuration& base_conf) return c2; } -static libremidi::input_configuration +LIBREMIDI_STATIC_IMPLEMENTATION libremidi::input_configuration convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_conf) noexcept { libremidi::input_configuration c2; @@ -41,12 +41,11 @@ convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_c converter.convert( msg.bytes.data(), msg.bytes.size(), msg.timestamp, [cb](const uint32_t* ump, std::size_t n, int64_t ts) { - if(n >= 4) - { - libremidi::ump u{ump[0],ump[1],ump[2],ump[3]}; - u.timestamp = ts; - cb(std::move(u)); - } + libremidi::ump u{ump[0]}; + for (std::size_t i = 1; i < n && i < 4; i++) + u.data[i] = ump[i]; + u.timestamp = ts; + cb(std::move(u)); return stdx::error{}; }); }; @@ -60,15 +59,15 @@ convert_midi2_to_midi1_input_configuration(const ump_input_configuration& base_c return c2; } -static LIBREMIDI_INLINE std::unique_ptr -make_midi_in(auto base_conf, std::any api_conf, auto backends) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr +make_midi_in(auto base_conf, input_api_configuration api_conf, auto backends) { std::unique_ptr ptr; assert(base_conf.on_message || base_conf.on_raw_data); auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -80,7 +79,7 @@ make_midi_in(auto base_conf, std::any api_conf, auto backends) } /// MIDI 1 helpers -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi1_in(const input_configuration& base_conf) { for (const auto& api : available_apis()) @@ -117,8 +116,9 @@ make_midi1_in(const input_configuration& base_conf) return std::make_unique(input_configuration{}, dummy_configuration{}); } -static LIBREMIDI_INLINE std::unique_ptr -make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, libremidi::API api) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi1_in( + const input_configuration& base_conf, const input_api_configuration& api_conf, + libremidi::API api) { if (libremidi::is_midi1(api)) { @@ -132,14 +132,14 @@ make_midi1_in(const input_configuration& base_conf, const std::any& api_conf, li return {}; } -static LIBREMIDI_INLINE std::unique_ptr -make_midi1_in(const input_configuration& base_conf, const std::any& api_conf) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr +make_midi1_in(const input_configuration& base_conf, const input_api_configuration& api_conf) { - if (!api_conf.has_value()) + if (get_if(&api_conf)) { return make_midi1_in(base_conf); } - else if (auto api_p = std::any_cast(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -161,24 +161,24 @@ make_midi1_in(const input_configuration& base_conf, const std::any& api_conf) /// MIDI 1 constructors LIBREMIDI_INLINE midi_in::midi_in(const input_configuration& base_conf) noexcept - : impl_{make_midi1_in(base_conf)} + : m_impl{make_midi1_in(base_conf)} { } LIBREMIDI_INLINE -midi_in::midi_in(input_configuration base_conf, std::any api_conf) - : impl_{make_midi1_in(base_conf, api_conf)} +midi_in::midi_in(const input_configuration& base_conf, const input_api_configuration& api_conf) + : m_impl{make_midi1_in(base_conf, api_conf)} { - if (!impl_) + if (!m_impl) { error_handler e; e.libremidi_handle_error(base_conf, "Could not open midi in for the given api"); - impl_ = std::make_unique(input_configuration{}, dummy_configuration{}); + m_impl = std::make_unique(input_configuration{}, dummy_configuration{}); } } /// MIDI 2 helpers -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi2_in(const ump_input_configuration& base_conf) { for (const auto& api : available_ump_apis()) @@ -213,8 +213,9 @@ make_midi2_in(const ump_input_configuration& base_conf) return {}; } -static LIBREMIDI_INLINE std::unique_ptr make_midi2_in( - const ump_input_configuration& base_conf, const std::any& api_conf, libremidi::API api) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi2_in( + const ump_input_configuration& base_conf, const input_api_configuration& api_conf, + libremidi::API api) { if (is_midi2(api)) { @@ -229,14 +230,14 @@ static LIBREMIDI_INLINE std::unique_ptr make_midi2_in( return {}; } -static LIBREMIDI_INLINE std::unique_ptr -make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr +make_midi2_in(const ump_input_configuration& base_conf, const input_api_configuration& api_conf) { - if (!api_conf.has_value()) + if (get_if(&api_conf)) { return make_midi2_in(base_conf); } - else if (auto api_p = std::any_cast(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -257,45 +258,45 @@ make_midi2_in(const ump_input_configuration& base_conf, const std::any& api_conf } /// MIDI 2 constructors -LIBREMIDI_INLINE midi_in::midi_in(ump_input_configuration base_conf) noexcept - : impl_{make_midi2_in(base_conf)} +LIBREMIDI_INLINE midi_in::midi_in(const ump_input_configuration& base_conf) noexcept + : m_impl{make_midi2_in(base_conf)} { } LIBREMIDI_INLINE -midi_in::midi_in(ump_input_configuration base_conf, std::any api_conf) - : impl_{make_midi2_in(base_conf, api_conf)} +midi_in::midi_in(const ump_input_configuration& base_conf, const input_api_configuration& api_conf) + : m_impl{make_midi2_in(base_conf, api_conf)} { - if (!impl_) + if (!m_impl) { error_handler e; e.libremidi_handle_error(base_conf, "Could not open midi in for the given api"); - impl_ = std::make_unique(input_configuration{}, dummy_configuration{}); + m_impl = std::make_unique(input_configuration{}, dummy_configuration{}); } } LIBREMIDI_INLINE midi_in::~midi_in() = default; LIBREMIDI_INLINE midi_in::midi_in(midi_in&& other) noexcept - : impl_{std::move(other.impl_)} + : m_impl{std::move(other.m_impl)} { - other.impl_ + other.m_impl = std::make_unique(input_configuration{}, dummy_configuration{}); } LIBREMIDI_INLINE stdx::error midi_in::set_port_name(std::string_view portName) { - if(impl_->is_port_open()) - return impl_->set_port_name(portName); + if (m_impl->is_port_open()) + return m_impl->set_port_name(portName); return std::errc::not_connected; } LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept { - this->impl_ = std::move(other.impl_); - other.impl_ + this->m_impl = std::move(other.m_impl); + other.m_impl = std::make_unique(input_configuration{}, dummy_configuration{}); return *this; } @@ -303,23 +304,26 @@ LIBREMIDI_INLINE midi_in& midi_in::operator=(midi_in&& other) noexcept LIBREMIDI_INLINE libremidi::API midi_in::get_current_api() const noexcept { - return impl_->get_current_api(); + return m_impl->get_current_api(); } LIBREMIDI_INLINE stdx::error midi_in::open_port(const input_port& port, std::string_view portName) { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (port.api != get_current_api()) + return std::errc::invalid_argument; + + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - if (impl_->is_port_open()) + if (m_impl->is_port_open()) return std::errc::operation_not_supported; - auto ret = impl_->open_port(port, portName); + auto ret = m_impl->open_port(port, portName); if (ret == stdx::error{}) { - impl_->connected_ = true; - impl_->port_open_ = true; + m_impl->connected_ = true; + m_impl->port_open_ = true; } return ret; } @@ -327,28 +331,28 @@ stdx::error midi_in::open_port(const input_port& port, std::string_view portName LIBREMIDI_INLINE stdx::error midi_in::open_virtual_port(std::string_view portName) { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - if (impl_->is_port_open()) + if (m_impl->is_port_open()) return std::errc::operation_not_supported; - auto ret = impl_->open_virtual_port(portName); + auto ret = m_impl->open_virtual_port(portName); if (ret == stdx::error{}) - impl_->port_open_ = true; + m_impl->port_open_ = true; return ret; } LIBREMIDI_INLINE stdx::error midi_in::close_port() { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - auto ret = impl_->close_port(); + auto ret = m_impl->close_port(); - impl_->connected_ = false; - impl_->port_open_ = false; + m_impl->connected_ = false; + m_impl->port_open_ = false; return ret; } @@ -356,18 +360,18 @@ stdx::error midi_in::close_port() LIBREMIDI_INLINE bool midi_in::is_port_open() const noexcept { - return impl_->is_port_open(); + return m_impl->is_port_open(); } LIBREMIDI_INLINE bool midi_in::is_port_connected() const noexcept { - return impl_->is_port_connected(); + return m_impl->is_port_connected(); } LIBREMIDI_INLINE int64_t midi_in::absolute_timestamp() const noexcept { - return impl_->absolute_timestamp(); + return m_impl->absolute_timestamp(); } } diff --git a/include/libremidi/midi_out.cpp b/include/libremidi/midi_out.cpp index 82b3b7a..205f369 100644 --- a/include/libremidi/midi_out.cpp +++ b/include/libremidi/midi_out.cpp @@ -8,14 +8,14 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { -static LIBREMIDI_INLINE std::unique_ptr -make_midi_out_impl(auto base_conf, std::any api_conf) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr +make_midi_out_impl(auto base_conf, output_api_configuration api_conf) { std::unique_ptr ptr; auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -23,11 +23,12 @@ make_midi_out_impl(auto base_conf, std::any api_conf) return false; }; std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends); - std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends); + if (!ptr) + std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends); return ptr; } -static LIBREMIDI_INLINE std::unique_ptr +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi_out(const output_configuration& base_conf) { for (const auto& api : available_apis()) @@ -57,14 +58,14 @@ make_midi_out(const output_configuration& base_conf) return std::make_unique(output_configuration{}, dummy_configuration{}); } -static LIBREMIDI_INLINE std::unique_ptr -make_midi_out(const output_configuration& base_conf, const std::any& api_conf) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr +make_midi_out(const output_configuration& base_conf, const output_api_configuration& api_conf) { - if (!api_conf.has_value()) + if (get_if(&api_conf)) { return make_midi_out(base_conf); } - else if (auto api_p = std::any_cast(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -85,35 +86,35 @@ make_midi_out(const output_configuration& base_conf, const std::any& api_conf) } LIBREMIDI_INLINE midi_out::midi_out(const output_configuration& base_conf) noexcept - : impl_{make_midi_out(base_conf)} + : m_impl{make_midi_out(base_conf)} { } LIBREMIDI_INLINE -midi_out::midi_out(output_configuration base_conf, std::any api_conf) - : impl_{make_midi_out(base_conf, api_conf)} +midi_out::midi_out(const output_configuration& base_conf, const output_api_configuration& api_conf) + : m_impl{make_midi_out(base_conf, api_conf)} { - if (!impl_) + if (!m_impl) { error_handler e; e.libremidi_handle_error(base_conf, "Could not open midi out for the given api"); - impl_ = std::make_unique(output_configuration{}, dummy_configuration{}); + m_impl = std::make_unique(output_configuration{}, dummy_configuration{}); } } LIBREMIDI_INLINE midi_out::~midi_out() = default; LIBREMIDI_INLINE midi_out::midi_out(midi_out&& other) noexcept - : impl_{std::move(other.impl_)} + : m_impl{std::move(other.m_impl)} { - other.impl_ + other.m_impl = std::make_unique(output_configuration{}, dummy_configuration{}); } LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept { - this->impl_ = std::move(other.impl_); - other.impl_ + this->m_impl = std::move(other.m_impl); + other.m_impl = std::make_unique(output_configuration{}, dummy_configuration{}); return *this; } @@ -121,8 +122,8 @@ LIBREMIDI_INLINE midi_out& midi_out::operator=(midi_out&& other) noexcept LIBREMIDI_INLINE stdx::error midi_out::set_port_name(std::string_view portName) const { - if(impl_->is_port_open()) - return impl_->set_port_name(portName); + if (m_impl->is_port_open()) + return m_impl->set_port_name(portName); return std::errc::not_connected; } @@ -130,23 +131,26 @@ stdx::error midi_out::set_port_name(std::string_view portName) const LIBREMIDI_INLINE libremidi::API midi_out::get_current_api() const noexcept { - return impl_->get_current_api(); + return m_impl->get_current_api(); } LIBREMIDI_INLINE stdx::error midi_out::open_port(const output_port& port, std::string_view portName) const { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (port.api != get_current_api()) + return std::errc::invalid_argument; + + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - if (impl_->is_port_open()) + if (m_impl->is_port_open()) return std::errc::operation_not_supported; - auto ret = impl_->open_port(port, portName); + auto ret = m_impl->open_port(port, portName); if (ret == stdx::error{}) { - impl_->connected_ = true; - impl_->port_open_ = true; + m_impl->connected_ = true; + m_impl->port_open_ = true; } return ret; } @@ -154,40 +158,40 @@ stdx::error midi_out::open_port(const output_port& port, std::string_view portNa LIBREMIDI_INLINE stdx::error midi_out::open_virtual_port(std::string_view portName) const { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - if (impl_->is_port_open()) + if (m_impl->is_port_open()) return std::errc::operation_not_supported; - auto ret = impl_->open_virtual_port(portName); + auto ret = m_impl->open_virtual_port(portName); if (ret == stdx::error{}) - impl_->port_open_ = true; + m_impl->port_open_ = true; return ret; } LIBREMIDI_INLINE stdx::error midi_out::close_port() const { - if (auto err = impl_->is_client_open(); err != stdx::error{}) + if (auto err = m_impl->is_client_open(); err != stdx::error{}) return std::errc::not_connected; - auto ret = impl_->close_port(); - impl_->connected_ = false; - impl_->port_open_ = false; + auto ret = m_impl->close_port(); + m_impl->connected_ = false; + m_impl->port_open_ = false; return ret; } LIBREMIDI_INLINE bool midi_out::is_port_open() const noexcept { - return impl_->is_port_open(); + return m_impl->is_port_open(); } LIBREMIDI_INLINE bool midi_out::is_port_connected() const noexcept { - return impl_->is_port_connected(); + return m_impl->is_port_connected(); } LIBREMIDI_INLINE @@ -227,13 +231,13 @@ stdx::error midi_out::send_message(const unsigned char* message, size_t size) co assert(size > 0); #endif - return impl_->send_message(message, size); + return m_impl->send_message(message, size); } LIBREMIDI_INLINE int64_t midi_out::current_time() { - return impl_->current_time(); + return m_impl->current_time(); } LIBREMIDI_INLINE @@ -243,7 +247,7 @@ stdx::error midi_out::schedule_message(int64_t ts, const unsigned char* message, assert(size > 0); #endif - return impl_->schedule_message(ts, message, size); + return m_impl->schedule_message(ts, message, size); } LIBREMIDI_INLINE @@ -254,7 +258,7 @@ stdx::error midi_out::send_ump(const uint32_t* message, size_t size) const assert(size <= 4); #endif - return impl_->send_ump(message, size); + return m_impl->send_ump(message, size); } LIBREMIDI_INLINE stdx::error midi_out::send_ump(const libremidi::ump& message) const @@ -321,6 +325,6 @@ stdx::error midi_out::schedule_ump(int64_t ts, const uint32_t* message, size_t s assert(size > 0); #endif - return impl_->schedule_ump(ts, message, size); + return m_impl->schedule_ump(ts, message, size); } } diff --git a/include/libremidi/observer.cpp b/include/libremidi/observer.cpp index 442cffb..156cba9 100644 --- a/include/libremidi/observer.cpp +++ b/include/libremidi/observer.cpp @@ -5,17 +5,19 @@ #include #include -namespace libremidi +#include + +NAMESPACE_LIBREMIDI { -static LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_conf) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_observer(const auto& base_conf) { for (const auto& api : available_apis()) { try { - if (auto impl_ = make_observer(base_conf, observer_configuration_for(api))) - return impl_; + if (auto impl = make_observer(base_conf, observer_configuration_for(api))) + return impl; } catch (const std::exception& e) { @@ -26,8 +28,8 @@ static LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_co { try { - if (auto impl_ = make_observer(base_conf, observer_configuration_for(api))) - return impl_; + if (auto impl = make_observer(base_conf, observer_configuration_for(api))) + return impl; } catch (const std::exception& e) { @@ -37,11 +39,11 @@ static LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_co return std::make_unique(observer_configuration{}, dummy_configuration{}); } -LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf) +LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, observer_api_configuration api_conf) { std::unique_ptr ptr; auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::any_cast(&api_conf)) + if (auto conf = get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -49,17 +51,19 @@ LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, std::any api_conf) return false; }; std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi1::available_backends); - std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends); + if (!ptr) + std::apply([&](auto&&... b) { (from_api(b) || ...); }, midi2::available_backends); return ptr; } -LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_conf, std::any api_conf) +LIBREMIDI_INLINE std::unique_ptr +make_observer(const auto& base_conf, observer_api_configuration api_conf) { - if (!api_conf.has_value()) + if (get_if(&api_conf)) { return make_observer(base_conf); } - else if (auto api_p = std::any_cast(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -80,32 +84,33 @@ LIBREMIDI_INLINE std::unique_ptr make_observer(auto base_conf, std } LIBREMIDI_INLINE observer::observer(const observer_configuration& base_conf) noexcept - : impl_{make_observer(base_conf)} + : m_impl{make_observer(base_conf)} { } -LIBREMIDI_INLINE observer::observer(observer_configuration base_conf, std::any api_conf) - : impl_{make_observer(base_conf, api_conf)} +LIBREMIDI_INLINE +observer::observer(const observer_configuration& base_conf, observer_api_configuration api_conf) + : m_impl{make_observer(base_conf, std::move(api_conf))} { - if (!impl_) + if (!m_impl) { error_handler e; e.libremidi_handle_error(base_conf, "Could not open observer for the given api"); - impl_ = std::make_unique(observer_configuration{}, dummy_configuration{}); + m_impl = std::make_unique(observer_configuration{}, dummy_configuration{}); } } LIBREMIDI_INLINE observer::observer(observer&& other) noexcept - : impl_{std::move(other.impl_)} + : m_impl{std::move(other.m_impl)} { - other.impl_ = std::make_unique( + other.m_impl = std::make_unique( observer_configuration{}, dummy_configuration{}); } LIBREMIDI_INLINE observer& observer::operator=(observer&& other) noexcept { - this->impl_ = std::move(other.impl_); - other.impl_ = std::make_unique( + this->m_impl = std::move(other.m_impl); + other.m_impl = std::make_unique( observer_configuration{}, dummy_configuration{}); return *this; } @@ -116,18 +121,18 @@ observer::~observer() = default; LIBREMIDI_INLINE libremidi::API observer::get_current_api() const noexcept { - return impl_->get_current_api(); + return m_impl->get_current_api(); } LIBREMIDI_INLINE std::vector observer::get_input_ports() const noexcept { - return impl_->get_input_ports(); + return m_impl->get_input_ports(); } LIBREMIDI_INLINE std::vector observer::get_output_ports() const noexcept { - return impl_->get_output_ports(); + return m_impl->get_output_ports(); } } diff --git a/include/libremidi/observer_configuration.hpp b/include/libremidi/observer_configuration.hpp index 0e427a3..1572272 100644 --- a/include/libremidi/observer_configuration.hpp +++ b/include/libremidi/observer_configuration.hpp @@ -1,59 +1,8 @@ #pragma once -#include -#include +#include -#include -#include - -namespace libremidi +NAMESPACE_LIBREMIDI { -using client_handle = std::uint64_t; -using port_handle = std::uint64_t; - -struct LIBREMIDI_EXPORT port_information -{ - // Handle to the client object: - - // ALSA Raw: unused - // ALSA Seq: snd_seq_t* - // CoreMIDI: MidiClientRef - // WebMIDI: unused - // JACK: jack_client_t* - // PipeWire: unused - // WinMM: unused - // WinUWP: unused - client_handle client = static_cast(-1); - - // ALSA Raw: { uint16_t card, device, sub, padding; } - // ALSA Seq: { uint32_t client, uint32_t port; } - // CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t) - // WebMIDI: unused - // JACK: jack_port_id_t - // PipeWire: port.id - // WinMM: unset, identified by port_name - // WinUWP: unused - port_handle port = static_cast(-1); - - std::string manufacturer{}; - std::string device_name{}; - std::string port_name{}; - std::string display_name{}; - - bool operator==(const port_information& other) const noexcept = default; - std::strong_ordering operator<=>(const port_information& other) const noexcept = default; -}; - -struct input_port : port_information -{ - bool operator==(const input_port& other) const noexcept = default; - std::strong_ordering operator<=>(const input_port& other) const noexcept = default; -}; -struct output_port : port_information -{ - bool operator==(const output_port& other) const noexcept = default; - std::strong_ordering operator<=>(const output_port& other) const noexcept = default; -}; - using input_port_callback = std::function; using output_port_callback = std::function; struct observer_configuration @@ -72,6 +21,9 @@ struct observer_configuration // Observe software (virtual) ports if the API provides it uint32_t track_virtual : 1 = false; + // Observe network ports if the API provides it + uint32_t track_network : 1 = false; + // Observe any port - some systems have other weird port types than hw / sw, this covers them uint32_t track_any : 1 = false; diff --git a/include/libremidi/output_configuration.hpp b/include/libremidi/output_configuration.hpp index 44e3373..10e776c 100644 --- a/include/libremidi/output_configuration.hpp +++ b/include/libremidi/output_configuration.hpp @@ -3,7 +3,7 @@ #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct output_configuration { diff --git a/include/libremidi/port_comparison.hpp b/include/libremidi/port_comparison.hpp new file mode 100644 index 0000000..4d7ef25 --- /dev/null +++ b/include/libremidi/port_comparison.hpp @@ -0,0 +1,565 @@ +#pragma once +#include +#if __has_include() + #include +namespace libremidi +{ +template +using temp_map_type = boost::container::flat_map; +} +#else + #include +namespace libremidi +{ +template +using temp_map_type = std::map; +} +#endif +#include +#include +#include +#include + +namespace libremidi +{ +// Simple compare-the-name equality. Useful for saving / reloading +struct port_name_equal +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return lhs.api == rhs.api && lhs.port_name == rhs.port_name; + } +}; +struct port_name_less +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return std::tie(lhs.api, lhs.port_name) < std::tie(rhs.api, rhs.port_name); + } +}; + +// Compare an existing port with others. +// Note that on multiple APIs, this comparison method is only valid as long +// as no device gets connected / disconnected, as the port identifier / handle +// is sadly just the index in the list of devices returned by the OS, which will +// change as soon as a device changes. +// Thus, it should only ever be used to compare devices between a group obtained +// from a single call to get_input_ports / get_output_ports or in situations where we can +// be sure that there is no hot-plugging. +struct port_identity_equal +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return lhs.api == rhs.api && lhs.port == rhs.port; + } +}; +struct port_identity_less +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return std::tie(lhs.api, lhs.port) < std::tie(rhs.api, rhs.port); + } +}; + +struct port_exactly_equal +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return lhs.api == rhs.api && lhs.container == rhs.container && lhs.device == rhs.device + && lhs.port == rhs.port && lhs.manufacturer == rhs.manufacturer + && lhs.product == rhs.product && lhs.serial == rhs.serial + && lhs.device_name == rhs.device_name && lhs.port_name == rhs.port_name + && lhs.display_name == rhs.display_name; + } +}; + +struct port_mostly_equal +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return lhs.api == rhs.api && lhs.container == rhs.container && lhs.device == rhs.device + && lhs.port == rhs.port && lhs.manufacturer == rhs.manufacturer + && lhs.product == rhs.product && lhs.serial == rhs.serial + && lhs.device_name == rhs.device_name && lhs.port_name == rhs.port_name; + } +}; + +struct port_heuristic_matcher +{ + // Configuration for weights + static constexpr int W_HARDWARE_ID = 1000; // Unique HW IDs (Container, Device) + static constexpr int W_SERIAL = 800; // Serial Number + static constexpr int W_NAME_EXACT = 400; // Display/Port/Device Names + static constexpr int W_METADATA = 100; // Manufacturer/Product + static constexpr int W_HANDLE = 50; // Port Index/Handle + + // Penalties for mismatches when data is present in both but differs + static constexpr int P_HARDWARE_MISMATCH = -2000; + static constexpr int P_SERIAL_MISMATCH = -1000; + static constexpr int P_NAME_MISMATCH = -100; + static constexpr int P_PRODUCT_MISMATCH = -10; + + struct match_score + { + int score = 0; + bool api_mismatch = false; + + bool is_match() const { return !api_mismatch && score > 0; } + constexpr auto operator<=>(const match_score& other) const noexcept = default; + }; + + static inline constexpr bool chars_equal_ignore_case(char lhs, char rhs) { + if(lhs >= 'A' && lhs <= 'Z') + lhs -= 'A' - 'a'; + if(rhs >= 'A' && rhs <= 'Z') + rhs -= 'A' - 'a'; + + return lhs == rhs; + } + + static inline double fuzzy_match_name(std::string_view s1, std::string_view s2) + { + const size_t len1 = s1.size(); + const size_t len2 = s2.size(); + if (len1 == 0 && len2 == 0) + return 1.0; + if (len1 == 0 || len2 == 0) + return 0; + if (len1 > 1024 || len2 > 1024) + return 0; + if (len1 > len2) + return fuzzy_match_name(s2, s1); + + auto col = (size_t*)alloca(sizeof(size_t) * (len1 + 1)); + std::fill_n(col, len1 + 1, 0); + + // Initialize first column (0, 1, 2... len1) + for (size_t i = 0; i <= len1; ++i) col[i] = i; + + // Compute Levenshtein distance + for (size_t j = 1; j <= len2; ++j) { + size_t prev_diag = col[0]; + col[0] = j; + + for (size_t i = 1; i <= len1; ++i) { + size_t prev_col = col[i]; + size_t cost = chars_equal_ignore_case(s1[i - 1], s2[j - 1]) ? 0 : 1; + + col[i] = std::min({ + col[i] + 1, // Deletion + col[i - 1] + 1, // Insertion + prev_diag + cost // Substitution + }); + + prev_diag = prev_col; + } + } + + const size_t distance = col[len1]; + const size_t max_len = std::max(len1, len2); + + if (max_len == 0) + return 1.0; + + return 1.0 - (static_cast(distance) / static_cast(max_len)); + } + + match_score calculate(const port_information& target, const port_information& candidate) const + { + match_score result; + + // 1. API Mismatch + // It is impossible for a port to be the same if the API is different. + if (target.api != libremidi::API::UNSPECIFIED) + { + if (target.api != candidate.api) + { + result.api_mismatch = true; + result.score = std::numeric_limits::min(); + return result; + } + } + + // 2. Hardware Identifiers & Serial Number + // High value, but unreliable presence. + switch(target.api) + { + case libremidi::API::COREMIDI: + case libremidi::API::COREMIDI_UMP: + case libremidi::API::WINDOWS_MM: + { + score_variant(result.score, target.device, candidate.device, W_HARDWARE_ID, P_HARDWARE_MISMATCH); + break; + } + default: + break; + } + + score_string(result.score, target.manufacturer, candidate.manufacturer, W_METADATA, P_HARDWARE_MISMATCH); + score_string(result.score, target.product, candidate.product, W_METADATA, P_HARDWARE_MISMATCH); + score_string(result.score, target.serial, candidate.serial, W_SERIAL, P_SERIAL_MISMATCH); + + // 3. Names + // We accumulate score for every name that matches. + score_string(result.score, target.display_name, candidate.display_name, W_NAME_EXACT, P_NAME_MISMATCH); + score_string(result.score, target.port_name, candidate.port_name, W_NAME_EXACT, P_NAME_MISMATCH); + score_string(result.score, target.device_name, candidate.device_name, W_NAME_EXACT, P_NAME_MISMATCH); + + // 4. Port Handle (Index) + // Only check if it's not the default -1. + // We rely on this primarily as a tie-breaker if names/hardware IDs are identical + // (e.g. two identical controllers plugged in). + if (target.port != static_cast(-1)) + { + if (target.port == candidate.port) + { + result.score += W_HANDLE; + } + } + + return result; + } + +private: + void score_string(int& score, std::string_view target_s, std::string_view cand_s, int reward, int penalty) const + { + // If the target doesn't know this info, we can't judge. Skip. + if (target_s.empty()) + return; + + const double res = fuzzy_match_name(cand_s, target_s); + if (res >= 0.5) + { + score += res * reward; + } + else if (!cand_s.empty()) + { + // If candidate value is empty, it's just missing info, not necessarily a mismatch. + score += penalty; + } + } + + // Helper for std::variant fields (device / container identifiers) + template + void score_variant(int& score, const T& target_v, const T& cand_v, int reward, int penalty) const + { + if (holds_alternative(target_v)) + return; + + // For those we want an exact search + if (target_v == cand_v) + { + score += reward; + } + else if (!holds_alternative(cand_v)) + { + // Candidate has a specific ID, and it differs from Target's specific ID. + score += penalty; + } + } +}; + +template +struct port_search_result +{ + const T* port = nullptr; + int score = 0; + bool found = false; +}; + +template +inline port_search_result find_closest_port(const T& target, std::span candidates) +{ + port_heuristic_matcher matcher{}; + + const T* best_match = nullptr; + port_heuristic_matcher::match_score best_score; + best_score.score = -1; + + for (const auto& candidate : candidates) + { + port_heuristic_matcher::match_score current = matcher.calculate(target, candidate); + + if (current.is_match() && current > best_score) + { + best_score = current; + best_match = &candidate; + } + } + + if (best_match) + return { best_match, best_score.score, true }; + + return { nullptr, 0, false }; +} + +template +inline std::vector +optimistic_serialized_port_lookup(const T& target, std::span ports) +{ + if (ports.empty()) + return {}; + + // 1. Look for an exact match on all fields + std::vector candidates; + for (auto& candidate : ports) + { + if (port_mostly_equal{}(target, candidate)) + candidates.push_back(&candidate); + } + switch (candidates.size()) + { + case 0: { + break; + } + case 1: { + return candidates; + } + default: { + // If we have an exact match return it + for (auto* candidate : candidates) + if (target.display_name == candidate->display_name) + return {candidate}; + + // Else return the entire bunch as we have no way to differentiate + return candidates; + } + } + + // 2. Heuristics + + // Look for candidates in the same API + candidates.clear(); + for (auto& candidate : ports) + { + if (target.api != libremidi::API::UNSPECIFIED) + { + if (target.api == candidate.api) + { + // Port was set, let's give a high trust to this + if (target.port != static_cast(-1)) + { + if (target.port == candidate.port) + { + if (target.port_name == candidate.port_name + && target.device_name == candidate.device_name) + { + // We can be 99% confident it's the right one + candidates.push_back(&candidate); + } + else if ( + port_heuristic_matcher::fuzzy_match_name(target.port_name, candidate.port_name) + >= 0.8 + && port_heuristic_matcher::fuzzy_match_name( + target.device_name, candidate.device_name) + >= 0.8) + { + candidates.push_back(&candidate); + } + else + { + // Same API & same port, different port_name & device_name: + // very likely it's the wrong one + continue; + } + } + } + else + { +#define do_compare(MEMBER) \ + { \ + ok &= target.MEMBER == candidate.MEMBER || target.MEMBER.empty(); \ + if (!ok) \ + continue; \ + } + bool ok = true; + + // These three are compared later + // do_compare(display_name); + // do_compare(container); + // do_compare(device); + do_compare(manufacturer); + do_compare(product); + do_compare(serial); + do_compare(device_name); + do_compare(port_name); + +#undef do_compare + // If we got there it's a very good candidate + candidates.push_back(&candidate); + } + } + } + } + + switch (candidates.size()) + { + case 0: { + break; + } + case 1: { + // One candidate in the same API + return {candidates[0]}; + } + default: { + // Let's look if we have one that has the same container + if (!get_if(&target.container) + && !get_if(&target.device) + && !target.display_name.empty()) + { + for (auto* candidate : candidates) + if (target.container == candidate->container && target.device == candidate->device + && target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.container == candidate->container && target.device == candidate->device) + return {candidate}; + for (auto* candidate : candidates) + if (target.container == candidate->container) + return {candidate}; + for (auto* candidate : candidates) + if (target.device == candidate->device) + return {candidate}; + } + else if ( + !get_if(&target.container) + && !target.display_name.empty()) + { + for (auto* candidate : candidates) + if (target.container == candidate->container + && target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.container == candidate->container) + return {candidate}; + } + else if ( + !get_if(&target.device) + && !target.display_name.empty()) + { + for (auto* candidate : candidates) + if (target.device == candidate->device && target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.device == candidate->device) + return {candidate}; + } + // Else return them all as we have no way to differentiate + return candidates; + } + } + + // Look for candidates in different APIs. + // Here most informations are different, so we only do a fuzzy match + candidates.clear(); + libremidi::temp_map_type ranked_candidates; + for (auto& candidate : ports) + { + int score = 0; + if (!target.port_name.empty() && !candidate.port_name.empty()) + { + float res = port_heuristic_matcher::fuzzy_match_name(target.port_name, candidate.port_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.device_name.empty() && !candidate.device_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.device_name, candidate.device_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.display_name.empty() && !candidate.display_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.display_name, candidate.display_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.display_name.empty() && !candidate.port_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.display_name, candidate.port_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.port_name.empty() && !candidate.display_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.port_name, candidate.display_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.display_name.empty() && !candidate.device_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.display_name, candidate.device_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.device_name.empty() && !candidate.display_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.device_name, candidate.display_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.manufacturer.empty() && !candidate.manufacturer.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.manufacturer, candidate.manufacturer); + if (res > 0.7) + { + score += 3 * res; + } + } + if (!target.product.empty() && !candidate.product.empty()) + { + float res = port_heuristic_matcher::fuzzy_match_name(target.product, candidate.product); + if (res > 0.7) + { + score += 5 * res; + } + } + if (!target.serial.empty() && !candidate.serial.empty()) + { + float res = port_heuristic_matcher::fuzzy_match_name(target.serial, candidate.serial); + if (res > 0.7) + { + score += 10 * res; + } + } + if (score > 0) + ranked_candidates[score] = &candidate; + } + + candidates.clear(); + candidates.reserve(ranked_candidates.size()); + for (auto [score, candidate] : ranked_candidates) + candidates.insert(candidates.begin(), candidate); + return candidates; +} +} diff --git a/include/libremidi/port_information.hpp b/include/libremidi/port_information.hpp new file mode 100644 index 0000000..9edeffb --- /dev/null +++ b/include/libremidi/port_information.hpp @@ -0,0 +1,164 @@ +#pragma once +#include +#include +#include +#include + +#include +#include +#include + +namespace libremidi +{ + +struct LIBREMIDI_EXPORT port_information +{ + // Compat + using port_type = libremidi::transport_type; + + /// Which API is this port for. port_information objects are in general + /// not useable for different APIs than the API of the observer that created them. + libremidi::API api{}; + + /// Handle to the API client object if the API provides one + // Android: unused + // ALSA Raw: unused + // ALSA Seq: snd_seq_t* + // CoreMIDI: MidiClientRef + // JACK: jack_client_t* + // PipeWire: unused // FIXME: pw_context? pw_main_loop? + // WebMIDI: unused + // WinMIDI: TODO + // WinMM: unused + // WinUWP: unused + client_handle client = static_cast(-1); + + /// Container identifier if the API provides one + // Android: unavailable + // ALSA: device id (std::string), e.g. ID_PATH as returned by udev: "pci-0000:00:14.0-usb-0:12:1.0" + // CoreMIDI: USBLocationID (int32_t) + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: ContainerID GUID (bit_cast to a winapi or winrt::GUID ; + // this is not the string but the binary representation). + // WinMM: unavailable + // WinUWP: unavailable + container_identifier container = libremidi_variant_alias::monostate{}; + + /// Device identifier if the API provides one + // Android: unavailable + // ALSA: sysfs path (std::string), e.g. "/sys/devices/pci0000:00/0000:00:02.2/0000:02:00.0/sound/card0/controlC0" + // CoreMIDI: USBVendorProduct (int32_t) + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: EndpointDeviceId (std::string), e.g. "\\?\swd#midisrv#midiu_ksa..." + // WinMM: MIDI{IN,OUT}CAPS mId / pId { uint16_t manufacturer_id, uint16_t product_id; } + // WinUWP: unavailable + device_identifier device = libremidi_variant_alias::monostate{}; + + /// Handle to the port identifier if the API provides one + // Android: index of the MIDI device in the list provided by the OS. + // ALSA Raw: bit_cast to struct { uint16_t card, device, sub, padding; }. + // ALSA Seq: bit_cast to struct { uint32_t client, port; } + // CoreMIDI: MidiObjectRef's kMIDIPropertyUniqueID (uint32_t) + // JACK: jack_port_id_t + // PipeWire: "port.id" (uint32_t) + // WebMIDI: index of the MIDI device in the list provided by the browser. + // WinMIDI: uint64_t terminal_block_number; (MidiGroupTerminalBlock::Number(), index is 1-based) + // WinMM: port index between 0 and midi{In,Out}GetNumDevs() + // WinUWP: index of the MIDI device in the list provided by the OS. + port_handle port = static_cast(-1); + + /// User-readable information + // Android: unavailable + // ALSA Raw: ID_VENDOR_FROM_DATABASE if provided by udev + // ALSA Seq: ID_VENDOR_FROM_DATABASE if provided by udev + // CoreMIDI: kMIDIPropertyManufacturer + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().ManufacturerName + // WinMM: unavailable + // WinUWP: unavailable + std::string manufacturer{}; + + // Android: unavailable + // ALSA Raw: ID_MODEL_FROM_DATABASE if provided by udev + // ALSA Seq: ID_MODEL_FROM_DATABASE if provided by udev + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().Name + // WinUWP: unavailable + std::string product{}; + + /// "Unique" serial number. Note that this is super unreliable - pretty + /// much no MIDI device manufacturer bothers with unique per-device serial number + /// unlike most USB devices. + // Android: unavailable + // ALSA Raw: ID_USB_SERIAL if provided by udev. + // ALSA Seq: ID_USB_SERIAL if provided by udev. + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: MidiEndpointDeviceInformation::GetTransportSuppliedInfo().SerialNumber + // WinUWP: unavailable + std::string serial{}; + + // Android: unavailable + // ALSA Raw: Name returned by snd_rawmidi_info_get_name + // ALSA Seq: Name returned by snd_seq_client_info_get_name + // CoreMIDI: kMIDIPropertyModel + // JACK: jack_get_client_name + // PipeWire: first part of port_alias, e.g. "A" in A:B + // WinMIDI: MidiEndpointDeviceInformation::Name + // WinMM: unavailable + // WinUWP: unavailable + std::string device_name{}; + + // Android: MidiDeviceInfo.getProperties().getString("name") + // ALSA Raw: Name returned by snd_rawmidi_info_get_subdevice_name + // ALSA Seq: Name returned by snd_seq_port_info_get_name + // CoreMIDI: kMIDIPropertyName + // JACK: jack_port_name + // PipeWire: "port.name" + // WinMIDI: MidiGroupTerminalBlock::Name + // WinMM: szPname + // WinUWP: DeviceInformation.Id() + std::string port_name{}; + + // CoreMIDI: kMIDIPropertyDisplayName + // WinUWP: DeviceInformation.Name() + // Otherwise: the closest to a unique name we can get + std::string display_name{}; + + /// Port type + // Android: unavailable + // ALSA Raw / Seq: available if udev is avaialble + // CoreMIDI: available + // JACK: unavailable + // PipeWire: unavailable + // WinMIDI: available + // WinMM: unavailable + // WinUWP: unavailable + port_type type = port_type::unknown; + + // Equality and comparison operators are deleted as there is not one + // single correct way to compare two port_information: + // in some cases it may be useful to only compare the names, while in other cases + // it is necessary to check whether this is the exact same low-level identifier. + // Thus, the end-user must define their own custom equality operators + // if using std:: containers or algorithms + bool operator==(const port_information& other) const noexcept = delete; + std::strong_ordering operator<=>(const port_information& other) const noexcept = delete; +}; + +struct input_port : port_information +{ + bool operator==(const input_port& other) const noexcept = delete; + std::strong_ordering operator<=>(const input_port& other) const noexcept = delete; +}; +struct output_port : port_information +{ + bool operator==(const output_port& other) const noexcept = delete; + std::strong_ordering operator<=>(const output_port& other) const noexcept = delete; +}; + +} diff --git a/include/libremidi/protocols/remote_control.hpp b/include/libremidi/protocols/remote_control.hpp new file mode 100644 index 0000000..8281509 --- /dev/null +++ b/include/libremidi/protocols/remote_control.hpp @@ -0,0 +1,858 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +static constexpr auto to_underlying(auto e) +{ + return static_cast>(e); +} + +// A clean-room reverse-engineered remote control protocol compatible with many hardware devices. +// Thanks https://github.com/NicoG60/TouchMCU ! +struct remote_control_protocol +{ + enum class device_type : uint8_t + { + logic_control = 0x10, + logic_control_xt = 0x11, + mackie_control = 0x14 + }; + + enum class command_to_device : uint8_t + { + device_query = 0x00, + host_connection_reply = 0x02, + + transport_click = 0x0A, + lcd_backlight_save = 0x0B, + touchless_movable_fader = 0x0C, + + faders_touch_sensitivity = 0x0E, + go_offline = 0x0F, + update_tc_display = 0x10, + update_assignment_display = 0x11, + update_lcd = 0x12, + firmware_version_request = 0x13, + version_reply = 0x14, + + firmware_update = 0x18, + + channel_meter_mode = 0x20, + global_lcd_meter_mode = 0x21, + + faders_to_minimum = 0x61, + all_leds_off = 0x62, + reset = 0x63, + }; + + enum class command_from_device : uint8_t + { + host_connection_query = 0x01, + host_connection_confirmation = 0x03, + host_connection_error = 0x04, + version_reply = 0x14, + }; + + enum class lcd_meter_mode : uint8_t + { + horizontal = 0x00, + vertical = 0x01, + }; + + enum class fader_sensitivity : uint8_t + { + sensitivity_0 = 0x00, + sensitivity_1 = 0x01, + sensitivity_2 = 0x02, + sensitivity_3 = 0x03, + sensitivity_4 = 0x04, + + sensitivity_default = sensitivity_3, + }; + + // CC Message: + // 0b0LMMVVVV + // L: toggle underneath LED + // MM: mode as led_ring_mode + // VVVV: value + enum class led_ring_mode : uint8_t + { + mode_0 = 0b00, // one led only + mode_1 = 0b01, // pan pot + mode_2 = 0b10, // fill leds from left + mode_3 = 0b11, // fill leds from middle + }; + + enum class pot : uint8_t + { + pot_0 = 0x00, + pot_1 = 0x01, + pot_2 = 0x02, + pot_3 = 0x03, + pot_4 = 0x04, + pot_5 = 0x05, + pot_6 = 0x06, + pot_7 = 0x07, + }; + + enum class fader : uint8_t + { + fader_0 = 0x00, + fader_1 = 0x01, + fader_2 = 0x02, + fader_3 = 0x03, + fader_4 = 0x04, + fader_5 = 0x05, + fader_6 = 0x06, + fader_7 = 0x07, + fader_master = 0x08, + }; + + // control changes + enum class mixer_control : uint8_t + { + // rotation: CC + // 0b00000001 : clockwise + // 0b01000001 : counter-clockwise + vpot_rotation_0 = 0x10 + 0x00, + vpot_rotation_1 = 0x10 + 0x01, + vpot_rotation_2 = 0x10 + 0x02, + vpot_rotation_3 = 0x10 + 0x03, + vpot_rotation_4 = 0x10 + 0x04, + vpot_rotation_5 = 0x10 + 0x05, + vpot_rotation_6 = 0x10 + 0x06, + vpot_rotation_7 = 0x10 + 0x07, + + external_control = 0x2E, + + // led ring: CC + vpot_led_0 = 0x30 + 0x00, + vpot_led_1 = 0x30 + 0x01, + vpot_led_2 = 0x30 + 0x02, + vpot_led_3 = 0x30 + 0x03, + vpot_led_4 = 0x30 + 0x04, + vpot_led_5 = 0x30 + 0x05, + vpot_led_6 = 0x30 + 0x06, + vpot_led_7 = 0x30 + 0x07, + + jog_wheel = 0x3C, + + timecode_digit_0 = 0x40 + 0x00, + timecode_digit_1 = 0x40 + 0x01, + timecode_digit_2 = 0x40 + 0x02, + timecode_digit_3 = 0x40 + 0x03, + timecode_digit_4 = 0x40 + 0x04, + timecode_digit_5 = 0x40 + 0x05, + timecode_digit_6 = 0x40 + 0x06, + timecode_digit_7 = 0x40 + 0x07, + timecode_digit_8 = 0x40 + 0x08, + timecode_digit_9 = 0x40 + 0x09, + + assignment_digit_0 = 0x4A, + assignment_digit_1 = 0x4B, + }; + + // note events + enum class mixer_command : uint8_t + { + vpot_click_0 = 0x20 + 0x00, + vpot_click_1 = 0x20 + 0x01, + vpot_click_2 = 0x20 + 0x02, + vpot_click_3 = 0x20 + 0x03, + vpot_click_4 = 0x20 + 0x04, + vpot_click_5 = 0x20 + 0x05, + vpot_click_6 = 0x20 + 0x06, + vpot_click_7 = 0x20 + 0x07, + + rec_0 = 0x00 + 0x00, + rec_1 = 0x00 + 0x01, + rec_2 = 0x00 + 0x02, + rec_3 = 0x00 + 0x03, + rec_4 = 0x00 + 0x04, + rec_5 = 0x00 + 0x05, + rec_6 = 0x00 + 0x06, + rec_7 = 0x00 + 0x07, + + solo_0 = 0x08 + 0x00, + solo_1 = 0x08 + 0x01, + solo_2 = 0x08 + 0x02, + solo_3 = 0x08 + 0x03, + solo_4 = 0x08 + 0x04, + solo_5 = 0x08 + 0x05, + solo_6 = 0x08 + 0x06, + solo_7 = 0x08 + 0x07, + + mute_0 = 0x10 + 0x00, + mute_1 = 0x10 + 0x01, + mute_2 = 0x10 + 0x02, + mute_3 = 0x10 + 0x03, + mute_4 = 0x10 + 0x04, + mute_5 = 0x10 + 0x05, + mute_6 = 0x10 + 0x06, + mute_7 = 0x10 + 0x07, + + sel_0 = 0x18 + 0x00, + sel_1 = 0x18 + 0x01, + sel_2 = 0x18 + 0x02, + sel_3 = 0x18 + 0x03, + sel_4 = 0x18 + 0x04, + sel_5 = 0x18 + 0x05, + sel_6 = 0x18 + 0x06, + sel_7 = 0x18 + 0x07, + + // TODO metering + assign_track = 0x28, + assign_send = 0x29, + assign_pan = 0x2A, + assign_plugin = 0x2B, + assign_eq = 0x2C, + assign_instrument = 0x2D, + + bank_left = 0x2E, + bank_right = 0x2F, + channel_left = 0x30, + channel_right = 0x31, + flip = 0x32, + global = 0x33, + + name_value_button = 0x34, + smpte_beats_button = 0x35, + + f1 = 0x36 + 0x00, + f2 = 0x36 + 0x01, + f3 = 0x36 + 0x02, + f4 = 0x36 + 0x03, + f5 = 0x36 + 0x04, + f6 = 0x36 + 0x05, + f7 = 0x36 + 0x06, + f8 = 0x36 + 0x07, + + midi_tracks = 0x3E, + inputs = 0x3F, + audio_tracks = 0x40, + audio_instruments = 0x41, + aux = 0x42, + busses = 0x43, + outputs = 0x44, + user = 0x45, + + shift = 0x46, + option = 0x47, + control = 0x48, + alt = 0x49, + + save = 0x50, + undo = 0x51, + cancel = 0x52, + enter = 0x53, + + markers = 0x54, + nudge = 0x55, + cycle = 0x56, + drop = 0x57, + replace = 0x58, + click = 0x59, + solo = 0x5a, + + rewind = 0x5b, + forward = 0x5c, + stop = 0x5d, + play = 0x5e, + record = 0x5f, + + up = 0x60, + down = 0x61, + left = 0x62, + right = 0x63, + zoom = 0x64, + scrub = 0x65, + + user_switch_1 = 0x66, + user_switch_2 = 0x67, + + fader_touched_0 = 0x68, + fader_touched_1 = 0x69, + fader_touched_2 = 0x6a, + fader_touched_3 = 0x6b, + fader_touched_4 = 0x6c, + fader_touched_5 = 0x6d, + fader_touched_6 = 0x6e, + fader_touched_7 = 0x6f, + fader_touched_master = 0x70, + + smpte_led = 0x71, + beats_led = 0x72, + rude_solo_led = 0x73, + + relay_click = 0x76, + }; + + template + using arr = std::array; + + device_type type = device_type::mackie_control; + + static libremidi::message make_command_impl(auto&&... data) + { + using namespace std; + libremidi::message m; + m.bytes.reserve((std::ssize(data) + ...)); + (m.bytes.insert(m.bytes.end(), begin(data), end(data)), ...); + return m; + } + + libremidi::message make_command(command_to_device c, auto&&... data) + { + using namespace std; + const auto type = to_underlying(this->type); + const auto cmd = to_underlying(c); + const uint8_t header[6]{0xF0, 0x00, 0x00, 0x66, type, cmd}; + const uint8_t footer[1]{0xF7}; + return make_command_impl(header, data..., footer); + } + + auto device_query() { return make_command(command_to_device::device_query); } + + auto response_to_challenge(arr<4> c) + { + arr<4> r; + + r[0] = 0x7F & (c[0] + (c[1] ^ 0x0A) - c[3]); + r[1] = 0x7F & ((c[2] >> 4) ^ (c[0] + c[3])); + r[2] = 0x7F & (c[3] - (c[2] << 2) ^ (c[0] | c[1])); + r[3] = 0x7F & (c[1] - c[2] + (0xF0 ^ (c[3] << 4))); + + return r; + } + + auto host_connection_reply(arr<7> serial, arr<4> challenge_code) + { + const auto res = response_to_challenge(challenge_code); + return make_command(command_to_device::host_connection_reply, serial, res); + } + + auto transport_click(bool enabled) + { + return make_command( + command_to_device::transport_click, arr<1>{uint8_t(enabled ? 0x01 : 0x00)}); + } + + auto lcd_backlight_save(uint8_t timeout) + { + // 0: instant off otherwise timeout in minutes + return make_command(command_to_device::lcd_backlight_save, arr<1>{timeout}); + } + + auto touchless_movable_fader(bool enabled) + { + return make_command( + command_to_device::touchless_movable_fader, arr<1>{uint8_t(enabled ? 0x01 : 0x00)}); + } + + auto faders_touch_sensitivity(uint8_t fader_id, fader_sensitivity sens) + { + return make_command( + command_to_device::faders_touch_sensitivity, arr<2>{fader_id, to_underlying(sens)}); + } + + auto go_offline() { return make_command(command_to_device::go_offline, arr<1>{0x7F}); } + + auto update_tc_display() + { + // FIXME 1 .. 10 + return make_command(command_to_device::update_tc_display, arr<10>{}); + } + + auto update_assignment_display() + { + // FIXME 1 .. 2 + return make_command(command_to_device::update_assignment_display, arr<2>{}); + } + + auto update_lcd(std::string_view txt, int pos) + { + // FIXME + if (pos < 0 || pos >= 112) + return libremidi::message{}; + + int len = int(std::ssize(txt)); + + if (len > (112 - pos)) + { + txt = txt.substr(0, 112 - pos); + len = 112 - pos; + } + + uint8_t buf[128]; + const int N = std::min(len, 112 - pos); + for (int i = 0; i < N; i++) + { + buf[i + pos] = charmap_lcd(txt[i]); + } + buf[55] = '\n'; + buf[111] = '\n'; + + uint8_t cmd_pos = pos; + + return make_command(command_to_device::update_lcd, arr<1>{cmd_pos}, std::span(buf + pos, len)); + } + + auto update_lcd(std::string_view txt) + { + uint8_t buf[112] = {}; + for (int i = 0; i < std::min(int(std::ssize(txt)), 112); i++) + { + buf[i] = charmap_lcd(txt[i]); + } + buf[55] = '\n'; + buf[111] = '\n'; + return make_command(command_to_device::update_lcd, arr<1>{0}, std::span(buf, 112)); + } + + auto firmware_version_request() + { + return make_command(command_to_device::firmware_version_request, arr<1>{0}); + } + + auto firmware_update(std::span firmware) + { + return make_command(command_to_device::firmware_update, firmware); + } + + auto channel_meter_mode(uint8_t fader_id, bool level_meter, bool peak_hold, bool signal_led) + { + uint8_t mode = 0; + + if (signal_led) + mode |= 0b1; + if (peak_hold) + mode |= 0b10; + if (level_meter) + mode |= 0b100; + + return make_command(command_to_device::channel_meter_mode, arr<2>{fader_id, mode}); + } + + auto global_lcd_meter_mode(lcd_meter_mode mode) + { + return make_command(command_to_device::global_lcd_meter_mode, arr<1>{to_underlying(mode)}); + } + + auto faders_to_minimum() { return make_command(command_to_device::faders_to_minimum); } + + auto all_leds_off() { return make_command(command_to_device::all_leds_off); } + + auto reset() { return make_command(command_to_device::reset); } + + static auto timecode(int hi, int mi, int si, int framei) + { + std::vector msg; + auto h = std::to_string(hi); + while (h.size() < 3) + h.insert(h.begin(), '0'); + auto m = std::to_string(mi); + while (m.size() < 2) + m.insert(m.begin(), '0'); + auto s = std::to_string(si); + while (s.size() < 2) + s.insert(s.begin(), '0'); + auto f = std::to_string(framei); + while (f.size() < 3) + f.insert(f.begin(), '0'); + + using ce = libremidi::channel_events; + msg.push_back(ce::control_change(1, 0x49, charmap_7segment(h[0]))); + msg.push_back(ce::control_change(1, 0x48, charmap_7segment(h[1]))); + msg.push_back(ce::control_change(1, 0x47, charmap_7segment(h[2]))); + + msg.push_back(ce::control_change(1, 0x46, charmap_7segment(m[0]))); + msg.push_back(ce::control_change(1, 0x45, charmap_7segment(m[1]))); + + msg.push_back(ce::control_change(1, 0x44, charmap_7segment(s[0]))); + msg.push_back(ce::control_change(1, 0x43, charmap_7segment(s[1]))); + + msg.push_back(ce::control_change(1, 0x42, charmap_7segment(f[0]))); + msg.push_back(ce::control_change(1, 0x41, charmap_7segment(f[1]))); + msg.push_back(ce::control_change(1, 0x40, charmap_7segment(f[2]))); + + return msg; + } + + static uint8_t charmap_7segment(char c, bool dot) + { + uint8_t res = charmap_7segment(c); + if (dot) + res |= 0b00100000; + return res; + }; + + static uint8_t charmap_7segment(char c) + { + // FIXME there are some more characters but what to map them to ? :) + if (c >= 'a' && c <= 'z') + return c - 'a' + 1; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 1; + else if (c >= '0' && c <= '9') + return c - '0' + 0x30; + else + switch (c) + { + case '[': + case '{': + return 0x1B; + case '\\': + case '~': // yen too ? + return 0x1C; + case ']': + case '}': + return 0x1D; + case '^': + return 0x1E; + case '_': + return 0x1F; + + case '!': + return 0x21; + case '"': + return 0x22; + case '#': + return 0x23; + case '$': + return 0x24; + case '%': + return 0x25; + case '&': + return 0x26; + case '\'': + return 0x27; + case '(': + return 0x28; + case ')': + return 0x29; + case '*': + return 0x2A; + case '+': + return 0x2B; + case ',': + return 0x2C; + case '-': + return 0x2D; + case '.': + return 0x2E; + case '/': + return 0x2F; + + case ':': + return 0x3A; + case ';': + return 0x3B; + case '<': + return 0x3C; + case '=': + return 0x3D; + case '>': + return 0x3E; + case '?': + return 0x3F; + + default: + return 0x00; + } + } + static uint8_t charmap_lcd(char c) + { + // FIXME there are some more characters but what to map them to ? :) + if (c >= 'a' && c <= 'z') + return c - 'a' + 0x61; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 0x41; + else if (c >= '0' && c <= '9') + return c - '0' + 0x30; + else + switch (c) + { + case '!': + return 0x21; + case '"': + return 0x22; + case '#': + return 0x23; + case '$': + return 0x24; + case '%': + return 0x25; + case '&': + return 0x26; + case '\'': + return 0x27; + case '(': + return 0x28; + case ')': + return 0x29; + case '*': + return 0x2A; + case '+': + return 0x2B; + case ',': + return 0x2C; + case '-': + return 0x2D; + case '.': + return 0x2E; + case '/': + return 0x2F; + + case ':': + return 0x3A; + case ';': + return 0x3B; + case '<': + return 0x3C; + case '=': + return 0x3D; + case '>': + return 0x3E; + case '?': + return 0x3F; + + case '@': + return 0x40; + case '[': + return 0x5B; + case '~': // Yen symbol... builtin mojibake? + return 0x5C; + case ']': + return 0x5D; + case '^': + return 0x5E; + case '_': + return 0x5F; + case '`': + return 0x60; + case '{': + return 0x7B; + case '|': + return 0x7C; + case '}': + return 0x7D; + case '\u000E': + return 0x7E; + case '\u000F': + return 0x7F; + default: + return c; // gives access to the bubble first row 0x00 > 0x0F + } + } +}; + +struct rcp_configuration +{ + //! How to send MIDI messages to the device. + //! Note: this function *will* be called from different thread, + //! thus it has to be thread-safe, for instance + //! by storing the message in an event queue. + std::function midi_out; + + std::function on_connected; + std::function on_command; + std::function on_control; + std::function on_fader; + + libremidi::midi_error_callback on_error{}; +}; + +struct remote_control_processor : libremidi::error_handler +{ + using rcp = libremidi::remote_control_protocol; + rcp_configuration configuration; + rcp impl; + + explicit remote_control_processor(rcp_configuration conf) + : configuration{std::move(conf)} + { + assert(configuration.midi_out); + + if (!configuration.on_error) + configuration.on_error = [](std::string_view s, auto&&...) { + std::fprintf(stderr, "libremidi: rcp error: %s\n", s.data()); + }; + + if (!configuration.on_connected) + configuration.on_connected + = [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_connected"); }; + + if (!configuration.on_command) + configuration.on_command + = [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_command"); }; + + if (!configuration.on_control) + configuration.on_control + = [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_control"); }; + + if (!configuration.on_fader) + configuration.on_fader + = [this](auto&&...) { libremidi_handle_error(configuration, "Unhandled on_fader"); }; + } + + void start() + { + current_state = waiting_for_query; + configuration.midi_out(impl.device_query()); + } + + void on_midi(const libremidi::message& message) + { + switch (message.get_message_type()) + { + case libremidi::message_type::SYSTEM_EXCLUSIVE: + if (auto N = message.size(); N >= 7) + { + const uint8_t* bytes = message.bytes.data(); + + // strip 0xF0 & 0xF7 + bytes += 1; + N -= 2; + + // Mackie manufacturer ID check + if (bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0x66) + { + impl.type = static_cast(bytes[3]); + + // strip header + bytes += 4; + N -= 4; + on_rcp_command(std::span(bytes, N)); + } + } + else + { + libremidi_handle_error(configuration, "Invalid sysex"); + } + break; + case libremidi::message_type::NOTE_ON: + configuration.on_command(static_cast(message[1]), message[2] > 0); + break; + case libremidi::message_type::NOTE_OFF: + break; + case libremidi::message_type::CONTROL_CHANGE: + configuration.on_control(static_cast(message[1]), message[2]); + break; + case libremidi::message_type::PITCH_BEND: { + uint16_t value = message.bytes[2] * 128 + message.bytes[1]; + configuration.on_fader(static_cast(uint8_t(message.get_channel() - 1)), value); + break; + } + default: + break; + } + } + + void on_rcp_command(std::span cmd) + { + if (cmd.empty()) + { + libremidi_handle_error(configuration, "on_rcp_command: empty command"); + return; + } + + auto command = static_cast(cmd[0]); + cmd = cmd.subspan(1); + switch (command) + { + case rcp::command_from_device::host_connection_query: { + if (cmd.size() == 11) + { + current_state = got_query; + + std::array serial; + std::array challenge; + std::copy_n(cmd.data(), 7, serial.begin()); + std::copy_n(cmd.data() + 7, 4, challenge.begin()); + + configuration.midi_out(impl.host_connection_reply(serial, challenge)); + } + else + libremidi_handle_error(configuration, "host_connection_query: invalid size"); + break; + } + case rcp::command_from_device::host_connection_confirmation: + current_state = connected; + configuration.on_connected(impl.type); + break; + case rcp::command_from_device::host_connection_error: + current_state = errored; + libremidi_handle_error(configuration, "host_connection_error"); + break; + case rcp::command_from_device::version_reply: { + // TODO + break; + } + default: + break; + } + } + + void update_timecode(int h, int m, int s, int f) + { + for (auto&& m : rcp::timecode(h, m, s, f)) + configuration.midi_out(std::move(m)); + } + + void update_lcd(std::string_view v) + { + auto res = impl.update_lcd(v); + if (!res.empty()) + configuration.midi_out(std::move(res)); + } + + void update_lcd(std::string_view v, int pos) + { + auto res = impl.update_lcd(v, pos); + if (!res.empty()) + configuration.midi_out(std::move(res)); + } + + void command(remote_control_protocol::mixer_command c, bool press) + { + using ce = libremidi::channel_events; + configuration.midi_out(ce::note_on(1, to_underlying(c), press ? 127 : 0)); + configuration.midi_out(ce::note_off(1, to_underlying(c), press ? 127 : 0)); + } + + void control(remote_control_protocol::mixer_control c, int value) + { + using ce = libremidi::channel_events; + configuration.midi_out(ce::control_change(1, to_underlying(c), value)); + } + + void fader(remote_control_protocol::fader c, uint16_t value) + { + int idx = to_underlying(c); + + using ce = libremidi::channel_events; + configuration.midi_out(ce::pitch_bend(idx + 1, value)); + } + + // State machine + enum + { + waiting_for_query, + got_query, + connected, + errored + } current_state{waiting_for_query}; +}; +} diff --git a/include/libremidi/reader.cpp b/include/libremidi/reader.cpp index 5beb5b1..cf59923 100644 --- a/include/libremidi/reader.cpp +++ b/include/libremidi/reader.cpp @@ -55,18 +55,18 @@ std::ostream& operator<<(std::ostream& s, const libremidi::message& m) } #endif -namespace libremidi +NAMESPACE_LIBREMIDI { namespace util { struct no_validator { - static inline bool validate_track([[maybe_unused]] const midi_track& track) { return true; } + static bool validate_track([[maybe_unused]] const midi_track& track) { return true; } }; struct validator { - static inline bool validate_track(const midi_track& track) + static bool validate_track(const midi_track& track) { if (track.empty()) { @@ -108,13 +108,13 @@ struct read_unchecked { // Read a MIDI-style variable-length integer (big-endian value in groups of 7 bits, // with top bit set to signify that another byte follows). - static inline void ensure_size( + static void ensure_size( [[maybe_unused]] const uint8_t* begin, [[maybe_unused]] const uint8_t* end, [[maybe_unused]] int64_t needed) { } - static inline uint32_t + static uint32_t read_variable_length(uint8_t const*& data, [[maybe_unused]] uint8_t const* end) { uint32_t result = 0; @@ -133,7 +133,7 @@ struct read_unchecked } } - static inline void read_bytes( + static void read_bytes( midi_bytes& buffer, uint8_t const*& data, [[maybe_unused]] const uint8_t* end, const std::size_t num) { @@ -142,20 +142,20 @@ struct read_unchecked buffer.push_back(*data++); } - static inline void read_bytes( + static void read_bytes( midi_bytes& buffer, uint8_t const*& data, [[maybe_unused]] const uint8_t* end, const int num) { read_bytes(buffer, data, end, static_cast(num)); } - static inline uint16_t read_uint16_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) + static uint16_t read_uint16_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) { uint16_t result = *data++ << 8u; result += *data++; return result; } - static inline uint32_t read_uint24_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) + static uint32_t read_uint24_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) { uint32_t result = *data++ << 16u; result += static_cast(*data++ << 8u); @@ -163,7 +163,7 @@ struct read_unchecked return result; } - static inline uint32_t read_uint32_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) + static uint32_t read_uint32_be(uint8_t const*& data, [[maybe_unused]] const uint8_t* end) { uint32_t result = *data++ << 24u; result += static_cast(*data++ << 16u); @@ -178,14 +178,14 @@ struct read_checked { // Read a MIDI-style variable-length integer (big-endian value in groups of 7 bits, // with top bit set to signify that another byte follows). - static inline void + static void ensure_size(const uint8_t* begin, const uint8_t* end, const std::size_t needed) { if (const auto available = static_cast(end - begin); available < needed) throw std::runtime_error("MIDI reader: not enough data to process"); } - static inline std::size_t read_variable_length(uint8_t const*& data, uint8_t const* end) + static std::size_t read_variable_length(uint8_t const*& data, uint8_t const* end) { std::size_t result = 0; while (true) @@ -204,26 +204,26 @@ struct read_checked } } - static inline void + static void read_bytes(midi_bytes& buffer, uint8_t const*& data, uint8_t const* end, const std::size_t num) { ensure_size(data, end, num); read_unchecked::read_bytes(buffer, data, end, num); } - static inline uint16_t read_uint16_be(uint8_t const*& data, uint8_t const* end) + static uint16_t read_uint16_be(uint8_t const*& data, uint8_t const* end) { ensure_size(data, end, 2); return read_unchecked::read_uint16_be(data, end); } - static inline uint32_t read_uint24_be(uint8_t const*& data, uint8_t const* end) + static uint32_t read_uint24_be(uint8_t const*& data, uint8_t const* end) { ensure_size(data, end, 3); return read_unchecked::read_uint24_be(data, end); } - static inline uint32_t read_uint32_be(uint8_t const*& data, uint8_t const* end) + static uint32_t read_uint32_be(uint8_t const*& data, uint8_t const* end) { ensure_size(data, end, 4); return read_unchecked::read_uint32_be(data, end); @@ -521,7 +521,7 @@ LIBREMIDI_INLINE reader::reader(bool useAbsolute) : ticksPerBeat(480) , startingTempo(120) - , useAbsoluteTicks(useAbsolute) + , m_useAbsoluteTicks(useAbsolute) { } @@ -537,7 +537,7 @@ LIBREMIDI_INLINE auto reader::parse(const uint8_t* dataPtr, std::size_t size) noexcept -> parse_result try { - using namespace libremidi::util; + using namespace util; tracks.clear(); @@ -549,12 +549,12 @@ try return parse_result::invalid; } - const uint8_t* const dataEnd = dataPtr + size; + const uint8_t* const data_end = dataPtr + size; - uint32_t headerId = read_checked::read_uint32_be(dataPtr, dataEnd); - uint32_t headerLength = read_checked::read_uint32_be(dataPtr, dataEnd); + uint32_t header_id = read_checked::read_uint32_be(dataPtr, data_end); + uint32_t header_length = read_checked::read_uint32_be(dataPtr, data_end); - if (static_cast(headerId) != str_to_headerid("MThd") || headerLength != 6) + if (static_cast(header_id) != str_to_headerid("MThd") || header_length != 6) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "couldn't parse header" << std::endl; @@ -563,7 +563,7 @@ try } format = read_checked::read_uint16_be( - dataPtr, dataEnd); //@tofix format type -> save for later eventually + dataPtr, data_end); //@tofix format type -> save for later eventually if (format > 2) { #if defined(__LIBREMIDI_DEBUG__) @@ -571,12 +571,12 @@ try #endif return parse_result::invalid; } - int trackCount = read_checked::read_uint16_be(dataPtr, dataEnd); - uint16_t timeDivision = read_checked::read_uint16_be(dataPtr, dataEnd); + int track_count = read_checked::read_uint16_be(dataPtr, data_end); + uint16_t time_division = read_checked::read_uint16_be(dataPtr, data_end); // CBB: deal with the SMPTE style time coding // timeDivision is described here http://www.sonicspot.com/guide/midifiles.html - if (timeDivision & 0x8000) + if (time_division & 0x8000) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "found SMPTE time frames (unsupported)" << std::endl; @@ -589,19 +589,19 @@ try return parse_result::invalid; } - startingTempo = 120.0f; // midi default - ticksPerBeat = float(timeDivision); // ticks per beat (a beat is defined as a quarter note) + startingTempo = 120.0f; // midi default + ticksPerBeat = float(time_division); // ticks per beat (a beat is defined as a quarter note) parse_result result = parse_result::validated; - for (int i = 0; i < trackCount; ++i) + for (int i = 0; i < track_count; ++i) { midi_track track; - headerId = read_checked::read_uint32_be(dataPtr, dataEnd); - headerLength = read_checked::read_uint32_be(dataPtr, dataEnd); + header_id = read_checked::read_uint32_be(dataPtr, data_end); + header_length = read_checked::read_uint32_be(dataPtr, data_end); - if (headerId != str_to_headerid("MTrk")) + if (header_id != str_to_headerid("MTrk")) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "couldn't find track header" << std::endl; @@ -609,8 +609,8 @@ try return parse_result::incomplete; } - int64_t available = dataEnd - dataPtr; - if (available < headerLength) + int64_t available = data_end - dataPtr; + if (available < header_length) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "not enough data available" << std::endl; @@ -618,35 +618,35 @@ try return parse_result::incomplete; } - track.reserve(headerLength / 3); + track.reserve(header_length / 3); - const uint8_t* const trackEnd = dataPtr + headerLength; + const uint8_t* const track_end = dataPtr + header_length; - auto runningEvent = message_type::INVALID; + auto running_event = message_type::INVALID; - std::size_t tickCount = 0; + std::size_t tick_count = 0; - while (dataPtr < trackEnd) + while (dataPtr < track_end) { - const auto tick = read_checked::read_variable_length(dataPtr, trackEnd); - if (useAbsoluteTicks) + const auto tick = read_checked::read_variable_length(dataPtr, track_end); + if (m_useAbsoluteTicks) { - tickCount += tick; + tick_count += tick; } else { - tickCount = tick; + tick_count = tick; } try { track_event ev - = parse_event(static_cast(tickCount), i, dataPtr, trackEnd, runningEvent); + = parse_event(static_cast(tick_count), i, dataPtr, track_end, running_event); if (!ev.m.empty()) { if (!ev.m.is_meta_event()) { - runningEvent = static_cast(ev.m.bytes[0]); + running_event = static_cast(ev.m.bytes[0]); } } else @@ -654,7 +654,7 @@ try #if defined(__LIBREMIDI_DEBUG__) std::cerr << "could not read event" << std::endl; #endif - dataPtr = trackEnd; + dataPtr = track_end; result = parse_result::incomplete; continue; } @@ -666,7 +666,7 @@ try #if defined(__LIBREMIDI_DEBUG__) std::cerr << "" << e.what() << std::endl; #endif - dataPtr = trackEnd; + dataPtr = track_end; result = parse_result::incomplete; continue; } @@ -684,7 +684,7 @@ try if (result == parse_result::validated) { - if (dataPtr != dataEnd) + if (dataPtr != data_end) { #if defined(__LIBREMIDI_DEBUG__) std::cerr << "midifile has junk at end: " << std::intptr_t(dataEnd - dataPtr) << std::endl; @@ -706,33 +706,33 @@ catch (const std::exception& e) LIBREMIDI_INLINE double reader::get_end_time() const noexcept { - if (useAbsoluteTicks) + if (m_useAbsoluteTicks) { - double totalLength = 0.; + double total_length = 0.; for (const auto& t : tracks) { if (!t.empty()) { const auto& last_event = t.back(); - if (last_event.tick > totalLength) - totalLength = last_event.tick; + if (last_event.tick > total_length) + total_length = last_event.tick; } } - return totalLength; + return total_length; } else { - double totalLength = 0.; + double total_length = 0.; for (const auto& t : tracks) { - double trackLength = 0.; + double track_length = 0.; for (const auto& e : t) - trackLength += e.tick; + track_length += e.tick; - if (trackLength > totalLength) - totalLength = trackLength; + if (track_length > total_length) + total_length = track_length; } - return totalLength; + return total_length; } } diff --git a/include/libremidi/reader.hpp b/include/libremidi/reader.hpp index 1d99b46..2cb8d34 100644 --- a/include/libremidi/reader.hpp +++ b/include/libremidi/reader.hpp @@ -27,7 +27,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -namespace libremidi +NAMESPACE_LIBREMIDI { /** * @brief reads Standard MIDI files (SMF). @@ -66,7 +66,7 @@ public: std::vector tracks; private: - bool useAbsoluteTicks{}; + bool m_useAbsoluteTicks{}; }; } diff --git a/include/libremidi/shared_context.hpp b/include/libremidi/shared_context.hpp index f794aa2..5d9e68e 100644 --- a/include/libremidi/shared_context.hpp +++ b/include/libremidi/shared_context.hpp @@ -1,10 +1,8 @@ #pragma once #include -#include +#include -#include - -namespace libremidi +NAMESPACE_LIBREMIDI { class shared_context @@ -24,7 +22,9 @@ public: struct shared_configurations { std::shared_ptr context; - std::any observer, in, out; + observer_api_configuration observer; + input_api_configuration in; + output_api_configuration out; }; LIBREMIDI_EXPORT diff --git a/include/libremidi/system_error2.hpp b/include/libremidi/system_error2.hpp index b5dc1cd..cee4fe2 100644 --- a/include/libremidi/system_error2.hpp +++ b/include/libremidi/system_error2.hpp @@ -1,3 +1,4 @@ +#pragma once #ifndef STDX_COMPILER_HPP #define STDX_COMPILER_HPP @@ -63,6 +64,12 @@ #define STDX_TRIVIALLY_RELOCATABLE #endif +#if defined(__clang__) && defined(__has_warning) + #pragma clang diagnostic push + #if __has_warning("-Wdeprecated-declarations") + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + #endif +#endif #endif // STDX_COMPILER_HPP @@ -71,7 +78,7 @@ #include -namespace stdx { +NAMESPACE_STDX { // Implementation of std::void_t for use with pre-C++17 compilers. // @@ -180,7 +187,11 @@ using is_trivially_move_constructible = is_trivially_copyable; #if defined(__cpp_lib_trivially_relocatable) using std::is_trivially_relocatable; #elif defined(__has_builtin) - #if __has_builtin(__is_trivially_relocatable) + #if __has_builtin(__builtin_is_cpp_trivially_relocatable) +template +struct is_trivially_relocatable : std::bool_constant<__builtin_is_cpp_trivially_relocatable(T)> { }; + #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE + #elif __has_builtin(__is_trivially_relocatable) template struct is_trivially_relocatable : std::bool_constant<__is_trivially_relocatable(T)> { }; #define STDX_MUST_SPECIALIZE_IS_TRIVIALLY_RELOCATABLE @@ -214,7 +225,7 @@ struct is_trivially_relocatable : is_trivially_copyable { }; #include -namespace stdx { +NAMESPACE_STDX { namespace detail { @@ -374,7 +385,7 @@ struct is_bit_castable #include #include -namespace stdx { +NAMESPACE_STDX { struct default_intrusive_reference_count; class default_intrusive_reference_control; @@ -1151,7 +1162,7 @@ struct is_trivially_relocatable> : std::true_type #include #include -namespace stdx { +NAMESPACE_STDX { class string_ref; @@ -1596,7 +1607,7 @@ public: #include -namespace stdx { +NAMESPACE_STDX { #if __cplusplus >= 201703L #if defined(__cpp_lib_launder) @@ -1638,7 +1649,7 @@ constexpr T* launder(T* p) noexcept #include -namespace stdx { +NAMESPACE_STDX { class error; @@ -1685,7 +1696,7 @@ constexpr auto construct_error_from_adl(Args&&... args) noexcept( } // end namespace stdx_adl -namespace stdx { +NAMESPACE_STDX { enum class dynamic_exception_errc { @@ -2844,9 +2855,9 @@ struct is_error_code_enum : std::true_type #include -namespace stdx { +NAMESPACE_STDX { -namespace { +// namespace { inline const char* dynamic_exception_errc_str(unsigned ev) noexcept { @@ -2924,7 +2935,7 @@ inline const std::error_category& dynamic_exception_category() noexcept return dynamic_exception_error_category_instance; } -} // end anonymous namespace +//} // end anonymous namespace inline std::error_code make_error_code(dynamic_exception_errc code) noexcept { @@ -3143,9 +3154,9 @@ inline bool generic_error_domain::equivalent(const error& lhs, const error& rhs) return false; } -namespace { +// namespace { -string_ref generic_error_code_message(std::errc code) noexcept +inline string_ref generic_error_code_message(std::errc code) noexcept { switch (code) { @@ -3312,7 +3323,7 @@ string_ref generic_error_code_message(std::errc code) noexcept } } -} // end anonymous namespace +// } // end anonymous namespace inline string_ref generic_error_domain::message(const error& e) const noexcept { @@ -3406,9 +3417,9 @@ inline string_ref dynamic_exception_error_domain::message(const error& e) const return string_ref{"Unknown dynamic exception"}; } -namespace { +// namespace { -std::errc dynamic_exception_code_to_generic_code(dynamic_exception_errc code) noexcept +inline std::errc dynamic_exception_code_to_generic_code(dynamic_exception_errc code) noexcept { switch (code) { @@ -3432,7 +3443,7 @@ std::errc dynamic_exception_code_to_generic_code(dynamic_exception_errc code) no return std::errc{}; } -} // end anonymous namespace +// } // end anonymous namespace inline bool dynamic_exception_error_domain::equivalent(const error& lhs, const error& rhs) const noexcept { @@ -3497,5 +3508,6 @@ inline string_ref dynamic_exception_code_error_domain::message(const error& e) c } // end namespace stdx - - +#if defined(__clang__) && defined(__has_warning) +#pragma clang diagnostic pop +#endif diff --git a/include/libremidi/types.hpp b/include/libremidi/types.hpp new file mode 100644 index 0000000..dfd0e2e --- /dev/null +++ b/include/libremidi/types.hpp @@ -0,0 +1,70 @@ +#pragma once +#include + +#include +#include +#include +#include + +NAMESPACE_LIBREMIDI +{ +/// MIDI protocol supported by an endpoint or function block +enum class midi_protocol : uint8_t +{ + midi1 = (1 << 1), + midi2 = (1 << 2), + both = midi1 | midi2 +}; + +enum transport_type : uint8_t +{ + unknown = 0, + + software = (1 << 1), + loopback = (1 << 2), + + hardware = (1 << 3), + usb = (1 << 4), + bluetooth = (1 << 5), + pci = (1 << 6), + + network = (1 << 7), +}; + +/// UMP version information +struct ump_version +{ + uint8_t major{1}; + uint8_t minor{1}; + + bool operator==(const ump_version&) const noexcept = default; + std::strong_ordering operator<=>(const ump_version&) const noexcept = default; +}; + +using client_handle = std::uint64_t; +using port_handle = std::uint64_t; + +struct uuid +{ + std::array bytes; + + bool operator==(const uuid& other) const noexcept = default; + std::strong_ordering operator<=>(const uuid& other) const noexcept = default; +}; + +struct usb_device_identifier +{ + uint16_t vendor_id; + uint16_t product_id; + + bool operator==(const usb_device_identifier& other) const noexcept = default; + std::strong_ordering operator<=>(const usb_device_identifier& other) const noexcept = default; +}; + +using container_identifier = libremidi_variant_alias::variant< + libremidi_variant_alias::monostate, uuid, std::string, std::uint64_t>; +using device_identifier = libremidi_variant_alias::variant< + libremidi_variant_alias::monostate, std::string, std::uint64_t, usb_device_identifier>; +using endpoint_identifier = libremidi_variant_alias::variant< + libremidi_variant_alias::monostate, std::string, std::uint64_t>; +} diff --git a/include/libremidi/ump.hpp b/include/libremidi/ump.hpp index 59d3091..2efd89f 100644 --- a/include/libremidi/ump.hpp +++ b/include/libremidi/ump.hpp @@ -1,14 +1,27 @@ #pragma once #include -#include - #if LIBREMIDI_NI_MIDI2_COMPAT #include #endif -namespace libremidi +NAMESPACE_LIBREMIDI { +namespace midi2 +{ +// Imported from cmidi2 +enum class message_type +{ + // MIDI 2.0 UMP Section 3. + UTILITY = 0, + SYSTEM = 1, + MIDI_1_CHANNEL = 2, + SYSEX7 = 3, + MIDI_2_CHANNEL = 4, + SYSEX8_MDS = 5, +}; +} + struct ump { alignas(4) uint32_t data[4] = {}; @@ -62,28 +75,16 @@ struct ump constexpr std::size_t size() const noexcept { - // Imported from cmidi2 - enum midi2_message_type + switch (midi2::message_type(((data[0] & 0xF0000000) >> 28) & 0xF)) { - // MIDI 2.0 UMP Section 3. - UTILITY = 0, - SYSTEM = 1, - MIDI_1_CHANNEL = 2, - SYSEX7 = 3, - MIDI_2_CHANNEL = 4, - SYSEX8_MDS = 5, - }; - - switch (((data[0] & 0xF0000000) >> 28) & 0xF) - { - case UTILITY: - case SYSTEM: - case MIDI_1_CHANNEL: + case midi2::message_type::UTILITY: + case midi2::message_type::SYSTEM: + case midi2::message_type::MIDI_1_CHANNEL: return 1; - case MIDI_2_CHANNEL: - case SYSEX7: + case midi2::message_type::MIDI_2_CHANNEL: + case midi2::message_type::SYSEX7: return 2; - case SYSEX8_MDS: + case midi2::message_type::SYSEX8_MDS: return 4; default: return 0; @@ -109,5 +110,14 @@ struct ump constexpr auto cend() const noexcept { return data + size(); } constexpr auto cbegin() noexcept { return data; } constexpr auto cend() noexcept { return data + size(); } + + constexpr midi2::message_type get_type() const noexcept + { + return midi2::message_type(((data[0] & 0xF0000000) >> 28) & 0xF); + } + constexpr uint8_t get_group() const noexcept { return (data[0] >> 24) & 0x0F; } + constexpr uint8_t get_channel_0_15() const noexcept { return (data[0] >> 16) & 0x0F; } + constexpr uint8_t get_channel() const noexcept { return get_channel_0_15() + 1; } + constexpr uint8_t get_status_code() const noexcept { return (data[0] >> 16) & 0xF0; } }; } diff --git a/include/libremidi/ump_events.hpp b/include/libremidi/ump_events.hpp new file mode 100644 index 0000000..24e201e --- /dev/null +++ b/include/libremidi/ump_events.hpp @@ -0,0 +1,374 @@ +#pragma once +#include +#include +#include + +// FIXME! we need to review them all to check the channels +NAMESPACE_LIBREMIDI::ump_events +{ +inline libremidi::ump note_on(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_on(group, channel, pitch, 0, vel, 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump note_off(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_off(group, channel, pitch, 0, vel, 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump poly_pressure(uint8_t group, uint8_t channel, uint8_t pitch, uint32_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_paf(group, channel, pitch, vel); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump control_change(uint8_t group, uint8_t channel, uint8_t index, uint32_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_cc(group, channel, index, value); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump program_change(uint8_t group, uint8_t channel, uint8_t index) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_program(group, channel, 0, index, 0, 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump aftertouch(uint8_t group, uint8_t channel, uint32_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_caf(group, channel, value); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump pitch_bend(uint8_t group, uint8_t channel, uint32_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(group, channel, value); + cmidi2_reverse(msg, u.data); + return u; +} +} + +NAMESPACE_LIBREMIDI::from_midi1 +{ +inline libremidi::ump note_on(uint8_t channel, uint8_t pitch, uint8_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_on(0, channel, pitch, 0, u7_to_u16(vel), 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump note_off(uint8_t channel, uint8_t pitch, uint8_t vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_off(0, channel, pitch, 0, u7_to_u16(vel), 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump poly_pressure(uint8_t channel, uint8_t pitch, uint8_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_paf(0, channel, pitch, u7_to_u32(value)); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump control_change(uint8_t channel, uint8_t index, uint8_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_cc(0, channel, index, u7_to_u32(value)); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump program_change(uint8_t channel, uint8_t index) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_program(0, channel, 0, index, 0, 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump aftertouch(uint8_t channel, uint8_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_caf(0, channel, u7_to_u32(value)); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump pitch_bend(uint8_t channel, uint16_t value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct(0, channel, u16_to_u32(value)); + cmidi2_reverse(msg, u.data); + return u; +} +} + +NAMESPACE_LIBREMIDI::from_01 +{ +inline libremidi::ump note_on(uint8_t channel, uint8_t pitch, double vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_on( + 0, channel, pitch, 0, std::clamp(vel, 0., 1.) * std::numeric_limits::max(), 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump note_off(uint8_t channel, uint8_t pitch, double vel) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_note_off( + 0, channel, pitch, 0, std::clamp(vel, 0., 1.) * std::numeric_limits::max(), 0); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump poly_pressure(uint8_t channel, uint8_t pitch, double value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_paf( + 0, channel, pitch, std::clamp(value, 0., 1.) * std::numeric_limits::max()); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump control_change(uint8_t channel, uint8_t index, double value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_cc( + 0, channel, index, std::clamp(value, 0., 1.) * std::numeric_limits::max()); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump aftertouch(uint8_t channel, double value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_caf( + 0, channel, std::clamp(value, 0., 1.) * std::numeric_limits::max()); + cmidi2_reverse(msg, u.data); + return u; +} + +inline libremidi::ump pitch_bend(uint8_t channel, double value) +{ + libremidi::ump u; + const int64_t msg = cmidi2_ump_midi2_pitch_bend_direct( + 0, channel, std::clamp(value, 0., 1.) * std::numeric_limits::max()); + cmidi2_reverse(msg, u.data); + return u; +} +} + +NAMESPACE_LIBREMIDI::as_midi1 +{ +inline auto note_off(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + uint8_t velocity; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_note_note(ump); + res.velocity = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200; + return res; +} + +inline auto note_on(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + uint8_t velocity; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_note_note(ump); + res.velocity = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200; + return res; +} + +inline auto poly_pressure(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + uint8_t value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_paf_note(ump); + res.value = cmidi2_ump_get_midi2_paf_data(ump) / 0x2000000; + return res; +} + +inline auto control_change(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t control; + uint8_t value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.control = cmidi2_ump_get_midi2_cc_index(ump); + res.value = cmidi2_ump_get_midi2_cc_data(ump) / 0x2000000; + return res; +} + +inline auto program_change(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t program; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.program = cmidi2_ump_get_midi2_program_program(ump); + return res; +} + +inline auto aftertouch(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.value = cmidi2_ump_get_midi2_caf_data(ump) / 0x2000000; + return res; +} + +inline auto pitch_bend(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint16_t value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.value = cmidi2_ump_get_midi2_pitch_bend_data(ump) / 0x40000; + return res; +} +} + +NAMESPACE_LIBREMIDI::as_01 +{ +inline auto note_off(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + double velocity; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_note_note(ump); + res.velocity + = double(cmidi2_ump_get_midi2_note_velocity(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto note_on(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + double velocity; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_note_note(ump); + res.velocity + = double(cmidi2_ump_get_midi2_note_velocity(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto poly_pressure(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t note; + double value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.note = cmidi2_ump_get_midi2_paf_note(ump); + res.value = double(cmidi2_ump_get_midi2_paf_data(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto control_change(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + uint8_t control; + double value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.control = cmidi2_ump_get_midi2_cc_index(ump); + res.value = double(cmidi2_ump_get_midi2_cc_data(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto aftertouch(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + double value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.value = double(cmidi2_ump_get_midi2_caf_data(ump)) / std::numeric_limits::max(); + return res; +} + +inline auto pitch_bend(const libremidi::ump& mess) +{ + struct + { + uint8_t channel; + double value; + } res; + auto& ump = mess.data; + res.channel = mess.get_channel_0_15(); + res.value + = double(cmidi2_ump_get_midi2_pitch_bend_data(ump)) / std::numeric_limits::max(); + return res; +} +} diff --git a/include/libremidi/writer.cpp b/include/libremidi/writer.cpp index 984865c..1d65b78 100644 --- a/include/libremidi/writer.cpp +++ b/include/libremidi/writer.cpp @@ -47,12 +47,12 @@ constexpr R bit_cast(const T& v) noexcept } } #endif -namespace libremidi +NAMESPACE_LIBREMIDI { namespace util { template -static LIBREMIDI_INLINE std::ostream& write_be(std::ostream& out, T value) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::ostream& write_be(std::ostream& out, T value) { static_assert( std::endian::native == std::endian::big || std::endian::native == std::endian::little); @@ -62,14 +62,14 @@ static LIBREMIDI_INLINE std::ostream& write_be(std::ostream& out, T value) } else { - static constexpr auto N = sizeof(value); + static constexpr auto n = sizeof(value); struct storage { - uint8_t bytes[N]; + uint8_t bytes[n]; }; auto data = std::bit_cast(value); - std::reverse(data.bytes, data.bytes + N); - out.write(reinterpret_cast(data.bytes), static_cast(N)); + std::reverse(data.bytes, data.bytes + n); + out.write(reinterpret_cast(data.bytes), static_cast(n)); } return out; } @@ -77,7 +77,7 @@ static LIBREMIDI_INLINE std::ostream& write_be(std::ostream& out, T value) // Write a number to the midifile // as a variable length value which segments a file into 7-bit // values. Maximum size of aValue is 0x7fffffff -static LIBREMIDI_INLINE void write_variable_length(uint32_t aValue, std::vector& outdata) +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION void write_variable_length(uint32_t aValue, std::vector& outdata) { uint8_t bytes[5] = {0}; @@ -99,7 +99,7 @@ static LIBREMIDI_INLINE void write_variable_length(uint32_t aValue, std::vector< outdata.push_back(bytes[4]); } -static LIBREMIDI_INLINE void +LIBREMIDI_STATIC_INLINE_IMPLEMENTATION void add_event_track_count_check(std::vector& tracks, int track) { if (track < 0) @@ -146,12 +146,12 @@ void writer::write(std::ostream& out) const util::write_be(out, static_cast(tracks.size())); util::write_be(out, ticksPerQuarterNote); - std::vector trackRawData; + std::vector track_raw_data; for (const auto& event_list : tracks) { - trackRawData.clear(); + track_raw_data.clear(); // Rough estimation of the memory to allocate - trackRawData.reserve(event_list.size() * 3); + track_raw_data.reserve(event_list.size() * 3); for (const auto& event : event_list) { @@ -164,7 +164,7 @@ void writer::write(std::ostream& out) const if (msg.get_meta_event_type() == meta_event_type::END_OF_TRACK) continue; - util::write_variable_length(static_cast(event.tick), trackRawData); + util::write_variable_length(static_cast(event.tick), track_raw_data); if ((msg.get_message_type() == message_type::SYSTEM_EXCLUSIVE) || (event.m.get_message_type() == message_type::EOX)) @@ -176,38 +176,38 @@ void writer::write(std::ostream& out) const // In other words, when creating a 0xf0 or 0xf7 MIDI message, // do not insert the VLV byte length yourself, as this code will // do it for you automatically. - trackRawData.emplace_back(msg.bytes[0]); // 0xf0 or 0xf7; + track_raw_data.emplace_back(msg.bytes[0]); // 0xf0 or 0xf7; - util::write_variable_length(static_cast(msg.size()) - 1, trackRawData); + util::write_variable_length(static_cast(msg.size()) - 1, track_raw_data); - trackRawData.insert( - trackRawData.end(), msg.bytes.data() + 1, msg.bytes.data() + msg.bytes.size()); + track_raw_data.insert( + track_raw_data.end(), msg.bytes.data() + 1, msg.bytes.data() + msg.bytes.size()); } else { // Non-sysex type of message, so just output the bytes of the message: - trackRawData.insert( - trackRawData.end(), msg.bytes.data(), msg.bytes.data() + msg.bytes.size()); + track_raw_data.insert( + track_raw_data.end(), msg.bytes.data(), msg.bytes.data() + msg.bytes.size()); } } - auto size = trackRawData.size(); + auto size = track_raw_data.size(); const auto eot = meta_events::end_of_track(); - if ((size < 3) || !((trackRawData[size - 3] == 0xFF) && (trackRawData[size - 2] == 0x2F))) + if ((size < 3) || !((track_raw_data[size - 3] == 0xFF) && (track_raw_data[size - 2] == 0x2F))) { - trackRawData.emplace_back(0x0); // tick - trackRawData.emplace_back(eot[0]); - trackRawData.emplace_back(eot[1]); - trackRawData.emplace_back(eot[2]); + track_raw_data.emplace_back(0x0); // tick + track_raw_data.emplace_back(eot[0]); + track_raw_data.emplace_back(eot[1]); + track_raw_data.emplace_back(eot[2]); } // Write the track ID marker "MTrk": out.write("MTrk", 4); - util::write_be(out, static_cast(trackRawData.size())); + util::write_be(out, static_cast(track_raw_data.size())); out.write( - reinterpret_cast(trackRawData.data()), - static_cast(trackRawData.size())); + reinterpret_cast(track_raw_data.data()), + static_cast(track_raw_data.size())); } } } diff --git a/include/libremidi/writer.hpp b/include/libremidi/writer.hpp index 489df86..348ce86 100644 --- a/include/libremidi/writer.hpp +++ b/include/libremidi/writer.hpp @@ -30,7 +30,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -namespace libremidi +NAMESPACE_LIBREMIDI { struct LIBREMIDI_EXPORT writer { diff --git a/src/midi.cpp b/src/midi.cpp index 52a9b09..ccccba0 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -11,12 +11,20 @@ namespace VISEQ::MIDI { void Midi::enumerate(){ std::cout << (int)obs.get_current_api() << std::endl; + + int i = 0; midi_out_ports.clear(); for(const libremidi::output_port& port : obs.get_output_ports()){ midi_out_ports.push_back({port, i, false}); i++; } + + auto out_conf = libremidi::midi1::out_default_configuration(); + virtual_midi_out = libremidi::midi_out(libremidi::output_configuration(), out_conf); + virtual_midi_out.open_virtual_port("viseq out"); + + //midi_out_ports.push_back({&virtual_midi_out, 99, false}); } void Midi::open_midi_out(MidiOutPort *out_port){ @@ -41,6 +49,8 @@ namespace VISEQ::MIDI { notes.emplace_back(midi_out, channel, note, velocity, GetTime(), gate); } } + virtual_midi_out.send_message(libremidi::channel_events::note_on(channel, note, velocity)); + notes.emplace_back(99, channel, note, velocity, GetTime(), gate); } void Midi::cc(int midi_out, int channel, int cc, int value){ @@ -64,6 +74,7 @@ namespace VISEQ::MIDI { for(auto &n : notes){ if(time - n.start >= (float)n.gate / 1000.0f){ if(midi_outs.contains(n.midi_out) && midi_outs[n.midi_out]->is_port_open()) midi_outs[n.midi_out]->send_message(libremidi::channel_events::note_off(n.channel, n.note, n.velocity)); + virtual_midi_out.send_message(libremidi::channel_events::note_off(n.channel, n.note, n.velocity)); n.erase = true; } } diff --git a/src/midi.h b/src/midi.h index 768d20e..b2442fc 100644 --- a/src/midi.h +++ b/src/midi.h @@ -3,6 +3,9 @@ #include "libremidi/libremidi.hpp" #include "libremidi/observer_configuration.hpp" +#include +#include +#include #include #include #include "datatypes.h" @@ -31,6 +34,8 @@ namespace VISEQ::MIDI { std::map> midi_outs; std::vector midi_in_ports; std::vector midi_out_ports; + + libremidi::midi_out virtual_midi_out; std::vector notes; diff --git a/src/signal-manager.cpp b/src/signal-manager.cpp index e1fe7ac..ff5c9a3 100644 --- a/src/signal-manager.cpp +++ b/src/signal-manager.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace VISEQ::SIGNAL { @@ -104,6 +105,7 @@ namespace VISEQ::SIGNAL { if(midinode->send_pitch_bend) midi.pitch_bend(m.second.i, midinode->getPitchBend().channel, midinode->getPitchBend().value); } } + if(midinode->send_note) midi.note(99, midinode->getNotes().channel, (int)Clamp(midinode->getNotes().note + s->midi_note_offset, 0, 127), midinode->getNotes().velocity, midinode->getNotes().gate); s->last = s->next; s->next = next_node; } diff --git a/src/signal-manager.h b/src/signal-manager.h index 005a53a..91f85c0 100644 --- a/src/signal-manager.h +++ b/src/signal-manager.h @@ -6,6 +6,7 @@ #include "midi.h" #include +#include using namespace VISEQ::GRAPH; From 5a1f5acae817650ad966ce33c9c079e4af0d2834 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 23 Jun 2026 01:26:20 +0200 Subject: [PATCH 9/9] window resizing sorted sort of --- src/main.cpp | 4 ++-- src/state.cpp | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 38c4084..f128c0a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,8 +43,8 @@ int main(){ state->settings.speed = (state->settings.grid * 16.0f) * (state->settings.bpm / 120.f); - state->settings.width = monitor_width - 300; - state->settings.height = monitor_height - 300; + state->settings.width = GetScreenWidth(); + state->settings.height = GetScreenHeight(); SetTargetFPS(state->settings.fps); diff --git a/src/state.cpp b/src/state.cpp index 1ca3a72..4dd3f83 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -22,8 +22,11 @@ namespace VISEQ::APP{ void State::update(Camera2D camera){ - if(istate != old_istate) std::cout << state_string[istate] << std::endl; + settings.width = GetScreenWidth(); + settings.height = GetScreenHeight(); + if(istate != old_istate) std::cout << state_string[istate] << std::endl; + mouse_delta = {GetMouseDelta().x, GetMouseDelta().y}; settings.speed = (settings.grid * 16.0f) * (settings.bpm / 120.f);