64 Commits

Author SHA1 Message Date
Sebastian d881ef43a3 compiling but mouse interaction is dead 2026-01-12 00:16:36 +01:00
Sebastian 77a227f40d more refactoring and breaking things 2026-01-06 02:44:18 +01:00
Sebastian f086834da6 refctoring WIP 2026-01-05 02:56:30 +01:00
Sebastian e5f614ce06 More UI tweaks and some refactoring. Nice! 2024-12-29 01:17:44 +01:00
Sebastian 011552c020 UI tweaks done. Code tweaks now 2024-12-28 15:13:16 +01:00
Sebastian 2725806749 UI tweaks 2024-12-28 14:45:57 +01:00
Sebastian bf2f34c902 Exp scale for gate time 2024-12-28 02:10:30 +01:00
Sebastian 0e6f2c7256 Values and indicators match now 2024-12-28 01:02:29 +01:00
Sebastian 9e9c32bff4 orbit ui done 2024-12-25 02:05:29 +01:00
Sebastian 6aef199984 Serialization of handles working! 2024-12-20 01:57:28 +01:00
Sebastian c3543cffce Workaround for vector_shared_ptr_templated serialization mess. 2024-12-19 01:00:38 +01:00
Sebastian 9b75b36c41 Serialization of NodeHandle<T> stays a mystery... 2024-12-16 02:10:58 +01:00
Sebastian 996ed16c01 Better centered text. Pass by reference for midi variable is not going to work with json serialization. 2024-12-15 03:17:10 +01:00
Sebastian 96c2bde0e1 Vector UI on its way! 2024-12-15 01:47:53 +01:00
Sebastian b2ed35a704 ControlNodes init. Feels like a good idea! 2024-12-13 01:15:55 +01:00
Sebastian 7f2d16932f Resize on hover taken out. ModNode introduced but needs a better ui approach. 2024-12-13 00:30:31 +01:00
Sebastian bc8c433e47 Fun with config nodes that modifiy other config nodes. Circular connections lead to a crash. 2024-12-11 01:16:06 +01:00
Sebastian 456528f230 Some tweaks, not much progress. 2024-12-10 00:00:53 +01:00
Sebastian ddf9e640b1 Good working protoype with config nodes and a experimental channel setting ui. 2024-12-09 01:40:13 +01:00
Sebastian 3b08037f92 Config nodes do what they should do now. UX is still off, modifier keys do different things on MidiNodes and ConfigNodes. 2024-12-08 14:21:46 +01:00
Sebastian 558829f619 Config nodes not working yet, but everything is prepared. 2024-12-08 02:25:57 +01:00
Sebastian d9c97be326 Inbetween states. Midi nodes and config nodes use same interface now. 2024-12-07 22:09:06 +01:00
Sebastian 3b0e7e986f Config nodes change midi notes. Better would be midi nodes read from config nodes. 2024-12-07 15:28:33 +01:00
Sebastian 36273ba7ac Working on config nodes. They exist now, but do nothing yet. 2024-12-07 03:44:03 +01:00
Sebastian ade33b0608 Context menu is back, but not quite yet. 2024-12-05 23:39:01 +01:00
Sebastian 638df54ee6 Connecting works again (almost). Connectors need to be copied when splitting edges. 2024-12-05 01:25:00 +01:00
Sebastian b6c2029386 Running again. Next step: making connections work again! 2024-12-05 00:01:42 +01:00
Sebastian 036c6f4723 Running again after weak_ptr refactoring. No connecting of nodes atm. Copy/Paste might not work, too. 2024-12-04 01:43:06 +01:00
Sebastian 1fde652495 More weak_ptr and clipboard shenanigans :) 2024-12-03 11:08:40 +01:00
Sebastian 0040956801 Rework to weak_ptr from lots of shared_ptr. 2024-12-03 01:25:49 +01:00
Sebastian 722848d46b Deleting Connectable from the connectables vector is resulting in segfault. 2024-12-02 00:28:44 +01:00
Sebastian f926b95092 Still the ownership problem. 5, why 5? It should be 3... 2024-12-01 01:45:36 +01:00
Sebastian b6a049ea0d Connector shared_ptr keeps one owner and i don't know where it is! 2024-12-01 00:55:59 +01:00
Sebastian c63ee3eabe Double connections fixed 2024-11-30 12:47:33 +01:00
Sebastian c7e76ae7ac Tidied up main 2024-11-30 03:01:26 +01:00
Sebastian fcc5d74e18 Node interactions working again, graph editing functions not complete yet 2024-11-30 02:54:40 +01:00
Sebastian 635c94e3cf On the road to connector -> connector with edges... 2024-11-28 08:49:35 +01:00
Sebastian 492971ed4f Dragging and selecting works. Copy / Paste does not copy connectors yet. 2024-11-28 01:08:04 +01:00
Sebastian 813958002b Drag selection and snap to grid ok. Deselect not working though... 2024-11-26 01:58:09 +01:00
Sebastian 9c0aac8618 Dragging and selecting works. Dragging the selection not yet. 2024-11-25 00:53:10 +01:00
Sebastian f04838bffa Super simple events added. Seems to be a good way for handling interaction. Modified a few base classes. 2024-11-24 03:08:06 +01:00
Sebastian 62a5c53969 Mini commit 2024-11-23 00:54:31 +01:00
Sebastian b12f1d0f9d Might be a better way to handle hovering, dragging and mouse interaction in general 2024-11-22 00:24:31 +01:00
Sebastian a6b6ff1d16 Introduction of mouse.h for z-index mouse events 2024-11-21 23:58:35 +01:00
Sebastian bfc14d5f6e copy/paste partially. z-index experiments, but not working yet. 2024-11-21 02:03:32 +01:00
Sebastian df91a1b107 Back to work! 2024-11-20 11:13:13 +01:00
Sebastian 5779c93339 Clipboard introduced, but not incorporated yet. Some defines for convenience and better readability. 2024-11-19 01:04:06 +01:00
Sebastian 22842f9deb Drag selection working again - not much progress 2024-11-18 01:57:10 +01:00
Sebastian e9633ff19d Some progress on selecting, hovering but still buggy and not a good design really. 2024-11-17 02:40:31 +01:00
Sebastian 55926a5021 Selection working partially. Needs global z-index hovering to work reliably. 2024-11-16 00:29:01 +01:00
Sebastian 96526f709a preparing for node->connector->edge->connector->node 2024-11-15 02:09:06 +01:00
Sebastian 9cccbab703 refactored from main to canvas.h - basic functionality working 2024-11-15 01:49:10 +01:00
Sebastian ab34081dbb raw_mouse changed to screen_mouse 2024-11-14 23:21:04 +01:00
Sebastian 2e103dc561 global mouse moved to state and removed some GetMousePosition()s. 2024-11-14 01:37:16 +01:00
Sebastian 5d3b72d4ef Major refactoring: State, mouse and more... 2024-11-14 00:55:09 +01:00
Sebastian 300001d385 Global snap to grid. 2024-11-13 15:35:39 +01:00
Sebastian e6f370ba4f Connectables working now. On to more refactoring! 2024-11-13 14:39:46 +01:00
Sebastian 1e0a131169 connecting works, but position is off. addConnector is the problem 2024-11-13 01:58:44 +01:00
Sebastian 96082e1190 connecting does weird things! 2024-11-12 01:42:05 +01:00
Sebastian 31a1a264d4 little progress on the connector idea 2024-11-11 02:11:54 +01:00
Sebastian 4f48aa2d5a dragging works. introducing connectors maybe 2024-11-10 01:56:33 +01:00
Sebastian 71dc08d21f interactable base class 2024-11-09 23:56:40 +01:00
Sebastian cdc0fb7075 working on node ui 2024-11-08 01:43:06 +01:00
Sebastian 529106e815 introduction of config nodes 2024-11-07 00:56:33 +01:00
53 changed files with 4201 additions and 1722 deletions
-1
View File
@@ -4,4 +4,3 @@ viseq
viseq.exe
compile_commands.json
*.json
.DS_STORE
+5 -21
View File
@@ -1,32 +1,16 @@
CXX = g++
CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1
CXXINC = -Iinclude
LDLIBS = -lraylib -lasound -pthread
LDLIBS = -lraylib -lasound -llibremidi -pthread
LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib
hdr_files = ${wildcard src/*.h}
src_files = ${wildcard src/*.cpp}
src_files += ${wildcard include/libremidi/*.cpp}
hdr_files = $(wildcard src/*.h)
all: viseq
viseq: $(src_files) $(hdr_files)
viseq:
mkdir -p build
$(CXX) ${wildcard build/*.o} -o viseq $(LDFLAGS) $(LDLIBS)
$(src_files): %.cpp : %_src
$(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC)
$(hdr_files): %.h : %_hdr
@echo "G++ >---------- $(*F).h -----------<"
%_src:
@echo "G++ >---------- $(*F).cpp -----------<"
%_hdr:
@echo "G++ >---------- $(*F).h -----------<"
cd src; $(MAKE)
$(CXX) $(wildcard build/*.o) -o viseq $(LDFLAGS) $(LDLIBS)
clean:
rm -f build/*
-33
View File
@@ -1,33 +0,0 @@
CXX = g++
CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing
CXXINC = -Iinclude
LDLIBS = -lraylib -pthread -lobjc
LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib -framework CoreFoundation -framework Cocoa -framework IOKit
hdr_files = ${wildcard src/*.h}
src_files = ${wildcard src/*.cpp}
src_files += ${wildcard include/libremidi/*.cpp}
all: viseq
viseq: $(src_files) $(hdr_files)
mkdir -p build
$(CXX) ${wildcard build/*.o} -o viseq $(LDFLAGS) $(LDLIBS)
$(src_files): %.cpp : %_src
$(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC)
$(hdr_files): %.h : %_hdr
@echo "G++ >---------- $(*F).h -----------<"
%_src:
@echo "G++ >---------- $(*F).cpp -----------<"
%_hdr:
@echo "G++ >---------- $(*F).h -----------<"
clean:
rm -f build/*
rm -f viseq
-5
View File
@@ -1,5 +0,0 @@
prerequisite for MacOS ARM64:
local raylib install (e.g. brew install raylib)
Build:
make -f MakefileMacOSarm64
Binary file not shown.
BIN
View File
Binary file not shown.
+40
View File
@@ -0,0 +1,40 @@
CXX = g++
CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1
CXXINC = -I../include -isysteminclude
obj_files = main.o app-manager.o graph-manager.o signal-manager.o interaction-manager.o ui.o node.o midi-node.o config-node.o modifier-node.o midi.o signal.o canvas.o state.o edge.o
all: $(obj_files)
$(obj_files): %.o : %.cpp
$(CXX) -c $^ -o ../build/$@ $(CXXFLAGS) $(CXXINC)
#all: viseq
#viseq: main.o app-manager.o graph-manager.o signal-manager.o interaction-manager.o ui.o node.o midi-node.o config-node.o modifier-node.o midi.o signal.o canvas.o state.o edge.o
# $(CXX) ${wildcard build/*.o} -o viseq $(LDFLAGS) $(LDLIBS)
#main.o:
# $(CXX) -c src/main.cpp -o ../build/main.o $(CXXFLAGS) $(CXXINC)
#
#app-manager.o:
# $(CXX) -c src/app-manager.cpp -o ../build/app-manager.o $(CXXFLAGS) $(CXXINC)
#src/canvas.cpp
#src/signal.cpp
#src/midi.cpp
#src/node.cpp
#src/graph-manager.cpp
#src/modifier-node.cpp
#src/config-node.cpp
#src/midi-node.cpp
#src/ui.cpp
#src/state.cpp
#src/edge.cpp
#src/interaction-manager.cpp
#src/signal-manager.cpp
#src/app-manager.cpp
#src/main.cpp
+158
View File
@@ -0,0 +1,158 @@
#include "app-manager.h"
#include "interaction-manager.h"
#include "nlohmann/json.hpp"
#include "graph-manager.h"
#include "midi-node.h"
#include "state.h"
#include "ui.h"
#include <memory>
#include <fstream>
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 AppManager::close(){
std::ofstream file("state.json");
file << json(*this);
std::cout << std::setw(4) << json(*this) << std::endl;
}
void AppManager::startSignals(){
sm->startSignalThread();
}
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<int>() << std::endl;
if(n.at("nodeType").get<int>() == NodeType::MIDINODE) gm->addNode(n.template get<std::shared_ptr<MidiNode>>());
if(n.at("nodeType").get<int>() == NodeType::CONFIGNODE) gm->addNode(n.template get<std::shared_ptr<ConfigNode>>());
}
}
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<int>() > highest_id) {
highest_id = n.at("id").get<int>();
}
}
}
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>();
int end_id = e.at("end").get<int>();
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>();
int next_id = s.at("next").get<int>();
int last_id = s.at("last").get<int>();
std::shared_ptr<VISEQ::SIGNAL::Signal> ms = s;
VEC_WPTR_Node start = gm->getNodesByID(start_id);
VEC_WPTR_Node last = gm->getNodesByID(last_id);
VEC_WPTR_Node next = gm->getNodesByID(next_id);
ms->start = start.front();
ms->next = next.front();
ms->last = last.front();
sm->signals.push_back(ms);
std::cout << std::format("adding signal with start node {}", start_id) << std::endl;
}
} else {
for(int i = 0; i < 4; ++i){
gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 2 * state->settings.grid*4});
}
for(int i = 3; i >= 0; --i){
gm->createMidiNode(Vec2Df{(float)i * state->settings.grid*4 + 7 * state->settings.grid, 3 * state->settings.grid*4});
}
for(int i = 0; i < 8; ++i){
gm->addConnection(gm->getNode(i), gm->getNode((i+1) % 8));
}
sm->addSignalAtNode(gm->getFirstNode());
}
}
void AppManager::updateMidiPorts(){
for(auto& n : gm->getMidiNodes()){
auto nc = std::dynamic_pointer_cast<MidiNode>(n.lock());
for(auto& mo : sm->midi.midi_out_ports){
if(!nc->midi_outs.contains(mo.number) && mo.enabled){
nc->midi_outs[mo.number] = {mo.number, mo.port.port_name, false};
}
if(nc->midi_outs.contains(mo.number) && !mo.enabled){
nc->midi_outs.erase(mo.number);
}
}
}
}
void AppManager::update(){
state->update(canvas->camera);
canvas->update();
gm->update();
mutex.lock();
updateMidiPorts();
mutex.unlock();
im.update();
}
void 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 AppManager::drawUI(){
mutex.lock();
ui.draw(SCREEN_MOUSE, canvas->camera);
mutex.unlock();
}
}
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include "interaction-manager.h"
#include "nlohmann/json.hpp"
#include "canvas.h"
#include "graph-manager.h"
#include "midi-node.h"
#include "signal-manager.h"
#include "state.h"
#include "ui.h"
#include <memory>
using json = nlohmann::json;
namespace VISEQ::APP {
class AppManager{
public:
AppManager() : im(gm->getPtr(), sm->getPtr(), canvas), ui(sm->getPtr(), gm->getPtr()) {}
std::mutex mutex;
std::shared_ptr<APP::State> state = APP::State::get();
std::shared_ptr<CANVAS::Canvas> canvas = std::make_shared<CANVAS::Canvas>();
std::shared_ptr<GraphManager> gm = std::make_shared<GraphManager>();
std::shared_ptr<SIGNAL::SignalManager> sm = std::make_shared<SIGNAL::SignalManager>(gm->getPtr(), &mutex);
VISEQ::INTERACTION::InteractionManager im;
UI::AppUI ui;
void init();
void close();
void startSignals();
void loadState();
void updateMidiPorts();
void update();
void draw();
void drawUI();
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AppManager, gm, sm)
}
+61
View File
@@ -1,15 +1,76 @@
#pragma once
#include "datatypes.h"
#include "interactable.h"
#include "vmath.h"
#include "raylib.h"
#include "raymath.h"
#include "state.h"
inline bool ViButton(Vec2Df mp, float x, float y, float radius, Color color){
DrawCircle(x,y,radius, color);
return (Vector2Distance(mp, (Vector2){x,y}) < radius && IsMouseButtonPressed(MOUSE_BUTTON_LEFT));
}
inline bool ViButton(Vec2Df mp, Vec2Df position, float radius, Color color){
return ViButton(mp, position.x, position.y, radius, color);
}
inline bool ViButtonText(Vec2Df mp, Vec2Df position, float radius, Color color, std::string text, float text_size, Color text_color){
float offset = MeasureText(text.c_str(), text_size);
DrawCircle(position.x, position.y, radius, color);
DrawText(text.c_str(), position.x-offset/2.0f, position.y - text_size/2, text_size, text_color);
return (Vector2Distance(mp, position) < radius && IsMouseButtonPressed(MOUSE_BUTTON_LEFT));
}
inline bool ViSegButton(Vec2Df mp, float x, float y, float start_angle, float end_angle, float radius, Color color){
DrawCircleSector((Vector2){x,y},radius, start_angle, end_angle, 5, color);
return (Vector2Distance(mp, (Vector2){x,y}) < radius && IsMouseButtonPressed(MOUSE_BUTTON_LEFT));
}
inline void DrawCenteredText(std::string text, Vec2Df position, float text_size, Color color){
float offset = MeasureText(text.c_str(), text_size);
DrawText(text.c_str(), position.x-offset/2.0f, position.y-text_size/2.0f, text_size, color);
}
namespace VISEQ::APP::UI{
class Button : public BASE::Interactable{
public:
Button() {}
Button(Vec2Df position, Color color) : color(color), position(position) {}
Color color = VS_COLOR_YELLOW;
Color hover_color = VS_COLOR_SUN;
Vec2Df position = {0,0};
std::string text = "";
Color text_color = VS_COLOR_BROWN;
float text_size = 10;
virtual void draw() = 0;
virtual GRAPH::GraphItemType getType() override {return GRAPH::GraphItemType::VS_BUTTON;}
};
class ButtonRound : public Button{
public:
ButtonRound() {}
ButtonRound(Vec2Df position, float radius, Color color) : Button(position, color), radius(radius){}
std::shared_ptr<APP::State> state = APP::State::get();
float radius = 0.0f;
virtual bool hovering() override {
return CheckCollisionPointCircle(SCREEN_MOUSE, position, radius);
}
virtual void pickUp() override {}
virtual void pickUp(Vec2Df position) override {}
virtual void drag(Vec2Df position) override {}
virtual void putDown() override {}
virtual void draw() override {
float offset = MeasureText(text.c_str(), text_size);
if(!hovering()) DrawCircle(position.x, position.y, radius, color);
else DrawCircle(position.x, position.y, radius, hover_color);
if(text != "") DrawText(text.c_str(), position.x-offset/2.0f, position.y - text_size/2, text_size, text_color);
}
};
}
+88
View File
@@ -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 <memory>
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> 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});
}
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include "datatypes.h"
#include "draggable.h"
#include "raylib.h"
#include "vmath.h"
#include <memory>
namespace VISEQ::APP::CANVAS {
class Canvas : public BASE::Draggable, public std::enable_shared_from_this<Canvas> {
public:
Canvas() {}
Camera2D camera;
Vec2Df camera_delta;
Vec2Df select_start;
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<Canvas> getPtr();
virtual void update() override;
virtual void draw();
};
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "node.h"
namespace VISEQ::APP {
class Clipboard {
public:
Clipboard() {}
std::vector<GRAPH::NODES::VEC_SPTR_Node> clipboard;
GRAPH::NODES::VEC_SPTR_Node pasteNodes(){
return clipboard.back();
}
void copyNodes(GRAPH::NODES::VEC_SPTR_Node selected_nodes){
if(clipboard.size() == 20) clipboard.pop_back();
clipboard.emplace(clipboard.begin());
for(auto n : selected_nodes){
clipboard.front().push_back(n);
}
}
};
}
+293
View File
@@ -0,0 +1,293 @@
#include "config-node.h"
#include "datatypes.h"
#include "edge.h"
#include "node.h"
#include "raymath.h"
#include "state.h"
#include <memory>
namespace VISEQ::GRAPH::NODES {
std::shared_ptr<ConfigNode> ConfigNode::getPtr() {
return std::static_pointer_cast<ConfigNode>(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<std::weak_ptr<ConfigNode>> 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<std::shared_ptr<EDGES::Edge>>>();
std::vector<std::weak_ptr<ConfigNode>> 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<ConfigNode>(e->start.lock()));
//if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast<ConfigNode>(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<int> 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<Vec2Df>(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);
}
void to_json(json& j, const std::shared_ptr<ConfigNode>& N){
j = *N;
}
void from_json(const json& j, std::shared_ptr<ConfigNode>& N){
N.reset(new ConfigNode(j.get<ConfigNode>()));
}
};
+51
View File
@@ -0,0 +1,51 @@
#pragma once
#include "datatypes.h"
#include "node.h"
#include <memory>
namespace VISEQ::GRAPH::NODES {
class ConfigNode : public Node{
public:
ConfigNode() {std::cout << "ConfigNode Constructor std" << std::endl;}
ConfigNode(Vec2Df position) : Node(position) {}
std::shared_ptr<ConfigNode> getPtr();
NodeType nodeType = NodeType::CONFIGNODE;
MIDI::MidiNote midiNote;
MIDI::MidiCC midiCC;
MIDI::MidiPitchBend midiPitchBend;
MIDI::MidiPolyPressure midiPolyPressure;
MIDI::MidiPC midiPC;
MIDI::MidiAftertouch midiAftertouch;
int mode = 0;
float radius = 20.0f;
virtual NodeType getNodeType() const override;
virtual void trigger() override;
virtual 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<std::weak_ptr<ConfigNode>> 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);
friend void to_json(json& j, const std::shared_ptr<ConfigNode>& N);
friend void from_json(const json& j, std::shared_ptr<ConfigNode>& N);
};
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include "draggable.h"
#include "vmath.h"
#include <memory>
namespace VISEQ::BASE {
class Connectable : public Draggable {
public:
Connectable() {}
Connectable(Vec2Df position) : Draggable(position) {}
std::weak_ptr<Connectable> partner;
bool new_partner = false;
bool connecting = false;
bool connected = false;
Vec2Df connecting_position;
virtual void drag(Vec2Df new_position) override {
connecting = true;
connecting_position = new_position;
}
void putDown() override {
connect();
Draggable::putDown();
}
virtual void connect() {
if(picked_up){
connecting = false;
connected = true;
}
}
virtual void draw(){
if(connecting) DrawLineV(position, connecting_position, (Color){229, 181, 103, 255});
}
};
}
+84
View File
@@ -0,0 +1,84 @@
#pragma once
#include "connectable.h"
#include "datatypes.h"
#include "graph-item.h"
#include "interaction-event.h"
#include "raymath.h"
#include "state.h"
#include "vmath.h"
#include <fcntl.h>
#include <memory>
namespace VISEQ::GRAPH {
class Connector : public BASE::Connectable, public GraphItem, public std::enable_shared_from_this<Connector> {
public:
Connector() {}
Connector(std::shared_ptr<GraphItem> parent, Vec2Df center_position, float orbit) : orbit(orbit) {
setPosition(center_position);
setParent(parent);
}
std::weak_ptr<GraphItem> parent;
bool passable = false;
float radius = 8.0f;
float orbit = 20.0f;
Vec2Df center_position;
GraphItemType getType() override{
return GraphItemType::CONNECTOR;
}
std::shared_ptr<Connector> getPtr(){
return shared_from_this();
}
virtual bool hovering() override {
return partner.expired() && CheckCollisionPointCircle(SCREEN_MOUSE, position, radius);
}
void setParent(std::shared_ptr<GraphItem> _parent){
parent = _parent;
}
void setPosition(Vec2Df pos) override {
center_position = pos;
}
void setOrbit(float r) {
orbit = r;
}
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(connected) {
state->pushEvent({getPtr(), APP::Interaction::CONNECT});
connected = false;
}
if(partner.expired()) position = center_position.orbitAlongVector(SCREEN_MOUSE, orbit);
if(!partner.expired()) position = center_position .orbitAlongVector(partner.lock()->position, orbit);
if(new_partner) {
new_partner = false;
state->pushEvent({getPtr(), APP::Interaction::CONNECT});
}
}
void draw() override {
BASE::Connectable::draw();
float opacity = 150.0f - Clamp(position.dist(SCREEN_MOUSE), 0.0f, 150.0f);
if(hovering()){
DrawCircle(position.x, position.y, 8.0f, (Color){108, 203, 237, 255});
} else {
DrawCircle(position.x, position.y, 8.0f, (Color){108, 153, 187, (unsigned char)opacity});
}
}
};
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include "node.h"
namespace VISEQ::GRAPH::NODES{
class ControlNode: public Node{
public:
ControlNode(){}
float radius = 6;
NodeType nodeType = NodeType::CONTROLNODE;
Vec2Df center;
std::shared_ptr<ControlNode> getPtr(){
return std::static_pointer_cast<ControlNode>(shared_from_this());
}
virtual GRAPH::GraphItemType getType() override {
return GraphItemType::NODE;
}
virtual NodeType getNodeType() const override {return nodeType;}
virtual bool hovering() override {
return CheckCollisionPointCircle(SCREEN_MOUSE, position, radius);
}
virtual void update() override {
Node::update();
}
virtual void draw() override {
DrawCircleV(position, radius, VS_COLOR_SUN);
}
virtual bool select() override {
return false;
}
};
}
+233 -17
View File
@@ -4,7 +4,9 @@
#define _USE_MATH_DEFINES
#endif
#define VS_COLOR_BLACK (Color){20, 20, 20, 255}
#define VS_COLOR_DARKGREY (Color){46, 46, 46, 255}
#define VS_COLOR_GREY (Color){80, 80, 80, 255}
#define VS_COLOR_LIGHTGREY (Color){100, 100, 100, 255}
#define VS_COLOR_WHITE (Color){214, 214, 214, 255}
#define VS_COLOR_YELLOW (Color){229, 181, 103, 255}
@@ -18,8 +20,6 @@
#define VS_COLOR_BROWN (Color){84, 75, 61, 255}
#define VS_COLOR_SUN (Color){214, 170, 21, 255}
#include "vmath.h"
#include "nlohmann/json.hpp"
using json = nlohmann::json;
@@ -33,27 +33,56 @@ namespace VISEQ::GRAPH::NODES {
enum Distribution {GEOMETRIC, ROUNDROBIN, RANDOM};
}
namespace VISEQ::APP{
struct AppSettings{
int mode = 0;
int width;
int height;
float grid;
float bpm;
float fps;
float speed = 16.0f;
int gui_selected_midi_out;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(AppSettings, mode, width, height, grid, bpm, fps, speed, gui_selected_midi_out)
}
namespace VISEQ::GRAPH{
enum GraphItemType {NODE, EDGE};
enum NodeType {OSCNODE, MIDINODE};
enum GraphItemType {NODE, EDGE, CONNECTOR, CANVAS, HANDLE, VS_BUTTON};
enum NodeType {OSCNODE, MIDINODE, CONFIGNODE, CONTROLNODE};
enum EdgeType {MIDIEDGE, CONFIGEDGE};
}
namespace VISEQ::MIDI{
struct MidiNote{
int channel = 0;
int note = 0;
int velocity = 0;
int gate = 0; // gate time in milliseconds
NLOHMANN_DEFINE_TYPE_INTRUSIVE(MidiNote, channel, note, velocity, gate);
};
struct MidiCC{
int channel = 0;
int control = 0;
int value = 0;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(MidiCC, channel, control, value);
};
struct MidiPC{
int channel = 0;
int program = 0;
int value = 0;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(MidiPC, channel, program, value);
};
struct MidiPitchBend{
int channel = 0;
int value = 0;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(MidiPitchBend, channel, value);
};
struct MidiAftertouch{
int channel = 0;
int value = 0;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(MidiAftertouch, channel, value);
};
struct MidiPolyPressure{
int channel = 0;
int note = 0;
int value = 0;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(MidiPolyPressure, channel, note, value);
};
struct MidiOutPort{
libremidi::output_port port;
int number;
@@ -65,6 +94,193 @@ namespace VISEQ::MIDI{
int number;
};
inline std::ostream& operator<<(std::ostream& os, const MidiNote note){
os << "note: " << note.note << " channel: " << note.channel << " velocity: " << note.velocity << " gate: " << note.gate;
return os;
}
inline MidiNote operator+(const MidiNote left, const MidiNote right){
MidiNote added;
added.note = std::clamp(left.note + right.note, 0, 127);
added.channel = std::clamp(left.channel + right.channel, 0, 15);
added.gate = std::max(left.gate + right.gate, 0);
added.velocity = std::clamp(left.velocity + right.velocity, 0, 127);
return added;
}
inline MidiCC operator+(const MidiCC left, const MidiCC right){
MidiCC added;
added.channel = left.channel + right.channel;
added.control = left.control + right.control;
added.value = left.value+ right.value;
return added;
}
inline MidiPC operator+(const MidiPC left, const MidiPC right){
MidiPC added;
added.channel = left.channel + right.channel;
added.program = left.program + right.program;
added.value = left.value+ right.value;
return added;
}
inline MidiPitchBend operator+(const MidiPitchBend left, const MidiPitchBend right){
MidiPitchBend added;
added.channel = left.channel + right.channel;
added.value = left.value+ right.value;
return added;
}
inline MidiAftertouch operator+(const MidiAftertouch left, const MidiAftertouch right){
MidiAftertouch added;
added.channel = left.channel + right.channel;
added.value = left.value+ right.value;
return added;
}
inline MidiPolyPressure operator+(const MidiPolyPressure left, const MidiPolyPressure right){
MidiPolyPressure added;
added.channel = left.channel + right.channel;
added.note = left.note + right.note;
added.value = left.value+ right.value;
return added;
}
inline MidiNote operator-(const MidiNote left, const MidiNote right){
MidiNote added;
added.note = left.note - right.note;
added.channel = left.channel - right.channel;
added.gate = left.gate - right.gate;
added.velocity = left.velocity - right.velocity;
return added;
}
inline MidiCC operator-(const MidiCC left, const MidiCC right){
MidiCC added;
added.channel = left.channel - right.channel;
added.control = left.control - right.control;
added.value = left.value- right.value;
return added;
}
inline MidiPC operator-(const MidiPC left, const MidiPC right){
MidiPC added;
added.channel = left.channel - right.channel;
added.program = left.program - right.program;
added.value = left.value- right.value;
return added;
}
inline MidiPitchBend operator-(const MidiPitchBend left, const MidiPitchBend right){
MidiPitchBend added;
added.channel = left.channel - right.channel;
added.value = left.value- right.value;
return added;
}
inline MidiAftertouch operator-(const MidiAftertouch left, const MidiAftertouch right){
MidiAftertouch added;
added.channel = left.channel - right.channel;
added.value = left.value- right.value;
return added;
}
inline MidiPolyPressure operator-(const MidiPolyPressure left, const MidiPolyPressure right){
MidiPolyPressure added;
added.channel = left.channel - right.channel;
added.note = left.note - right.note;
added.value = left.value- right.value;
return added;
}
// COMPOUND
inline MidiNote& operator+=(MidiNote& left, const MidiNote& right){
left.note += right.note;
left.channel += right.channel;
left.gate += right.gate;
left.velocity += right.velocity;
return left;
}
inline MidiCC& operator+=(MidiCC& left, const MidiCC right){
left.channel += right.channel;
left.control += right.control;
left.value += right.value;
return left;
}
inline MidiPC& operator+=(MidiPC& left, const MidiPC right){
left.channel += right.channel;
left.program += right.program;
left.value += right.value;
return left;
}
inline MidiPitchBend& operator+=(MidiPitchBend& left, const MidiPitchBend right){
left.channel += right.channel;
left.value += right.value;
return left;
}
inline MidiAftertouch& operator+=(MidiAftertouch& left, const MidiAftertouch right){
left.channel += right.channel;
left.value += right.value;
return left;
}
inline MidiPolyPressure& operator+=(MidiPolyPressure& left, const MidiPolyPressure right){
left.channel += right.channel;
left.note += right.note;
left.value += right.value;
return left;
}
inline MidiNote& operator-=(MidiNote& left, const MidiNote& right){
left.note -= right.note;
left.channel -= right.channel;
left.gate -= right.gate;
left.velocity -= right.velocity;
return left;
}
inline MidiCC& operator-=(MidiCC& left, const MidiCC right){
left.channel -= right.channel;
left.control -= right.control;
left.value -= right.value;
return left;
}
inline MidiPC& operator-=(MidiPC& left, const MidiPC right){
left.channel -= right.channel;
left.program -= right.program;
left.value -= right.value;
return left;
}
inline MidiPitchBend& operator-=(MidiPitchBend& left, const MidiPitchBend right){
left.channel -= right.channel;
left.value -= right.value;
return left;
}
inline MidiAftertouch& operator-=(MidiAftertouch& left, const MidiAftertouch right){
left.channel -= right.channel;
left.value -= right.value;
return left;
}
inline MidiPolyPressure& operator-=(MidiPolyPressure& left, const MidiPolyPressure right){
left.channel -= right.channel;
left.note -= right.note;
left.value -= right.value;
return left;
}
/*Vec2D<T> operator*(const Vec2D<T> left, T scalar){*/
/* return Vec2D<T>(left.x * scalar, left.y * scalar);*/
/*};*/
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(MidiOut, number)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(MidiOutPort, number, enabled)
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include "moveable.h"
#include "raylib.h"
#include "selectable.h"
#include "state.h"
#include "vmath.h"
namespace VISEQ::BASE {
class Draggable : public Movable, public Selectable{
public:
Draggable() {}
Draggable(float x, float y) : Movable(Vec2Df(x, y), 960.0f) {}
Draggable(Vec2Df p) : Movable(p, 960.0f) {}
virtual ~Draggable() {}
std::shared_ptr<APP::State> state = APP::State::get();
Vec2Df drag_offset;
bool picked_up = false;
virtual bool entered() override {
return !is_hovering && hovering();
}
virtual bool exited() override {
return is_hovering && !hovering();
}
virtual bool pickedUp() {
return held() && picked_up;
}
virtual void pickUp() override {
picked_up = true;
drag_offset = position - SCREEN_MOUSE;
}
virtual void pickUp(Vec2Df selection_origin) override {
picked_up = true;
drag_offset = position - selection_origin;
}
virtual void drag(Vec2Df new_position) override {
position = new_position;
}
virtual void putDown() override {
picked_up = false;
}
virtual bool select() override {
return CheckCollisionPointRec(position, state->selection);
}
virtual void deselect() override {
selected = false;
}
};
}
+36
View File
@@ -0,0 +1,36 @@
#include "edge.h"
#include <memory>
#include "node.h"
namespace VISEQ::GRAPH::EDGES {
bool Edge::GActive() {return active;}
EdgeType Edge::getEdgeType() const {return edgeType;}
std::weak_ptr<NODES::Node> Edge::getNextNode(std::weak_ptr<NODES::Node> origin){
return start.lock() == origin.lock() ? end : start;
}
void Edge::draw(Vec2Df mouse_pos, Camera2D cam) {}
void to_json(json& j, const VISEQ::GRAPH::EDGES::Edge& E){
j = json{{"id",E.id}, {"start", E.start.lock()->id}, {"end",E.end.lock()->id}, {"pass_forward",E.pass_forward}, {"pass_backward", E.pass_backward}};
}
void from_json(const json& j, Edge& E){
j.at("id").get_to(E.id);
j.at("pass_forward").get_to(E.pass_forward);
j.at("pass_backward").get_to(E.pass_backward);
}
void to_json(json& j, const VISEQ::GRAPH::EDGES::SPTR_Edge& E){
j = *E;
}
void from_json(const json& j, VISEQ::GRAPH::EDGES::SPTR_Edge& E){
E.reset(new Edge(j.get<Edge>()));
}
}
+22 -10
View File
@@ -1,5 +1,6 @@
#pragma once
#include "connector.h"
#include "graph-item.h"
#include <memory>
#include "node.h"
@@ -11,10 +12,15 @@ namespace VISEQ::GRAPH::EDGES {
public:
Edge() {}
Edge(std::shared_ptr<NODES::Node> start, std::shared_ptr<NODES::Node> end) : start(start), end(end) {}
Edge(std::weak_ptr<NODES::Node> start, std::weak_ptr<NODES::Node> end) : start(start), end(end) {}
std::shared_ptr<NODES::Node> start;
std::shared_ptr<NODES::Node> end;
std::weak_ptr<NODES::Node> start;
std::weak_ptr<NODES::Node> end;
std::weak_ptr<Connector> start_conn;
std::weak_ptr<Connector> end_conn;
EdgeType edgeType;
bool pass_forward = true;
bool pass_backward = true;
@@ -23,18 +29,24 @@ namespace VISEQ::GRAPH::EDGES {
bool config = false;
bool split = false;
float speed = 0;
float bpm = 120.0f;
float connector_offset = 8.0f;
float switches_offset = 25.0f;
virtual bool GActive() {return active;}
std::shared_ptr<NODES::Node> getNextNode(std::shared_ptr<NODES::Node> origin){
return start == origin ? end : start;
}
void draw(Vec2Df mouse_pos, Camera2D cam) {}
virtual bool GActive();
virtual EdgeType getEdgeType() const;
std::weak_ptr<NODES::Node> getNextNode(std::weak_ptr<NODES::Node> origin);
void draw(Vec2Df mouse_pos, Camera2D cam);
friend void to_json(json& j, const Edge& E);
friend void from_json(const json& j, Edge& E);
friend void to_json(json& j, const std::shared_ptr<Edge>& E);
friend void from_json(const json& j, std::shared_ptr<Edge>& E);
};
typedef std::shared_ptr<Edge> SPTR_Edge;
typedef std::weak_ptr<Edge> WPTR_Edge;
typedef std::vector<SPTR_Edge> VEC_SPTR_Edge;
typedef std::vector<WPTR_Edge> VEC_WPTR_Edge;
}
+7 -6
View File
@@ -2,19 +2,19 @@
#include "raylib.h"
#include "datatypes.h"
#include <memory>
#include <vector>
#include "vmath.h"
namespace VISEQ::GRAPH {
class GraphItem{
class GraphItem {
public:
GraphItem() {newId(-1);}
GraphItem(const GraphItem& G) {
newId(-1);
graphItemType = G.graphItemType;
gItemType = G.gItemType;
}
GraphItemType graphItemType;
GraphItemType gItemType;
int id = 0;
@@ -25,8 +25,9 @@ namespace VISEQ::GRAPH {
_id++;
}
virtual void draw() {};
virtual void draw(Vec2Df mouse, Camera2D cam) {};
virtual GraphItemType getType() {return graphItemType;}
virtual GraphItemType getType() {return gItemType;}
virtual ~GraphItem() = default;
};
+471
View File
@@ -0,0 +1,471 @@
#include "graph-manager.h"
#include "config-node.h"
#include "datatypes.h"
#include "state.h"
#include "vmath.h"
#include "raylib.h"
#include "raymath.h"
#include "button.h"
#include "midi-node.h"
#include "edge.h"
#include "node.h"
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <ostream>
#include <random>
#include <ranges>
#include <vector>
#include "state-io.h"
namespace VISEQ::GRAPH{
std::shared_ptr<GraphManager> GraphManager::getPtr(){
return shared_from_this();
}
int GraphManager::getNodesSize(){
return nodes.size();
}
void GraphManager::createMidiNode(Vec2Df position){
nodes.emplace_back(std::make_shared<MidiNode>(position));
nodes.back()->update();
}
void GraphManager::createConfigNode(Vec2Df position){
nodes.emplace_back(std::make_shared<ConfigNode>(position));
nodes.back()->update();
}
void GraphManager::setMidiPortForAll(int number, bool _state){
for(auto n : nodes){
if(n->getNodeType() == NodeType::MIDINODE) std::dynamic_pointer_cast<MidiNode>(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<Edge>());
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<VEC_SPTR_Node>();
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<VEC_SPTR_Node>();
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<Node>(*n));
/*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared<MidiNode>(*n));*/
/*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared<ConfigNode>(*n));*/
nodes.back()->setPosition(position + nodes.back()->drag_offset);
nodes.back()->selected = false;
}
}
void GraphManager::copyNodes(std::vector<std::shared_ptr<MidiNode>> _nodes, Vec2Df position){
for(auto n: _nodes){
nodes.insert(nodes.end(), std::make_shared<MidiNode>(*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<Node>(*n));
/*if(n->getNodeType() == NodeType::MIDINODE) copied_nodes.insert(copied_nodes.end(), std::make_shared<MidiNode>(*n));*/
/*if(n->getNodeType() == NodeType::CONFIGNODE) copied_nodes.insert(copied_nodes.end(), std::make_shared<ConfigNode>(*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<Node>(*n));
/*if(n->getNodeType() == NodeType::MIDINODE) nodes.insert(nodes.end(), std::make_shared<MidiNode>(*n));*/
/*if(n->getNodeType() == NodeType::CONFIGNODE) nodes.insert(nodes.end(), std::make_shared<ConfigNode>(*n));*/
nodes.back()->setPosition(position);
return nodes.back();
}
SPTR_Node GraphManager::copyNode(std::shared_ptr<MidiNode> n, Vec2Df position){
std::cout << "copy midinode" << std::endl;
nodes.insert(nodes.end(), std::make_shared<MidiNode>(*n));
nodes.back()->clearConnectors();
nodes.back()->setPosition(position);
return nodes.back();
}
void GraphManager::setNodeMode(int mode){
for(auto n: nodes){
n->setMode(mode);
}
}
void GraphManager::reset(){
counter.clear();
}
WPTR_Node GraphManager::getNode(int i){
return nodes.at(i);
}
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<VEC_WPTR_Node>();
}
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<NODES::Node>(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<EDGES::Edge>(o.front());
}
SPTR_Node GraphManager::getClosestNode(Vec2Df position){
auto neighbors = nodes | RANGE_FILTER([](SPTR_Node n){ return !n->picked_up;}) | std::ranges::to<VEC_SPTR_Node>();
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<VEC_WPTR_Node>(); // 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<VEC_WPTR_Edge>();
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 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<MidiNode>(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;
}
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 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<Edge>(*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<MidiNode>(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);
// 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<MidiNode>(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<MidiNode>(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<int>(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<VEC_SPTR_Node>();
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<VEC_SPTR_Node>();
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<int>(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 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 to_json(json& j, const std::shared_ptr<GraphManager>& G){
j = *G;
}
void from_json(const json& j, std::shared_ptr<GraphManager>& G){
G.reset(new GraphManager(j.get<GraphManager>()));
}
}
+57 -279
View File
@@ -1,29 +1,19 @@
#pragma once
#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 <algorithm>
#include <cstdio>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <random>
#include <ranges>
#include <vector>
#include "state-io.h"
using json = nlohmann::json;
using namespace VISEQ::GRAPH::NODES;
@@ -31,280 +21,68 @@ using namespace VISEQ::GRAPH::EDGES;
namespace VISEQ::GRAPH {
class GraphManager{
class GraphManager : public std::enable_shared_from_this<GraphManager>{
private:
NODES::VEC_SPTR_Node nodes;
EDGES::VEC_SPTR_Edge edges;
EDGES::VEC_WPTR_Edge remove_edges;
EDGES::VEC_WPTR_Edge split_edges;
VEC_SPTR_Node selected_nodes;
std::map<NODES::SPTR_Node, int> counter;
std::shared_ptr<APP::State> state = APP::State::get();
public:
GraphManager() {}
NODES::VEC_SPTR_Node nodes;
EDGES::VEC_SPTR_Edge edges;
std::shared_ptr<GraphManager> getPtr();
int getNodesSize();
void createMidiNode(Vec2Df position);
void createConfigNode(Vec2Df position);
void setMidiPortForAll(int number, bool _state);
void addNode(SPTR_Node node);
void addConnection(WPTR_Node from, WPTR_Node to);
void addToSelection();
void select();
void deselect();
VEC_SPTR_Node getSelection();
void copyNodes(VEC_SPTR_Node _nodes, Vec2Df position);
void copyNodes(std::vector<std::shared_ptr<MidiNode>> _nodes, Vec2Df position);
VEC_SPTR_Node getCopiesOfSelectedNodes();
SPTR_Node copyNode(SPTR_Node n, Vec2Df position);
SPTR_Node copyNode(std::shared_ptr<MidiNode> n, Vec2Df position);
void setNodeMode(int mode);
void reset();
WPTR_Node getNode(int i);
WPTR_Node getFirstNode();
WPTR_Node getLastNode();
VEC_WPTR_Node getMidiNodes();
NODES::SPTR_Node getRandomNode();
EDGES::SPTR_Edge getRandomEdge();
SPTR_Node getClosestNode(Vec2Df position);
VEC_WPTR_Node getNodesByID(int _id);
WPTR_Edge lastEdge();
WPTR_Node next(WPTR_Node current, WPTR_Node last);
void splitEdge(SPTR_Edge edge);
void splitEdges();
void removeEdge(SPTR_Edge edge);
void removeEdges();
VEC_WPTR_Node getAdjacentNodes(VEC_SPTR_Node selected_nodes);
void removeNodesAndEdges(VEC_SPTR_Node selected_nodes);
void pasteNodesAndEdges(VEC_SPTR_Node nodes, Vec2Df position);
void update();
void drawEdges(Vec2Df mouse_pos, Camera2D cam);
void drawNeighborLines(SPTR_Node node, Vec2Df mouse_pos, Camera2D camera);
void drawNodes(Vec2Df mouse_pos, Camera2D camera);
EDGES::VEC_SPTR_Edge remove_edges;
EDGES::VEC_SPTR_Edge split_edges;
std::map<NODES::SPTR_Node, int> counter;
NODES::SPTR_Node getRandomNode(){
VEC_SPTR_GraphItem o;
std::ranges::sample(nodes, std::back_inserter(o), 1, std::mt19937{std::random_device{}()});
return std::dynamic_pointer_cast<VISEQ::GRAPH::NODES::Node>(o.front());
}
EDGES::SPTR_Edge getRandomEdge(){
VEC_SPTR_GraphItem o;
std::ranges::sample(edges, std::back_inserter(o), 1, std::mt19937{std::random_device{}()});
return std::dynamic_pointer_cast<VISEQ::GRAPH::EDGES::Edge>(o.front());
}
void copyNodes(VEC_SPTR_Node _nodes, Vec2Df position){
for(auto n: _nodes){
nodes.insert(nodes.end(), std::make_shared<Node>(*n));
nodes.back()->setPosition(position + nodes.back()->drag_offset);
nodes.back()->selected = false;
}
}
void copyNodes(std::vector<std::shared_ptr<MidiNode>> _nodes, Vec2Df position){
for(auto n: _nodes){
nodes.insert(nodes.end(), std::make_shared<MidiNode>(*n));
nodes.back()->setPosition(position + nodes.back()->drag_offset);
nodes.back()->selected = false;
}
}
SPTR_Node copyNode(SPTR_Node n, Vec2Df position){
std::cout << "copy basenode" << std::endl;
nodes.insert(nodes.end(), std::make_shared<Node>(*n));
nodes.back()->setPosition(position);
return nodes.back();
}
SPTR_Node copyNode(std::shared_ptr<MidiNode> n, Vec2Df position){
std::cout << "copy midinode" << std::endl;
nodes.insert(nodes.end(), std::make_shared<MidiNode>(*n));
nodes.back()->setPosition(position);
return nodes.back();
}
SPTR_Node addMidiNode(Vec2Df position){
nodes.emplace_back(std::make_shared<MidiNode>(position));
return nodes.back();
}
void setNodeMode(int mode){
for(auto n: nodes){
n->SMode(mode);
}
}
void reset(){
counter.clear();
}
SPTR_Node next(SPTR_Node current, SPTR_Node last){
VEC_SPTR_Edge possible_connections = current->connections | RANGE_FILTER([current](SPTR_Edge c){
return (c->start == current && c->pass_forward) || (c->end == current && c->pass_backward);
}) | std::ranges::to<VEC_SPTR_Edge>();
if(possible_connections.empty()) return SPTR_Node(nullptr);
int return_path = 0;
float angle = -1;
if(current->distributionMode == Distribution::GEOMETRIC){
Vec2Df coming_from = (current->position - last->position).norm();
int i = 0;
for(auto n : possible_connections){
SPTR_Node next_node = n->getNextNode(current);
float angle_v1_v2 = coming_from.norm().dot((next_node->position - current->position).norm());
if(angle_v1_v2 > angle) {
return_path = i;
angle = angle_v1_v2;
}
++i;
}
}
if(current->distributionMode == Distribution::ROUNDROBIN){
if(counter.find(current) == counter.end()) {
counter[current] = 0;
} else {
counter[current] = (counter[current] + 1) % possible_connections.size();
return_path = counter[current];
}
}
if(current->distributionMode == Distribution::RANDOM){
return_path = rand() % possible_connections.size();
}
//possible_connections.at(return_path)->active = true;
return possible_connections.at(return_path)->getNextNode(current);
}
SPTR_Node getClosestNode(Vec2Df position){
auto neighbors = nodes | RANGE_FILTER([](SPTR_Node n){ return !n->dragging;}) | std::ranges::to<VEC_SPTR_Node>();
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();
}
void addConnection(SPTR_Node from, SPTR_Node to){
if(from != to) {
edges.emplace_back(std::make_shared<Edge>());
edges.back()->start = from;
edges.back()->end = to;
from->connections.push_back(edges.back());
to->connections.push_back(edges.back());
}
}
void splitEdge(SPTR_Edge edge){
split_edges.push_back(edge);
}
void splitEdges(){
for(auto s : split_edges){
Vec2Df center = s->end->position.center(s->start->position);
SPTR_Node cn = copyNode(std::dynamic_pointer_cast<MidiNode>(s->start), center);
addConnection(s->start, cn);
addConnection(cn, s->end);
remove_edges.push_back(s);
}
split_edges.clear();
}
void removeEdge(SPTR_Edge edge){
remove_edges.push_back(edge);
}
void removeEdges(){
for(auto edge : remove_edges){
std::erase(edge->start->connections, edge);
std::erase(edge->end->connections, edge);
std::erase(edges, edge);
}
remove_edges.clear();
}
void drawEdges(Vec2Df mouse_pos, Camera2D cam){
if(!remove_edges.empty()) removeEdges();
if(!split_edges.empty()) splitEdges();
for(auto c : edges){
Vec2Df v1_position = c->start->position - ((c->start->position - c->end->position)).norm() * (c->start->radius + c->connector_offset);
Vec2Df v2_position = c->end->position - ((c->end->position - c->start->position)).norm() * (c->end->radius + c->connector_offset);
float v1_v2_distance = v1_position.dist(&v2_position);
float midi_gate_v1 = Clamp((4.f / 1000.f) * c->start->GGate(), 1.f, 4.f);
float midi_gate_v2 = Clamp((4.f / 1000.f) * c->end->GGate(), 1.f, 4.f);
// Line with small connector circles
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->position.dist(c->end->position);
std::string frac = MIDI::Fractions.at(std::clamp<int>(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->position + (c->end->position - c->start->position) * ((d - text_length.x)*0.5f)/d;
Vec2Df middle_handle_frac = c->start->position + (c->end->position - c->start->position) * ((d - text_length_frac.x)*0.5f)/d;
Vec2Df text_position = middle_handle + (c->start->position - c->end->position).norm().orth() * 12.0f;
Vec2Df text_position_frac = middle_handle_frac - (c->start->position - c->end->position).norm().orth() * 4.0f;
float rotation = Vector2Angle((Vector2){1,0}, (c->end->position - c->start->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(v1_v2_distance < 100) c->switches_offset = v1_v2_distance/2.3f;
Vec2Df from_to_handle = c->start->position - ((c->start->position - c->end->position)).norm() * (c->start->radius + c->switches_offset);
Vec2Df to_from_handle = c->end->position - ((c->end->position - c->start->position)).norm() * (c->end->radius + c->switches_offset);
Vec2Df middle_handle = c->end->position - ((c->end->position - c->start->position))*0.4f;
Vec2Df middle_handle_2 = c->end->position - ((c->end->position - c->start->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);
if(!c->start->dragging && !c->end->dragging){
// 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.x, flip_handle.y, 8.0f, (Color){232, 50, 32, opacity} )) removeEdge(c);
if(ViButton(mouse_pos, flip_handle_2.x, flip_handle_2.y, 8.0f, (Color){232, 125, 62, opacity} )) splitEdge(c);
}
}
}
void drawNodes(Vec2Df mouse_pos, Camera2D camera){
float min_d = 480;
for(auto n : nodes){
if(n->dragging){
auto neighbors_local = nodes | RANGE_FILTER([mouse_pos](SPTR_Node n){ return !n->dragging && !n->selected && n->position.dist(mouse_pos) < 960;}) | std::ranges::to<VEC_SPTR_Node>();
std::sort(neighbors_local.begin(), neighbors_local.end(), [n, 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);
n->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<VEC_SPTR_Node>();
for(auto nb : n->neighbors){
DrawCircleLinesV(nb->position, 20.0f, VS_COLOR_RED);
float d = nb->position.dist(mouse_pos);
float dn = nb->position.dist(n->position);
if(d > 0 && d < min_d*1.5 && d < 960) {
std::string frac = MIDI::Fractions.at(std::clamp<int>(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 + (n->position - nb->position) * ((dn - text_length.x)*0.5f)/dn;
Vec2Df middle_handle_frac = nb->position + (n->position - nb->position) * ((dn - text_length_frac.x)*0.5f)/dn;
Vec2Df text_position = middle_handle + (nb->position - n->position).norm().orth() * 12.0f;
Vec2Df text_position_frac = middle_handle_frac - (nb->position - n->position).norm().orth() * 4.0f;
float rotation = Vector2Angle((Vector2){1,0}, (n->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 = n->position - ((n->position - nb->position)).norm() * 20.0f;
Vec2Df nb_position = nb->position - ((nb->position - n->position)).norm() * 20.0f;
DrawLineV(n_position, nb_position, VS_COLOR_PINK);
}
}
}
}
n->draw(mouse_pos, camera);
}
}
NLOHMANN_DEFINE_TYPE_INTRUSIVE(GraphManager, nodes, edges, selected_nodes)
friend void from_json(const json& j, std::shared_ptr<GraphManager>& G);
friend void to_json(json& j, const std::shared_ptr<GraphManager>& G);
};
inline void to_json(json& j, const GraphManager& G){
j = json{{"nodes", G.nodes}, {"edges", G.edges}};
}
inline void from_json(const json& j, GraphManager& G){
j.at("nodes").template get_to<VEC_SPTR_Node>(G.nodes); // = json{{"nodes", G.nodes}, {"edges", G.edges}};
}
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "vmath.h"
#include <raymath.h>
#include <string>
namespace VISEQ::HELPER{
inline void DrawSuperText(std::string text, Vec2Df start, Vec2Df end, float pos, float dist, float size, Color col){
float d = start.dist(end);
Vector2 text_length = MeasureTextEx(GetFontDefault(), text.c_str(), size, 2.0f);
Vec2Df middle_handle = start + (end - start) * ((d - text_length.x)*pos)/d;
Vec2Df text_position = middle_handle + (start - end).norm().orth() * dist;
float rotation = Vector2Angle((Vector2){1,0}, (end - start)) * RAD2DEG;
DrawTextPro(GetFontDefault(), text.c_str(), text_position, (Vector2){0,0}, rotation, size, 2.0f, col);
}
}
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include "datatypes.h"
#include "raylib.h"
#include "vmath.h"
namespace VISEQ::BASE {
class Interactable{
public:
Interactable() {
static int _z_index = 0;
z_index = _z_index;
_z_index++;
}
virtual ~Interactable() {}
bool is_hovering = false;
int z_index = 0;
inline static int z_hovering = 0;
virtual bool hovering() = 0;
virtual bool entered() {return false;};
virtual bool exited() {return false;};
virtual bool clicked() {
return hovering() && IsMouseButtonPressed(MOUSE_BUTTON_LEFT);
}
virtual bool held() {
return IsMouseButtonDown(MOUSE_BUTTON_LEFT);
}
virtual bool released() {
return IsMouseButtonReleased(MOUSE_BUTTON_LEFT);
}
virtual GRAPH::GraphItemType getType() = 0;
virtual void pickUp() = 0;
virtual void pickUp(Vec2Df position) = 0;
virtual void drag(Vec2Df position) = 0;
virtual void putDown() = 0;
};
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "interactable.h"
#include <deque>
#include <memory>
namespace VISEQ::APP{
enum Interaction {NONE, HOVER, CLICK, RIGHTCLICK, HOLD, RELEASE, PICKUP, DRAG, PUTDOWN, SELECT, DESELECT, CONNECT};
struct InteractionEvent{
std::shared_ptr<BASE::Interactable> item;
Interaction action;
};
inline bool operator==(const InteractionEvent& lhs, const InteractionEvent& rhs){
return lhs.item == rhs.item;
}
inline static std::deque<APP::InteractionEvent> event_queue;
}
+255
View File
@@ -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 <algorithm>
#include <memory>
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> node_tl;
std::shared_ptr<Node> 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<Node> 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<Connector>(e.item);
if(found && partner != e){
auto connector_to = std::dynamic_pointer_cast<Connector>(partner.item);
connector->partner = connector_to;
connector_to->partner = connector;
gm->addConnection(std::dynamic_pointer_cast<Node>(connector->parent.lock()), std::dynamic_pointer_cast<Node>(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<Node>(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<Node>(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();
}
}
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include "clipboard.h"
#include "datatypes.h"
#include "draggable.h"
#include "graph-item.h"
#include "graph-manager.h"
#include "interaction-event.h"
#include "node.h"
#include "raylib.h"
#include "signal-manager.h"
#include "state.h"
#include "canvas.h"
#include <memory>
namespace VISEQ::INTERACTION{
class ScaleHandle : public BASE::Draggable, public GraphItem, public std::enable_shared_from_this<ScaleHandle>{
public:
ScaleHandle(){}
float radius = 6;
std::shared_ptr<ScaleHandle> getPtr(){
return shared_from_this();
}
virtual GRAPH::GraphItemType getType() override {
return GraphItemType::HANDLE;
}
virtual bool hovering() override {
return CheckCollisionPointCircle(SCREEN_MOUSE, position, radius);
}
virtual 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});
}
virtual void draw() override {
DrawCircleV(position, radius, VS_COLOR_SUN);
}
virtual bool select() override {
return false;
}
};
class InteractionManager{
public:
InteractionManager() {}
InteractionManager(std::shared_ptr<GRAPH::GraphManager> gm, std::shared_ptr<SIGNAL::SignalManager> sm, std::shared_ptr<APP::CANVAS::Canvas> canvas) : canvas(canvas), gm(gm), sm(sm) {}
std::shared_ptr<APP::CANVAS::Canvas> canvas;
std::shared_ptr<GraphManager> gm;
std::shared_ptr<SIGNAL::SignalManager> sm;
VISEQ::APP::Clipboard cb;
std::shared_ptr<APP::State> state = APP::State::get();
Vec2Df scale_start;
std::shared_ptr<ScaleHandle> scale_handle = std::make_shared<ScaleHandle>();
void update();
void draw();
void removeNodes(VEC_SPTR_Node nodes);
void scaleSelection();
void snapPolar(std::shared_ptr<Node> dragNode);
void doInteraction();
};
}
+21 -508
View File
@@ -1,4 +1,3 @@
/*
*
* VISEQ - The Visual Sequencer
@@ -6,41 +5,21 @@
*
*/
#include "graph-manager.h"
#include "node.h"
#include "vmath.h"
#include <filesystem>
#include <fstream>
#define RAYGUI_IMPLEMENTATION
#include <raylib.h>
#include <raymath.h>
#include "state-manager.h"
#include "datatypes.h"
#include "midi-node.h"
#include "nlohmann/json.hpp"
#include "state.h"
#include "app-manager.h"
#include <cstdlib>
#include <stdio.h>
#include <cmath>
#include <ranges>
#include <algorithm>
#include <iterator>
#include <memory>
using json = nlohmann::json;
VISEQ::APP::AppState app;
VISEQ::APP::AppManager app;
Vec2Df mouse_pos;
Vec2Df drag_start;
Camera2D camera = { {0} };
bool mouse_clicked = false;
bool mouse_touched = false;
bool mouse_dragging = false;
std::shared_ptr<VISEQ::APP::State> state = VISEQ::APP::State::get();
int main(){
@@ -59,496 +38,30 @@ int main(){
//ToggleFullscreen();
state->settings.fps = 60.0f;
state->settings.bpm = 120.0f;
state->settings.grid = 60.0f; // 120px for a 16th note
state->settings.speed = (state->settings.grid * 16.0f) * (state->settings.bpm / 120.f);
state->settings.width = monitor_width - 300;
state->settings.height = monitor_height - 300;
SetTargetFPS(state->settings.fps);
app.init();
app.ui.init();
app.settings.fps = 60.0f;
app.settings.bpm = 120.0f;
app.settings.grid = 60.0f; // 120px for a 16th note
app.settings.speed = (app.settings.grid * 16.0f) * (app.settings.bpm / 120.f);
app.settings.width = monitor_width - 300;
app.settings.height = monitor_height - 300;
SetTargetFPS(app.settings.fps);
camera.target = (Vector2){ app.settings.width/2.0f, app.settings.height/2.0f };
camera.offset = (Vector2){ app.settings.width/2.0f, app.settings.height/2.0f };
camera.rotation = 0.0f;
camera.zoom = 1.0f;
if(std::filesystem::exists("state.json")){
std::ifstream f("state.json");
json graph = json::parse(f);
app.settings = graph.at("settings");
int highest_id = 0;
std::shared_ptr<Node> hid;
for(auto n : graph.at("graphmanager").at("nodes")){
std::shared_ptr<MidiNode> mn = n;
if(mn->id > highest_id) {
highest_id = mn->id;
hid = mn;
}
}
for(auto e : graph.at("graphmanager").at("edges")){
if(e.at("id") > highest_id){
highest_id = e.at("id");
}
}
for(auto n : graph.at("graphmanager").at("nodes")){
std::shared_ptr<MidiNode> mn = n;
app.gm.nodes.push_back(mn);
std::cout << std::format("adding node {}", mn->id) << std::endl;
}
for(auto e : graph.at("graphmanager").at("edges")){
int start_id = e.at("start").get<int>();
int end_id = e.at("end").get<int>();
VEC_SPTR_Node start = app.gm.nodes | RANGE_FILTER([start_id](SPTR_Node n){return n->id == start_id;}) | std::ranges::to<VEC_SPTR_Node>();
VEC_SPTR_Node end = app.gm.nodes | RANGE_FILTER([end_id](SPTR_Node n){return n->id == end_id;}) | std::ranges::to<VEC_SPTR_Node>();
if(!start.empty() && !end.empty()) {
std::cout << std::format("connection {} to {}", start_id, end_id) << std::endl;
app.gm.addConnection(start.front(), end.front());
}
}
app.gm.edges.back()->newId(highest_id); // set static id counter in base class to highest value from json file
for(auto s : graph.at("signalmanager").at("signals")){
int start_id = s.at("start").get<int>();
int next_id = s.at("next").get<int>();
int last_id = s.at("last").get<int>();
std::shared_ptr<VISEQ::SIGNAL::Signal> ms = s;
VEC_SPTR_Node start = app.gm.nodes | RANGE_FILTER([start_id](SPTR_Node n){return n->id == start_id;}) | std::ranges::to<VEC_SPTR_Node>();
VEC_SPTR_Node last = app.gm.nodes | RANGE_FILTER([last_id](SPTR_Node n){return n->id == last_id;}) | std::ranges::to<VEC_SPTR_Node>();
VEC_SPTR_Node next = app.gm.nodes | RANGE_FILTER([next_id](SPTR_Node n){return n->id == next_id;}) | std::ranges::to<VEC_SPTR_Node>();
ms->start = start.front();
ms->next = next.front();
ms->last = last.front();
app.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){
app.gm.addMidiNode(Vec2Df{(float)i * app.settings.grid*4 + 7 * app.settings.grid, 2 * app.settings.grid*4});
}
for(int i = 3; i >= 0; --i){
app.gm.addMidiNode(Vec2Df{(float)i * app.settings.grid*4 + 7 * app.settings.grid, 3 * app.settings.grid*4});
}
for(int i = 0; i < 8; ++i){
app.gm.addConnection(app.gm.nodes[i], app.gm.nodes[(i+1) % 8]);
}
app.sm.addSignalAtNode(app.gm.nodes.front());
}
app.sm.startSignalThread();
enum InteractionState {HOVER_CANVAS, HOVER_NODE, HOVER_CONNECTOR, DRAG_NODE, CONNECT_NODE, DRAW_SELECTION, SCALE_SELECTION, DELETE_SELECTED_NODES, COPY_SELECTED_NODES, PASTE_SELECTED_NODES};
InteractionState istate = HOVER_CANVAS;
Vec2Df select_start;
Rectangle selection;
Vec2Df scale_start;
Vec2Df scale_rectangle;
VEC_SPTR_Node copied_nodes;
app.loadState();
app.startSignals();
while(!WindowShouldClose()){
BeginDrawing();
ClearBackground((Color){46,46,46});
BeginMode2D(camera);
app.settings.speed = (app.settings.grid * 16.0f) * (app.settings.bpm / 120.f);
Vector2 mp = GetScreenToWorld2D(GetMousePosition(), camera);
Vec2Df mouse_pos = {(float)mp.x, (float)mp.y};
Vec2Df cam_pos = {camera.offset.x, camera.offset.y};
bool mouse_down = IsMouseButtonDown(MOUSE_BUTTON_LEFT);
bool mouse_clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT);
bool mouse_released = IsMouseButtonReleased(MOUSE_BUTTON_LEFT);
bool mouse_right_down = IsMouseButtonDown(MOUSE_BUTTON_RIGHT);
bool mouse_right_clicked = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT);
bool mouse_right_released = IsMouseButtonReleased(MOUSE_BUTTON_RIGHT);
Vec2Df mouse_delta = {GetMouseDelta().x, GetMouseDelta().y};
Vec2Df delta = mouse_delta * (-1.0f/camera.zoom);
auto dragging_nodes = app.gm.nodes | RANGE_FILTER([](SPTR_Node n) { return n->dragging;});
auto connecting_nodes = app.gm.nodes | RANGE_FILTER([](SPTR_Node n) { return n->connecting;});
auto selected_nodes = app.gm.nodes | RANGE_FILTER([](SPTR_Node n) { return n->selected && !n->dragging;}) | std::ranges::to<VEC_SPTR_Node>();
auto hovering_nodes = app.gm.nodes | RANGE_FILTER([&mouse_pos](SPTR_Node n) { return CheckCollisionPointCircle(mouse_pos, n->position, n->radius);});
auto hovering_connectors = app.gm.nodes | RANGE_FILTER([&mouse_pos](SPTR_Node n) { return CheckCollisionPointCircle(mouse_pos, (n->position - ((n->position - mouse_pos)).norm() * (n->radius + 14.0f)), 8.0f);});
for(auto &n : app.gm.nodes ){
if(hovering_nodes.empty()) n->hovering = false;
if(hovering_connectors.empty()) n->hover_connect = false;
}
if(!mouse_down && !mouse_right_down && dragging_nodes.empty() && connecting_nodes.empty() && hovering_nodes.empty() && hovering_connectors.empty()) istate = InteractionState::HOVER_CANVAS;
if((mouse_clicked || mouse_down || mouse_released) && !mouse_right_down && dragging_nodes.empty() && connecting_nodes.empty() && hovering_nodes.empty() && hovering_connectors.empty() && istate != InteractionState::SCALE_SELECTION) istate = InteractionState::DRAW_SELECTION;
if(!hovering_nodes.empty() && !mouse_down && !mouse_right_down) istate = InteractionState::HOVER_NODE;
if(!dragging_nodes.empty()) istate = InteractionState::DRAG_NODE;
if(!hovering_connectors.empty() && !mouse_right_down && istate == InteractionState::HOVER_CANVAS) istate = InteractionState::HOVER_CONNECTOR;
if(!connecting_nodes.empty()) istate = InteractionState::CONNECT_NODE;
if(!selected_nodes.empty() && IsKeyPressed(KEY_DELETE)) istate = InteractionState::DELETE_SELECTED_NODES;
if(!selected_nodes.empty() && IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) istate = InteractionState::COPY_SELECTED_NODES;
if(!copied_nodes.empty() && IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V)) istate = InteractionState::PASTE_SELECTED_NODES;
if(IsKeyPressed(KEY_TAB)) {
app.settings.mode = (app.settings.mode + 1) % 3;
app.gm.setNodeMode(app.settings.mode);
}
if(!selected_nodes.empty()){
float offset = 30;
Vec2Df corner_tl = {50000, 50000};
Vec2Df corner_br = {-50000, -50000};
std::shared_ptr<Node> node_tl;
std::shared_ptr<Node> node_br;
for(auto n : selected_nodes){
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);
DrawRectangleLines(corner_tl.x, corner_tl.y, (corner_br.x - corner_tl.x), (corner_br.y - corner_tl.y), VS_COLOR_LIGHTGREY);
DrawCircleV(corner_br, 5, YELLOW);
if(istate == InteractionState::SCALE_SELECTION && mouse_released){
istate = InteractionState::HOVER_CANVAS;
}
if(mouse_clicked && corner_br.dist(mouse_pos) < 10){
scale_start = (corner_br - corner_tl);
for(auto n : selected_nodes){
n->drag_offset = n->position - node_tl->position;
}
istate = InteractionState::SCALE_SELECTION;
}
if(istate == InteractionState::SCALE_SELECTION && mouse_down){
Vec2Df scale = (mouse_pos - 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 : selected_nodes){
n->position = node_tl->position + (n->drag_offset * scale);
}
istate = InteractionState::SCALE_SELECTION;
}
}
switch(istate){
case InteractionState::SCALE_SELECTION:
case InteractionState::HOVER_CANVAS:
{
for(auto &n : app.gm.nodes){
n->hover_connect = false;
}
if(mouse_right_down) {
camera.target = Vector2Add(camera.target, delta);
}
break;
}
case InteractionState::DRAW_SELECTION:
{
if(mouse_clicked){
select_start = mouse_pos;
}
if(mouse_down){
Vec2Df select1 = {mouse_pos.x > select_start.x ? select_start.x : mouse_pos.x, mouse_pos.y > select_start.y ? select_start.y : mouse_pos.y};
Vec2Df select2 = {mouse_pos.x > select_start.x ? mouse_pos.x - select_start.x : select_start.x - mouse_pos.x, mouse_pos.y > select_start.y ? mouse_pos.y - select_start.y : select_start.y - mouse_pos.y};
DrawRectangleLines(select1.x, select1.y, select2.x, select2.y, (Color){150,150,150,150});
selection = (Rectangle){select1.x, select1.y, select2.x, select2.y};
for(auto& n : app.gm.nodes){
if(CheckCollisionPointRec(n->position, selection)) {
n->in_rect = true;
} else {
n->in_rect = false;
}
}
}
if(mouse_released){
int n_selected = 0;
for(auto& n : app.gm.nodes){
if(CheckCollisionPointRec(n->position, selection)) {
n->selected = true;
n->in_rect = false;
n_selected++;
} else {
if(!IsKeyDown(KEY_LEFT_SHIFT)) n->selected = false;
}
}
if(n_selected > 0) copied_nodes.clear();
selection = (Rectangle){0,0,0,0};
}
break;
}
case InteractionState::HOVER_NODE:
{
if(mouse_clicked) {
if(IsKeyDown(KEY_LEFT_SHIFT)) {
hovering_nodes.front()->selected = true;
} else {
hovering_nodes.front()->dragging = true;
hovering_nodes.front()->drag_offset = hovering_nodes.front()->position - mouse_pos;
}
}
if(mouse_right_clicked) hovering_nodes.front()->config = true;
break;
}
case InteractionState::DRAG_NODE:
{
if(!dragging_nodes.empty()){
SPTR_Node dragNode = dragging_nodes.front();
for(auto &n : selected_nodes){
if(!n->picked_up) n->drag_offset = n->position - dragNode->position;
n->picked_up = true;
}
if(IsKeyDown(KEY_LEFT_CONTROL)) {
dragNode->position.x = round((mouse_pos.x + dragNode->drag_offset.x) / app.settings.grid) * app.settings.grid;
dragNode->position.y = round((mouse_pos.y + dragNode->drag_offset.y) / app.settings.grid) * app.settings.grid;
} else if(IsKeyDown(KEY_LEFT_SHIFT)){
float steps = round((dragNode->neighbors.front()->position.dist(mouse_pos)) / app.settings.grid) * app.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(mouse_pos.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},(mouse_pos - dragNode->neighbors.front()->position));
Vec2Df polar = VISEQ::VMATH::polarToCartesian(pol_r, pol_theta);
dragNode->position = dragNode->neighbors.front()->position + polar;
}
} else {
dragNode->position = mouse_pos + dragNode->drag_offset;
}
for(auto &n : selected_nodes){
n->position = dragNode->position + n->drag_offset;
}
if(mouse_released) {
for(auto &n : dragging_nodes){
n->dragging = false;
n->picked_up = false;
}
for(auto &n : selected_nodes){
n->picked_up = false;
n->dragging = false;
}
}
}
break;
}
case InteractionState::HOVER_CONNECTOR:
{
if(!hovering_connectors.empty()){
hovering_connectors.front()->hover_connect = true;
if(mouse_clicked) hovering_connectors.front()->connecting = true;
}
break;
}
case InteractionState::CONNECT_NODE:
{
Vec2Df connector_position = connecting_nodes.front()->position - ((connecting_nodes.front()->position - mouse_pos)).norm() * (connecting_nodes.front()->radius + 14.0f);
Vec2Df target_position = mouse_pos;
if(IsKeyDown(KEY_LEFT_CONTROL)) {
target_position.x = round((mouse_pos.x) / app.settings.grid) * app.settings.grid;
target_position.y = round((mouse_pos.y) / app.settings.grid) * app.settings.grid;
} else if(IsKeyDown(KEY_LEFT_SHIFT)){
std::shared_ptr<Node> closest = app.gm.getClosestNode(mouse_pos);
float steps = round((closest->position.dist(mouse_pos)) / app.settings.grid) * app.settings.grid;
float pol_r = steps;
float pol_theta = Vector2Angle(Vec2Df{1,0},(mouse_pos - closest->position));
Vec2Df polar = VISEQ::VMATH::polarToCartesian(pol_r, pol_theta);
target_position = closest->position + polar;
} else {
target_position = mouse_pos;
}
if(connecting_nodes.front()->connecting && mouse_down){
DrawLine(connector_position.x, connector_position.y, target_position.x, target_position.y, (Color){229, 181, 103, 255});
connecting_nodes.front()->hovering = true;
connecting_nodes.front()->hover_connect = true;
}
if(!hovering_connectors.empty()) {
hovering_connectors.front()->hovering = true;
hovering_connectors.front()->hover_connect = true;
}
if(!hovering_nodes.empty()) hovering_nodes.front()->hovering = true;
if(mouse_released) {
if(&hovering_nodes.front() != &connecting_nodes.front() && &connecting_nodes.front() != &hovering_connectors.front()){
if(!hovering_nodes.empty()){
app.gm.addConnection(connecting_nodes.front(), hovering_nodes.front());
} else if(!hovering_connectors.empty()){
app.gm.addConnection(connecting_nodes.front(), hovering_connectors.front());
} else {
SPTR_Node new_node = app.gm.copyNode(std::dynamic_pointer_cast<MidiNode>(connecting_nodes.front()), target_position);
new_node->connecting = false;
app.gm.addConnection(connecting_nodes.front(), new_node);
}
}
connecting_nodes.front()->connecting = false;
istate = InteractionState::HOVER_CANVAS;
}
break;
}
case InteractionState::DELETE_SELECTED_NODES:
{
app.removeNodes(selected_nodes | std::ranges::to<VEC_SPTR_Node>());
break;
}
case InteractionState::COPY_SELECTED_NODES:
{
std::ranges::copy(selected_nodes.begin(), selected_nodes.end(), std::back_inserter(copied_nodes));
std::ranges::for_each(selected_nodes.begin(), selected_nodes.end(), [](SPTR_Node n){n->selected = false;});
selected_nodes.clear();
break;
}
case InteractionState::PASTE_SELECTED_NODES:
{
for(auto n: copied_nodes){
n->drag_offset = (n->position - copied_nodes.front()->position);
}
app.copyNodesAndConnections(copied_nodes, mouse_pos);
//copied_nodes.clear();
break;
}
};
float wheel = GetMouseWheelMove();
if(wheel != 0){
if(!hovering_nodes.empty() && hovering_nodes.front()->getNodeType() == NodeType::MIDINODE) {
VEC_SPTR_Node modify_nodes;
std::copy_if(selected_nodes.begin(), selected_nodes.end(), std::back_inserter(modify_nodes), [](SPTR_Node n){ return n->getNodeType() == NodeType::MIDINODE;});
if(selected_nodes.empty()) std::copy_if(hovering_nodes.begin(), hovering_nodes.end(), std::back_inserter(modify_nodes), [modify_nodes](SPTR_Node n){ return n->getNodeType() == NodeType::MIDINODE && !std::ranges::contains(modify_nodes, n);});
for(auto n : modify_nodes){
std::shared_ptr<MidiNode> midiNode = std::dynamic_pointer_cast<MidiNode>(n);
if(IsKeyDown(KEY_LEFT_CONTROL) && !IsKeyDown(KEY_LEFT_ALT)){
switch(app.settings.mode){
case 0:{
midiNode->midi_note += wheel;
break;}
case 1:{
midiNode->midi_cc += wheel;
break;}
case 2:{
midiNode->midi_pitch_bend += wheel;
break;}
};
if(midiNode->midi_note < 0 ) midiNode->midi_note = VISEQ::MIDI::Notes.size()-1;
if(midiNode->midi_note >= VISEQ::MIDI::Notes.size() ) midiNode->midi_note = 0;
if(midiNode->midi_cc < 0 ) midiNode->midi_cc = 127;
if(midiNode->midi_cc > 127 ) midiNode->midi_cc = 0;
if(midiNode->midi_pitch_bend < 0 ) midiNode->midi_pitch_bend = 0;
if(midiNode->midi_pitch_bend > 16383 ) midiNode->midi_pitch_bend = 16383;
} else if(IsKeyDown(KEY_LEFT_SHIFT) && !IsKeyDown(KEY_LEFT_ALT)) {
midiNode->midi_velocity += wheel;
if(midiNode->midi_velocity < 0 ) midiNode->midi_velocity = 127;
if(midiNode->midi_velocity > 127 ) midiNode->midi_velocity = 0;
} else if(IsKeyDown(KEY_LEFT_ALT)) {
switch(app.settings.mode){
case 0:{
float incr = (float)wheel * (IsKeyDown(KEY_LEFT_SHIFT) ? 100.0f : IsKeyDown(KEY_LEFT_CONTROL) ? 1000.0f : 10.0f);
midiNode->midi_gate += incr;
if(midiNode->midi_gate < 0 ) midiNode->midi_gate = 0;
break;}
case 1:{
midiNode->midi_cc_value += wheel;
if(midiNode->midi_cc_value < 0 ) midiNode->midi_cc_value = 0;
if(midiNode->midi_cc_value > 127 ) midiNode->midi_cc_value = 127;
break;}
case 2:{
float incr = (float)wheel * (IsKeyDown(KEY_LEFT_SHIFT) ? 100.0f : IsKeyDown(KEY_LEFT_CONTROL) ? 1000.0f : 10.0f);
midiNode->midi_pitch_bend += incr;
if(midiNode->midi_pitch_bend < 0 ) midiNode->midi_pitch_bend = 0;
if(midiNode->midi_pitch_bend > 16383 ) midiNode->midi_pitch_bend = 16383;
break;}
};
}
}
}
if(!IsKeyDown(KEY_LEFT_SHIFT) && !IsKeyDown(KEY_LEFT_CONTROL) && !IsKeyDown(KEY_LEFT_ALT)){
Vector2 mouseWorldPos = GetScreenToWorld2D(GetMousePosition(), camera);
camera.offset = GetMousePosition();
camera.target = mouseWorldPos;
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);
}
}
for(int x = -app.settings.grid*100; x < app.settings.grid*100; x+=app.settings.grid){
for(int y = -app.settings.grid*100; y < app.settings.grid*100; y+=app.settings.grid){
DrawPixel(x, y, (Color){130,130,130,200});
}
}
app.draw(mouse_pos, camera);
EndMode2D();
app.drawUI(&mouse_pos, &camera);
EndDrawing();
app.update();
app.draw();
}
std::ofstream file("state.json");
file << json(app);
std::cout << std::setw(4) << json(app) << std::endl;
app.close();
CloseWindow();
+399
View File
@@ -0,0 +1,399 @@
#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 <iostream>
#include <map>
#include <memory>
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<NodeHandle<int>>(midiNote.note, position, VS_COLOR_GREEN, "NOTE", orbit_5, 12));
midiHandles[NOTE].emplace_back(std::make_shared<NodeHandle<int>>(midiNote.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 16, 1, 16, 40, 150));
midiHandles[NOTE].emplace_back(std::make_shared<NodeHandle<int>>(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<NodeHandle<int>>(midiNote.velocity, position, VS_COLOR_PURPLE, "VELOCITY", orbit_4, 32));
midiHandles[CC].emplace_back(std::make_shared<NodeHandle<int>>(midiCC.control, position, VS_COLOR_SUN, "CONTROL", orbit_3, 32));
midiHandles[CC].emplace_back(std::make_shared<NodeHandle<int>>(midiCC.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150));
midiHandles[CC].emplace_back(std::make_shared<NodeHandle<int>>(midiCC.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32));
midiHandles[PC].emplace_back(std::make_shared<NodeHandle<int>>(midiPC.program, position, VS_COLOR_SUN, "PROGRAM", orbit_3, 32));
midiHandles[PC].emplace_back(std::make_shared<NodeHandle<int>>(midiPC.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150));
midiHandles[PC].emplace_back(std::make_shared<NodeHandle<int>>(midiPC.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32));
midiHandles[PB].emplace_back(std::make_shared<NodeHandle<int>>(midiPitchBend.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150));
midiHandles[PB].emplace_back(std::make_shared<NodeHandle<int>>(midiPitchBend.value, position, VS_COLOR_GREEN, "VALUE", orbit_3, 256, -8192, 8192, 40, 300));
midiHandles[AT].emplace_back(std::make_shared<NodeHandle<int>>(midiAftertouch.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150));
midiHandles[AT].emplace_back(std::make_shared<NodeHandle<int>>(midiAftertouch.value, position, VS_COLOR_GREEN, "VALUE", orbit_3, 32));
midiHandles[PP].emplace_back(std::make_shared<NodeHandle<int>>(midiPolyPressure.channel, position, VS_COLOR_PINK, "CHANNEL", orbit_1, 1, 1, 16, 40, 150));
midiHandles[PP].emplace_back(std::make_shared<NodeHandle<int>>(midiPolyPressure.value, position, VS_COLOR_GREEN, "VALUE", orbit_4, 32));
midiHandles[PP].emplace_back(std::make_shared<NodeHandle<int>>(midiPolyPressure.note, position, VS_COLOR_SUN, "NOTE", orbit_5, 12));
}
std::shared_ptr<MidiNode> MidiNode::getPtr(){
return std::static_pointer_cast<MidiNode>(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<std::weak_ptr<ConfigNode>> 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<std::shared_ptr<EDGES::Edge>>>();
std::vector<std::weak_ptr<ConfigNode>> config_nodes;
for(auto e : config_conns){
if(e->start.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast<ConfigNode>(e->start.lock()));
if(e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast<ConfigNode>(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<int> 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<std::vector<Vec2Df>> 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<Vec2Df>(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<std::vector<Vec2Df>> 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 to_json(json& j, const std::shared_ptr<MidiNode>& N){
j = *N;
}
void from_json(const json& j, std::shared_ptr<MidiNode>& N){
N.reset(new MidiNode(j.get<MidiNode>()));
}
}
+51 -128
View File
@@ -1,15 +1,33 @@
#pragma once
#include "config-node.h"
#include "datatypes.h"
#include "node-handle.h"
#include "node.h"
#include "raymath.h"
#include "raylib.h"
#include <map>
#include <memory>
#define RADIUS 14.0f
namespace VISEQ::APP::UI{
class AppUI;
}
namespace VISEQ::GRAPH::NODES {
enum Handles {NOTE = 0, CC, PC, PB, AT, PP};
class MidiNode : public Node{
friend class APP::UI::AppUI;
private:
MIDI::MidiNote midiNote;
MIDI::MidiCC midiCC;
MIDI::MidiPC midiPC;
MIDI::MidiPitchBend midiPitchBend;
MIDI::MidiAftertouch midiAftertouch;
MIDI::MidiPolyPressure midiPolyPressure;
std::map<Handles, std::vector<std::shared_ptr<NodeHandle<int>>>> midiHandles;
public:
@@ -19,11 +37,13 @@ namespace VISEQ::GRAPH::NODES {
bool enabled;
};
MidiNode() {}
MidiNode(Vec2Df position) : Node(position) {}
MidiNode() {init();}
MidiNode(Vec2Df position) : Node(position) {init();}
NodeType nodeType = NodeType::MIDINODE;
//std::vector<std::shared_ptr<ControlNode>> controlNodes;
bool send_note = true;
bool send_cc = false;
bool send_pc = false;
@@ -38,135 +58,38 @@ namespace VISEQ::GRAPH::NODES {
bool gui_midi_velocity_edit = false;
bool gui_midi_gate_edit = false;
bool triggered = false;
int mode = 0;
int midi_note_channel = 1;
int midi_note = 48;
int midi_velocity = 60;
int midi_gate = 10; // gate time in milliseconds
int midi_cc_channel = 0;
int midi_cc = 0;
int midi_cc_value = 0;
int midi_pc_channel = 0;
int midi_pc = 0;
int midi_pc_value = 0;
int midi_pitch_bend_channel = 0;
int midi_pitch_bend = 8192;
float start = 0;
float fade = 0;
float time = 0;
float last = 0;
virtual NodeType getNodeType() const override {return nodeType;}
void init();
std::shared_ptr<MidiNode> 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<std::weak_ptr<ConfigNode>> 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 ;
void setMidiPort(int midi_port, bool state){
midi_outs[midi_port].enabled = state;
}
void trigger() override {
triggered = true;
start = GetTime();
fade = midi_gate;
}
void SMode(int _mode) override{
mode = _mode;
}
int Ggate(){
return midi_gate;
}
void draw(Vec2Df mouse, Camera2D cam) override {
last = time;
time = GetTime();
if(time - start >= (float)midi_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) * midi_velocity - 90.0f, 24, (Color){158, 134, 200, 255});
DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255});
float midi_gate_map = round((255.f / midi_gate) * fade);
Color gate_color = VS_COLOR_GREEN;
switch(mode){
case 0:{
gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115, (unsigned char)Clamp(255 - midi_gate_map, 0, 255)}, WHITE);
break;}
case 1:{
gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){229, 181, 103, (unsigned char)Clamp(255 - midi_gate_map, 0, 255)}, WHITE);
break;}
case 2:{
gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){232, 125, 62, (unsigned char)Clamp(255 - midi_gate_map, 0, 255)}, WHITE);
break;}
}
if(!dragging && !selected && !in_rect && !hovering) DrawCircle(position.x, position.y, radius, gate_color);
if(selected || dragging || in_rect || hovering) DrawCircle(position.x, position.y, radius, (Color){108, 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(IsKeyDown(KEY_LEFT_ALT)){
float value = 0;
int offset_gate = 0;
switch(mode){
case 0:
value = (float)midi_gate/1000.0f;
offset_gate = MeasureText(std::format("{:.2f}", value).c_str(), 12);
DrawText(std::format("{:.2f}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, WHITE );
break;
case 1:
value = (float)midi_cc_value;
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, WHITE );
break;
case 2:
value = (float)midi_pitch_bend;
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, WHITE );
break;
}
} else {
switch(mode){
case 0:{
int offset = MeasureText(MIDI::Notes[(int)midi_note].c_str(), 12);
DrawText(MIDI::Notes[midi_note].c_str(), position.x-offset/2.0f, position.y-5, 12, WHITE );
break;}
case 1:{
int offset = MeasureText(std::format("{}", midi_cc).c_str(), 12);
DrawText(std::format("{}", midi_cc).c_str(), position.x-offset/2.0f, position.y-5, 12, WHITE );
break;}
case 2:{
int offset = MeasureText(std::format("{}", midi_pitch_bend).c_str(), 12);
DrawText(std::format("{}", midi_pitch_bend).c_str(), position.x-offset/2.0f, position.y-5, 12, WHITE );
break;}
}
}
Vec2Df connector = position - ((position - mouse)).norm() * (radius + RADIUS);
float opacity = 150.0f - Clamp(position.dist(mouse), 0.0f, 150.0f);
if(hover_connect){
DrawCircle(connector.x, connector.y, 8.0f, (Color){108, 153, 187, 255});
} else {
DrawCircle(connector.x, connector.y, 8.0f, (Color){108, 153, 187, (unsigned char)opacity});
}
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 MidiNode& N);
friend void from_json(const json& j, MidiNode& N);
friend void to_json(json& j, const std::shared_ptr<MidiNode>& N);
friend void from_json(const json& j, std::shared_ptr<MidiNode>& N);
};
}
+80
View File
@@ -0,0 +1,80 @@
#include "midi.h"
#include "libremidi/libremidi.hpp"
#include "raylib.h"
#include <iostream>
#include <memory>
#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<libremidi::midi_out>();
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);
}
}
}
}
+7 -71
View File
@@ -1,11 +1,8 @@
#pragma once
#include "libremidi/libremidi.hpp"
#include "libremidi/message.hpp"
#include "libremidi/observer_configuration.hpp"
#include "raylib.h"
#include <iostream>
#include <map>
#include <memory>
#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<libremidi::midi_out>();
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();
};
}
+289
View File
@@ -0,0 +1,289 @@
#include "modifier-node.h"
namespace VISEQ::GRAPH::NODES {
std::shared_ptr<ModNode> ModNode::getPtr() {
return std::static_pointer_cast<ModNode>(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<std::weak_ptr<ModNode>> 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<std::shared_ptr<EDGES::Edge>>>();
std::vector<std::weak_ptr<ModNode>> 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<ModNode>(e->start.lock()));
//if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::CONFIGNODE) config_nodes.push_back(std::dynamic_pointer_cast<ModNode>(e->end.lock()));
}
/*std::cout << "id: " << id << " n cn: " << config_nodes.size() << std::endl;*/
return config_nodes;
}
std::vector<std::weak_ptr<ModNode>> 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<std::shared_ptr<EDGES::Edge>>>();
std::vector<std::weak_ptr<ModNode>> 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<ModNode>(e->start.lock()));
if(e->end.lock().get() != this && e->end.lock()->getNodeType() == NodeType::MIDINODE) config_nodes.push_back(std::dynamic_pointer_cast<ModNode>(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<int> 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<Vec2Df>(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);
}
}
+52
View File
@@ -0,0 +1,52 @@
#pragma once
#include "datatypes.h"
#include "edge.h"
#include "node.h"
#include "raymath.h"
#include "state.h"
#include <memory>
namespace VISEQ::GRAPH::NODES {
class ModNode : public Node{
public:
ModNode() {std::cout << "ModNode Constructor std" << std::endl;}
ModNode(Vec2Df position) : Node(position) {}
std::shared_ptr<ModNode> getPtr();
NodeType nodeType = NodeType::CONFIGNODE;
MIDI::MidiNote midiNote;
MIDI::MidiCC midiCC;
MIDI::MidiPitchBend midiPitchBend;
MIDI::MidiPolyPressure midiPolyPressure;
MIDI::MidiPC midiPC;
MIDI::MidiAftertouch midiAftertouch;
int mode = 0;
float radius = 20.0f;
virtual NodeType getNodeType() const override;
virtual void trigger() override;
virtual 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<std::weak_ptr<ModNode>> getConnectedModNodes() const;
std::vector<std::weak_ptr<ModNode>> 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);
};
}
+6 -17
View File
@@ -1,6 +1,8 @@
#pragma once
#include "datatypes.h"
#include "state.h"
#include "vmath.h"
#include <memory>
namespace VISEQ::BASE {
class Movable{
@@ -12,31 +14,18 @@ namespace VISEQ::BASE {
Movable(Vec2Df p, Vec2Df d, float s) : speed(s), position(p), direction(d) {}
Movable(Vec2Df p, float s) : speed(s), position(p) {}
std::shared_ptr<APP::State> state = APP::State::get();
float speed = 960.0f;
Vec2Df position;
Vec2Df direction;
Vec2Df drag_offset;
bool in_rect = false;
bool picked_up = false;
bool dragging = false;
bool hover_connect = false;
bool hovering = false;
bool connecting = false;
bool selected = false;
bool highlighted = false;
bool copied = false;
bool erase = false;
virtual void setPosition(Vec2Df _position) {position = _position;}
virtual Vec2Df getPosition() {return position;};
virtual void update(double frametime) {};
virtual void update() {};
virtual void update(Vec2Df pos) {};
};
}
+182
View File
@@ -0,0 +1,182 @@
#pragma once
#include "button.h"
#include "datatypes.h"
#include "draggable.h"
#include "graph-item.h"
#include "helper.h"
#include "raylib.h"
#include "raymath.h"
#include "state.h"
#include <memory>
#include <nlohmann/json.hpp>
namespace VISEQ::GRAPH::NODES{
template<typename T>
class NodeHandle : public BASE::Draggable, public GraphItem, public std::enable_shared_from_this<NodeHandle<T>>{
public:
NodeHandle() {}
NodeHandle(T &_assignedValue, Vec2Df parentPos, Color _col, std::string name, float orbit, int steps) :
assignedValueRef(_assignedValue),
parentPosition(parentPos),
col(_col),
name(name),
orbit(orbit),
steps(steps) {init();}
NodeHandle(T &_assignedValue, Vec2Df parentPos, Color _col, std::string name, float orbit, int steps, float min, float max, float minDist, float maxDist) :
assignedValueRef(_assignedValue),
parentPosition(parentPos),
col(_col),
name(name),
minValue(min),
maxValue(max),
minDistance(minDist),
maxDistance(maxDist),
orbit(orbit),
steps(steps) {init();}
virtual GRAPH::GraphItemType getType() override {
return GraphItemType::HANDLE;
}
std::shared_ptr<NodeHandle<T>> getPtr(){
return this->shared_from_this();
}
T &assignedValueRef;
Vec2Df parentPosition = {0,0};
Color col;
std::string name = "";
float minValue = 0;
float maxValue = 127;
float minDistance = 40;
float maxDistance = 40 + 2*127;
bool stopAtMax = true;
bool logScale = false;
float radius = 10.0f;
float orbit = 0.0f;
int steps = 1;
float angle_g = 0;
float angle_start = 0;
Vec2Df minP;
Vec2Df maxP;
bool locked = true;
bool visible = false;
void init(){
position = parentPosition.orbitAtAngle(orbit, 0) - parentPosition;
}
void assignValue(T &value){
assignedValueRef = value;
}
float distance(){
return (parentPosition + position).dist(parentPosition);
}
virtual void drag(Vec2Df mouse_pos) override {
float range = (maxValue - minValue) + 1;
float stepping = 360.0f / range;
if(KEYS_DOWN.shift) stepping = stepping * steps;
float angle = Vector2Angle((SCREEN_MOUSE - parentPosition), Vector2(0,1)) * RAD2DEG - 180; // -180 because round(180 / 33.75) * 33.75 is not 0
if(KEYS_PRESSED.ctrl) angle_start = angle;
if(KEYS_DOWN.ctrl) angle = angle_start + (angle - angle_start) / 10;
float stepped_angle = (round(angle / stepping) * stepping + 180) * DEG2RAD; // see above. -180 + 180 to get 0 right round(0 / 33.75) * 33.75 is still 0
position = parentPosition.orbitAtAngle(orbit, stepped_angle) - parentPosition;
}
void lock(){
visible = false;
locked = true;
}
void unlock(){
visible = true;
locked = false;
}
void assignValue(T &value, float min, float max, float minDistance, float maxDistance){
assignedValueRef = value;
}
void updateAssignedValue(){
float angle = 360 - (Vector2Angle(position, Vector2(0,1)) * RAD2DEG + 180);
float range = (maxValue - minValue) + 1;
float stepping = 360.0f / range;
angle_g = angle;
T value = (T)Remap(angle, 0, 359 - stepping, minValue, maxValue);
if(logScale) {
float exp = value / maxValue;
float p = 2.2;
value = (pow(exp, p) / pow(1.0f, p)) * maxValue;
}
assignedValueRef = value;
}
virtual bool hovering() override {
return CheckCollisionPointCircle(SCREEN_MOUSE, position + parentPosition, radius);
}
virtual void update(Vec2Df pos) override {
if(!locked && visible){
if(hovering()) state->pushEvent({getPtr(), APP::Interaction::HOVER});
if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP});
if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG});
}
if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN});
parentPosition = pos;
if(!locked && visible) updateAssignedValue();
minP = parentPosition.orbitAlongVector((parentPosition + position), minDistance);
maxP = parentPosition.orbitAlongVector((parentPosition + position), maxDistance);
}
virtual void draw() override {
if(!locked && visible){
bool show = hovering() || picked_up;
Color dcol = ColorBrightness(col, -0.5f);
Color dyncol = show ? col : dcol;
Color dGrey = ColorBrightness(VS_COLOR_GREY, -0.2f);
Color dynGrey = show ? VS_COLOR_GREY : dGrey;
if(steps > 0){
for(int i = 0; i < steps; i++){
Vec2Df pos = parentPosition.orbitAtAngle(orbit, DEG2RAD * i * (360.0f/steps));
Vec2Df pos1 = parentPosition.orbitAtAngle(orbit - 3.0f, DEG2RAD * i * (360.0f/steps));
DrawLineV(pos, pos1, dynGrey);
}
}
float r = (parentPosition + position).dist(parentPosition);
DrawRing(parentPosition, r - 5, r + 5, 0, 360, 90, ColorAlpha(dyncol, 0.1));
DrawRing(parentPosition, r - 5, r + 5, -90, angle_g - 90, 90, ColorAlpha(dyncol, 0.3));
DrawCircleV(position + parentPosition, radius, dyncol);
//VISEQ::HELPER::DrawSuperText(std::format("{}", assignedValueRef), position + parentPosition, position + parentPosition + Vec2Df(50,0), 1, 0, 8, dyncol);
/*if(show) {*/
/* std::string text = std::format("{}", assignedValueRef);*/
/* DrawCenteredText(text, parentPosition, 12, ColorBrightness(dyncol, 0.75f));*/
/*}*/
}
}
virtual bool select() override {
return false;
}
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(NodeHandle, position, name);
};
}
+109
View File
@@ -0,0 +1,109 @@
#include "node.h"
#include <memory>
#include <raylib.h>
#include "connector.h"
#include "datatypes.h"
#include "interaction-event.h"
#include "state.h"
#include "vmath.h"
#include "midi-node.h"
#include "config-node.h"
namespace VISEQ::GRAPH::NODES {
void Node::setMode(int _mode) {mode = _mode;}
std::shared_ptr<Node> Node::getPtr(){
return shared_from_this();
}
bool Node::hovering() {
return CheckCollisionPointCircle(SCREEN_MOUSE, position, radius);
}
void Node::addConnector() {
connectors.emplace_back(std::make_shared<Connector>(getPtr(), position, radius + 5.0f));
}
void Node::removeConnector(std::weak_ptr<Connector> 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();
}
}
void to_json(json& j, const Node& N){
if(N.getNodeType() == NodeType::MIDINODE){
j = dynamic_cast<const MidiNode&>(N);
}
if(N.getNodeType() == NodeType::CONFIGNODE){
j = dynamic_cast<const ConfigNode&>(N);
}
}
void from_json(const json& j, Node& N){
if(j.at("nodeType") == NodeType::MIDINODE){
N = j.template get<MidiNode>();
}
if(j.at("nodeType") == NodeType::CONFIGNODE){
N = j.template get<ConfigNode>();
}
}
void to_json(json& j, const SPTR_Node& N){
j = *N;
}
void from_json(const json& j, std::shared_ptr<Node>& N){
std::cout << "SPTR_Node" << std::endl;
if(N->getNodeType() == NodeType::MIDINODE){
N.reset(new MidiNode(j.get<MidiNode>()));
}
if(N->getNodeType() == NodeType::CONFIGNODE){
N.reset(new ConfigNode(j.get<ConfigNode>()));
}
}
}
+33 -11
View File
@@ -3,9 +3,11 @@
#include <memory>
#include <raylib.h>
#include "connector.h"
#include "datatypes.h"
#include "draggable.h"
#include "graph-item.h"
#include "moveable.h"
#include "vmath.h"
namespace VISEQ::GRAPH::EDGES{
class Edge;
@@ -13,33 +15,53 @@ namespace VISEQ::GRAPH::EDGES{
namespace VISEQ::GRAPH::NODES {
class Node : public BASE::Movable, public GraphItem{
class Node : public BASE::Draggable, public GraphItem, public std::enable_shared_from_this<Node> {
public:
Node() {}
Node(float x, float y) : Movable(Vec2Df(x, y), 960.0f) {}
Node(Vec2Df p) : Movable(p, 960.0f) {}
Node(float x, float y) : Draggable(Vec2Df(x, y)) {}
Node(Vec2Df p) : Draggable(p) {}
NodeType nodeType;
std::vector<std::shared_ptr<Node>> neighbors;
std::vector<std::shared_ptr<VISEQ::GRAPH::EDGES::Edge>> connections;
std::vector<std::shared_ptr<Connector>> connectors;
Distribution distributionMode = Distribution::GEOMETRIC;
float radius = 15.0f;
float node_radius = 15.0f;
float hover_radius = 32.0f;
bool config = false;
virtual NodeType getNodeType() const {return nodeType;}
bool triggered = false;
int mode = 0;
virtual void trigger() {}
virtual void setMode(int _mode);
std::shared_ptr<Node> getPtr();
virtual bool hovering() override;
void addConnector();
void removeConnector(std::weak_ptr<Connector> 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;
friend void to_json(json& j, const Node& N);
friend void from_json(const json& j, Node& N);
friend void to_json(json& j, const std::shared_ptr<Node>& N);
friend void from_json(const json& j, std::shared_ptr<Node>& N);
virtual void SMode(int mode) {};
virtual int GGate() {return 0;};
virtual int GActive() {return 0;};
virtual void trigger() {};
void draw(Vec2Df mouse_pos, Camera2D cam) override {}
};
typedef std::shared_ptr<VISEQ::GRAPH::NODES::Node> SPTR_Node;
typedef std::weak_ptr<VISEQ::GRAPH::NODES::Node> WPTR_Node;
typedef std::vector<SPTR_Node> VEC_SPTR_Node;
typedef std::vector<WPTR_Node> VEC_WPTR_Node;
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "interactable.h"
#include "vmath.h"
namespace VISEQ::BASE {
class Selectable : public Interactable{
public:
Selectable() {}
bool in_selection_rect = false;
bool selected = false;
bool copied = false;
bool highlighted = false;
bool erase = false;
Vec2Df selection_offset;
virtual bool clicked() override {
return (selected || hovering()) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT);
}
virtual bool select() = 0;
virtual void deselect() = 0;
};
}
+141
View File
@@ -0,0 +1,141 @@
#include "signal-manager.h"
#include "datatypes.h"
#include "midi-node.h"
#include "node.h"
#include "signal.h"
#include "midi.h"
#include <chrono>
#include <memory>
#include <thread>
namespace VISEQ::SIGNAL {
void SignalManager::pause(){
play = false;
}
void SignalManager::stop(){
play = false;
reset();
}
void SignalManager::start(){
play = true;
}
void SignalManager::reset(){
for(auto &s : signals){
s->position = s->start.lock()->position + Vec2Df{0.1, 0.1};
s->next = s->start;
s->last = s->start;
}
gm->reset();
}
void SignalManager::startSignalThread(){
std::thread t1(&SignalManager::updateSignals, this);
t1.detach();
}
void SignalManager::stopSignalThread(){
loop = false;
}
void SignalManager::addSignalAtNode(WPTR_Node node){
Color c = {(unsigned char)(20 + (rand() % 200)), 100, (unsigned char)(20 + (int)(rand() % 200)), 200};
signals.emplace_back(std::make_shared<Signal>(node.lock()->position + Vec2Df{0.5f, 0.5f}, 8.0f, 480, c)); // position + 1 or else it does not render. why? no idea!
signals.back()->next = node;
signals.back()->last = node;
signals.back()->start = node;
}
void SignalManager::rerouteSignal(VEC_SPTR_Node nodes, VEC_WPTR_Node adjacent_nodes){
auto reroute_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->next.lock());});
for(auto s : reroute_signals){
float dist = 10000000;
SPTR_Node next_node;
for(auto n : adjacent_nodes){
if(n.lock()->position.dist(s->position) < dist) {
dist = n.lock()->position.dist(s->position);
next_node = n.lock();
}
}
s->next = next_node;
s->last = next_node;
}
}
void SignalManager::rebaseSignal(VEC_SPTR_Node nodes){
auto rebase_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->start.lock());});
for(auto s : rebase_signals){
s->start = s->next;
}
}
void SignalManager::removeSignal(std::shared_ptr<Signal> s){
erase_signals.push_back(s);
}
void SignalManager::updateSignals(){
auto last = std::chrono::high_resolution_clock::now();
while(loop){
std::chrono::duration<double, std::micro> diff = std::chrono::high_resolution_clock::now() - last;
if((int)diff.count() >= 250){
mutex->lock();
if(play){
for(auto& s : signals){
s->direction = (s->next.lock()->position - s->position).norm();
s->update(diff.count());
if(s->position.dist(s->next.lock()->position) < 0.6f) {
WPTR_Node next_node = gm->next(s->next, s->last);
s->next.lock()->trigger();
if(next_node.lock() != s->next.lock() && !next_node.expired() && s->next.lock()->getNodeType() == NodeType::MIDINODE) {
auto midinode = std::dynamic_pointer_cast<MidiNode>(s->next.lock());
for(auto &m : midinode->midi_outs){
if(m.second.enabled) {
if(midinode->send_note) midi.note(m.second.i, midinode->getNotes().channel, (int)Clamp(midinode->getNotes().note + s->midi_note_offset, 0, 127), midinode->getNotes().velocity, midinode->getNotes().gate);
if(midinode->send_cc) midi.cc(m.second.i, midinode->getCC().channel, midinode->getCC().control, (int)Clamp(midinode->getCC().value + s->midi_cc_offset, 0, 127));
if(midinode->send_pitch_bend) midi.pitch_bend(m.second.i, midinode->getPitchBend().channel, midinode->getPitchBend().value);
}
}
s->last = s->next;
s->next = next_node;
}
}
}
}
midi.update();
mutex->unlock();
last = std::chrono::high_resolution_clock::now();
}
}
}
void SignalManager::drawSignals(Vec2Df mouse_pos, Camera2D camera){
mutex->lock();
if(!erase_signals.empty()){
for(auto s: erase_signals){
std::erase(signals, s);
}
erase_signals.clear();
}
for(auto s : signals){
s->draw(mouse_pos, camera);
}
mutex->unlock();
}
void to_json(json& j, const std::shared_ptr<SignalManager>& G){
j = *G;
}
void from_json(const json& j, std::shared_ptr<SignalManager>& G){
G.reset(new SignalManager(j.get<SignalManager>()));
}
}
+23 -102
View File
@@ -1,15 +1,11 @@
#pragma once
#include "datatypes.h"
#include "graph-manager.h"
#include "midi-node.h"
#include "node.h"
#include "signal.h"
#include "midi.h"
#include <chrono>
#include <locale>
#include <memory>
#include <thread>
using namespace VISEQ::GRAPH;
@@ -18,16 +14,20 @@ namespace VISEQ::SIGNAL {
typedef std::shared_ptr<Signal> SPTR_Signal;
typedef std::vector<SPTR_Signal> VEC_SPTR_Signal;
class SignalManager{
class SignalManager : public std::enable_shared_from_this<SignalManager>{
public:
SignalManager() {}
SignalManager(GraphManager *gm, std::mutex *mutex) : mutex(mutex), gm(gm) {}
SignalManager(std::shared_ptr<GraphManager> gm, std::mutex *mutex) : mutex(mutex), gm(gm) {}
std::shared_ptr<SignalManager> getPtr(){
return shared_from_this();
}
VISEQ::MIDI::Midi midi;
std::mutex *mutex;
GraphManager *gm;
std::shared_ptr<GraphManager> gm;
VEC_SPTR_Signal signals;
@@ -36,102 +36,23 @@ namespace VISEQ::SIGNAL {
bool loop = true;
bool play = false;
void pause(){
play = false;
}
void pause();
void stop();
void start();
void reset();
void startSignalThread();
void stopSignalThread();
void addSignalAtNode(WPTR_Node node);
void rerouteSignal(VEC_SPTR_Node nodes, VEC_WPTR_Node adjacent_nodes);
void rebaseSignal(VEC_SPTR_Node nodes);
void removeSignal(std::shared_ptr<Signal> s);
void updateSignals();
void drawSignals(Vec2Df mouse_pos, Camera2D camera);
void stop(){
play = false;
reset();
}
NLOHMANN_DEFINE_TYPE_INTRUSIVE(SignalManager, signals)
void start(){
play = true;
}
void reset(){
for(auto &s : signals){
s->position = s->start->position + Vec2Df{0.1, 0.1};
s->next = s->start;
s->last = s->start;
}
gm->reset();
}
void startSignalThread(){
std::thread t1(&SignalManager::updateSignals, this);
t1.detach();
}
void stopSignalThread(){
loop = false;
}
void addSignalAtNode(SPTR_Node node){
Color c = {(unsigned char)(20 + (rand() % 200)), 100, (unsigned char)(20 + (int)(rand() % 200)), 200};
signals.emplace_back(std::make_shared<Signal>(node->position + Vec2Df{0.5f, 0.5f}, 8.0f, 0, c)); // position + 1 or else it does not render. why? no idea!
signals.back()->next = node;
signals.back()->last = node;
signals.back()->start = node;
}
void removeSignal(std::shared_ptr<Signal> s){
erase_signals.push_back(s);
}
void updateSignals(){
auto last = std::chrono::high_resolution_clock::now();
while(loop){
std::chrono::duration<double, std::micro> diff = std::chrono::high_resolution_clock::now() - last;
if((int)diff.count() >= 250){
mutex->lock();
if(play){
for(auto& s : signals){
s->direction = (s->next->position - s->position).norm();
s->update(diff.count());
if(s->position.dist(s->next->position) < 0.6f) {
SPTR_Node next_node = gm->next(s->next, s->last);
s->next->trigger();
if(next_node != s->next && next_node != SPTR_Node(nullptr) && s->next->getNodeType() == NodeType::MIDINODE) {
auto midinode = std::dynamic_pointer_cast<MidiNode>(s->next);
for(auto &m : midinode->midi_outs){
if(m.second.enabled) {
if(midinode->send_note) midi.note(m.second.i, midinode->midi_note_channel, (int)Clamp(midinode->midi_note + s->midi_note_offset, 0, 127), midinode->midi_velocity, midinode->midi_gate);
if(midinode->send_cc) midi.cc(m.second.i, midinode->midi_cc_channel, midinode->midi_cc, (int)Clamp(midinode->midi_cc_value + s->midi_cc_offset, 0, 127));
if(midinode->send_pitch_bend) midi.pitch_bend(m.second.i, midinode->midi_pitch_bend_channel, midinode->midi_pitch_bend);
}
}
s->last = s->next;
s->next = next_node;
}
}
}
}
midi.update();
mutex->unlock();
last = std::chrono::high_resolution_clock::now();
}
}
}
void drawSignals(Vec2Df mouse_pos, Camera2D camera){
mutex->lock();
if(!erase_signals.empty()){
for(auto s: erase_signals){
std::erase(signals, s);
}
erase_signals.clear();
}
for(auto s : signals){
s->draw(mouse_pos, camera);
}
mutex->unlock();
}
friend void from_json(const json& j, std::shared_ptr<SignalManager>& G);
friend void to_json(json& j, const std::shared_ptr<SignalManager>& G);
};
inline void to_json(json& j, const SignalManager& G){
j = json{{"signals", G.signals}};
}
}
+55
View File
@@ -0,0 +1,55 @@
#include "signal.h"
#include <raylib.h>
#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);
}
void to_json(json& j, const Signal& S){
std::map<std::string, int> fc = {{"r",S.color.r},{"g",S.color.g},{"b",S.color.b},{"a",S.color.a}};
j = json{{"color",fc},{"position", S.position},{"radius",S.radius},{"start",S.start.lock()->id},{"next",S.next.lock()->id},{"last",S.last.lock()->id},{"speed",S.speed},{"midi_note_offset",S.midi_note_offset},{"midi_cc_offset",S.midi_cc_offset},{"multiplier",S.multiplier}};
}
void from_json(const json& j, Signal& S){
Color c;
c.r = j.at("color")["r"].get<int>();
c.g = j.at("color")["g"].get<int>();
c.b = j.at("color")["b"].get<int>();
c.a = j.at("color")["a"].get<int>();
j.at("position").get_to<Vec2Df>(S.position);
j.at("speed").get_to<float>(S.speed);
j.at("radius").get_to<float>(S.radius);
j.at("midi_note_offset").get_to(S.midi_note_offset);
j.at("midi_cc_offset").get_to(S.midi_cc_offset);
S.color = c;
}
void to_json(json& j, const std::shared_ptr<Signal>& S){
j = *S;
}
void from_json(const json& j, std::shared_ptr<Signal>& S){
S.reset(new Signal(j.get<Signal>()));
}
}
+13 -13
View File
@@ -15,11 +15,11 @@ namespace VISEQ::SIGNAL {
Signal() {}
Signal(Vec2Df position, float r, float _speed, Color color) : Movable(position, Vec2Df(0,0), _speed), radius(r), color(color) {}
NODES::SPTR_Node next;
NODES::SPTR_Node last;
EDGES::SPTR_Edge edge;
NODES::WPTR_Node next;
NODES::WPTR_Node last;
EDGES::WPTR_Edge edge;
NODES::SPTR_Node start;
NODES::WPTR_Node start;
bool at_target = false;
bool run = true;
@@ -35,16 +35,16 @@ namespace VISEQ::SIGNAL {
int midi_note_offset = 0;
int midi_cc_offset = 0;
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 reset();
void toStart();
void update(double t_delta);
void draw(Vec2Df mouse, Camera2D cam);
friend void to_json(json& j, const Signal& S);
friend void from_json(const json& j, Signal& S);
friend void to_json(json& j, const std::shared_ptr<Signal>& S);
friend void from_json(const json& j, std::shared_ptr<Signal>& S);
void draw(Vec2Df mouse, Camera2D cam) {
DrawCircle(position.x, position.y, radius, color);
}
};
}
+53 -150
View File
@@ -1,10 +1,10 @@
#pragma once
#include "config-node.h"
#include "midi-node.h"
#include "graph-item.h"
#include "node.h"
#include "edge.h"
//#include "graph-manager.h"
#include "datatypes.h"
#include "signal.h"
@@ -12,131 +12,34 @@
#include <iostream>
#include <memory>
/*using namespace VISEQ::BASE;*/
/*using namespace VISEQ::SIGNAL;*/
/*using namespace VISEQ::GRAPH::NODES;*/
/*using namespace VISEQ::GRAPH::EDGES;*/
typedef std::shared_ptr<VISEQ::GRAPH::GraphItem> SPTR_GraphItem;
typedef std::vector<SPTR_GraphItem> VEC_SPTR_GraphItem;
namespace VISEQ::GRAPH::EDGES {
using namespace VISEQ::GRAPH::NODES;
inline void to_json(json& j, const VISEQ::GRAPH::EDGES::Edge& E){
j = json{{"id",E.id}, {"start", E.start->id}, {"end",E.end->id}, {"pass_forward",E.pass_forward}, {"pass_backward", E.pass_backward}};
}
inline void from_json(const json& j, Edge& E){
j.at("id").get_to(E.id);
j.at("pass_forward").get_to(E.pass_forward);
j.at("pass_backward").get_to(E.pass_backward);
}
inline void to_json(json& j, const VISEQ::GRAPH::EDGES::SPTR_Edge& E){
j = *E;
}
inline void from_json(const json& j, VISEQ::GRAPH::EDGES::SPTR_Edge& E){
E.reset(new Edge(j.get<Edge>()));
}
}
namespace VISEQ::GRAPH::NODES {
/* ------------------- MIDINODE ---------------------- */
inline void to_json(json& j, const MidiNode& N){
std::vector<int> 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.midi_note_channel},
{"midi_note",N.midi_note},
{"midi_velocity",N.midi_velocity},
{"midi_gate",N.midi_gate},
{"midi_cc_channel",N.midi_cc_channel},
{"midi_cc",N.midi_cc},
{"midi_cc_value",N.midi_cc_value},
{"midi_pc_channel",N.midi_pc_channel},
{"midi_pc_value",N.midi_pc_value},
{"midi_pitch_bend_channel",N.midi_pitch_bend_channel},
{"midi_pitch_bend",N.midi_pitch_bend},
{"connections",conns}};
}
inline 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<Vec2Df>(N.position);
j.at("distributionMode").get_to(N.distributionMode);
j.at("nodeType").get_to(N.nodeType);
j.at("midi_note_channel").get_to(N.midi_note_channel);
j.at("midi_note").get_to(N.midi_note);
j.at("midi_velocity").get_to(N.midi_velocity);
j.at("midi_gate").get_to(N.midi_gate);
j.at("midi_cc_channel").get_to(N.midi_cc_channel);
j.at("midi_cc").get_to(N.midi_cc);
j.at("midi_cc_value").get_to(N.midi_cc_value);
j.at("midi_pc_channel").get_to(N.midi_pc_channel);
j.at("midi_pc_value").get_to(N.midi_pc_value);
j.at("midi_pitch_bend_channel").get_to(N.midi_pitch_bend_channel);
j.at("midi_pitch_bend").get_to(N.midi_pitch_bend);
//j.at("connections").get_to(N.connections);
}
inline void to_json(json& j, const std::shared_ptr<MidiNode>& N){
j = *N;
}
inline void from_json(const json& j, std::shared_ptr<MidiNode>& N){
N.reset(new MidiNode(j.get<MidiNode>()));
}
inline void to_json(json& j, const MidiNode::NodeMidiOut& MO){
j = json{{"port_id", MO.i}, {"enabled", MO.enabled}, {"name", MO.name}};
}
/* ------------------- NODE ---------------------- */
inline void to_json(json& j, const Node& N){
if(N.getNodeType() == NodeType::MIDINODE){
j = dynamic_cast<const MidiNode&>(N);
}
}
inline void from_json(const json& j, Node& N){
if(j.at("nodeType") == NodeType::MIDINODE){
std::cout << j.at("nodeType") << std::endl;
N = j.template get<MidiNode>();
}
}
inline void to_json(json& j, const SPTR_Node& N){
j = *N;
}
inline void from_json(const json& j, std::shared_ptr<Node>& N){
std::cout << "SPTR_Node" << std::endl;
N.reset(new Node(j.get<Node>()));
}
}
//namespace nlohmann {
// template <typename T>
// struct adl_serializer<std::shared_ptr<T>> {
// static void to_json(json& j, const std::shared_ptr<T>& opt) {
// if (opt) {
// j = *opt;
// } else {
// j = nullptr;
// }
// }
//
// static void from_json(const json& j, std::shared_ptr<T>& opt) {
// if (j.is_null()) {
// opt = nullptr;
// } else {
// opt.reset(new T(j.get<T>()));
// }
// }
// };
//}
namespace VISEQ::GRAPH {
inline void to_json(json& j, const GraphItem& G){
std::cout << "Movable" << std::endl;
j = json{{"id", G.id}, {"type", G.graphItemType}};
j = json{{"id", G.id}, {"type", G.gItemType}};
}
inline void to_json(json& j, const SPTR_GraphItem& G){
@@ -156,44 +59,44 @@ namespace VISEQ::BASE{
inline void from_json(const json& j, Movable& G){
std::cout << "Movable" << std::endl;
// j = json{{"position", G.position}};
j.at("position").get_to<Vec2Df>(G.position);
}
inline void from_json(json& j, const std::shared_ptr<Movable>& G){
// j = *G;
j = *G;
}
}
namespace VISEQ::SIGNAL{
inline void to_json(json& j, const Signal& G){
std::map<std::string, int> fc = {{"r",G.color.r},{"g",G.color.g},{"b",G.color.b},{"a",G.color.a}};
j = json{{"color",fc},{"position", G.position},{"radius",G.radius},{"start",G.start->id},{"next",G.next->id},{"last",G.last->id},{"speed",G.speed},{"midi_note_offset",G.midi_note_offset},{"midi_cc_offset",G.midi_cc_offset},{"multiplier",G.multiplier}};
}
inline void from_json(const json& j, Signal& S){
Color c;
c.r = j.at("color")["r"].get<int>();
c.g = j.at("color")["g"].get<int>();
c.b = j.at("color")["b"].get<int>();
c.a = j.at("color")["a"].get<int>();
j.at("position").get_to<Vec2Df>(S.position);
j.at("speed").get_to<float>(S.speed);
j.at("radius").get_to<float>(S.radius);
j.at("midi_note_offset").get_to(S.midi_note_offset);
j.at("midi_cc_offset").get_to(S.midi_cc_offset);
S.color = c;
}
inline void to_json(json& j, const std::shared_ptr<Signal>& G){
j = *G;
}
inline void from_json(const json& j, std::shared_ptr<Signal>& S){
S.reset(new Signal(j.get<Signal>()));
}
// inline void to_json(json& j, const Signal& G){
// std::map<std::string, int> fc = {{"r",G.color.r},{"g",G.color.g},{"b",G.color.b},{"a",G.color.a}};
// j = json{{"color",fc},{"position", G.position},{"radius",G.radius},{"start",G.start.lock()->id},{"next",G.next.lock()->id},{"last",G.last.lock()->id},{"speed",G.speed},{"midi_note_offset",G.midi_note_offset},{"midi_cc_offset",G.midi_cc_offset},{"multiplier",G.multiplier}};
// }
//
// inline void from_json(const json& j, Signal& S){
//
// Color c;
// c.r = j.at("color")["r"].get<int>();
// c.g = j.at("color")["g"].get<int>();
// c.b = j.at("color")["b"].get<int>();
// c.a = j.at("color")["a"].get<int>();
//
// j.at("position").get_to<Vec2Df>(S.position);
// j.at("speed").get_to<float>(S.speed);
// j.at("radius").get_to<float>(S.radius);
// j.at("midi_note_offset").get_to(S.midi_note_offset);
// j.at("midi_cc_offset").get_to(S.midi_cc_offset);
//
// S.color = c;
// }
//
// inline void to_json(json& j, const std::shared_ptr<Signal>& G){
// j = *G;
// }
//
// inline void from_json(const json& j, std::shared_ptr<Signal>& S){
// S.reset(new Signal(j.get<Signal>()));
// }
}
-154
View File
@@ -1,154 +0,0 @@
#pragma once
#include "datatypes.h"
#include "graph-manager.h"
#include "midi-node.h"
#include "signal-manager.h"
#include "ui.h"
#include <algorithm>
#include <iterator>
#include <memory>
#include <ranges>
#include <vector>
namespace VISEQ::APP {
class AppState{
public:
AppState() : sm(&gm, &mutex), ui(&settings, &sm, &gm) {}
enum States {IDLE, CONFIG};
AppSettings settings;
States state;
std::mutex mutex;
VISEQ::GRAPH::GraphManager gm;
VISEQ::SIGNAL::SignalManager sm;
UI::AppUI ui;
void updateMidiPorts(){
for(auto&n : gm.nodes | RANGE_FILTER([](VISEQ::GRAPH::NODES::SPTR_Node n){return n->getNodeType() == NodeType::MIDINODE;})){
auto nc = std::dynamic_pointer_cast<MidiNode>(n);
for(auto& mo : sm.midi.midi_out_ports){
if(!nc->midi_outs.contains(mo.number) && mo.enabled){
nc->midi_outs[mo.number] = {mo.number, mo.port.port_name, false};
}
if(nc->midi_outs.contains(mo.number) && !mo.enabled){
nc->midi_outs.erase(mo.number);
}
}
}
}
void copyNodesAndConnections(VEC_SPTR_Node nodes, Vec2Df position){
VEC_SPTR_Edge copy_connections;
for(auto c : gm.edges){
if(std::ranges::contains(nodes, c->start) && std::ranges::contains(nodes, c->end)) {
copy_connections.push_back(std::make_shared<Edge>(*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 = gm.copyNode(std::dynamic_pointer_cast<MidiNode>(old_node), position + old_node->drag_offset);
} else {
new_node = gm.copyNode(old_node, position + old_node->drag_offset);
}
new_node->copied = false;
new_node->selected = true;
for(auto c : copy_connections){
if(c->start == old_node) {
c->start = new_node;
c->start->connections.push_back(c);
}
if(c->end == old_node) {
c->end = new_node;
c->end->connections.push_back(c);
}
}
}
std::ranges::copy(copy_connections.begin(), copy_connections.end(), std::back_inserter(gm.edges));
}
void removeNodes(VEC_SPTR_Node nodes){
for(auto n : gm.nodes){
n->highlighted = false;
}
auto erase_connections = gm.edges | RANGE_FILTER([nodes](SPTR_Edge c){ return std::ranges::contains(nodes, c->start) || std::ranges::contains(nodes, c->end);});
VEC_SPTR_Node adjacent_nodes;
for(auto c : erase_connections){
if(!c->start->selected) adjacent_nodes.push_back(c->start);
if(!c->end->selected) adjacent_nodes.push_back(c->end);
}
auto reroute_signals = sm.signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->next);});
for(auto s : reroute_signals){
float dist = 10000000;
SPTR_Node next_node;
for(auto n : adjacent_nodes){
if(n->position.dist(s->position) < dist) {
dist = n->position.dist(s->position);
next_node = n;
}
}
s->next = next_node;
s->last = next_node;
}
auto rebase_signals = sm.signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->start);});
for(auto s : rebase_signals){
s->start = s->next;
}
for(auto n : adjacent_nodes){
std::erase_if(n->connections, [n, nodes](VISEQ::GRAPH::EDGES::SPTR_Edge c){ return std::ranges::contains(nodes, c->start) || std::ranges::contains(nodes, c->end);});
}
for(auto n : nodes){
std::erase_if(gm.edges, [n](SPTR_Edge c){ return c->start == n || c->end == n;});
}
std::erase_if(gm.nodes, [nodes](SPTR_Node n){ return std::ranges::contains(nodes, n);});
}
void draw(Vec2Df mouse_pos, Camera2D camera){
mutex.lock();
updateMidiPorts();
mutex.unlock();
gm.drawNodes(mouse_pos, camera);
gm.drawEdges(mouse_pos, camera);
sm.drawSignals(mouse_pos, camera);
}
void drawUI(Vec2Df *mouse_pos, Camera2D *camera){
mutex.lock();
ui.draw(mouse_pos, camera);
mutex.unlock();
}
};
inline void to_json(json& j, const AppState& A){
j = json{{"graphmanager", A.gm}, {"signalmanager", A.sm}, {"settings",A.settings}};
}
inline void from_json(const json& j, AppState& A){
j.at("graphmanager").get_to(A.gm); // = json{{"graphmanager", A.gm}, {"signalmanager", A.sm}};
}
}
+73
View File
@@ -0,0 +1,73 @@
#include "state.h"
#include "interaction-event.h"
#include "raylib.h"
#include "vmath.h"
#include <memory>
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;
}
std::shared_ptr<State> State::get() {
if(!State::instance){
State::instance.reset(new State());
}
return State::instance;
}
}
+101
View File
@@ -0,0 +1,101 @@
#pragma once
#include "interaction-event.h"
#include "nlohmann/json.hpp"
#include "raylib.h"
#include "vmath.h"
#include <memory>
#define STATE state->istate
#define SETTINGS state->settings
#define SCREEN_MOUSE state->screen_mouse
#define RAW_MOUSE state->raw_mouse
#define MOD_MOUSE state->mod_mouse
#define MOUSE_LEFT state->mouse_left
#define MOUSE_RIGHT state->mouse_right
#define MOUSE_MIDDLE state->mouse_middle
#define KEYS_DOWN state->keys_down
#define KEYS_PRESSED state->keys_pressed
#define KEYS_RELEASED state->keys_released
namespace VISEQ::APP{
struct AppSettings{
int width;
int height;
float grid;
float bpm;
float fps;
float speed = 16.0f;
int gui_selected_midi_out;
Color mode_colors[7] = {VS_COLOR_GREEN, VS_COLOR_SUN, VS_COLOR_PINK, VS_COLOR_ORANGE, VS_COLOR_PURPLE, VS_COLOR_LIGHTGREY, (Color){0,0,0,0}};
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(AppSettings, width, height, grid, bpm, fps, speed, gui_selected_midi_out)
enum InteractionState {HOVER_CANVAS, HOVER_NODE, HOVER_CONNECTOR, ADD_NODES_MENU, DRAG_NODE, CONNECT_NODE, DRAW_SELECTION, SCALE_SELECTION, DELETE_SELECTED_NODES, COPY_SELECTED_NODES, PASTE_SELECTED_NODES};
inline std::vector<std::string> state_string = {"HOVER_CANVAS"," HOVER_NODE"," HOVER_CONNECTOR"," ADD_NODES_MENU"," DRAG_NODE"," CONNECT_NODE"," DRAW_SELECTION"," SCALE_SELECTION"," DELETE_SELECTED_NODES"," COPY_SELECTED_NODES"," PASTE_SELECTED_NODES"};
struct MouseState{
bool down = false;
bool clicked = false;
bool released = false;
};
struct ModifierKeys{
bool ctrl = false;
bool shift = false;
bool alt = false;
};
class State{
public:
State(){}
State(State &state) = delete;
void operator=(const State &) = delete;
AppSettings settings;
Vec2Df raw_mouse;
Vec2Df screen_mouse;
Vec2Df mod_mouse;
InteractionState istate = HOVER_CANVAS;
InteractionState old_istate = HOVER_CANVAS;
int node_mode = 0;
bool context_menu = false;
bool hovering = false;
int mouse_z = 0;
MouseState mouse_left;
MouseState mouse_right;
MouseState mouse_middle;
ModifierKeys keys_down;
ModifierKeys keys_pressed;
ModifierKeys keys_released;
Rectangle selection;
Rectangle bounding_box;
Vec2Df mouse_delta;
Vec2Df selection_origin;
Color mode_color = settings.mode_colors[0];
void pushEvent(InteractionEvent e);
InteractionEvent pullEvent();
void update(Camera2D camera);
inline static std::shared_ptr<State> instance;
static std::shared_ptr<State> get();
};
}
namespace VISEQ{
using IState = VISEQ::APP::InteractionState;
}
+195
View File
@@ -0,0 +1,195 @@
#include "ui.h"
#include "state.h"
#include "midi-node.h"
#include "raylib.h"
#include "raygui/raygui.h"
#include "datatypes.h"
#include <memory>
#include <ranges>
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<Node> 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<Node> 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<MidiNode> mn = std::dynamic_pointer_cast<MidiNode>(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);
}
}
+11 -187
View File
@@ -1,208 +1,32 @@
#pragma once
#include "midi-node.h"
#include "state.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 <memory>
#include <ranges>
#include <set>
namespace VISEQ::APP::UI{
class AppUI{
public:
AppUI() {}
AppUI(AppSettings *settings, SIGNAL::SignalManager *sm, GraphManager *gm) : settings(settings), gm(gm), sm(sm) {}
AppUI(std::shared_ptr<SIGNAL::SignalManager> sm, std::shared_ptr<GraphManager> gm) : gm(gm), sm(sm) {}
AppSettings *settings;
GraphManager *gm;
SIGNAL::SignalManager *sm;
std::shared_ptr<State> state = State::get();
std::shared_ptr<GraphManager> gm;
std::shared_ptr<SIGNAL::SignalManager> sm;
bool bpm_spinner_edit_mode = false;
void init(){
GuiLoadStyleDark();
}
void drawSideBar(){
GuiPanel((Rectangle){-1,-30,280,(float)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("{}",settings->bpm).c_str());
int _bpm = 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;
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){
for(auto n : gm->nodes){
if(n->getNodeType() == NodeType::MIDINODE) std::dynamic_pointer_cast<MidiNode>(n)->setMidiPort(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<Node> 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 = settings->bpm / 120.0f;
float signal_bpm = round(settings->bpm * s->multiplier * (s->speed / settings->speed));
if(GuiSpinner((Rectangle){70, (float)s_signal+70 + signal_configs*20, 100, 20}, "", &s_speed, 1, 16 * 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 = settings->speed / 2;
//if(GuiButton((Rectangle){200,(float)s_signal+70 + signal_configs*20, 30, 20},"1/4")) s_speed = 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->addMidiNode(Vec2Df{200.0f, 200.0f});
}
DrawText(std::format("{}", gm->nodes.size()).c_str(), 20, settings->height - 70, 15, VS_COLOR_WHITE);
DrawFPS(20, settings->height - 30);
}
void drawPropertiesBar(){
float left = settings->width - 280.f;
GuiPanel((Rectangle){left,-30,280,(float)settings->height + 31}, "");
const int s_controls = 25;
}
void drawPerNodeConfig(Camera2D *camera){
for(auto& n : gm->nodes | std::views::filter([](std::shared_ptr<Node> n){ return n->config; })){
Vector2 np = GetWorldToScreen2D((Vector2){n->position.x, n->position.y}, *camera);
n->config = !GuiWindowBox((Rectangle){np.x + 10, np.y + 10, 240, 440}, "config");
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<MidiNode> mn = std::dynamic_pointer_cast<MidiNode>(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(settings->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->midi_note_channel);
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "NOTE");
float v = mn->midi_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->midi_note).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 127);
mn->midi_note = round(v);
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 60, 100, 20}, "VELOCITY");
v = mn->midi_velocity;
GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 60, 20, 20}, std::format("{}",mn->midi_velocity).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 90, 220, 20}, "", "", &v, 0, 127);
mn->midi_velocity = round(v);
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 120, 100, 20}, "GATE");
v = mn->midi_gate;
GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 120, 20, 20}, std::format("{}",(float)mn->midi_gate/1000.0f).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 150, 220, 20}, "", "", &v, 0, 30000);
mn->midi_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->midi_cc_channel);
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "CC");
float v = mn->midi_cc;
GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midi_cc).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 127);
mn->midi_cc = round(v);
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 60, 100, 20}, "CC VALUE");
v = mn->midi_cc_value;
GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 60, 20, 20}, std::format("{}",mn->midi_cc_value).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 90, 220, 20}, "", "", &v, 0, 127);
mn->midi_cc_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->midi_pitch_bend_channel);
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "CC");
float v = mn->midi_pitch_bend;
GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midi_pitch_bend).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 16383);
mn->midi_pitch_bend = round(v);
break;}
}
}
}
}
void draw(Vec2Df *mouse_pos, Camera2D *camera){
drawSideBar();
drawPropertiesBar();
drawPerNodeConfig(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);
};
}
+8 -3
View File
@@ -61,6 +61,14 @@ namespace VISEQ::VMATH{
return Vec2D<T>{x,y} - (Vec2D<T>{x,y} - v)*0.5f;
}
Vec2D orbitAlongVector(Vec2D d, float r){
return Vec2D<T>{x,y} - ((Vec2D<T>{x,y} - d)).norm() * r;
}
Vec2D orbitAtAngle(float r, float angle){
return {x + sin(angle) * r, y + cos(angle) * r};
}
Vec2D& operator=(const Vec2D& vec){
if (this == &vec){
return *this;
@@ -162,6 +170,3 @@ namespace VISEQ::VMATH{
using Vec2Df = VISEQ::VMATH::Vec2D<float>;