From 77a227f40d4a2c782c3c3f79955ff96b3ab79889 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 6 Jan 2026 02:44:18 +0100 Subject: [PATCH] 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); }; }