78 Commits

Author SHA1 Message Date
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
Sebastian 96a19d55ef signal ui is next! 2024-11-07 00:01:54 +01:00
Sebastian e519b7c758 scale groups of nodes is so cool! 2024-11-06 15:35:25 +01:00
Sebastian 95620a5f07 two node snap for triangles 2024-11-06 02:28:37 +01:00
Sebastian @ HfG 9878b7b2a4 alsa lib removed 2024-11-05 17:44:12 +01:00
Sebastian 4a346350f4 static id fixed 2024-11-05 00:51:25 +01:00
Sebastian @ HfG dc7eae1931 changed the name! 2024-11-04 19:28:12 +01:00
Sebastian 0c797807dd id is not set correctly, fix needed 2024-11-04 09:53:08 +01:00
Sebastian 374efbe47c state save/load working! signal pause/play button added, and speed control is now better. 2024-11-04 01:00:59 +01:00
Sebastian bfd8bb192e partial reload from json working, but edges make problems 2024-11-03 15:15:42 +01:00
Sebastian 7bdd68bdcc json is loading, but does nothing yet 2024-11-03 03:48:49 +01:00
Sebastian a4ea20b2b3 still in the middle of json export, last attempt did not work 2024-11-02 23:12:38 +01:00
Sebastian 522e9d456a json serialization beginnings, but still much to do 2024-11-02 15:52:16 +01:00
Sebastian 70d0a186a4 split edge: check, copy paste: check with a little bug 2024-11-02 02:06:26 +01:00
Sebastian 41cad27806 copy paste mysteries with ghost nodes and disappearing connections 2024-11-01 14:46:51 +01:00
Sebastian a10335ed47 polar snapping now very goood! 2024-11-01 14:08:27 +01:00
Sebastian 19b2c9549f conversion to smart pointers done but still buggy 2024-11-01 02:38:47 +01:00
Sebastian c0eb7c98ba heavy restructuring with smart pointers, not finished 2024-10-31 01:43:00 +01:00
Sebastian f7a13e48b7 fishy loss of changes here... 2024-10-30 14:55:24 +01:00
Sebastian ba5874837c connection split button added, but no functionality yes 2024-10-30 11:27:37 +01:00
Sebastian c0f5138765 a little clean up 2024-10-30 02:03:03 +01:00
Sebastian ad7b0e4524 GEO mode for signals takes direction of next connection into account 2024-10-30 02:00:40 +01:00
Sebastian c63eeed1b4 polar snapping (almost bug free) 2024-10-29 00:50:39 +01:00
Sebastian fc48c2e7ed merge win32 2024-10-28 21:52:28 +01:00
42 changed files with 32185 additions and 1236 deletions
+2
View File
@@ -1,4 +1,6 @@
build/** build/**
.cache .cache
viseq viseq
viseq.exe
compile_commands.json compile_commands.json
*.json
+2924
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -1,14 +1,14 @@
CXX = g++ CXX = g++
CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 CXXFLAGS = -std=c++23 -Wall -g -fno-omit-frame-pointer -gdwarf-4 -Og -Wnarrowing -DLIBREMIDI_ALSA=1 -DLIBREMIDI_HEADER_ONLY=1
CXXINC = -Iinclude CXXINC = -isysteminclude
LDLIBS = -lraylib -lasound -pthread LDLIBS = -lraylib -lasound -llibremidi -pthread
LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib LDFLAGS = -Llib -L/usr/lib -L/usr/local/lib
hdr_files = ${wildcard src/*.h} hdr_files = ${wildcard src/*.h}
src_files = ${wildcard src/*.cpp} src_files = ${wildcard src/*.cpp}
src_files += ${wildcard include/libremidi/*.cpp} #src_files += ${wildcard include/libremidi/*.cpp}
all: viseq all: viseq
@@ -20,13 +20,13 @@ $(src_files): %.cpp : %_src
$(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC) $(CXX) -c $@ -o build/$(*F).o $(CXXFLAGS) $(CXXINC)
$(hdr_files): %.h : %_hdr $(hdr_files): %.h : %_hdr
@echo "G++ >---------- $(*F).h -----------<" $(NOECHO) $(NOOP)
%_src: %_src:
@echo "G++ >---------- $(*F).cpp -----------<" $(NOECHO) $(NOOP)
%_hdr: %_hdr:
@echo "G++ >---------- $(*F).h -----------<" $(NOECHO) $(NOOP)
clean: clean:
rm -f build/* rm -f build/*
File diff suppressed because it is too large Load Diff
+176
View File
@@ -0,0 +1,176 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // vector
// #include <nlohmann/detail/abi_macros.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// This file contains all macro definitions affecting or depending on the ABI
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3
#warning "Already included a different version of the library!"
#endif
#endif
#endif
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum)
#ifndef JSON_DIAGNOSTICS
#define JSON_DIAGNOSTICS 0
#endif
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
#endif
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
#else
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
_v ## major ## _ ## minor ## _ ## patch
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_VERSION
#else
#define NLOHMANN_JSON_NAMESPACE_VERSION \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
NLOHMANN_JSON_VERSION_MINOR, \
NLOHMANN_JSON_VERSION_PATCH)
#endif
// Combine namespace components
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
#ifndef NLOHMANN_JSON_NAMESPACE
#define NLOHMANN_JSON_NAMESPACE \
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION)
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
namespace nlohmann \
{ \
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION) \
{
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_END
#define NLOHMANN_JSON_NAMESPACE_END \
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
} // namespace nlohmann
#endif
/*!
@brief namespace for Niels Lohmann
@see https://github.com/nlohmann
@since version 1.0.0
*/
NLOHMANN_JSON_NAMESPACE_BEGIN
/*!
@brief default JSONSerializer template argument
This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;
/// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
class CustomBaseClass = void>
class basic_json;
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
class json_pointer;
/*!
@brief default specialization
@sa https://json.nlohmann.me/api/json/
*/
using json = basic_json<>;
/// @brief a minimal map-like container that preserves insertion order
/// @sa https://json.nlohmann.me/api/ordered_map/
template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map;
/// @brief specialization that maintains the insertion order of object keys
/// @sa https://json.nlohmann.me/api/ordered_json/
using ordered_json = basic_json<nlohmann::ordered_map>;
NLOHMANN_JSON_NAMESPACE_END
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_
Binary file not shown.
+187
View File
@@ -0,0 +1,187 @@
#pragma once
#include <filesystem>
#include <fstream>
#include "interaction-manager.h"
#include "nlohmann/json.hpp"
#include "datatypes.h"
#include "canvas.h"
#include "graph-manager.h"
#include "midi-node.h"
#include "node.h"
#include "raylib.h"
#include "signal-manager.h"
#include "state.h"
#include "ui.h"
#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;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(AppManager, gm, sm);
void init(){
canvas->camera.target = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f };
canvas->camera.offset = (Vector2){ state->settings.width/2.0f, state->settings.height/2.0f };
canvas->camera.rotation = 0.0f;
canvas->camera.zoom = 1.0f;
}
void close(){
std::ofstream file("state.json");
file << json(*this);
std::cout << std::setw(4) << json(*this) << std::endl;
}
void startSignals(){
sm->startSignalThread();
}
void loadState(){
if(std::filesystem::exists("state.json")){
std::ifstream f("state.json");
json graph = json::parse(f);
for(auto n : graph.at("gm").at("nodes")){
std::cout << "object? " << n << std::endl;
if(n.is_object()){
std::cout << "nodeType = " << n.at("nodeType").get<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 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 update(){
state->update(canvas->camera);
canvas->update();
gm->update();
mutex.lock();
updateMidiPorts();
mutex.unlock();
im.update();
}
void draw(){
BeginDrawing();
ClearBackground((Color){46,46,46});
canvas->drawStart();
canvas->draw();
gm->drawNodes(SCREEN_MOUSE, canvas->camera);
gm->drawEdges(SCREEN_MOUSE, canvas->camera);
sm->drawSignals(SCREEN_MOUSE, canvas->camera);
im.draw();
canvas->drawEnd();
drawUI();
EndDrawing();
}
void drawUI(){
mutex.lock();
ui.draw(SCREEN_MOUSE, canvas->camera);
mutex.unlock();
}
};
}
+63 -4
View File
@@ -1,17 +1,76 @@
#pragma once #pragma once
#include "datatypes.h" #include "datatypes.h"
#include "interactable.h"
#include "vmath.h"
#include "raylib.h" #include "raylib.h"
#include "raymath.h" #include "raymath.h"
#include "state.h"
inline bool ViButton(Vec2D<float> *mouse_pos, float x, float y, float radius, Color color){ inline bool ViButton(Vec2Df mp, float x, float y, float radius, Color color){
Vector2 mp = {mouse_pos->x, mouse_pos->y}; //GetMousePosition(); TODO: Refactor my own vectors to raylib vectors
DrawCircle(x,y,radius, color); DrawCircle(x,y,radius, color);
return (Vector2Distance(mp, (Vector2){x,y}) < radius && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)); return (Vector2Distance(mp, (Vector2){x,y}) < radius && IsMouseButtonPressed(MOUSE_BUTTON_LEFT));
} }
inline bool ViSegButton(Vec2D<float> *mouse_pos, float x, float y, float start_angle, float end_angle, float radius, Color color){ inline bool ViButton(Vec2Df mp, Vec2Df position, float radius, Color color){
Vector2 mp = {mouse_pos->x, mouse_pos->y}; //GetMousePosition(); TODO: Refactor my own vectors to raylib vectors 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); DrawCircleSector((Vector2){x,y},radius, start_angle, end_angle, 5, color);
return (Vector2Distance(mp, (Vector2){x,y}) < radius && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)); 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);
}
};
}
+100
View File
@@ -0,0 +1,100 @@
#pragma once
#include "datatypes.h"
#include "interaction-event.h"
#include "state.h"
#include "draggable.h"
#include "raylib.h"
#include "raymath.h"
#include "vmath.h"
#include <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 {
return state->mouse_z == z_index;
}
virtual void drag(Vec2Df position) override {
Vec2Df select1 = {SCREEN_MOUSE.x > select_start.x ? select_start.x : SCREEN_MOUSE.x, SCREEN_MOUSE.y > select_start.y ? select_start.y : SCREEN_MOUSE.y};
Vec2Df select2 = {SCREEN_MOUSE.x > select_start.x ? SCREEN_MOUSE.x - select_start.x : select_start.x - SCREEN_MOUSE.x, SCREEN_MOUSE.y > select_start.y ? SCREEN_MOUSE.y - select_start.y : select_start.y - SCREEN_MOUSE.y};
state->selection = (Rectangle){select1.x, select1.y, select2.x, select2.y};
}
virtual void pickUp() override {
std::cout << "canvas pickUp" << std::endl;
BASE::Draggable::pickUp();
select_start = SCREEN_MOUSE;
state->pushEvent({getPtr(), Interaction::DESELECT});
}
virtual void putDown() override {
std::cout << "canvas putDown" << std::endl;
BASE::Draggable::putDown();
state->pushEvent({getPtr(), Interaction::SELECT});
}
void clearSelectionRect(){
state->selection = (Rectangle){0,0,0,0};
}
virtual void drawStart(){
BeginMode2D(camera);
}
virtual void drawEnd(){
EndMode2D();
}
GRAPH::GraphItemType getType() override {
return GRAPH::GraphItemType::CANVAS;
}
std::shared_ptr<Canvas> getPtr(){
return shared_from_this();
}
virtual void update() override {
camera_delta = state->mouse_delta * (-1.0f/camera.zoom);
if(state->mouse_right.down) {
camera.target = Vector2Add(camera.target, camera_delta);
}
if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP});
if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG});
if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN});
float wheel = GetMouseWheelMove();
if(wheel != 0){
if(!KEYS_DOWN.shift && !KEYS_DOWN.ctrl && !KEYS_DOWN.alt){
camera.offset = state->raw_mouse;
camera.target = SCREEN_MOUSE;
float scaleFactor = 1.0f + (0.25f*fabsf(wheel));
if (wheel < 0) scaleFactor = 1.0f/scaleFactor;
camera.zoom = Clamp(camera.zoom*scaleFactor, 0.125f, 64.0f);
}
}
}
virtual void draw() {
for(int x = -SETTINGS.grid*100; x < SETTINGS.grid*100; x+=SETTINGS.grid){
for(int y = -SETTINGS.grid*100; y < SETTINGS.grid*100; y+=SETTINGS.grid){
DrawPixel(x, y, (Color){130,130,130,200});
}
}
if(state->selection.width > 0 || state->selection.height > 0) DrawRectangleLines(state->selection.x, state->selection.y, state->selection.width, state->selection.height, (Color){150,150,150,150});
}
};
}
+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);
}
}
};
}
+304
View File
@@ -0,0 +1,304 @@
#pragma once
#include "datatypes.h"
#include "edge.h"
#include "node.h"
#include "raymath.h"
#include "state.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() {
return std::static_pointer_cast<ConfigNode>(shared_from_this());
}
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 {return nodeType;}
virtual void trigger() override {}
virtual void setMode(int _mode) override {
mode = _mode;
}
MIDI::MidiNote getNotes() const {
MIDI::MidiNote cNote;
for(auto cn : getConnectedConfigNodes()){
cNote += cn.lock()->getNotes();
}
return midiNote + cNote;
}
MIDI::MidiCC getCC() const {
MIDI::MidiCC cCC;
for(auto cn : getConnectedConfigNodes()){
cCC += cn.lock()->getCC();
}
return midiCC + cCC;
}
MIDI::MidiPC getPC() const {
MIDI::MidiPC cPC;
for(auto cn : getConnectedConfigNodes()){
cPC += cn.lock()->getPC();
}
return midiPC + cPC;
}
MIDI::MidiPitchBend getPitchBend() const {
MIDI::MidiPitchBend cPitchBend;
for(auto cn : getConnectedConfigNodes()){
cPitchBend += cn.lock()->getPitchBend();
}
return midiPitchBend + cPitchBend;
}
MIDI::MidiAftertouch getAftertouch() const {
MIDI::MidiAftertouch cAftertouch;
for(auto cn : getConnectedConfigNodes()){
cAftertouch += cn.lock()->getAftertouch();
}
return midiAftertouch + cAftertouch;
}
MIDI::MidiPolyPressure getPolyPressure() const {
MIDI::MidiPolyPressure cPolyPressure;
for(auto cn : getConnectedConfigNodes()){
cPolyPressure += cn.lock()->getPolyPressure();
}
return midiPolyPressure + cPolyPressure;
}
std::vector<std::weak_ptr<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 update() override {
Node::update();
mode = state->node_mode;
}
void interact(){
float wheel = GetMouseWheelMove();
if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){
switch(state->node_mode){
case 0:{
midiNote.note += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel;
break;}
case 1:{
midiCC.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel;
break;}
case 2:{
midiPitchBend.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel;
break;}
};
if(midiNote.note < -127 ) midiNote.note = -127;
if(midiNote.note > 127 ) midiNote.note = 127;
if(midiCC.value < -127 ) midiCC.value = -127;
if(midiCC.value > 127 ) midiCC.value = 127;
if(midiPitchBend.value < -16383 ) midiPitchBend.value = -16383;
if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383;
} else if(KEYS_DOWN.alt) {
switch(state->node_mode){
case 0:{
midiNote.channel += wheel;
if(midiNote.channel < 0 ) midiNote.channel = 0;
if(midiNote.channel > 15) midiNote.channel = 15;
break;}
case 1:{
midiCC.channel += wheel;
if(midiCC.channel < 0 ) midiCC.channel = 0;
if(midiCC.channel > 15 ) midiCC.channel = 15;
break;}
case 2:{
midiPitchBend.channel += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 100 : wheel;
if(midiPitchBend.channel < 0 ) midiPitchBend.channel = 0;
if(midiPitchBend.channel > 15 ) midiPitchBend.channel = 15;
break;}
};
}
}
void draw(Vec2Df mouse, Camera2D cam) override {
Node::draw();
if(hovering() || selected) interact();
DrawRing(position, radius, radius+4, 0, 360, 24, (Color){60, 60, 60, 255});
DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255});
Color gate_color = VS_COLOR_GREEN;
switch(mode){
case 0:{
gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE);
break;}
case 1:{
gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){229, 181, 103 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE);
break;}
case 2:{
gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){232, 125, 62 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE);
break;}
}
if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, gate_color);
if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255});
if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255});
if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK);
if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW);
if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255});
if(KEYS_DOWN.alt){
float value = 0;
int offset_gate = 0;
switch(mode){
case 0:
value = (float)getNotes().channel;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;
case 1:
value = (float)getCC().channel;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;
case 2:
value = (float)getPC().channel;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;
case 3:
value = (float)getPitchBend().channel;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;
case 4:
value = (float)getAftertouch().channel;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;
case 5:
value = (float)getPolyPressure().channel;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;
}
} else {
switch(mode){
case 0:{
int offset = MeasureText(std::format("{:+}", getNotes().note).c_str(), 12);
DrawText(std::format("{:+}", getNotes().note).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;}
case 1:{
int offset = MeasureText(std::format("{:+}", getCC().value).c_str(), 12);
DrawText(std::format("{:+}", getCC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;}
case 2:{
int offset = MeasureText(std::format("{:+}", getPC().value).c_str(), 12);
DrawText(std::format("{:+}", getPC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;}
case 3:{
int offset = MeasureText(std::format("{:+}", getPitchBend().value).c_str(), 12);
DrawText(std::format("{:+}", getPitchBend().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;}
case 4:{
int offset = MeasureText(std::format("{:+}", getAftertouch().value).c_str(), 12);
DrawText(std::format("{:+}", getAftertouch().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;}
case 5:{
int offset = MeasureText(std::format("{:+}", getPolyPressure().value).c_str(), 12);
DrawText(std::format("{:+}", getPolyPressure().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;}
}
}
if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200});
//DrawText(std::format("{}", id).c_str(), position.x + 20.0f, position.y + 20.0f, 10, WHITE);
}
friend void to_json(json& j, const ConfigNode& N){
std::vector<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}};
}
friend void from_json(const json& j, ConfigNode& N){
std::cout << "configNode from_json" << std::endl;
j.at("id").get_to(N.id);
j.at("position").get_to<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);
}
};
}
+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});
}
};
}
-73
View File
@@ -1,73 +0,0 @@
#pragma once
#include "connection.h"
#include "node-manager.h"
#include "raylib.h"
#include <map>
#include <vector>
class ConnectionManager{
public:
ConnectionManager(NodeManager *nm) : nm(nm) {}
NodeManager *nm;
std::vector<Connection> connections;
std::map<int, int> counter;
void reset(){
counter.clear();
}
int next(int current, int last){
std::vector<Connection*> paths;
std::vector<int> node_ids;
for(auto& p : connections){
if((p.v1 == current && p.v1_v2) || (p.v2 == current && p.v2_v1)) {
p.active = false;
paths.push_back(&p);
node_ids.push_back( p.v1 == current ? p.v2 : p.v1 );
}
}
if(paths.size() == 2) {
int next = node_ids[0] == last ? node_ids[1] : node_ids[0];
if(paths[0]->v1 == next || paths[0]->v2 == next) paths[0]->active = true;
if(paths[1]->v1 == next || paths[1]->v2 == next) paths[1]->active = true;
return next;
}
if(paths.empty()) return -1;
int return_path = 0;
if(nm->nodes.at(current).mode == Distribution::ROUNDROBIN){
if(counter.find(current) == counter.end()) {
counter[current] = 0;
} else {
counter[current] = (counter[current] + 1) % paths.size();
return_path = counter[current];
}
}
if(nm->nodes.at(current).mode == Distribution::RANDOM){
return_path = rand() % paths.size();
}
paths.at(return_path)->active = true;
return paths.at(return_path)->v1 == current ? paths.at(return_path)->v2 : paths.at(return_path)->v1;
}
void addConnection(int from, int to){
connections.emplace_back(&nm->nodes, from, to);
}
void drawConnections(Vec2D<float> *mouse_pos, Camera2D *cam){
std::erase_if(connections, [](Connection c){ return c.remove == true;});
for(auto& c : connections){
c.draw(mouse_pos, cam);
}
}
};
-87
View File
@@ -1,87 +0,0 @@
#pragma once
#include "button.h"
#include "datatypes.h"
#include "node.h"
#include "raylib.h"
class Connection{
public:
Connection(std::map<int, Node> *nodes, int v1, int v2) : v1(v1), v2(v2), nodes(nodes) {}
int v1;
int v2;
std::map<int, Node> *nodes;
bool v1_v2 = true;
bool v2_v1 = true;
bool active = false;
bool remove = false;
bool config = false;
float connector_offset = 8.0f;
float switches_offset = 25.0f;
void flip(){
int vt = v1;
v1 = v2;
v2 = vt;
}
void draw(Vec2D<float> *mouse_pos, Camera2D *cam){
Vec2D<float> v1_position = nodes->at(v1).position - ((nodes->at(v1).position - nodes->at(v2).position)).norm() * (nodes->at(v1).radius + connector_offset);
Vec2D<float> v2_position = nodes->at(v2).position - ((nodes->at(v2).position - nodes->at(v1).position)).norm() * (nodes->at(v2).radius + connector_offset);
float v1_v2_distance = v1_position.dist(&v2_position);
float midi_gate_v1 = Clamp((4.f / 1000.f) * nodes->at(v1).midi_gate, 1.f, 4.f);
float midi_gate_v2 = Clamp((4.f / 1000.f) * nodes->at(v2).midi_gate, 1.f, 4.f);
// Line with small connector circles
Color c = {229, 181, 103, 255};
if(active) c = {108, 153, 187, 255};
DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, c);
if(midi_gate_v1 == 4.0f) c = VS_COLOR_RED;
DrawCircle(v1_position.x, v1_position.y, midi_gate_v1, c);
c = {229, 181, 103, 255};
if(midi_gate_v2 == 4.0f) c = VS_COLOR_RED;
DrawCircle(v2_position.x, v2_position.y, midi_gate_v2, c);
if(v1_v2_distance < 100) switches_offset = v1_v2_distance/2.3f;
Vec2D<float> from_to_handle = nodes->at(v1).position - ((nodes->at(v1).position - nodes->at(v2).position)).norm() * (nodes->at(v1).radius + switches_offset);
Vec2D<float> to_from_handle = nodes->at(v2).position - ((nodes->at(v2).position - nodes->at(v1).position)).norm() * (nodes->at(v2).radius + switches_offset);
Vec2D<float> middle_handle = nodes->at(v2).position - ((nodes->at(v2).position - nodes->at(v1).position))*0.5f;
// Vec2D<float> config_handle = nodes.at(v2)->position - ((nodes.at(v2)->position - nodes.at(v1)->position))*0.66f;
Vec2D<float> 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);
// 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, 120.0f);
unsigned char opacity_to = 250.0f - Clamp(to_from_handle.dist(mouse_pos), 0.0f, 120.0f);
// On/Off switches on both sides
if(ViButton(mouse_pos, from_to_handle.x, from_to_handle.y, 8.0f, v1_v2 ? (Color){120, 210, 115, opacity_from} : (Color){232, 50, 32, opacity_from} )) v1_v2 = !v1_v2;
if(ViButton(mouse_pos, to_from_handle.x, to_from_handle.y, 8.0f, v2_v1 ? (Color){120, 210, 115, opacity_to} : (Color){232, 50, 32, opacity_to} )) v2_v1 = !v2_v1;
if(!v1_v2){
Vec2D<float> block_point1 = (v1_position - v2_position).norm().orth()*4.0f + from_to_handle;
Vec2D<float> 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(!v2_v1){
Vec2D<float> block_point1 = (v1_position - v2_position).norm().orth()*4.0f + to_from_handle;
Vec2D<float> 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} )) remove = true;
//if(ViButton(mouse_pos, config_handle.x, config_handle.y, 8.0f, (Color){158, 134, 200, opacity})) config = !config;
//if(ViButton(mouse_pos, config_handle.x + flip_handle.x, config_handle.y + flip_handle.y, 8.0f, (Color){158, 0, 200, opacity})) flip();
}
};
+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;
}
};
}
+242 -132
View File
@@ -1,11 +1,10 @@
#pragma once #pragma once
//#include "connection-manager.h"
//#include "signal-manager.h"
#ifdef _WIN32 #ifdef _WIN32
#define _USE_MATH_DEFINES #define _USE_MATH_DEFINES
#endif #endif
#define VS_COLOR_BLACK (Color){20, 20, 20, 255}
#define VS_COLOR_DARKGREY (Color){46, 46, 46, 255} #define VS_COLOR_DARKGREY (Color){46, 46, 46, 255}
#define VS_COLOR_LIGHTGREY (Color){100, 100, 100, 255} #define VS_COLOR_LIGHTGREY (Color){100, 100, 100, 255}
#define VS_COLOR_WHITE (Color){214, 214, 214, 255} #define VS_COLOR_WHITE (Color){214, 214, 214, 255}
@@ -20,161 +19,272 @@
#define VS_COLOR_BROWN (Color){84, 75, 61, 255} #define VS_COLOR_BROWN (Color){84, 75, 61, 255}
#define VS_COLOR_SUN (Color){214, 170, 21, 255} #define VS_COLOR_SUN (Color){214, 170, 21, 255}
#include "nlohmann/json.hpp"
using json = nlohmann::json;
#include <cmath> #include <cmath>
#include <math.h> #include <math.h>
#include "libremidi/libremidi.hpp" #include "libremidi/libremidi.hpp"
#include "raylib.h"
enum Distribution {ROUNDROBIN, RANDOM}; #define RANGE_FILTER(L) std::ranges::views::filter(L)
// 2D Vectors namespace VISEQ::GRAPH::NODES {
enum Distribution {GEOMETRIC, ROUNDROBIN, RANDOM};
template<typename T> }
class Vec2D{
public:
Vec2D() {};
Vec2D(T x, T y) : x(x), y(y) {}
T x = 0;
T y = 0;
T dist(Vec2D* v) { return sqrt(pow((v->x - x), 2) + pow((v->y - y), 2));};
T dist(Vec2D v) { return sqrt(pow((v.x - x), 2) + pow((v.y - y), 2));};
T length() { return (T)sqrt(pow(x, 2) + pow(y, 2));};
T angle(Vec2D v){
T angle = atan2(v.y, v.x) - atan2(y,x);
if(angle > M_PI) angle -= M_PI_2;
if(angle < -M_PI) angle += M_PI_2;
return angle;
//return acos(dot(v));
};
T angleDeg(Vec2D v){
return angle(v) * 180 / M_PI;
};
T dot(Vec2D &v){
return (x*v.x) + (y*v.y);
};
operator Vector2() const {
return (Vector2){x,y};
}
Vec2D orth(Vec2D &v){
return Vec2D(-v.y, v.x);
};
Vec2D orth(){
return Vec2D(-y, x);
};
Vec2D norm() { return *this / length();};
Vec2D& operator=(const Vec2D& vec){ namespace VISEQ::GRAPH{
if (this == &vec){ enum GraphItemType {NODE, EDGE, CONNECTOR, CANVAS, HANDLE, VS_BUTTON};
return *this; enum NodeType {OSCNODE, MIDINODE, CONFIGNODE, CONTROLNODE};
} enum EdgeType {MIDIEDGE, CONFIGEDGE};
x = vec.x; }
y = vec.y;
return *this;
};
Vec2D& operator+=(const Vec2D& vec){
this->x += vec.x;
this->y += vec.y;
return *this;
};
Vec2D& operator-=(const Vec2D& vec){ namespace VISEQ::MIDI{
this->x -= vec.x; struct MidiNote{
this->y -= vec.y; int channel = 0;
return *this; int note = 0;
}; int velocity = 0;
int gate = 0; // gate time in milliseconds
NLOHMANN_DEFINE_TYPE_INTRUSIVE(MidiNote, channel, note, velocity, gate);
};
Vec2D& operator*=(const Vec2D& vec){ struct MidiCC{
this->x *= vec.x; int channel = 0;
this->y *= vec.y; int control = 0;
return *this; int value = 0;
}; NLOHMANN_DEFINE_TYPE_INTRUSIVE(MidiCC, channel, control, value);
};
Vec2D& operator/=(const Vec2D& vec){ struct MidiPC{
this->x /= vec.x; int channel = 0;
this->y /= vec.y; int program = 0;
return *this; int value = 0;
}; NLOHMANN_DEFINE_TYPE_INTRUSIVE(MidiPC, channel, program, value);
};
Vec2D& operator*=(T scalar){ struct MidiPitchBend{
this->x *= scalar; int channel = 0;
this->y *= scalar; int value = 0;
return *this; NLOHMANN_DEFINE_TYPE_INTRUSIVE(MidiPitchBend, channel, value);
}; };
Vec2D& operator/=(T scalar){ struct MidiAftertouch{
this->x /= scalar; int channel = 0;
this->y /= scalar; int value = 0;
return *this; 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);
};
template<typename T> struct MidiOutPort{
Vec2D<T> operator+(const Vec2D<T>& left, const Vec2D<T>& right){ libremidi::output_port port;
return Vec2D<T>(left.x + right.x, left.y + right.y); int number;
}; bool enabled;
};
template<typename T> struct MidiOut{
Vec2D<T> operator-(const Vec2D<T>& left, const Vec2D<T>& right){ libremidi::midi_out out;
return Vec2D<T>(left.x - right.x, left.y - right.y); int number;
}; };
template<typename T> inline std::ostream& operator<<(std::ostream& os, const MidiNote note){
Vec2D<T> operator*(const Vec2D<T>& left, const Vec2D<T>& right){ os << "note: " << note.note << " channel: " << note.channel << " velocity: " << note.velocity << " gate: " << note.gate;
return Vec2D<T>(left.x * right.x, left.y * right.y); return os;
}; }
template<typename T> inline MidiNote operator+(const MidiNote left, const MidiNote right){
Vec2D<T> operator/(const Vec2D<T>& left, const Vec2D<T>& right){ MidiNote added;
return Vec2D<T>(left.x / right.x, left.y / right.y); 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;
}
template<typename T> inline MidiCC operator+(const MidiCC left, const MidiCC right){
Vec2D<T> operator/(const Vec2D<T>& left, T scalar){ MidiCC added;
return Vec2D<T>(left.x / scalar, left.y / scalar); added.channel = left.channel + right.channel;
}; added.control = left.control + right.control;
added.value = left.value+ right.value;
return added;
}
template<typename T> inline MidiPC operator+(const MidiPC left, const MidiPC right){
Vec2D<T> operator*(const Vec2D<T>& left, T scalar){ MidiPC added;
return Vec2D<T>(left.x * scalar, left.y * scalar); added.channel = left.channel + right.channel;
}; added.program = left.program + right.program;
added.value = left.value+ right.value;
return added;
}
// App Settings inline MidiPitchBend operator+(const MidiPitchBend left, const MidiPitchBend right){
struct AppSettings{ MidiPitchBend added;
int width; added.channel = left.channel + right.channel;
int height; added.value = left.value+ right.value;
float grid; return added;
float bpm; }
float fps;
float speed = 16.0f;
int gui_selected_midi_out;
};
// MIDI 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;
}
struct MidiOutPort{ inline MidiPolyPressure operator+(const MidiPolyPressure left, const MidiPolyPressure right){
libremidi::output_port port; MidiPolyPressure added;
int number; added.channel = left.channel + right.channel;
bool enabled; added.note = left.note + right.note;
}; added.value = left.value+ right.value;
return added;
}
struct MidiOut{ inline MidiNote operator-(const MidiNote left, const MidiNote right){
libremidi::midi_out out; MidiNote added;
int number; 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)
inline std::vector<std::string> Fractions = {"1/32","1/16","3/32","1/8","5/32","3/16","7/32","1/4","9/32","5/16","11/32","3/8","13/32","7/16","15/32","1/2","17/32","9/16","19/32","5/8","21/32","11/16","23/32","3/4","25/32","13/16","27/32","7/8","29/32","15/16","31/32","1/1"};
namespace MIDI{
inline std::vector<std::string> Notes = { inline std::vector<std::string> Notes = {
"C-1", "C#-1", "D-1", "D#-1", "E-1", "F-1", "F#-1", "G-1", "C-1", "C#-1", "D-1", "D#-1", "E-1", "F-1", "F#-1", "G-1",
"G#-1", "A-1", "A#-1", "B-1", "C0", "C#0", "D0", "D#0", "G#-1", "A-1", "A#-1", "B-1", "C0", "C#0", "D0", "D#0",
+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;
}
};
}
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include "connector.h"
#include "graph-item.h"
#include <memory>
#include "node.h"
using namespace VISEQ::GRAPH;
namespace VISEQ::GRAPH::EDGES {
class Edge : public GraphItem{
public:
Edge() {}
Edge(std::weak_ptr<NODES::Node> start, std::weak_ptr<NODES::Node> end) : start(start), end(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;
bool active = false;
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;}
virtual EdgeType getEdgeType() const {return edgeType;}
std::weak_ptr<NODES::Node> getNextNode(std::weak_ptr<NODES::Node> origin){
return start.lock() == origin.lock() ? end : start;
}
void draw(Vec2Df mouse_pos, Camera2D cam) {}
};
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;
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include "raylib.h"
#include "datatypes.h"
#include "vmath.h"
namespace VISEQ::GRAPH {
class GraphItem {
public:
GraphItem() {newId(-1);}
GraphItem(const GraphItem& G) {
newId(-1);
gItemType = G.gItemType;
}
GraphItemType gItemType;
int id = 0;
void newId(int start){
static int _id;
if(start > -1) _id = start;
id = _id;
_id++;
}
virtual void draw() {};
virtual void draw(Vec2Df mouse, Camera2D cam) {};
virtual GraphItemType getType() {return gItemType;}
virtual ~GraphItem() = default;
};
}
+495
View File
@@ -0,0 +1,495 @@
#pragma once
#include "config-node.h"
#include "datatypes.h"
#include "state.h"
#include "vmath.h"
#include "raylib.h"
#include "raymath.h"
#include "nlohmann/json.hpp"
#include "button.h"
#include "midi-node.h"
#include "edge.h"
#include "node.h"
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <ostream>
#include <random>
#include <ranges>
#include <vector>
#include "state-io.h"
using json = nlohmann::json;
using namespace VISEQ::GRAPH::NODES;
using namespace VISEQ::GRAPH::EDGES;
namespace VISEQ::GRAPH {
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() {}
std::shared_ptr<GraphManager> getPtr(){
return shared_from_this();
}
int getNodesSize(){
return nodes.size();
}
void createMidiNode(Vec2Df position){
nodes.emplace_back(std::make_shared<MidiNode>(position));
nodes.back()->update();
}
void createConfigNode(Vec2Df position){
nodes.emplace_back(std::make_shared<ConfigNode>(position));
nodes.back()->update();
}
void setMidiPortForAll(int number, bool _state){
for(auto n : nodes){
if(n->getNodeType() == NodeType::MIDINODE) std::dynamic_pointer_cast<MidiNode>(n)->setMidiPort(number, _state);
}
}
void addNode(SPTR_Node node){
std::cout << "added node " << node->id << std::endl;
nodes.insert(nodes.end(), node);
node->update();
}
void addConnection(WPTR_Node from, WPTR_Node to){
EdgeType et = EdgeType::MIDIEDGE;
if(from.lock()->getNodeType() == NodeType::CONFIGNODE || to.lock()->getNodeType() == NodeType::CONFIGNODE) et = EdgeType::CONFIGEDGE;
if(from.lock() != to.lock()) {
edges.emplace_back(std::make_shared<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 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 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 deselect(){
std::cout << "deselect" << std::endl;
for(auto s : selected_nodes){
s->selected = false;
}
selected_nodes.clear();
}
VEC_SPTR_Node getSelection(){
return selected_nodes;
}
void copyNodes(VEC_SPTR_Node _nodes, Vec2Df position){
for(auto n: _nodes){
nodes.insert(nodes.end(), std::make_shared<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 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 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 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 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 setNodeMode(int mode){
for(auto n: nodes){
n->setMode(mode);
}
}
void reset(){
counter.clear();
}
WPTR_Node getNode(int i){
return nodes.at(i);
}
WPTR_Node getFirstNode(){
return nodes.front();
}
WPTR_Node getLastNode(){
return nodes.back();
}
VEC_WPTR_Node getMidiNodes(){
return nodes | RANGE_FILTER([](VISEQ::GRAPH::NODES::SPTR_Node n){return n->getNodeType() == NodeType::MIDINODE;}) | std::ranges::to<VEC_WPTR_Node>();
}
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());
}
SPTR_Node 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 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 lastEdge(){
return edges.back();
}
WPTR_Node next(WPTR_Node current, WPTR_Node last){
VEC_WPTR_Edge possible_connections = current.lock()->connections | RANGE_FILTER([current](WPTR_Edge c){
return (c.lock()->getEdgeType() == EdgeType::MIDIEDGE) && ((c.lock()->start.lock() == current.lock() && c.lock()->pass_forward) || (c.lock()->end.lock() == current.lock() && c.lock()->pass_backward));
}) | std::ranges::to<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 splitEdge(SPTR_Edge edge){
split_edges.push_back(edge);
}
void splitEdges(){
for(auto s : split_edges){
Vec2Df center = s.lock()->end.lock()->position.center(s.lock()->start.lock()->position);
SPTR_Node cn = copyNode(std::dynamic_pointer_cast<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 removeEdge(SPTR_Edge edge){
remove_edges.push_back(edge);
}
void removeEdges(){
for(auto edge : remove_edges){
edge.lock()->start.lock()->removeConnector(edge.lock()->start_conn);
edge.lock()->end.lock()->removeConnector(edge.lock()->end_conn);
std::erase(edge.lock()->start.lock()->connections, edge.lock());
std::erase(edge.lock()->end.lock()->connections, edge.lock());
std::erase(edges, edge.lock());
}
remove_edges.clear();
}
VEC_WPTR_Node getAdjacentNodes(VEC_SPTR_Node selected_nodes){
for(auto n : nodes){
n->highlighted = false;
}
auto erase_connections = edges | RANGE_FILTER([selected_nodes](SPTR_Edge c){ return std::ranges::contains(selected_nodes, c->start.lock()) || std::ranges::contains(selected_nodes, c->end.lock());});
VEC_WPTR_Node adjacent_nodes;
for(auto c : erase_connections){
if(!c->start.lock()->selected) adjacent_nodes.push_back(c->start);
if(!c->end.lock()->selected) adjacent_nodes.push_back(c->end);
}
return adjacent_nodes;
}
void removeNodesAndEdges(VEC_SPTR_Node selected_nodes){
VEC_WPTR_Node adjacent_nodes = getAdjacentNodes(selected_nodes);
for(auto n : adjacent_nodes){
std::erase_if(n.lock()->connections, [n, selected_nodes](VISEQ::GRAPH::EDGES::WPTR_Edge c){ return std::ranges::contains(selected_nodes, c.lock()->start.lock()) || std::ranges::contains(selected_nodes, c.lock()->end.lock());});
}
for(auto n : selected_nodes){
std::erase_if(this->edges, [n](WPTR_Edge c){ return c.lock()->start.lock() == n || c.lock()->end.lock() == n;});
}
std::erase_if(this->nodes, [selected_nodes](SPTR_Node n){ return std::ranges::contains(selected_nodes, n);});
}
void pasteNodesAndEdges(VEC_SPTR_Node nodes, Vec2Df position){
VEC_SPTR_Edge copy_connections;
for(auto c : edges){
if(std::ranges::contains(nodes, c->start.lock()) && std::ranges::contains(nodes, c->end.lock())) {
copy_connections.push_back(std::make_shared<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 update(){
if(!remove_edges.empty()) removeEdges();
if(!split_edges.empty()) splitEdges();
}
void drawEdges(Vec2Df mouse_pos, Camera2D cam){
for(auto c : edges){
Vec2Df v1_position = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->connector_offset);
Vec2Df v2_position = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->connector_offset);
float v1_v2_distance = v1_position.dist(&v2_position);
if(v1_v2_distance < 100) c->switches_offset = v1_v2_distance/2.3f;
Vec2Df from_to_handle = c->start.lock()->position - ((c->start.lock()->position - c->end.lock()->position)).norm() * (c->start.lock()->radius + c->switches_offset);
Vec2Df to_from_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position)).norm() * (c->end.lock()->radius + c->switches_offset);
Vec2Df middle_handle = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.4f;
Vec2Df middle_handle_2 = c->end.lock()->position - ((c->end.lock()->position - c->start.lock()->position))*0.6f;
Vec2Df flip_handle = middle_handle + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*0.2, 0.f, 20.f) : 1.0f);
Vec2Df flip_handle_2 = middle_handle_2 + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*-0.2, -20.f, 0.f) : 1.0f);
// Dynamic opacity by distance to mouse
unsigned char opacity = 150.0f - Clamp(middle_handle.dist(mouse_pos), 0.0f, 150.0f);
unsigned char opacity_from = 250.0f - Clamp(from_to_handle.dist(mouse_pos), 0.0f, 220.0f);
unsigned char opacity_to = 250.0f - Clamp(to_from_handle.dist(mouse_pos), 0.0f, 220.0f);
float midi_gate_v1 = 0.0f;
float midi_gate_v2 = 0.0f;
if(c->start.lock()->getNodeType() == NodeType::MIDINODE){
midi_gate_v1 = Clamp((4.f / 1000.f) * std::dynamic_pointer_cast<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 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 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);
}
}
NLOHMANN_DEFINE_TYPE_INTRUSIVE(GraphManager, nodes, edges, selected_nodes)
};
}
+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;
}
+311
View File
@@ -0,0 +1,311 @@
#pragma once
#include "clipboard.h"
#include "connector.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 <algorithm>
#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(){
if(KEYS_DOWN.ctrl && IsKeyPressed(KEY_C)){
VEC_SPTR_Node selected_nodes = gm->getCopiesOfSelectedNodes();
if(!selected_nodes.empty()) cb.copyNodes(selected_nodes);
}
if(KEYS_DOWN.ctrl && IsKeyPressed(KEY_V)){
VEC_SPTR_Node paste_nodes = cb.pasteNodes();
if(!paste_nodes.empty()) gm->pasteNodesAndEdges(paste_nodes, MOD_MOUSE);
}
if(IsKeyPressed(KEY_DELETE)){
removeNodes(gm->getSelection());
}
if(!KEYS_DOWN.shift && IsKeyPressed(KEY_TAB)) {
state->node_mode = (state->node_mode + 1) % 7;
}
if(KEYS_DOWN.shift && IsKeyPressed(KEY_TAB)) {
state->node_mode -= state->node_mode > 0 ? 1 : -6;
}
}
void draw(){
doInteraction();
scaleSelection();
if(!gm->getSelection().empty()){
DrawRectangleRoundedLines(state->bounding_box, 0.1f, 3, VS_COLOR_LIGHTGREY);
scale_handle->draw();
}
}
void removeNodes(VEC_SPTR_Node nodes){
VEC_WPTR_Node adjacent_nodes = gm->getAdjacentNodes(nodes);
sm->rerouteSignal(nodes, adjacent_nodes);
sm->rebaseSignal(nodes);
gm->removeNodesAndEdges(nodes);
}
void scaleSelection(){
if(!gm->getSelection().empty()){
float offset = 30;
Vec2Df corner_tl = {50000, 50000};
Vec2Df corner_br = {-50000, -50000};
std::shared_ptr<Node> 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 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 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();
}
};
}
+27 -290
View File
@@ -1,51 +1,32 @@
/* /*
* *
* VISEQ - Visual Sequencer * VISEQ - The Visual Sequencer
* * 2024 Sebastian Kujas
* *
*/ */
/*Background: (46, 46, 46); #2e2e2e*/
/*Comments: (121, 121, 121); #797979*/
/*White: (214, 214, 214); #d6d6d6*/
/*Yellow: (229, 181, 103); #e5b567*/
/*Green: (180, 210, 115); #b4d273*/
/*Orange: (232, 125, 62); #e87d3e*/
/*Purple: (158, 134, 200); #9e86c8*/
/*Pink: (176, 82, 121); #b05279*/
/*Blue: (108, 153, 187); #6c99bb*/
#define RAYGUI_IMPLEMENTATION #define RAYGUI_IMPLEMENTATION
#include "datatypes.h"
#include <ranges>
#include <raylib.h> #include <raylib.h>
#include <raymath.h> #include <raymath.h>
#include "state.h"
#include "app-manager.h"
#include <cstdlib> #include <cstdlib>
#include <stdio.h> #include <stdio.h>
#include <cmath> #include <cmath>
#include <memory>
#include "state-manager.h"
AppState app; VISEQ::APP::AppManager app;
Vec2D<float> mouse_pos; std::shared_ptr<VISEQ::APP::State> state = VISEQ::APP::State::get();
Vec2D<float> drag_start;
Camera2D camera = { {0} };
bool mouse_clicked = false;
bool mouse_touched = false;
bool mouse_dragging = false;
int main(){ int main(){
srand(time(NULL)); srand(time(NULL));
InitWindow(1920, 1080, "VISEQ - The Visual Sequencer");
InitWindow(1920, 1080, "VISEQ");
int current_monitor = GetCurrentMonitor(); int current_monitor = GetCurrentMonitor();
int monitor_width = GetMonitorWidth(current_monitor); int monitor_width = GetMonitorWidth(current_monitor);
@@ -58,275 +39,31 @@ int main(){
//ToggleFullscreen(); //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.ui.init();
app.settings.fps = 60.0f; app.loadState();
app.settings.bpm = 120.0f;
app.settings.grid = 120.0f; // 120px for a 16th note
app.settings.speed = (app.settings.grid * 8.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;
for(int i = 0; i < 4; ++i){
app.nm.addNode((float)i * app.settings.grid + 10 * app.settings.grid, 5 * app.settings.grid);
}
for(int i = 3; i >= 0; --i){
app.nm.addNode((float)i * app.settings.grid + 10 * app.settings.grid, 6 * app.settings.grid);
}
for(int i = 0; i < 8; ++i){
app.cm.addConnection(app.nm.nodes[i].id, app.nm.nodes[(i+1) % 8].id);
}
app.sm.addSignalAtNode((app.nm.nodes | std::views::values).front().id);
app.sm.startSignalThread();
enum InteractionState {HOVER_CANVAS, HOVER_NODE, HOVER_CONNECTOR, DRAG_NODE, CONNECT_NODE, DRAW_SELECTION, DELETE_SELECTED_NODES, COPY_SELECTED_NODES, PASTE_SELECTED_NODES};
InteractionState istate = HOVER_CANVAS;
Vec2D<float> select_start;
Rectangle selection;
app.startSignals();
while(!WindowShouldClose()){ while(!WindowShouldClose()){
app.update();
BeginDrawing(); app.draw();
ClearBackground((Color){46,46,46});
BeginMode2D(camera);
app.settings.speed = (app.settings.grid * 8.0f) * (app.settings.bpm / 120.f);
Vector2 mp = GetScreenToWorld2D(GetMousePosition(), camera);
Vec2D<float> mouse_pos = {(float)mp.x, (float)mp.y};
Vec2D<float> 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);
Vec2D<float> mouse_delta = {GetMouseDelta().x, GetMouseDelta().y};
Vec2D<float> delta = mouse_delta * (-1.0f/camera.zoom);
auto dragging_nodes = app.nm.nodes | std::ranges::views::values | std::ranges::views::filter([](Node n) { return n.dragging;});
auto copied_nodes = app.nm.nodes | std::ranges::views::values | std::ranges::views::filter([](Node n) { return n.copied;});
auto connecting_nodes = app.nm.nodes | std::ranges::views::values| std::ranges::views::filter([](Node n) { return n.connecting;});
auto selected_nodes = app.nm.nodes | std::ranges::views::values| std::ranges::views::filter([](Node n) { return n.selected && !n.dragging;});
auto hovering_nodes = app.nm.nodes | std::ranges::views::values| std::ranges::views::filter([&mouse_pos](Node n) { return CheckCollisionPointCircle(mouse_pos, n.position, n.radius);});
auto hovering_connectors = app.nm.nodes | std::ranges::views::values| std::ranges::views::filter([&mouse_pos](Node n) { return CheckCollisionPointCircle(mouse_pos, (n.position - ((n.position - mouse_pos)).norm() * (n.radius + 14.0f)), 8.0f);});
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::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() && 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;
switch(istate){
case InteractionState::HOVER_CANVAS:
{
for(auto &n : app.nm.nodes | std::ranges::views::values){
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){
Vec2D<float> 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};
Vec2D<float> 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.nm.nodes | std::views::values){
if(CheckCollisionPointRec(n.position, selection)) {
n.in_rect = true;
} else {
n.in_rect = false;
}
}
}
if(mouse_released){
for(auto& n : app.nm.nodes | std::views::values){
if(CheckCollisionPointRec(n.position, selection)) {
n.selected = true;
n.in_rect = false;
} else {
if(!IsKeyDown(KEY_LEFT_SHIFT)) n.selected = false;
}
}
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()){
for(auto &n : selected_nodes){
if(!n.picked_up) n.drag_offset = n.position - dragging_nodes.front().position;
n.picked_up = true;
}
if(IsKeyDown(KEY_LEFT_CONTROL)) {
dragging_nodes.front().position = (Vec2D<float>){round((mouse_pos.x+dragging_nodes.front().drag_offset.x)/app.settings.grid)*app.settings.grid, round((mouse_pos.y+dragging_nodes.front().drag_offset.y)/app.settings.grid)*app.settings.grid};
} else {
dragging_nodes.front().position = mouse_pos + dragging_nodes.front().drag_offset;
}
for(auto &n : selected_nodes){
n.position = dragging_nodes.front().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:
{
hovering_connectors.front().hover_connect = true;
if(mouse_clicked) hovering_connectors.front().connecting = true;
break;
}
case InteractionState::CONNECT_NODE:
{
Vec2D<float> connector_position = connecting_nodes.front().position - ((connecting_nodes.front().position - mouse_pos)).norm() * (connecting_nodes.front().radius + 14.0f);
if(connecting_nodes.front().connecting && mouse_down){
DrawLine(connector_position.x, connector_position.y, mouse_pos.x, mouse_pos.y, (Color){229, 181, 103, 255});
}
if(mouse_released) {
if(&hovering_nodes.front() != &connecting_nodes.front() && &connecting_nodes.front() != &hovering_connectors.front()){
if(!hovering_nodes.empty()){
app.cm.addConnection(connecting_nodes.front().id, hovering_nodes.front().id);
} else if(!hovering_connectors.empty()){
app.cm.addConnection(connecting_nodes.front().id, hovering_connectors.front().id);
} else {
int id = app.nm.addNode(mouse_pos.x, mouse_pos.y);
app.cm.addConnection(connecting_nodes.front().id, id);
}
}
connecting_nodes.front().connecting = false;
istate = InteractionState::HOVER_CANVAS;
}
break;
}
case InteractionState::DELETE_SELECTED_NODES:
{
for(auto &n : selected_nodes){
n.erase = true;
}
app.removeNodes();
break;
}
case InteractionState::COPY_SELECTED_NODES:
{
for(auto &n: copied_nodes){
n.copied = false;
}
for(auto &n: selected_nodes){
n.copied = true;
n.selected = false;
}
break;
}
case InteractionState::PASTE_SELECTED_NODES:
{
for(auto &n: copied_nodes){
n.drag_offset = (n.position - copied_nodes.front().position);
}
// app.nm.copyNodes(mouse_pos);
app.copyNodesAndConnections(mouse_pos);
break;
}
};
float wheel = GetMouseWheelMove();
if(wheel != 0){
if(!hovering_nodes.empty() && IsKeyDown(KEY_LEFT_CONTROL) && !IsKeyDown(KEY_LEFT_ALT)){
hovering_nodes.front().midi_note += wheel;
if(hovering_nodes.front().midi_note < 0 ) hovering_nodes.front().midi_note = app.sm.midi.midiNotes.size()-1;
if(hovering_nodes.front().midi_note >= app.sm.midi.midiNotes.size() ) hovering_nodes.front().midi_note = 0;
} else if(!hovering_nodes.empty() && IsKeyDown(KEY_LEFT_SHIFT) && !IsKeyDown(KEY_LEFT_ALT)) {
hovering_nodes.front().midi_velocity += wheel;
if(hovering_nodes.front().midi_velocity < 0 ) hovering_nodes.front().midi_velocity = 127;
if(hovering_nodes.front().midi_velocity > 127 ) hovering_nodes.front().midi_velocity = 0;
} else if(!hovering_nodes.empty() && IsKeyDown(KEY_LEFT_ALT)) {
float incr = (float)wheel * (IsKeyDown(KEY_LEFT_SHIFT) ? 100.0f : IsKeyDown(KEY_LEFT_CONTROL) ? 1000.0f : 10.0f);
hovering_nodes.front().midi_gate += incr;
if(hovering_nodes.front().midi_gate < 0 ) hovering_nodes.front().midi_gate = 0;
} else {
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.close();
CloseWindow(); CloseWindow();
return 0; return 0;
+506
View File
@@ -0,0 +1,506 @@
#pragma once
#include "config-node.h"
#include "datatypes.h"
#include "edge.h"
#include "node-handle.h"
#include "node.h"
#include "raylib.h"
#include "state.h"
#include <map>
#include <memory>
namespace VISEQ::APP::UI{
class AppUI;
}
namespace VISEQ::GRAPH::NODES {
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::vector<std::shared_ptr<NodeHandle<int>>> midiNoteHandles;
std::vector<std::shared_ptr<NodeHandle<int>>> midiCCHandles;
std::vector<std::shared_ptr<NodeHandle<int>>> midiPCHandles;
std::vector<std::shared_ptr<NodeHandle<int>>> midiPitchBendHandles;
std::vector<std::shared_ptr<NodeHandle<int>>> midiAftertouchHandles;
std::vector<std::shared_ptr<NodeHandle<int>>> midiPolyPressureHandles;
public:
struct NodeMidiOut{
int i;
std::string name;
bool enabled;
};
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;
bool send_pitch_bend = false;
int midi_out = -1;
std::map<int, NodeMidiOut> midi_outs;
bool gui_midi_channel_edit = false;
bool gui_midi_note_edit = false;
bool gui_midi_velocity_edit = false;
bool gui_midi_gate_edit = false;
float start = 0;
float fade = 0;
float time = 0;
float last = 0;
void init(){
midiNoteHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiNote.note, position, VS_COLOR_GREEN, "NOTE"));
midiNoteHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiNote.channel, position, VS_COLOR_PINK, "CHANNEL", 1, 16, 40, 150));
midiNoteHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiNote.gate, position, VS_COLOR_RED, "GATE", 10, 30000, 40, 300));
midiNoteHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiNote.velocity, position, VS_COLOR_PURPLE, "VELOCITY"));
midiCCHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiCC.control, position, VS_COLOR_SUN, "CONTROL"));
midiCCHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiCC.channel, position, VS_COLOR_PINK, "CHANNEL", 1, 16, 40, 150));
midiCCHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiCC.value, position, VS_COLOR_GREEN, "VALUE"));
midiPCHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiPC.program, position, VS_COLOR_SUN, "PROGRAM"));
midiPCHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiPC.channel, position, VS_COLOR_PINK, "CHANNEL", 1, 16, 40, 150));
midiPCHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiPC.value, position, VS_COLOR_GREEN, "VALUE"));
midiPitchBendHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiPitchBend.channel, position, VS_COLOR_PINK, "CHANNEL", 1, 16, 40, 150));
midiPitchBendHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiPitchBend.value, position, VS_COLOR_GREEN, "VALUE", -8192, 8192, 40, 300));
midiAftertouchHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiAftertouch.channel, position, VS_COLOR_PINK, "CHANNEL", 1, 16, 40, 150));
midiAftertouchHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiAftertouch.value, position, VS_COLOR_GREEN, "VALUE"));
midiPolyPressureHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiPolyPressure.channel, position, VS_COLOR_PINK, "CHANNEL", 1, 16, 40, 150));
midiPolyPressureHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiPolyPressure.value, position, VS_COLOR_GREEN, "VALUE"));
midiPolyPressureHandles.emplace_back(std::make_shared<NodeHandle<int>>(midiPolyPressure.note, position, VS_COLOR_SUN, "NOTE"));
}
std::shared_ptr<MidiNode> getPtr(){
return std::static_pointer_cast<MidiNode>(shared_from_this());
}
virtual NodeType getNodeType() const override {return nodeType;}
void setMidiPort(int midi_port, bool _state){
midi_outs[midi_port].enabled = _state;
}
void trigger() override {
triggered = true;
start = GetTime();
fade = getNotes().gate;
}
void setMode(int _mode) override {
mode = _mode;
}
MIDI::MidiNote getNotes() const {
MIDI::MidiNote cNote;
for(auto cn : getConnectedConfigNodes()){
cNote += cn.lock()->getNotes();
}
return midiNote + cNote;
}
MIDI::MidiCC getCC() const {
MIDI::MidiCC cCC;
for(auto cn : getConnectedConfigNodes()){
cCC += cn.lock()->getCC();
}
return midiCC + cCC;
}
MIDI::MidiPC getPC() const {
MIDI::MidiPC cPC;
for(auto cn : getConnectedConfigNodes()){
cPC += cn.lock()->getPC();
}
return midiPC + cPC;
}
MIDI::MidiPitchBend getPitchBend() const {
MIDI::MidiPitchBend cPitchBend;
for(auto cn : getConnectedConfigNodes()){
cPitchBend += cn.lock()->getPitchBend();
}
return midiPitchBend + cPitchBend;
}
MIDI::MidiAftertouch getAftertouch() const {
MIDI::MidiAftertouch cAftertouch;
for(auto cn : getConnectedConfigNodes()){
cAftertouch += cn.lock()->getAftertouch();
}
return midiAftertouch + cAftertouch;
}
MIDI::MidiPolyPressure getPolyPressure() const {
MIDI::MidiPolyPressure cPolyPressure;
for(auto cn : getConnectedConfigNodes()){
cPolyPressure += cn.lock()->getPolyPressure();
}
return midiPolyPressure + cPolyPressure;
}
std::vector<std::weak_ptr<ConfigNode>> 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 update() override {
Node::update();
mode = state->node_mode;
for(auto mh : midiNoteHandles){
mh->update(position);
}
for(auto mh : midiCCHandles){
mh->update(position);
}
for(auto mh : midiPCHandles){
mh->update(position);
}
for(auto mh : midiPitchBendHandles){
mh->update(position);
}
for(auto mh : midiAftertouchHandles){
mh->update(position);
}
for(auto mh : midiPolyPressureHandles){
mh->update(position);
}
}
void modMidiNote(int incr){
midiNote.note += incr;
if(midiNote.note < 0 ) midiNote.note = VISEQ::MIDI::Notes.size()-1;
if(midiNote.note >= (int)VISEQ::MIDI::Notes.size()) midiNote.note = 0;
}
void modMidiCC(int incr){
midiCC.control += incr;
if(midiCC.control < 0 ) midiCC.control = 127;
if(midiCC.control > 127 ) midiCC.control = 0;
}
void modMidiPC(int incr){
midiPC.program += incr;
if(midiPC.program < 0 ) midiPC.program = 127;
if(midiPC.program > 127 ) midiPC.program = 0;
}
void modMidiPitchBend(int incr){
midiPitchBend.value += incr;
if(midiPitchBend.value < 0 ) midiPitchBend.value = 0;
if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383;
}
void modMidiAftertouch(int incr){
midiAftertouch.value += incr;
if(midiAftertouch.value < 0 ) midiAftertouch.value = 0;
if(midiAftertouch.value > 127 ) midiAftertouch.value = 127;
}
void modMidiPolyPressure(int incr){
midiPolyPressure.value += incr;
if(midiPolyPressure.value < 0 ) midiPolyPressure.value = 0;
if(midiPolyPressure.value > 127 ) midiPolyPressure.value = 127;
}
void interact(){
float wheel = GetMouseWheelMove();
if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){
switch(state->node_mode){
case 0:{
modMidiNote(wheel);
break;}
case 1:{
modMidiCC(wheel);
break;}
case 2:{
modMidiPC(wheel);
break;}
case 3:{
modMidiPitchBend(wheel);
break;}
case 4:{
modMidiAftertouch(wheel);
break;}
case 5:{
modMidiPolyPressure(wheel);
break;}
};
} else if(KEYS_DOWN.shift && !KEYS_DOWN.alt) {
midiNote.velocity += wheel;
if(midiNote.velocity < 0 ) midiNote.velocity = 127;
if(midiNote.velocity > 127 ) midiNote.velocity = 0;
} else if(KEYS_DOWN.alt) {
switch(state->node_mode){
case 0:{
float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f);
midiNote.gate += incr;
if(midiNote.gate < 0 ) midiNote.gate = 0;
break;}
case 1:{
midiCC.value += wheel;
if(midiCC.value < 0 ) midiCC.value = 0;
if(midiCC.value > 127 ) midiCC.value = 127;
break;}
case 2:{
midiPC.value += wheel;
if(midiPC.value < 0 ) midiPC.value = 0;
if(midiPC.value > 127 ) midiPC.value = 127;
break;}
case 3:{
float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f);
midiPitchBend.value += incr;
if(midiPitchBend.value < 0 ) midiPitchBend.value = 0;
if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383;
break;}
case 4:{
float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f);
midiAftertouch.value += incr;
if(midiAftertouch.value < 0 ) midiAftertouch.value = 0;
if(midiAftertouch.value > 127 ) midiAftertouch.value = 127;
break;}
case 5:{
float incr = (float)wheel * (KEYS_DOWN.shift ? 100.0f : KEYS_DOWN.ctrl ? 1000.0f : 10.0f);
midiPolyPressure.value += incr;
if(midiPolyPressure.value < 0 ) midiPolyPressure.value = 0;
if(midiPolyPressure.value > 127 ) midiPolyPressure.value = 127;
break;}
};
}
}
void draw(Vec2Df mouse, Camera2D cam) override {
Node::draw();
for(auto mh : midiNoteHandles){
if(mode == 0) mh->unlock();
else mh->lock();
mh->draw();
}
for(auto mh : midiCCHandles){
if(mode == 1) mh->unlock();
else mh->lock();
mh->draw();
}
for(auto mh : midiPCHandles){
if(mode == 2) mh->unlock();
else mh->lock();
mh->draw();
}
for(auto mh : midiPitchBendHandles){
if(mode == 3) mh->unlock();
else mh->lock();
mh->draw();
}
for(auto mh : midiAftertouchHandles){
if(mode == 4) mh->unlock();
else mh->lock();
mh->draw();
}
for(auto mh : midiPolyPressureHandles){
if(mode == 5) mh->unlock();
else mh->lock();
mh->draw();
}
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});
/*if(fade > 0) DrawCircle(position.x, position.y, radius, ColorAlpha(VS_COLOR_RED, trigger_alpha));*/
if(KEYS_DOWN.alt){
float value = 0;
int offset_gate = 0;
switch(mode){
case 0:
value = (float)getNotes().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)getCC().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)getPitchBend().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;
}
} else {
switch(mode){
case 0:{
int offset = MeasureText(MIDI::Notes[(int)getNotes().note].c_str(), 12);
DrawText(MIDI::Notes[getNotes().note].c_str(), position.x-offset/2.0f, position.y-5, 12, WHITE );
break;}
case 1:{
int offset = MeasureText(std::format("{}", getCC().control).c_str(), 12);
DrawText(std::format("{}", getCC().control).c_str(), position.x-offset/2.0f, position.y-5, 12, WHITE );
break;}
case 2:{
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, WHITE );
break;}
}
}
if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200});
//DrawText(std::format("{}", id).c_str(), position.x + 20.0f, position.y + 20.0f, 10, WHITE);
}
friend void to_json(json& j, const 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::map<std::string, Vec2Df> noteHandles;
std::map<std::string, Vec2Df> CCHandles;
std::map<std::string, Vec2Df> PCHandles;
std::map<std::string, Vec2Df> pitchBendHandles;
std::map<std::string, Vec2Df> aftertouchHandles;
std::map<std::string, Vec2Df> polyPressureHandles;
for(auto i : N.midiNoteHandles){
noteHandles.insert(std::make_pair(i->name, i->position));
}
for(auto i : N.midiCCHandles){
CCHandles.insert(std::make_pair(i->name, i->position));
}
for(auto i : N.midiPCHandles){
PCHandles.insert(std::make_pair(i->name, i->position));
}
for(auto i : N.midiPitchBendHandles){
pitchBendHandles.insert(std::make_pair(i->name, i->position));
}
for(auto i : N.midiAftertouchHandles){
pitchBendHandles.insert(std::make_pair(i->name, i->position));
}
for(auto i : N.midiPolyPressureHandles){
polyPressureHandles.insert(std::make_pair(i->name, i->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},
{"midiNoteHandle", noteHandles},
{"midiCCHandle", CCHandles},
{"midiPCHandle", PCHandles},
{"midiPitchBendHandle", pitchBendHandles},
{"midiAftertouchHandle", aftertouchHandles},
{"midiPolyPressureHandle", polyPressureHandles},
{"connections",conns}};
}
friend void from_json(const json& j, MidiNode& N){
std::cout << "MidiNode" << std::endl;
j.at("id").get_to(N.id);
j.at("position").get_to<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::map<std::string, Vec2Df> noteHandles;
std::map<std::string, Vec2Df> CCHandles;
std::map<std::string, Vec2Df> PCHandles;
std::map<std::string, Vec2Df> pitchBendHandles;
std::map<std::string, Vec2Df> aftertouchHandles;
std::map<std::string, Vec2Df> polyPressureHandles;
j.at("midiNoteHandle").get_to(noteHandles);
j.at("midiCCHandle").get_to(CCHandles);
j.at("midiPCHandle").get_to(PCHandles);
j.at("midiPitchBendHandle").get_to(pitchBendHandles);
j.at("midiAftertouchHandle").get_to(aftertouchHandles);
j.at("midiPolyPressureHandle").get_to(polyPressureHandles);
N.midiNoteHandles[0]->position = noteHandles["NOTE"];
N.midiNoteHandles[1]->position = noteHandles["CHANNEL"];
N.midiNoteHandles[2]->position = noteHandles["GATE"];
N.midiNoteHandles[3]->position = noteHandles["VELOCITY"];
N.midiCCHandles[0]->position = CCHandles["CONTROL"];
N.midiCCHandles[1]->position = CCHandles["CHANNEL"];
N.midiCCHandles[2]->position = CCHandles["VALUE"];
N.midiPCHandles[0]->position = PCHandles["PROGRAM"];
N.midiPCHandles[1]->position = PCHandles["CHANNEL"];
N.midiPCHandles[2]->position = PCHandles["VALUE"];
N.midiPitchBendHandles[0]->position = pitchBendHandles["CHANNEL"];
N.midiPitchBendHandles[1]->position = pitchBendHandles["VALUE"];
N.midiAftertouchHandles[0]->position = aftertouchHandles["CHANNEL"];
N.midiAftertouchHandles[1]->position = aftertouchHandles["VALUE"];
N.midiPolyPressureHandles[0]->position = polyPressureHandles["CHANNEL"];
N.midiPolyPressureHandles[1]->position = polyPressureHandles["VALUE"];
N.midiPolyPressureHandles[2]->position = polyPressureHandles["NOTE"];
//j.at("connections").get_to(N.connections);
}
};
}
+83 -84
View File
@@ -10,104 +10,103 @@
#include <memory> #include <memory>
#include "datatypes.h" #include "datatypes.h"
struct NoteTimer{ namespace VISEQ::MIDI {
int midi_out;
int channel;
int note;
int velocity;
float start;
int gate;
bool erase = false;
};
class Midi { struct NoteTimer{
int midi_out;
int channel;
int note;
int velocity;
float start;
int gate;
bool erase = false;
};
public: class Midi {
Midi() {
enumerate();
}
std::vector<std::string> midiNotes = { public:
"C-1", "C#-1", "D-1", "D#-1", "E-1", "F-1", "F#-1", "G-1", Midi() {
"G#-1", "A-1", "A#-1", "B-1", "C0", "C#0", "D0", "D#0", enumerate();
"E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0",
"C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1",
"G#1", "A1", "A#1", "B1", "C2", "C#2", "D2", "D#2",
"E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2",
"C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3",
"G#3", "A3", "A#3", "B3", "C4", "C#4", "D4", "D#4",
"E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",
"C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5",
"G#5", "A5", "A#5", "B5", "C6", "C#6", "D6", "D#6",
"E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6",
"C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7",
"G#7", "A7", "A#7", "B7", "C8", "C#8", "D8", "D#8",
"E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8",
"C9", "C#9", "D9", "D#9", "E9", "F9", "F#9", "G9",
"G#9", "A9", "A#9", "B9", "C10"
};
libremidi::observer obs;
std::map<int, std::shared_ptr<libremidi::midi_out>> midi_outs;
std::vector<libremidi::input_port> midi_in_ports;
std::vector<MidiOutPort> midi_out_ports;
std::vector<NoteTimer> notes;
float timer;
void enumerate(){
std::cout << (int)obs.get_current_api() << std::endl;
int i = 0;
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){ libremidi::observer obs;
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){ std::map<int, std::shared_ptr<libremidi::midi_out>> midi_outs;
if(midi_outs.contains(out_port->number)){ std::vector<libremidi::input_port> midi_in_ports;
midi_outs[out_port->number]->close_port(); std::vector<MidiOutPort> midi_out_ports;
midi_outs.erase(out_port->number);
}
}
void note(int midi_out, int channel, int note, int velocity, int gate){ std::vector<NoteTimer> notes;
if(midi_outs.contains(midi_out)){
if(midi_outs[midi_out]->is_port_open()){ float timer;
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 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 update(){ void open_midi_out(MidiOutPort *out_port){
float time = GetTime(); if(!midi_outs.contains(out_port->number)){
for(auto &n : notes){ auto out = std::make_shared<libremidi::midi_out>();
if(time - n.start >= (float)n.gate / 1000.0f){ out->open_port(out_port->port);
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)); midi_outs[out_port->number] = out;
n.erase = true;
} }
} }
std::erase_if(notes, [](const auto& item){return item.erase;});
for(auto& p : midi_out_ports){ void close_midi_out(MidiOutPort *out_port){
if(p.enabled) { if(midi_outs.contains(out_port->number)){
open_midi_out(&p); midi_outs[out_port->number]->close_port();
} else if(!p.enabled) { midi_outs.erase(out_port->number);
close_midi_out(&p);
} }
} }
}
};
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);
}
}
}
};
}
+315
View File
@@ -0,0 +1,315 @@
#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() {
return std::static_pointer_cast<ModNode>(shared_from_this());
}
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 {return nodeType;}
virtual void trigger() override {}
virtual void setMode(int _mode) override {
mode = _mode;
}
MIDI::MidiNote getNotes() const {
MIDI::MidiNote cNote;
for(auto cn : getConnectedModNodes()){
cNote += cn.lock()->getNotes();
}
return midiNote + cNote;
}
MIDI::MidiCC getCC() const {
MIDI::MidiCC cCC;
for(auto cn : getConnectedModNodes()){
cCC += cn.lock()->getCC();
}
return midiCC + cCC;
}
MIDI::MidiPC getPC() const {
MIDI::MidiPC cPC;
for(auto cn : getConnectedModNodes()){
cPC += cn.lock()->getPC();
}
return midiPC + cPC;
}
MIDI::MidiPitchBend getPitchBend() const {
MIDI::MidiPitchBend cPitchBend;
for(auto cn : getConnectedModNodes()){
cPitchBend += cn.lock()->getPitchBend();
}
return midiPitchBend + cPitchBend;
}
MIDI::MidiAftertouch getAftertouch() const {
MIDI::MidiAftertouch cAftertouch;
for(auto cn : getConnectedModNodes()){
cAftertouch += cn.lock()->getAftertouch();
}
return midiAftertouch + cAftertouch;
}
MIDI::MidiPolyPressure getPolyPressure() const {
MIDI::MidiPolyPressure cPolyPressure;
for(auto cn : getConnectedModNodes()){
cPolyPressure += cn.lock()->getPolyPressure();
}
return midiPolyPressure + cPolyPressure;
}
std::vector<std::weak_ptr<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>> 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 update() override {
Node::update();
mode = state->node_mode;
}
void interact(){
float wheel = GetMouseWheelMove();
if(KEYS_DOWN.ctrl && !KEYS_DOWN.alt){
switch(state->node_mode){
case 0:{
midiNote.note += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel;
break;}
case 1:{
midiCC.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel;
break;}
case 2:{
midiPitchBend.value += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 12 : wheel;
break;}
};
if(midiNote.note < -127 ) midiNote.note = -127;
if(midiNote.note > 127 ) midiNote.note = 127;
if(midiCC.value < -127 ) midiCC.value = -127;
if(midiCC.value > 127 ) midiCC.value = 127;
if(midiPitchBend.value < -16383 ) midiPitchBend.value = -16383;
if(midiPitchBend.value > 16383 ) midiPitchBend.value = 16383;
} else if(KEYS_DOWN.alt) {
switch(state->node_mode){
case 0:{
midiNote.channel += wheel;
if(midiNote.channel < 0 ) midiNote.channel = 0;
if(midiNote.channel > 15) midiNote.channel = 15;
break;}
case 1:{
midiCC.channel += wheel;
if(midiCC.channel < 0 ) midiCC.channel = 0;
if(midiCC.channel > 15 ) midiCC.channel = 15;
break;}
case 2:{
midiPitchBend.channel += KEYS_DOWN.shift ? ((wheel > 0) - (wheel < 0)) * 100 : wheel;
if(midiPitchBend.channel < 0 ) midiPitchBend.channel = 0;
if(midiPitchBend.channel > 15 ) midiPitchBend.channel = 15;
break;}
};
}
}
void draw(Vec2Df mouse, Camera2D cam) override {
Node::draw();
if(hovering() || selected) interact();
DrawRing(position, radius, radius+4, 0, 360, 24, (Color){60, 60, 60, 255});
DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255});
Color gate_color = VS_COLOR_GREEN;
switch(mode){
case 0:{
gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE);
break;}
case 1:{
gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){229, 181, 103 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE);
break;}
case 2:{
gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){232, 125, 62 + 70, (unsigned char)Clamp(255, 0, 255)}, WHITE);
break;}
}
if(!picked_up && !selected && !in_selection_rect && !hovering()) DrawCircle(position.x, position.y, radius, gate_color);
if(picked_up || in_selection_rect || hovering()) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255});
if(selected) DrawCircle(position.x, position.y, radius, (Color){255, 153, 187, 255});
if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK);
if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW);
if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255});
if(KEYS_DOWN.alt){
float value = 0;
int offset_gate = 0;
switch(mode){
case 0:
value = (float)getNotes().channel;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;
case 1:
value = (float)getCC().channel;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;
case 2:
value = (float)getPC().channel;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;
case 3:
value = (float)getPitchBend().channel;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;
case 4:
value = (float)getAftertouch().channel;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;
case 5:
value = (float)getPolyPressure().channel;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;
}
} else {
switch(mode){
case 0:{
int offset = MeasureText(std::format("{:+}", getNotes().note).c_str(), 12);
DrawText(std::format("{:+}", getNotes().note).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;}
case 1:{
int offset = MeasureText(std::format("{:+}", getCC().value).c_str(), 12);
DrawText(std::format("{:+}", getCC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;}
case 2:{
int offset = MeasureText(std::format("{:+}", getPC().value).c_str(), 12);
DrawText(std::format("{:+}", getPC().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;}
case 3:{
int offset = MeasureText(std::format("{:+}", getPitchBend().value).c_str(), 12);
DrawText(std::format("{:+}", getPitchBend().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;}
case 4:{
int offset = MeasureText(std::format("{:+}", getAftertouch().value).c_str(), 12);
DrawText(std::format("{:+}", getAftertouch().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;}
case 5:{
int offset = MeasureText(std::format("{:+}", getPolyPressure().value).c_str(), 12);
DrawText(std::format("{:+}", getPolyPressure().value).c_str(), position.x-offset/2.0f, position.y-5, 12, VS_COLOR_BLACK );
break;}
}
}
if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200});
//DrawText(std::format("{}", id).c_str(), position.x + 20.0f, position.y + 20.0f, 10, WHITE);
}
friend void to_json(json& j, const ModNode& N){
std::vector<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}};
}
friend void from_json(const json& j, ModNode& N){
std::cout << "configNode from_json" << std::endl;
j.at("id").get_to(N.id);
j.at("position").get_to<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);
}
};
}
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include "draggable.h"
#include "graph-manager.h"
#include "interaction-event.h"
#include <memory>
namespace VISEQ::APP {
class Mouse {
public:
Mouse() {}
Mouse(GraphManager *gm) : gm(gm) {}
GraphManager *gm;
void update(){
/*auto hovering_nodes = gm->nodes | RANGE_FILTER([](SPTR_Node n) { return n->hovering() || n->picked_up;}) | std::ranges::to<std::vector<std::shared_ptr<BASE::Draggable>>>();*/
/**/
/*if(!hovering_nodes.empty()){*/
/* hovering_nodes.front()->is_hovering = true;*/
/**/
/* if(hovering_nodes.front()->pickedUp()) hovering_nodes.front()->drag();*/
/* if(hovering_nodes.front()->released()) hovering_nodes.front()->putDown();*/
/**/
/*}*/
}
void interact(InteractionEvent e){
}
/*virtual void select() override {*/
/* if(STATE == IState::DRAW_SELECTION) in_selection_rect = CheckCollisionPointRec(position, state->selection);*/
/* if(MOUSE_LEFT.released) selected = (KEYS_DOWN.shift && selected) || in_selection_rect;*/
/*}*/
/**/
/*virtual void update() override {*/
/* if(STATE != IState::HOVER_NODE) select();*/
/* if((pickedUp() || hovering()) && z_hovering == -1) z_hovering = z_index;*/
/**/
/* if(z_index == z_hovering || selected){*/
/* if(pickedUp()){*/
/* drag();*/
/* STATE = IState::DRAG_NODE;*/
/* }*/
/* }*/
/* if(STATE != IState::DRAG_NODE){*/
/* if(entered()){*/
/* STATE = IState::HOVER_NODE;*/
/* std::cout << "z_index=" << z_index << " z_hovering=" << z_hovering << std::endl;*/
/* is_hovering = true;*/
/* }*/
/* if(exited()){*/
/* STATE = IState::HOVER_CANVAS;*/
/* is_hovering = false;*/
/* }*/
/* }*/
/* if(released() && picked_up){*/
/* putDown();*/
/* STATE = IState::HOVER_CANVAS;*/
/* if(hovering()) STATE = IState::HOVER_NODE;*/
/* }*/
/*}*/
};
}
+22 -16
View File
@@ -1,25 +1,31 @@
#pragma once #pragma once
#include "datatypes.h" #include "state.h"
#include "raylib.h" #include "vmath.h"
#include <memory>
class Movable{ namespace VISEQ::BASE {
class Movable{
public: public:
Movable() {} Movable() {}
virtual ~Movable() {} virtual ~Movable() {}
Movable(Vec2D<float> p, Vec2D<float> d, float s) : speed(s), position(p), direction(d) {} Movable(Vec2Df p, Vec2Df d, float s) : speed(s), position(p), direction(d) {}
Movable(Vec2D<float> p, float s) : speed(s), position(p) {} Movable(Vec2Df p, float s) : speed(s), position(p) {}
float speed = 1.0f; std::shared_ptr<APP::State> state = APP::State::get();
Vec2D<float> position; float speed = 960.0f;
Vec2D<float> direction;
virtual void update(int fps) {}; Vec2Df position;
virtual void update(double frametime) {}; Vec2Df direction;
virtual void update() {};
virtual void draw(Vec2D<float> *mouse, Camera2D *cam) {};
}; 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) {};
};
}
+136
View File
@@ -0,0 +1,136 @@
#pragma once
#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) : assignedValueRef(_assignedValue), parentPosition(parentPos), col(_col), name(name) {init();}
NodeHandle(T &_assignedValue, Vec2Df parentPos, Color _col, std::string name, float min, float max, float minDist, float maxDist) : assignedValueRef(_assignedValue), parentPosition(parentPos), col(_col), name(name), minValue(min), maxValue(max), minDistance(minDist), maxDistance(maxDist) {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;
float radius = 10.0f;
float orbit = 0.0f;
Vec2Df minP;
Vec2Df maxP;
bool locked = true;
bool visible = false;
void init(){
position = parentPosition.orbitAtAngle(minDistance, DEG2RAD * GetRandomValue(0, 360)) - parentPosition;
}
void assignValue(T &value){
assignedValueRef = value;
}
float distance(){
return (parentPosition + position).dist(parentPosition);
}
virtual void drag(Vec2Df mouse_pos) override {
if(KEYS_DOWN.shift) {
orbit = (parentPosition + position).dist(parentPosition);
position = parentPosition.orbitAlongVector(SCREEN_MOUSE, orbit) - parentPosition;
} else {
float step = (maxDistance - minDistance) / (maxValue - minValue);
orbit = round(parentPosition.dist(SCREEN_MOUSE) / step) * step;
position = parentPosition.orbitAlongVector(maxP, orbit) - parentPosition;
}
if(distance() >= maxDistance) position = parentPosition.orbitAlongVector(maxP, maxDistance) - parentPosition;
if(distance() <= minDistance) position = parentPosition.orbitAlongVector(maxP, minDistance) - 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(){
assignedValueRef = (T)Remap((float)(parentPosition + position).dist(parentPosition), minDistance, maxDistance, minValue, maxValue);
}
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;
DrawLineV(minP, maxP, dcol);
//DrawLineV(parentPosition, minP, VS_COLOR_LIGHTGREY);
DrawCircleV(minP, 3.0f, dyncol);
DrawCircleV(maxP, 3.0f, dyncol);
DrawCircleV(position + parentPosition, radius, dyncol);
if(show) VISEQ::HELPER::DrawSuperText(name, minP, maxP, 0.5f, 8.0f, 10.0f, dyncol);
}
}
virtual bool select() override {
return false;
}
/*friend void to_json(json& j, const NodeHandle<T>& N){*/
/* j = json{{"position",N.position}};*/
/*}*/
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(NodeHandle, position, name);
};
}
-81
View File
@@ -1,81 +0,0 @@
#pragma once
#include "datatypes.h"
#include "node.h"
#include "raylib.h"
#include "raymath.h"
#include <algorithm>
#include <iterator>
#include <random>
#include <ranges>
#include <utility>
#include <vector>
class NodeManager{
public:
NodeManager() {}
std::map<int, Node> nodes;
std::vector<int> keys;
int iterator = 0;
int getRandomNode(){
std::vector<int> o;
std::ranges::sample(keys, std::back_inserter(o), 1, std::mt19937{std::random_device{}()});
return o.front();
}
void copyNodes(Vec2D<float> position){
for(auto n: nodes | std::ranges::views::filter([](std::pair<int, Node> n){ return n.second.copied;})){
n.second.position = position + n.second.drag_offset;
n.second.id = iterator;
n.second.copied = false;
n.second.selected = true;
nodes.insert(std::make_pair(iterator, n.second));
keys.push_back(iterator);
iterator++;
}
}
int copyNode(int id, Vec2D<float> position){
Node n = nodes.at(id);
n.position = position;
n.id = iterator;
nodes.insert(std::make_pair(iterator, n));
keys.push_back(iterator);
iterator++;
return iterator - 1;
}
int addNode(float x, float y){
return addNode(Vec2D<float>(x,y));
}
int addNode(Vec2D<float> position){
nodes.emplace(std::make_pair(iterator, Node(iterator, position, 15.0f)));
keys.push_back(iterator);
iterator++;
return iterator - 1;
}
void drawNodes(Vec2D<float> *mouse_pos, Camera2D *camera){
auto flat_nodes = nodes | std::ranges::views::values | std::ranges::to<std::vector<Node>>();
for(auto& n : nodes | std::ranges::views::values ){
if(n.dragging){
std::sort(flat_nodes.begin(), flat_nodes.end(), [n](Node na, Node nb){ return na.position.dist(n.position) < nb.position.dist(n.position);});
float d = flat_nodes.at(1).position.dist(n.position);
if(d > 0 && d < 240) {
Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",d).c_str(), 10.0f, 2.0f);
Vec2D<float> middle_handle = flat_nodes.at(1).position + (n.position - flat_nodes.at(1).position) * ((d - text_length.x)*0.5f)/d;
Vec2D<float> text_position = middle_handle + (flat_nodes.at(1).position - n.position).norm().orth() * 12.0f;
float rotation = Vector2Angle((Vector2){1,0}, (n.position - flat_nodes.at(1).position)) * RAD2DEG;
DrawTextPro(GetFontDefault(), std::format("{:.1f}",d).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK);
DrawLineV(n.position, flat_nodes.at(1).position, VS_COLOR_PINK);
}
}
n.draw(mouse_pos, camera);
}
}
};
+90 -94
View File
@@ -1,117 +1,113 @@
#pragma once #pragma once
#include <map> #include <memory>
#include <raylib.h> #include <raylib.h>
#include "connector.h"
#include "datatypes.h" #include "datatypes.h"
#include "moveable.h" #include "draggable.h"
#include "raymath.h" #include "graph-item.h"
#include "interaction-event.h"
#include "state.h"
#include "vmath.h"
#define R 14.0f namespace VISEQ::GRAPH::EDGES{
class Edge;
}
struct NodeMidiOut{ namespace VISEQ::GRAPH::NODES {
int i;
std::string name;
bool enabled;
};
class Node : public Movable{ class Node : public BASE::Draggable, public GraphItem, public std::enable_shared_from_this<Node> {
public: public:
Node() {}
Node(int id, float x, float y, float r) : Movable(Vec2D<float>(x, y), 0.0f), radius(r), id(id) {}
Node(int id, Vec2D<float> p, float r) : Movable(p, 0.0f), radius(r), id(id) {}
float radius; Node() {std::cout << "Node constructor" << std::endl;}
Node(float x, float y) : Draggable(Vec2Df(x, y)) {}
Node(Vec2Df p) : Draggable(p) {}
int id; NodeType nodeType;
bool picked_up = false; std::vector<std::shared_ptr<Node>> neighbors;
bool dragging = false; std::vector<std::shared_ptr<VISEQ::GRAPH::EDGES::Edge>> connections;
bool in_rect = false;
bool hover_connect = false;
bool hovering = false;
bool connecting = false;
bool config = false;
bool selected = false;
bool erase = false;
bool highlighted = false;
bool copied = false;
bool triggered = false;
int midi_out = -1; std::vector<std::shared_ptr<Connector>> connectors;
Vec2D<float> drag_offset; Distribution distributionMode = Distribution::GEOMETRIC;
std::map<int, NodeMidiOut> midi_outs; float radius = 15.0f;
float node_radius = 15.0f;
float hover_radius = 32.0f;
bool config = false;
bool gui_midi_channel_edit = false; bool triggered = false;
bool gui_midi_note_edit = false; int mode = 0;
bool gui_midi_velocity_edit = false;
bool gui_midi_gate_edit = false;
int midi_channel = 1; virtual void trigger() {}
int midi_note = 48; virtual void setMode(int _mode) {mode = _mode;}
int midi_velocity = 60;
int midi_gate = 100;
int midi_cc = 0; std::shared_ptr<Node> getPtr(){
float start = 0; return shared_from_this();
float fade = 0;
float time = 0;
float last = 0;
Distribution mode = Distribution::ROUNDROBIN;
void setMidiPort(int midi_port, bool state){
midi_outs[midi_port].enabled = state;
}
void trigger(){
triggered = true;
start = GetTime();
fade = midi_gate;
}
void draw(Vec2D<float> *mouse, Camera2D *cam) {
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}); virtual bool hovering() override {
DrawRing(position, radius, radius+4, 0.0f - 90.0f, (360.0f/127.0f) * midi_velocity - 90.0f, 24, (Color){158, 134, 200, 255}); return CheckCollisionPointCircle(SCREEN_MOUSE, position, radius);
DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255});
float midi_gate_map = round((255.f / midi_gate) * fade);
Color gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115, (unsigned char)Clamp(255 - midi_gate_map, 0, 255)}, WHITE);
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(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255});
if(IsKeyDown(KEY_LEFT_ALT)){
int offset_gate = MeasureText(std::format("{:.2f}", (float)midi_gate/1000.0f).c_str(), 12);
DrawText(std::format("{:.2f}",(float)midi_gate/1000.0f).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, WHITE );
} else {
int offset = MeasureText(MIDI::Notes[midi_note].c_str(), 12);
DrawText(MIDI::Notes[midi_note].c_str(), position.x-offset/2.0f, position.y-5, 12, WHITE );
} }
if(mode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, (Color){229, 181, 103, 255});
Vec2D<float> connector = position - ((position - *mouse)).norm() * (radius + R); void addConnector() {
float opacity = 150.0f - Clamp(position.dist(mouse), 0.0f, 150.0f); connectors.emplace_back(std::make_shared<Connector>(getPtr(), position, radius + 5.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});
} void removeConnector(std::weak_ptr<Connector> connector){
}; std::erase(connectors, connector.lock());
}
virtual NodeType getNodeType() const {return nodeType;}
void deselect() override {
selected = false;
}
GraphItemType getType() override{
return GraphItemType::NODE;
}
void clearConnectors(){
connectors.clear();
update();
}
void update() override{
if(hovering()) state->pushEvent({getPtr(), APP::Interaction::HOVER});
if(hovering() && clicked()) state->pushEvent({getPtr(), APP::Interaction::PICKUP});
if(pickedUp()) state->pushEvent({getPtr(), APP::Interaction::DRAG});
if(picked_up && released()) state->pushEvent({getPtr(), APP::Interaction::PUTDOWN});
if(hovering() && MOUSE_RIGHT.clicked) {
selected = true;
config = true;
state->pushEvent({getPtr(), APP::Interaction::RIGHTCLICK});
}
int free_connectors = 0;
for(auto c : connectors){
if(c->partner.expired()) free_connectors++;
c->setPosition(position);
c->setOrbit(radius + 8.0f);
c->update();
}
if(free_connectors == 0) addConnector();
}
void draw(Vec2Df mp, Camera2D cam) override {}
void draw() override{
for(auto c : connectors){
c->draw();
}
}
};
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;
};
}
+131 -80
View File
@@ -1,114 +1,165 @@
#pragma once #pragma once
#include "connection-manager.h"
#include "datatypes.h" #include "datatypes.h"
#include "node-manager.h" #include "graph-manager.h"
#include "signal.h" #include "midi-node.h"
#include "node.h" #include "node.h"
#include "signal.h"
#include "midi.h" #include "midi.h"
#include <chrono> #include <chrono>
#include <memory>
#include <thread> #include <thread>
class SignalManager{ using namespace VISEQ::GRAPH;
public: namespace VISEQ::SIGNAL {
SignalManager() {}
SignalManager(NodeManager *nm, ConnectionManager *cm, std::mutex *mutex) : mutex(mutex), cm(cm), nm(nm) {}
Midi midi; typedef std::shared_ptr<Signal> SPTR_Signal;
typedef std::vector<SPTR_Signal> VEC_SPTR_Signal;
std::mutex *mutex; class SignalManager : public std::enable_shared_from_this<SignalManager>{
ConnectionManager *cm;
NodeManager *nm;
std::vector<Signal> signals; public:
SignalManager() {}
SignalManager(std::shared_ptr<GraphManager> gm, std::mutex *mutex) : mutex(mutex), gm(gm) {}
bool loop = true; std::shared_ptr<SignalManager> getPtr(){
bool play = false; return shared_from_this();
void pause(){
play = false;
}
void stop(){
play = false;
reset();
}
void start(){
play = true;
}
void reset(){
for(auto &s : signals){
s.position = nm->nodes[s.start].position + Vec2D<float>{0.1, 0.1};
s.next = s.start;
s.last = s.start;
} }
cm->reset();
}
void startSignalThread(){ VISEQ::MIDI::Midi midi;
std::thread t1(&SignalManager::updateSignals, this);
t1.detach();
}
void stopSignalThread(){ std::mutex *mutex;
loop = false; std::shared_ptr<GraphManager> gm;
}
void addSignalAtNode(int node_id){ VEC_SPTR_Signal signals;
Color c = {(unsigned char)(20 + (rand() % 200)), 100, (unsigned char)(20 + (int)(rand() % 200)), 200};
signals.emplace(signals.end(), Signal(nm->nodes[node_id].position.x+1, nm->nodes[node_id].position.y+1, 5.0f, 0, c)); // position + 1 or else it does not render. why? no idea!
signals.back().next = node_id;
signals.back().last = node_id;
signals.back().start = node_id;
}
void removeSignal(int n){ VEC_SPTR_Signal erase_signals;
signals.erase(signals.begin() + n);
}
void updateSignals(){ bool loop = true;
auto last = std::chrono::high_resolution_clock::now(); bool play = false;
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 = (nm->nodes[s.next].position - s.position).norm();
s.update(diff.count());
if(s.position.dist(&nm->nodes[s.next].position) < 0.6f) {
int n = cm->next(s.next, s.last);
nm->nodes[s.next].trigger();
if(n != s.next && n != -1) { void pause(){
for(auto &m : nm->nodes[s.next].midi_outs){ play = false;
if(m.second.enabled) { }
midi.note(m.second.i, nm->nodes[s.next].midi_channel, nm->nodes[s.next].midi_note, nm->nodes[s.next].midi_velocity, nm->nodes[s.next].midi_gate);
void stop(){
play = false;
reset();
}
void start(){
play = true;
}
void reset(){
for(auto &s : signals){
s->position = s->start.lock()->position + Vec2Df{0.1, 0.1};
s->next = s->start;
s->last = s->start;
}
gm->reset();
}
void startSignalThread(){
std::thread t1(&SignalManager::updateSignals, this);
t1.detach();
}
void stopSignalThread(){
loop = false;
}
void addSignalAtNode(WPTR_Node node){
Color c = {(unsigned char)(20 + (rand() % 200)), 100, (unsigned char)(20 + (int)(rand() % 200)), 200};
signals.emplace_back(std::make_shared<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 rerouteSignal(VEC_SPTR_Node nodes, VEC_WPTR_Node adjacent_nodes){
auto reroute_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->next.lock());});
for(auto s : reroute_signals){
float dist = 10000000;
SPTR_Node next_node;
for(auto n : adjacent_nodes){
if(n.lock()->position.dist(s->position) < dist) {
dist = n.lock()->position.dist(s->position);
next_node = n.lock();
}
}
s->next = next_node;
s->last = next_node;
}
}
void rebaseSignal(VEC_SPTR_Node nodes){
auto rebase_signals = signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->start.lock());});
for(auto s : rebase_signals){
s->start = s->next;
}
}
void removeSignal(std::shared_ptr<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.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;
} }
s.last = s.next;
s.next = n;
} }
} }
} }
midi.update();
mutex->unlock();
last = std::chrono::high_resolution_clock::now();
} }
midi.update();
mutex->unlock();
last = std::chrono::high_resolution_clock::now();
} }
} }
}
void drawSignals(Vec2D<float> *mouse_pos, Camera2D *camera){ void drawSignals(Vec2Df mouse_pos, Camera2D camera){
for(auto& s : signals){
mutex->lock(); mutex->lock();
s.draw(mouse_pos, camera); 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(); mutex->unlock();
} }
}
}; NLOHMANN_DEFINE_TYPE_INTRUSIVE(SignalManager, signals)
};
}
+43 -26
View File
@@ -1,41 +1,58 @@
#pragma once #pragma once
#include <raylib.h> #include <raylib.h>
#include "datatypes.h"
#include "moveable.h" #include "moveable.h"
class Signal : public Movable{ #include "node.h"
public: #include "edge.h"
Signal(); using namespace VISEQ::GRAPH;
Signal(float x, float y, float r, float _speed, Color color) : Movable(Vec2D<float>(x, y), Vec2D<float>(0,0), _speed), radius(r), color(color) {}
int id = 0; namespace VISEQ::SIGNAL {
class Signal : public BASE::Movable{
public:
int next; Signal() {}
int last; Signal(Vec2Df position, float r, float _speed, Color color) : Movable(position, Vec2Df(0,0), _speed), radius(r), color(color) {}
int start; NODES::WPTR_Node next;
NODES::WPTR_Node last;
EDGES::WPTR_Edge edge;
bool at_target = false; NODES::WPTR_Node start;
int target_node_id = 1;
int source_node_id = 0;
float radius; bool at_target = false;
bool run = true;
float multiplier = 1.0f; float radius = 8.0f;
Color color; float multiplier = 1.0f;
void update(double t_delta){ Color color;
// 120px / 16th = 1920px / whole note
// whole note = 2s at 120bpm
// 960px / s
position += direction.norm() * speed * multiplier * (float)t_delta / 1000000.0f;
}
void draw(Vec2D<float> *mouse, Camera2D *cam) { bool ui_edit_speed = false;
DrawCircle(position.x, position.y, radius, color);
} int midi_note_offset = 0;
}; int midi_cc_offset = 0;
void reset(){
toStart();
}
void toStart(){
position = start.lock()->position;
}
void update(double t_delta){
// 120px / 16th = 1920px / whole note
// whole note = 2s at 120bpm
// 960px / s
if(run) position += direction.norm() * speed * multiplier * (float)t_delta / 1000000.0f;
}
void draw(Vec2Df mouse, Camera2D cam) {
DrawCircle(position.x, position.y, radius, color);
}
};
}
+216
View File
@@ -0,0 +1,216 @@
#pragma once
#include "config-node.h"
#include "midi-node.h"
#include "graph-item.h"
#include "node-handle.h"
#include "node.h"
#include "edge.h"
#include "datatypes.h"
#include "signal.h"
#include <iostream>
#include <memory>
typedef std::shared_ptr<VISEQ::GRAPH::GraphItem> SPTR_GraphItem;
typedef std::vector<SPTR_GraphItem> VEC_SPTR_GraphItem;
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>()));
}
}
};
/*template <typename T>*/
/*struct adl_serializer<std::shared_ptr<T>> {*/
/* template <typename J>*/
/* static void to_json(J& j, const std::shared_ptr<T>& opt) {*/
/* if (opt) {*/
/* j = *opt;*/
/* } else {*/
/* j = nullptr;*/
/* }*/
/* }*/
/**/
/* template <typename J>*/
/* static void from_json(const J& j, std::shared_ptr<T>& opt) {*/
/* if (j.is_null()) {*/
/* opt = nullptr;*/
/* } else {*/
/* opt.reset(new T(j.template get<T>()));*/
/* }*/
/* }*/
/*};*/
}
namespace VISEQ::GRAPH::EDGES {
using namespace VISEQ::GRAPH::NODES;
inline void to_json(json& j, const VISEQ::GRAPH::EDGES::Edge& E){
j = json{{"id",E.id}, {"start", E.start.lock()->id}, {"end",E.end.lock()->id}, {"pass_forward",E.pass_forward}, {"pass_backward", E.pass_backward}};
}
inline void from_json(const json& j, Edge& E){
j.at("id").get_to(E.id);
j.at("pass_forward").get_to(E.pass_forward);
j.at("pass_backward").get_to(E.pass_backward);
}
inline void to_json(json& j, const VISEQ::GRAPH::EDGES::SPTR_Edge& E){
j = *E;
}
inline void from_json(const json& j, VISEQ::GRAPH::EDGES::SPTR_Edge& E){
E.reset(new Edge(j.get<Edge>()));
}
}
namespace VISEQ::GRAPH::NODES {
/* ------------------- MIDINODE ---------------------- */
/*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}};
}
/*template< typename T>*/
/*inline void to_json(json& j, const std::shared_ptr<NodeHandle<T>>& NH){*/
/* j = json{{"position", NH->position}};*/
/*}*/
/* ----------------- CONFIG NODE -----------------*/
/*inline void to_json(json& j, const std::shared_ptr<ConfigNode>& N){*/
/* j = *N;*/
/*}*/
/**/
/*inline void from_json(const json& j, std::shared_ptr<ConfigNode>& N){*/
/* N.reset(new ConfigNode(j.get<ConfigNode>()));*/
/*}*/
/* ------------------- NODE ---------------------- */
inline 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);
}
}
inline 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>();
}
}
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;
if(N->getNodeType() == NodeType::MIDINODE){
N.reset(new MidiNode(j.get<MidiNode>()));
}
if(N->getNodeType() == NodeType::CONFIGNODE){
N.reset(new ConfigNode(j.get<ConfigNode>()));
}
}
}
namespace VISEQ::GRAPH {
inline void to_json(json& j, const GraphItem& G){
std::cout << "Movable" << std::endl;
j = json{{"id", G.id}, {"type", G.gItemType}};
}
inline void to_json(json& j, const SPTR_GraphItem& G){
j = *G;
}
}
namespace VISEQ::BASE{
inline void to_json(json& j, const Movable& G){
std::cout << "Movable" << std::endl;
j = json{{"position", G.position}};
}
inline void to_json(json& j, const std::shared_ptr<Movable>& G){
j = *G;
}
inline void from_json(const json& j, Movable& G){
std::cout << "Movable" << std::endl;
j.at("position").get_to<Vec2Df>(G.position);
}
inline void from_json(json& j, const std::shared_ptr<Movable>& 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.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>()));
}
}
-137
View File
@@ -1,137 +0,0 @@
#pragma once
#include "connection.h"
#include "datatypes.h"
#include "node-manager.h"
#include "connection-manager.h"
#include "signal-manager.h"
#include "ui.h"
#include <algorithm>
#include <iterator>
#include <ranges>
#include <vector>
class AppState{
public:
AppState() : cm(&nm), sm(&nm, &cm, &mutex), ui(&settings, &nm, &cm, &sm) {}
enum States {IDLE, CONFIG};
AppSettings settings;
States state;
std::mutex mutex;
ConnectionManager cm;
SignalManager sm;
NodeManager nm;
AppUI ui;
void updateMidiPorts(){
for(auto&n : nm.nodes | std::views::values){
for(auto& mo : sm.midi.midi_out_ports){
if(!n.midi_outs.contains(mo.number) && mo.enabled){
n.midi_outs[mo.number] = {mo.number, mo.port.port_name, false};
}
if(n.midi_outs.contains(mo.number) && !mo.enabled){
n.midi_outs.erase(mo.number);
}
}
}
}
void copyNodesAndConnections(Vec2D<float> position){
auto copy_nodes_ids = nm.nodes | std::ranges::views::values | std::ranges::views::filter([](Node n){return n.copied;}) | std::views::transform([](Node n){return n.id;}) | std::ranges::to<std::vector<int>>();
std::vector<Connection> copy_connections;
std::ranges::copy_if(cm.connections, std::back_inserter(copy_connections), [copy_nodes_ids](Connection c){return std::ranges::contains(copy_nodes_ids, c.v1) && std::ranges::contains(copy_nodes_ids, c.v2);});
for(auto &n : copy_nodes_ids){
int old_id = n;
int new_id = nm.copyNode(n, position + nm.nodes[n].drag_offset);
nm.nodes[new_id].copied = false;
nm.nodes[new_id].selected = true;
for(auto &c : copy_connections){
if(c.v1 == old_id) c.v1 = new_id;
if(c.v2 == old_id) c.v2 = new_id;
}
}
cm.connections.insert(cm.connections.end(), copy_connections.cbegin(), copy_connections.cend());
}
void removeNodes(){
for(auto &n : nm.nodes){
n.second.highlighted = false;
}
auto erase_nodes = nm.nodes | std::ranges::views::values | std::ranges::views::filter([](Node n){return n.erase;});
std::vector<int> erase_nodes_ids;
for(auto &e : erase_nodes){
erase_nodes_ids.push_back(e.id);
}
auto erase_connections = cm.connections | std::ranges::views::filter([erase_nodes_ids](Connection c){ return std::ranges::contains(erase_nodes_ids, c.v1) || std::ranges::contains(erase_nodes_ids, c.v2);});
std::vector<int> adjacent_nodes_ids;
for(auto &c : erase_connections){
if(!nm.nodes.at(c.v1).selected) adjacent_nodes_ids.push_back(c.v1);
if(!nm.nodes.at(c.v2).selected) adjacent_nodes_ids.push_back(c.v2);
}
auto reroute_signals = sm.signals | std::ranges::views::filter([erase_nodes_ids](Signal s){
return std::ranges::contains(erase_nodes_ids, s.next);
});
for(auto &s : reroute_signals){
float dist = 10000000;
int next_id = -1;
for(int i : adjacent_nodes_ids){
if(nm.nodes.at(i).position.dist(&s.position) < dist) {
dist = nm.nodes.at(i).position.dist(&s.position);
next_id = i;
}
}
s.next = next_id;
s.last = next_id;
}
auto rebase_signals = sm.signals | std::ranges::views::filter([erase_nodes_ids](Signal s){
return std::ranges::contains(erase_nodes_ids, s.start);
});
for(auto &s : rebase_signals){
s.start = s.next;
}
for(auto &n : erase_nodes){
std::erase_if(cm.connections, [n](Connection c){ return c.v1 == n.id || c.v2 == n.id;});
}
std::erase_if(nm.nodes, [](std::pair<int, Node> n){ return n.second.erase;});
nm.keys.clear();
std::ranges::copy(nm.nodes | std::ranges::views::keys, std::back_inserter(nm.keys));
}
void draw(Vec2D<float> *mouse_pos, Camera2D *camera){
mutex.lock();
updateMidiPorts();
mutex.unlock();
nm.drawNodes(mouse_pos, camera);
cm.drawConnections(mouse_pos, camera);
sm.drawSignals(mouse_pos, camera);
}
void drawUI(Vec2D<float> *mouse_pos, Camera2D *camera){
mutex.lock();
ui.draw(mouse_pos, camera);
mutex.unlock();
}
};
+160
View File
@@ -0,0 +1,160 @@
#pragma once
#include "interaction-event.h"
#include "nlohmann/json.hpp"
#include "raylib.h"
#include "vmath.h"
#include <memory>
#include <queue>
#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){
event_queue.push_back(e);
}
InteractionEvent pullEvent(){
if(!event_queue.empty()){
InteractionEvent e = event_queue.front();
event_queue.pop_front();
return e;
}
return {nullptr, Interaction::NONE};
}
void update(Camera2D camera){
if(istate != old_istate) std::cout << state_string[istate] << std::endl;
mouse_delta = {GetMouseDelta().x, GetMouseDelta().y};
settings.speed = (settings.grid * 16.0f) * (settings.bpm / 120.f);
Vector2 mp = GetScreenToWorld2D(GetMousePosition(), camera);
Vec2Df mouse_pos = {(float)mp.x, (float)mp.y};
mouse_left.down = IsMouseButtonDown(MOUSE_BUTTON_LEFT);
mouse_left.clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT);
mouse_left.released = IsMouseButtonReleased(MOUSE_BUTTON_LEFT);
mouse_right.down = IsMouseButtonDown(MOUSE_BUTTON_RIGHT);
mouse_right.clicked = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT);
mouse_right.released = IsMouseButtonReleased(MOUSE_BUTTON_RIGHT);
keys_down.ctrl = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL);
keys_down.shift = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
keys_down.alt = IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT);
keys_pressed.ctrl = IsKeyPressed(KEY_LEFT_CONTROL) || IsKeyPressed(KEY_RIGHT_CONTROL);
keys_pressed.shift = IsKeyPressed(KEY_LEFT_SHIFT) || IsKeyPressed(KEY_RIGHT_SHIFT);
keys_pressed.alt = IsKeyPressed(KEY_LEFT_ALT) || IsKeyPressed(KEY_RIGHT_ALT);
keys_released.ctrl = IsKeyReleased(KEY_LEFT_CONTROL) || IsKeyReleased(KEY_RIGHT_CONTROL);
keys_released.shift = IsKeyReleased(KEY_LEFT_SHIFT) || IsKeyReleased(KEY_RIGHT_SHIFT);
keys_released.alt = IsKeyReleased(KEY_LEFT_ALT) || IsKeyReleased(KEY_RIGHT_ALT);
screen_mouse = {round(mouse_pos.x), round(mouse_pos.y)};
raw_mouse = {GetMousePosition().x, GetMousePosition().y};
mod_mouse = {round(mouse_pos.x), round(mouse_pos.y)};
mode_color = settings.mode_colors[node_mode];
if(keys_down.ctrl) {
mod_mouse.x = round(mouse_pos.x / settings.grid) * settings.grid;
mod_mouse.y = round(mouse_pos.y / settings.grid) * settings.grid;
}
old_istate = istate;
}
inline static std::shared_ptr<State> instance;
static std::shared_ptr<State> get() {
if(!instance){
instance.reset(new State());
}
return instance;
}
};
}
namespace VISEQ{
using IState = VISEQ::APP::InteractionState;
}
+158 -97
View File
@@ -1,50 +1,55 @@
#pragma once #pragma once
#include "state.h"
#include "midi-node.h"
#include "raylib.h"
#include "raygui/raygui.h" #include "raygui/raygui.h"
#include "raygui/dark/style_dark.h" #include "raygui/dark/style_dark.h"
#include "datatypes.h" #include "datatypes.h"
#include "graph-manager.h"
#include "connection-manager.h"
#include "node-manager.h"
#include "raylib.h"
#include "signal-manager.h" #include "signal-manager.h"
#include <memory>
#include <ranges> #include <ranges>
class AppUI{ namespace VISEQ::APP::UI{
public: class AppUI{
public:
AppUI() {}
AppUI(std::shared_ptr<SIGNAL::SignalManager> sm, std::shared_ptr<GraphManager> gm) : gm(gm), sm(sm) {}
AppUI(AppSettings *settings, NodeManager *nm, ConnectionManager *cm, SignalManager *sm) : settings(settings), nm(nm), sm(sm), cm(cm) {} std::shared_ptr<State> state = State::get();
AppSettings *settings; std::shared_ptr<GraphManager> gm;
NodeManager *nm; std::shared_ptr<SIGNAL::SignalManager> sm;
SignalManager *sm;
ConnectionManager *cm;
void init(){ bool bpm_spinner_edit_mode = false;
GuiLoadStyleDark();
}
void drawSideBar(){ void init(){
GuiLoadStyleDark();
}
GuiPanel((Rectangle){-1,-30,280,(float)settings->height + 31}, ""); void drawSideBar(){
{
const int s_controls = 20; GuiPanel((Rectangle){-1,-30,380,(float)state->settings.height + 31}, "");
const int s_controls = 25;
//GuiSetStyle(BUTTON, STATE_FOCUSED, 0xFF0000FF); //GuiSetStyle(BUTTON, STATE_FOCUSED, 0xFF0000FF);
if(GuiButton((Rectangle){20, (float)s_controls, 80, 30}, "PLAY")) sm->play ? sm->pause() : sm->start(); 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){120, (float)s_controls, 60, 30}, "STOP")) sm->stop();
if(GuiButton((Rectangle){200, (float)s_controls, 60, 30}, "RESET")) sm->reset(); if(GuiButton((Rectangle){200, (float)s_controls, 60, 30}, "RESET")) sm->reset();
const int s_bpm = s_controls + 35; const int s_bpm = s_controls + 45;
GuiLabel((Rectangle){20, (float)s_bpm, 120, 20}, "TIMING"); GuiLabel((Rectangle){20, (float)s_bpm, 120, 20}, "TIMING");
GuiLabel((Rectangle){200, (float)s_bpm, 40, 20}, std::format("{}",settings->bpm).c_str()); GuiLabel((Rectangle){200, (float)s_bpm, 40, 20}, std::format("{}",state->settings.bpm).c_str());
float _bpm = (float)settings->bpm; int _bpm = state->settings.bpm;
GuiSlider((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", "", &_bpm, 1, 300); // GuiSlider((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", "", &_bpm, 1, 300);
settings->bpm = _bpm; 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 + 55; int s_midi = s_bpm + 75;
GuiLabel((Rectangle){20, (float)s_midi, 240, 20}, "MIDI OUT"); GuiLabel((Rectangle){20, (float)s_midi, 240, 20}, "MIDI OUT");
if(GuiButton((Rectangle){20, (float)s_midi, 60, 30}, "REload")) sm->midi.enumerate(); if(GuiButton((Rectangle){200, (float)s_midi, 60, 20}, "RELOAD")) sm->midi.enumerate();
int i = 0; int i = 0;
for(auto& mo : sm->midi.midi_out_ports){ for(auto& mo : sm->midi.midi_out_ports){
@@ -55,97 +60,153 @@ class AppUI{
if(GuiButton((Rectangle){20, (float)s_midi + 45 + i*20.0f, 240, 20}, "ENABLE FOR ALL")) { if(GuiButton((Rectangle){20, (float)s_midi + 45 + i*20.0f, 240, 20}, "ENABLE FOR ALL")) {
for(auto &midis : sm->midi.midi_out_ports){ for(auto &midis : sm->midi.midi_out_ports){
if(midis.enabled){ if(midis.enabled){
for(auto &n : nm->nodes){ gm->setMidiPortForAll(midis.number, true);
n.second.setMidiPort(midis.number, true);
}
} }
} }
} }
int s_signal = s_midi + 90 + i*20; int s_signal = s_midi + 100 + i*20;
GuiLabel((Rectangle){20, (float)s_signal, 240, 20}, "SIGNALS"); GuiLabel((Rectangle){20, (float)s_signal, 240, 20}, "SIGNALS");
if(GuiButton((Rectangle){20, (float)s_signal + 35, 240, 20}, "ADD RANDOM SIGNAL")){ if(GuiButton((Rectangle){20, (float)s_signal + 35, 240, 20}, "ADD RANDOM SIGNAL")){
int ni = nm->getRandomNode(); std::shared_ptr<Node> ni = gm->getRandomNode();
sm->addSignalAtNode(ni); sm->addSignalAtNode(ni);
} }
int signal_configs = 0; int signal_configs = 0;
for(auto &s : sm->signals){ for(auto &s : sm->signals){
if(GuiButton((Rectangle){20,(float)s_signal+70 + signal_configs*20, 20, 20},"X")) sm->removeSignal(signal_configs); 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); DrawCircle(55, s_signal+80 + signal_configs*20, 6.0F, s->color);
float s_multiplier = round(s.multiplier*10.0f)/10.0f;
float signal_bpm = settings->bpm * s.multiplier; int s_speed = round(s->speed);
GuiSlider((Rectangle){70, (float)s_signal+70 + signal_configs*20, 160, 20}, "", std::format("{:.1f}",signal_bpm).c_str(), &s_multiplier, 0.1, 10); s->multiplier = state->settings.bpm / 120.0f;
s.multiplier = s_multiplier; float signal_bpm = round(state->settings.bpm * s->multiplier * (s->speed / state->settings.speed));
s.speed = 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++; signal_configs++;
} }
int s_nodes = s_signal + 60 + signal_configs*20; int s_nodes = s_signal + 70 + signal_configs*20;
GuiLabel((Rectangle){20, (float)s_nodes + 30, 240, 20}, "NODES"); GuiLabel((Rectangle){20, (float)s_nodes + 30, 240, 20}, "NODES");
if(GuiButton((Rectangle){20, (float)s_nodes + 60, 240, 20}, "ADD RANDOM NODE")){ if(GuiButton((Rectangle){20, (float)s_nodes + 60, 240, 20}, "ADD RANDOM NODE")){
nm->addNode(200.0f, 200.0f); 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);
} }
DrawText(std::format("{}", nm->nodes.size()).c_str(), 20, settings->height - 70, 15, VS_COLOR_WHITE);
DrawFPS(20, settings->height - 30);
} }
}
void drawPerNodeConfig(Camera2D *camera){ void DrawCenteredText(std::string text, Vec2Df position, int size, Color color){
for(auto& n : nm->nodes | std::ranges::views::values | std::views::filter([](Node n){ return n.config; })){ float offset = MeasureText(text.c_str(), size);
Vector2 np = GetWorldToScreen2D((Vector2){n.position.x, n.position.y}, *camera); DrawText(text.c_str(), position.x-offset/2.0f, position.y-(float)size/2.0f+1, size, color );
n.config = !GuiWindowBox((Rectangle){np.x + 10, np.y + 10, 240, 440}, "config");
int option = n.mode;
GuiToggleGroup((Rectangle){ np.x + 20, np.y + 40, 110, 20 }, "Round Robin;Random", &option);
n.mode = (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.id);
}
int y_midi = 120;
GuiLabel((Rectangle){np.x + 20, np.y + y_midi, 220, 20}, "MIDI OUT");
int i = 0;
for(auto& mo : n.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;
GuiLabel((Rectangle){np.x + 20, np.y + y_channel, 220, 20}, "CHANNEL");
GuiToggleGroup((Rectangle){np.x + 20, np.y + y_channel + 30, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &n.midi_channel);
GuiLabel((Rectangle){np.x + 20, np.y + y_channel + 90, 100, 20}, "NOTE");
float v = n.midi_note;
GuiLabel((Rectangle){np.x + 210, np.y + y_channel + 90, 20, 20}, sm->midi.midiNotes[(int)round(v)].c_str());
GuiLabel((Rectangle){np.x + 160, np.y + y_channel + 90, 40, 20}, std::format("{}",n.midi_note).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_channel + 120, 220, 20}, "", "", &v, 0, 127);
n.midi_note = round(v);
GuiLabel((Rectangle){np.x + 20, np.y + y_channel + 150, 100, 20}, "VELOCITY");
v = n.midi_velocity;
GuiLabel((Rectangle){np.x + 210, np.y + y_channel + 150, 20, 20}, std::format("{}",n.midi_velocity).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_channel + 180, 220, 20}, "", "", &v, 0, 127);
n.midi_velocity = round(v);
GuiLabel((Rectangle){np.x + 20, np.y + y_channel + 210, 100, 20}, "GATE");
v = n.midi_gate;
GuiLabel((Rectangle){np.x + 210, np.y + y_channel + 210, 20, 20}, std::format("{}",(float)n.midi_gate/1000.0f).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_channel + 240, 220, 20}, "", "", &v, 0, 30000);
n.midi_gate = round(v);
//if(GuiValueBox((Rectangle){n.position.x + 100, n.position.y + y_channel + 40, 140, 20}, "VELOCITY", &n.midi_velocity, 0, 127, n.gui_midi_velocity_edit)) n.gui_midi_velocity_edit = !n.gui_midi_velocity_edit;
//if(GuiValueBox((Rectangle){n.position.x + 100, n.position.y + y_channel + 60, 140, 20}, "GATE", &n.midi_gate, 0, 5000, n.gui_midi_gate_edit)) n.gui_midi_gate_edit = !n.gui_midi_gate_edit;
} }
}
void draw(Vec2D<float> *mouse_pos, Camera2D *camera){ void drawContextMenu(Camera2D camera){
drawSideBar(); for(auto n : gm->getSelection() | std::views::filter([](std::shared_ptr<Node> n){ return n->config; })){
drawPerNodeConfig(camera); 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 draw(Vec2Df mouse_pos, Camera2D camera){
drawSideBar();
if(state->context_menu) drawContextMenu(camera);
}
};
}
+172
View File
@@ -0,0 +1,172 @@
#pragma once
#include "raylib.h"
#include <cmath>
#include <iostream>
#include "nlohmann/json.hpp"
using json = nlohmann::json;
namespace VISEQ::VMATH{
template<typename T>
class Vec2D{
public:
Vec2D() {};
Vec2D(T x, T y) : x(x), y(y) {}
T x = 0;
T y = 0;
T dist(Vec2D* v) { return sqrt(pow((v->x - x), 2) + pow((v->y - y), 2));};
T dist(Vec2D v) { return sqrt(pow((v.x - x), 2) + pow((v.y - y), 2));};
T length() { return (T)sqrt(pow(x, 2) + pow(y, 2));};
T angle(Vec2D v){
T angle = atan2(v.y, v.x) - atan2(y,x);
if(angle > M_PI) angle -= M_PI_2;
if(angle < -M_PI) angle += M_PI_2;
return angle;
//return acos(dot(v));
};
T angleDeg(Vec2D v){
return angle(v) * 180 / M_PI;
};
T dot(Vec2D &v){
return (x*v.x) + (y*v.y);
};
T dot(Vec2D v){
return (x*v.x) + (y*v.y);
};
operator Vector2() const {
return (Vector2){x,y};
}
Vec2D orth(Vec2D &v){
return Vec2D(-v.y, v.x);
};
Vec2D orth(){
return Vec2D(-y, x);
};
Vec2D norm() { return *this / length();};
Vec2D center(Vec2D v){
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;
}
x = vec.x;
y = vec.y;
return *this;
};
Vec2D& operator+=(const Vec2D& vec){
this->x += vec.x;
this->y += vec.y;
return *this;
};
Vec2D& operator-=(const Vec2D& vec){
this->x -= vec.x;
this->y -= vec.y;
return *this;
};
Vec2D& operator*=(const Vec2D& vec){
this->x *= vec.x;
this->y *= vec.y;
return *this;
};
Vec2D& operator/=(const Vec2D& vec){
this->x /= vec.x;
this->y /= vec.y;
return *this;
};
Vec2D& operator*=(T scalar){
this->x *= scalar;
this->y *= scalar;
return *this;
};
Vec2D& operator/=(T scalar){
this->x /= scalar;
this->y /= scalar;
return *this;
};
};
template<typename T>
void to_json(json& j, const Vec2D<T>& V){
j = json{{"x",V.x},{"y",V.y}};
}
template<typename T>
void from_json(const json& j, Vec2D<T>& V){
std::cout << "Vec2D" << std::endl;
j.at("x").get_to(V.x);
j.at("y").get_to(V.y);
}
template<typename T>
Vec2D<T> operator+(const Vec2D<T> left, const Vec2D<T> right){
return Vec2D<T>(left.x + right.x, left.y + right.y);
};
template<typename T>
Vec2D<T> operator-(const Vec2D<T> left, const Vec2D<T> right){
return Vec2D<T>(left.x - right.x, left.y - right.y);
};
template<typename T>
Vec2D<T> operator*(const Vec2D<T> left, const Vec2D<T> right){
return Vec2D<T>(left.x * right.x, left.y * right.y);
};
template<typename T>
Vec2D<T> operator/(const Vec2D<T> left, const Vec2D<T> right){
return Vec2D<T>(left.x / right.x, left.y / right.y);
};
template<typename T>
Vec2D<T> operator/(const Vec2D<T> left, T scalar){
return Vec2D<T>(left.x / scalar, left.y / scalar);
};
template<typename T>
Vec2D<T> operator*(const Vec2D<T> left, T scalar){
return Vec2D<T>(left.x * scalar, left.y * scalar);
};
template<typename T>
Vec2D<T> cartesianToPolar(T x, T y) {
return Vec2D<T>{sqrt(x * x + y * y), atan(y / x)}; // r,theta
}
template<typename T>
Vec2D<T> polarToCartesian(T r, T q) {
return Vec2D<T>{r * cos(q), r * sin(q)};
}
}
using Vec2Df = VISEQ::VMATH::Vec2D<float>;