23 Commits

Author SHA1 Message Date
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
24 changed files with 29730 additions and 1014 deletions
+2
View File
@@ -1,4 +1,6 @@
build/**
.cache
viseq
viseq.exe
compile_commands.json
*.json
+2924
View File
File diff suppressed because it is too large Load Diff
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_
+2 -4
View File
@@ -4,14 +4,12 @@
#include "raylib.h"
#include "raymath.h"
inline bool ViButton(Vec2D<float> *mouse_pos, 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
inline bool ViButton(Vec2Df mp, float x, float y, float radius, Color color){
DrawCircle(x,y,radius, color);
return (Vector2Distance(mp, (Vector2){x,y}) < radius && IsMouseButtonPressed(MOUSE_BUTTON_LEFT));
}
inline bool ViSegButton(Vec2D<float> *mouse_pos, float x, float y, float start_angle, float end_angle, float radius, Color color){
Vector2 mp = {mouse_pos->x, mouse_pos->y}; //GetMousePosition(); TODO: Refactor my own vectors to raylib vectors
inline bool ViSegButton(Vec2Df mp, float x, float y, float start_angle, float end_angle, float radius, Color color){
DrawCircleSector((Vector2){x,y},radius, start_angle, end_angle, 5, color);
return (Vector2Distance(mp, (Vector2){x,y}) < radius && IsMouseButtonPressed(MOUSE_BUTTON_LEFT));
}
-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();
}
};
+45 -150
View File
@@ -1,7 +1,5 @@
#pragma once
//#include "connection-manager.h"
//#include "signal-manager.h"
#ifdef _WIN32
#define _USE_MATH_DEFINES
#endif
@@ -20,161 +18,58 @@
#define VS_COLOR_BROWN (Color){84, 75, 61, 255}
#define VS_COLOR_SUN (Color){214, 170, 21, 255}
#include "vmath.h"
#include "nlohmann/json.hpp"
using json = nlohmann::json;
#include <cmath>
#include <math.h>
#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:
namespace VISEQ::APP{
struct AppSettings{
int mode = 0;
int width;
int height;
float grid;
float bpm;
float fps;
float speed = 16.0f;
int gui_selected_midi_out;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(AppSettings, mode, width, height, grid, bpm, fps, speed, gui_selected_midi_out)
}
namespace VISEQ::GRAPH{
enum GraphItemType {NODE, EDGE};
enum NodeType {OSCNODE, MIDINODE};
}
namespace VISEQ::MIDI{
struct MidiOutPort{
libremidi::output_port port;
int number;
bool enabled;
};
struct MidiOut{
libremidi::midi_out out;
int number;
};
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"};
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){
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>
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);
};
// App Settings
struct AppSettings{
int width;
int height;
float grid;
float bpm;
float fps;
float speed = 16.0f;
int gui_selected_midi_out;
};
// MIDI
struct MidiOutPort{
libremidi::output_port port;
int number;
bool enabled;
};
struct MidiOut{
libremidi::midi_out out;
int number;
};
namespace MIDI{
inline std::vector<std::string> Notes = {
"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",
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#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::shared_ptr<NODES::Node> start, std::shared_ptr<NODES::Node> end) : start(start), end(end) {}
std::shared_ptr<NODES::Node> start;
std::shared_ptr<NODES::Node> end;
bool pass_forward = true;
bool pass_backward = true;
bool active = false;
bool config = false;
bool split = false;
float connector_offset = 8.0f;
float switches_offset = 25.0f;
virtual bool GActive() {return active;}
std::shared_ptr<NODES::Node> getNextNode(std::shared_ptr<NODES::Node> origin){
return start == origin ? end : start;
}
void draw(Vec2Df mouse_pos, Camera2D cam) {}
};
typedef std::shared_ptr<Edge> SPTR_Edge;
typedef std::vector<SPTR_Edge> VEC_SPTR_Edge;
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include "raylib.h"
#include "datatypes.h"
#include <memory>
#include <vector>
namespace VISEQ::GRAPH {
class GraphItem{
public:
GraphItem() {newId(-1);}
GraphItem(const GraphItem& G) {
newId(-1);
graphItemType = G.graphItemType;
}
GraphItemType graphItemType;
int id = 0;
void newId(int start){
static int _id;
if(start > -1) _id = start;
id = _id;
_id++;
}
virtual void draw(Vec2Df mouse, Camera2D cam) {};
virtual GraphItemType getType() {return graphItemType;}
virtual ~GraphItem() = default;
};
}
+310
View File
@@ -0,0 +1,310 @@
#pragma once
#include "datatypes.h"
#include "raylib.h"
#include "raymath.h"
#include "nlohmann/json.hpp"
#include "button.h"
#include "midi-node.h"
#include "edge.h"
#include "node.h"
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <random>
#include <ranges>
#include <vector>
#include "state-io.h"
using json = nlohmann::json;
using namespace VISEQ::GRAPH::NODES;
using namespace VISEQ::GRAPH::EDGES;
namespace VISEQ::GRAPH {
class GraphManager{
public:
GraphManager() {}
NODES::VEC_SPTR_Node nodes;
EDGES::VEC_SPTR_Edge edges;
EDGES::VEC_SPTR_Edge remove_edges;
EDGES::VEC_SPTR_Edge split_edges;
std::map<NODES::SPTR_Node, int> counter;
NODES::SPTR_Node getRandomNode(){
VEC_SPTR_GraphItem o;
std::ranges::sample(nodes, std::back_inserter(o), 1, std::mt19937{std::random_device{}()});
return std::dynamic_pointer_cast<VISEQ::GRAPH::NODES::Node>(o.front());
}
EDGES::SPTR_Edge getRandomEdge(){
VEC_SPTR_GraphItem o;
std::ranges::sample(edges, std::back_inserter(o), 1, std::mt19937{std::random_device{}()});
return std::dynamic_pointer_cast<VISEQ::GRAPH::EDGES::Edge>(o.front());
}
void copyNodes(VEC_SPTR_Node _nodes, Vec2Df position){
for(auto n: _nodes){
nodes.insert(nodes.end(), std::make_shared<Node>(*n));
nodes.back()->setPosition(position + nodes.back()->drag_offset);
nodes.back()->selected = false;
}
}
void copyNodes(std::vector<std::shared_ptr<MidiNode>> _nodes, Vec2Df position){
for(auto n: _nodes){
nodes.insert(nodes.end(), std::make_shared<MidiNode>(*n));
nodes.back()->setPosition(position + nodes.back()->drag_offset);
nodes.back()->selected = false;
}
}
SPTR_Node copyNode(SPTR_Node n, Vec2Df position){
std::cout << "copy basenode" << std::endl;
nodes.insert(nodes.end(), std::make_shared<Node>(*n));
nodes.back()->setPosition(position);
return nodes.back();
}
SPTR_Node copyNode(std::shared_ptr<MidiNode> n, Vec2Df position){
std::cout << "copy midinode" << std::endl;
nodes.insert(nodes.end(), std::make_shared<MidiNode>(*n));
nodes.back()->setPosition(position);
return nodes.back();
}
SPTR_Node addMidiNode(Vec2Df position){
nodes.emplace_back(std::make_shared<MidiNode>(position));
return nodes.back();
}
void setNodeMode(int mode){
for(auto n: nodes){
n->SMode(mode);
}
}
void reset(){
counter.clear();
}
SPTR_Node next(SPTR_Node current, SPTR_Node last){
VEC_SPTR_Edge possible_connections = current->connections | RANGE_FILTER([current](SPTR_Edge c){
return (c->start == current && c->pass_forward) || (c->end == current && c->pass_backward);
}) | std::ranges::to<VEC_SPTR_Edge>();
if(possible_connections.empty()) return SPTR_Node(nullptr);
int return_path = 0;
float angle = -1;
if(current->distributionMode == Distribution::GEOMETRIC){
Vec2Df coming_from = (current->position - last->position).norm();
int i = 0;
for(auto n : possible_connections){
SPTR_Node next_node = n->getNextNode(current);
float angle_v1_v2 = coming_from.norm().dot((next_node->position - current->position).norm());
if(angle_v1_v2 > angle) {
return_path = i;
angle = angle_v1_v2;
}
++i;
}
}
if(current->distributionMode == Distribution::ROUNDROBIN){
if(counter.find(current) == counter.end()) {
counter[current] = 0;
} else {
counter[current] = (counter[current] + 1) % possible_connections.size();
return_path = counter[current];
}
}
if(current->distributionMode == Distribution::RANDOM){
return_path = rand() % possible_connections.size();
}
//possible_connections.at(return_path)->active = true;
return possible_connections.at(return_path)->getNextNode(current);
}
SPTR_Node getClosestNode(Vec2Df position){
auto neighbors = nodes | RANGE_FILTER([](SPTR_Node n){ return !n->dragging;}) | std::ranges::to<VEC_SPTR_Node>();
std::sort(neighbors.begin(), neighbors.end(), [position](SPTR_Node na, SPTR_Node nb){ return na->position.dist(position) < nb->position.dist(position);});
return neighbors.front();
}
void addConnection(SPTR_Node from, SPTR_Node to){
if(from != to) {
edges.emplace_back(std::make_shared<Edge>());
edges.back()->start = from;
edges.back()->end = to;
from->connections.push_back(edges.back());
to->connections.push_back(edges.back());
}
}
void splitEdge(SPTR_Edge edge){
split_edges.push_back(edge);
}
void splitEdges(){
for(auto s : split_edges){
Vec2Df center = s->end->position.center(s->start->position);
SPTR_Node cn = copyNode(std::dynamic_pointer_cast<MidiNode>(s->start), center);
addConnection(s->start, cn);
addConnection(cn, s->end);
remove_edges.push_back(s);
}
split_edges.clear();
}
void removeEdge(SPTR_Edge edge){
remove_edges.push_back(edge);
}
void removeEdges(){
for(auto edge : remove_edges){
std::erase(edge->start->connections, edge);
std::erase(edge->end->connections, edge);
std::erase(edges, edge);
}
remove_edges.clear();
}
void drawEdges(Vec2Df mouse_pos, Camera2D cam){
if(!remove_edges.empty()) removeEdges();
if(!split_edges.empty()) splitEdges();
for(auto c : edges){
Vec2Df v1_position = c->start->position - ((c->start->position - c->end->position)).norm() * (c->start->radius + c->connector_offset);
Vec2Df v2_position = c->end->position - ((c->end->position - c->start->position)).norm() * (c->end->radius + c->connector_offset);
float v1_v2_distance = v1_position.dist(&v2_position);
float midi_gate_v1 = Clamp((4.f / 1000.f) * c->start->GGate(), 1.f, 4.f);
float midi_gate_v2 = Clamp((4.f / 1000.f) * c->end->GGate(), 1.f, 4.f);
// Line with small connector circles
Color col = {229, 181, 103, 255};
if(c->GActive()) col = {108, 153, 187, 255};
DrawLine(v1_position.x, v1_position.y, v2_position.x, v2_position.y, col);
{
float d = c->start->position.dist(c->end->position);
std::string frac = MIDI::Fractions.at(std::clamp<int>(round(d/60) - 1, 0, 31));
Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",d).c_str(), 10.0f, 2.0f);
Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f);
Vec2Df middle_handle = c->start->position + (c->end->position - c->start->position) * ((d - text_length.x)*0.5f)/d;
Vec2Df middle_handle_frac = c->start->position + (c->end->position - c->start->position) * ((d - text_length_frac.x)*0.5f)/d;
Vec2Df text_position = middle_handle + (c->start->position - c->end->position).norm().orth() * 12.0f;
Vec2Df text_position_frac = middle_handle_frac - (c->start->position - c->end->position).norm().orth() * 4.0f;
float rotation = Vector2Angle((Vector2){1,0}, (c->end->position - c->start->position)) * RAD2DEG;
DrawTextPro(GetFontDefault(), std::format("{:.1f}",d).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK);
if( (int)d % 60 == 0) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK);
}
if(midi_gate_v1 == 4.0f) col = VS_COLOR_RED;
DrawCircle(v1_position.x, v1_position.y, midi_gate_v1, col);
col = {229, 181, 103, 255};
if(midi_gate_v2 == 4.0f) col = VS_COLOR_RED;
DrawCircle(v2_position.x, v2_position.y, midi_gate_v2, col);
if(v1_v2_distance < 100) c->switches_offset = v1_v2_distance/2.3f;
Vec2Df from_to_handle = c->start->position - ((c->start->position - c->end->position)).norm() * (c->start->radius + c->switches_offset);
Vec2Df to_from_handle = c->end->position - ((c->end->position - c->start->position)).norm() * (c->end->radius + c->switches_offset);
Vec2Df middle_handle = c->end->position - ((c->end->position - c->start->position))*0.4f;
Vec2Df middle_handle_2 = c->end->position - ((c->end->position - c->start->position))*0.6f;
Vec2Df flip_handle = middle_handle + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*0.2, 0.f, 20.f) : 1.0f);
Vec2Df flip_handle_2 = middle_handle_2 + (v1_position - v2_position).norm().orth() * (v1_v2_distance < 120.0f ? Clamp((120.0f - v1_v2_distance)*-0.2, -20.f, 0.f) : 1.0f);
// Dynamic opacity by distance to mouse
unsigned char opacity = 150.0f - Clamp(middle_handle.dist(mouse_pos), 0.0f, 150.0f);
unsigned char opacity_from = 250.0f - Clamp(from_to_handle.dist(mouse_pos), 0.0f, 220.0f);
unsigned char opacity_to = 250.0f - Clamp(to_from_handle.dist(mouse_pos), 0.0f, 220.0f);
if(!c->start->dragging && !c->end->dragging){
// On/Off switches on both sides
if(ViButton(mouse_pos, from_to_handle.x, from_to_handle.y, 8.0f, c->pass_forward ? (Color){120, 210, 115, opacity_from} : (Color){232, 50, 32, opacity_from} )) c->pass_forward = !c->pass_forward;
if(ViButton(mouse_pos, to_from_handle.x, to_from_handle.y, 8.0f, c->pass_backward ? (Color){120, 210, 115, opacity_to} : (Color){232, 50, 32, opacity_to} )) c->pass_backward = !c->pass_backward;
if(!c->pass_forward){
Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + from_to_handle;
Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + from_to_handle;
DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_from});
}
if(!c->pass_backward){
Vec2Df block_point1 = (v1_position - v2_position).norm().orth()*4.0f + to_from_handle;
Vec2Df block_point2 = (v1_position - v2_position).norm().orth()*-4.0f + to_from_handle;
DrawLineEx(block_point1, block_point2, 2.0f, (Color){232,232,232,opacity_to});
}
if(ViButton(mouse_pos, flip_handle.x, flip_handle.y, 8.0f, (Color){232, 50, 32, opacity} )) removeEdge(c);
if(ViButton(mouse_pos, flip_handle_2.x, flip_handle_2.y, 8.0f, (Color){232, 125, 62, opacity} )) splitEdge(c);
}
}
}
void drawNodes(Vec2Df mouse_pos, Camera2D camera){
float min_d = 480;
for(auto n : nodes){
if(n->dragging){
auto neighbors_local = nodes | RANGE_FILTER([mouse_pos](SPTR_Node n){ return !n->dragging && !n->selected && n->position.dist(mouse_pos) < 960;}) | std::ranges::to<VEC_SPTR_Node>();
std::sort(neighbors_local.begin(), neighbors_local.end(), [n, mouse_pos](SPTR_Node na, SPTR_Node nb){ return na->position.dist(mouse_pos) < nb->position.dist(mouse_pos);});
if(!neighbors_local.empty()){
min_d = neighbors_local.front()->position.dist(mouse_pos);
DrawCircleLinesV(neighbors_local.front()->position, 22.0f, VS_COLOR_PINK);
n->neighbors = neighbors_local | std::ranges::views::filter([mouse_pos, min_d](SPTR_Node n){return abs(n->position.dist(mouse_pos) - min_d) < 5;}) | std::ranges::to<VEC_SPTR_Node>();
for(auto nb : n->neighbors){
DrawCircleLinesV(nb->position, 20.0f, VS_COLOR_RED);
float d = nb->position.dist(mouse_pos);
float dn = nb->position.dist(n->position);
if(d > 0 && d < min_d*1.5 && d < 960) {
std::string frac = MIDI::Fractions.at(std::clamp<int>(round(dn/60) - 1, 0, 31));
Vector2 text_length = MeasureTextEx(GetFontDefault(), std::format("{:.1f}",dn).c_str(), 10.0f, 2.0f);
Vector2 text_length_frac = MeasureTextEx(GetFontDefault(), frac.c_str(), 10.0f, 2.0f);
Vec2Df middle_handle = nb->position + (n->position - nb->position) * ((dn - text_length.x)*0.5f)/dn;
Vec2Df middle_handle_frac = nb->position + (n->position - nb->position) * ((dn - text_length_frac.x)*0.5f)/dn;
Vec2Df text_position = middle_handle + (nb->position - n->position).norm().orth() * 12.0f;
Vec2Df text_position_frac = middle_handle_frac - (nb->position - n->position).norm().orth() * 4.0f;
float rotation = Vector2Angle((Vector2){1,0}, (n->position - nb->position)) * RAD2DEG;
DrawTextPro(GetFontDefault(), std::format("{:.1f}",dn).c_str(), text_position, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK);
if(IsKeyDown(KEY_LEFT_SHIFT)) DrawTextPro(GetFontDefault(), frac.c_str(), text_position_frac, (Vector2){0,0}, rotation, 10.0f, 2.0f, VS_COLOR_PINK);
Vec2Df n_position = n->position - ((n->position - nb->position)).norm() * 20.0f;
Vec2Df nb_position = nb->position - ((nb->position - n->position)).norm() * 20.0f;
DrawLineV(n_position, nb_position, VS_COLOR_PINK);
}
}
}
}
n->draw(mouse_pos, camera);
}
}
};
inline void to_json(json& j, const GraphManager& G){
j = json{{"nodes", G.nodes}, {"edges", G.edges}};
}
inline void from_json(const json& j, GraphManager& G){
j.at("nodes").template get_to<VEC_SPTR_Node>(G.nodes); // = json{{"nodes", G.nodes}, {"edges", G.edges}};
}
}
+336 -113
View File
@@ -1,38 +1,40 @@
/*
*
* 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*/
#include "graph-manager.h"
#include "node.h"
#include "vmath.h"
#include <filesystem>
#include <fstream>
#define RAYGUI_IMPLEMENTATION
#include "datatypes.h"
#include <ranges>
#include <raylib.h>
#include <raymath.h>
#include "state-manager.h"
#include "datatypes.h"
#include "midi-node.h"
#include "nlohmann/json.hpp"
#include <cstdlib>
#include <stdio.h>
#include <cmath>
#include <ranges>
#include <algorithm>
#include <iterator>
#include <memory>
#include "state-manager.h"
using json = nlohmann::json;
AppState app;
VISEQ::APP::AppState app;
Vec2D<float> mouse_pos;
Vec2D<float> drag_start;
Vec2Df mouse_pos;
Vec2Df drag_start;
Camera2D camera = { {0} };
@@ -44,8 +46,7 @@ int main(){
srand(time(NULL));
InitWindow(1920, 1080, "VISEQ");
InitWindow(1920, 1080, "VISEQ - The Visual Sequencer");
int current_monitor = GetCurrentMonitor();
int monitor_width = GetMonitorWidth(current_monitor);
@@ -62,9 +63,9 @@ int main(){
app.settings.fps = 60.0f;
app.settings.bpm = 120.0f;
app.settings.grid = 120.0f; // 120px for a 16th note
app.settings.grid = 60.0f; // 120px for a 16th note
app.settings.speed = (app.settings.grid * 8.0f) * (app.settings.bpm / 120.f);
app.settings.speed = (app.settings.grid * 16.0f) * (app.settings.bpm / 120.f);
app.settings.width = monitor_width - 300;
app.settings.height = monitor_height - 300;
@@ -76,26 +77,94 @@ int main(){
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);
if(std::filesystem::exists("state.json")){
std::ifstream f("state.json");
json graph = json::parse(f);
app.settings = graph.at("settings");
int highest_id = 0;
std::shared_ptr<Node> hid;
for(auto n : graph.at("graphmanager").at("nodes")){
std::shared_ptr<MidiNode> mn = n;
if(mn->id > highest_id) {
highest_id = mn->id;
hid = mn;
}
}
for(auto e : graph.at("graphmanager").at("edges")){
if(e.at("id") > highest_id){
highest_id = e.at("id");
}
}
for(auto n : graph.at("graphmanager").at("nodes")){
std::shared_ptr<MidiNode> mn = n;
app.gm.nodes.push_back(mn);
std::cout << std::format("adding node {}", mn->id) << std::endl;
}
for(auto e : graph.at("graphmanager").at("edges")){
int start_id = e.at("start").get<int>();
int end_id = e.at("end").get<int>();
VEC_SPTR_Node start = app.gm.nodes | RANGE_FILTER([start_id](SPTR_Node n){return n->id == start_id;}) | std::ranges::to<VEC_SPTR_Node>();
VEC_SPTR_Node end = app.gm.nodes | RANGE_FILTER([end_id](SPTR_Node n){return n->id == end_id;}) | std::ranges::to<VEC_SPTR_Node>();
if(!start.empty() && !end.empty()) {
std::cout << std::format("connection {} to {}", start_id, end_id) << std::endl;
app.gm.addConnection(start.front(), end.front());
}
}
app.gm.edges.back()->newId(highest_id); // set static id counter in base class to highest value from json file
for(auto s : graph.at("signalmanager").at("signals")){
int start_id = s.at("start").get<int>();
int next_id = s.at("next").get<int>();
int last_id = s.at("last").get<int>();
std::shared_ptr<VISEQ::SIGNAL::Signal> ms = s;
VEC_SPTR_Node start = app.gm.nodes | RANGE_FILTER([start_id](SPTR_Node n){return n->id == start_id;}) | std::ranges::to<VEC_SPTR_Node>();
VEC_SPTR_Node last = app.gm.nodes | RANGE_FILTER([last_id](SPTR_Node n){return n->id == last_id;}) | std::ranges::to<VEC_SPTR_Node>();
VEC_SPTR_Node next = app.gm.nodes | RANGE_FILTER([next_id](SPTR_Node n){return n->id == next_id;}) | std::ranges::to<VEC_SPTR_Node>();
ms->start = start.front();
ms->next = next.front();
ms->last = last.front();
app.sm.signals.push_back(ms);
std::cout << std::format("adding signal with start node {}", start_id) << std::endl;
}
} else {
for(int i = 0; i < 4; ++i){
app.gm.addMidiNode(Vec2Df{(float)i * app.settings.grid*4 + 7 * app.settings.grid, 2 * app.settings.grid*4});
}
for(int i = 3; i >= 0; --i){
app.gm.addMidiNode(Vec2Df{(float)i * app.settings.grid*4 + 7 * app.settings.grid, 3 * app.settings.grid*4});
}
for(int i = 0; i < 8; ++i){
app.gm.addConnection(app.gm.nodes[i], app.gm.nodes[(i+1) % 8]);
}
app.sm.addSignalAtNode(app.gm.nodes.front());
}
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};
enum InteractionState {HOVER_CANVAS, HOVER_NODE, HOVER_CONNECTOR, DRAG_NODE, CONNECT_NODE, DRAW_SELECTION, SCALE_SELECTION, DELETE_SELECTED_NODES, COPY_SELECTED_NODES, PASTE_SELECTED_NODES};
InteractionState istate = HOVER_CANVAS;
Vec2D<float> select_start;
Vec2Df select_start;
Rectangle selection;
Vec2Df scale_start;
Vec2Df scale_rectangle;
VEC_SPTR_Node copied_nodes;
while(!WindowShouldClose()){
@@ -103,11 +172,11 @@ int main(){
ClearBackground((Color){46,46,46});
BeginMode2D(camera);
app.settings.speed = (app.settings.grid * 8.0f) * (app.settings.bpm / 120.f);
app.settings.speed = (app.settings.grid * 16.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};
Vec2Df mouse_pos = {(float)mp.x, (float)mp.y};
Vec2Df cam_pos = {camera.offset.x, camera.offset.y};
bool mouse_down = IsMouseButtonDown(MOUSE_BUTTON_LEFT);
bool mouse_clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT);
@@ -117,22 +186,26 @@ int main(){
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);
Vec2Df mouse_delta = {GetMouseDelta().x, GetMouseDelta().y};
Vec2Df delta = mouse_delta * (-1.0f/camera.zoom);
auto dragging_nodes = app.gm.nodes | RANGE_FILTER([](SPTR_Node n) { return n->dragging;});
auto connecting_nodes = app.gm.nodes | RANGE_FILTER([](SPTR_Node n) { return n->connecting;});
auto selected_nodes = app.gm.nodes | RANGE_FILTER([](SPTR_Node n) { return n->selected && !n->dragging;}) | std::ranges::to<VEC_SPTR_Node>();
auto hovering_nodes = app.gm.nodes | RANGE_FILTER([&mouse_pos](SPTR_Node n) { return CheckCollisionPointCircle(mouse_pos, n->position, n->radius);});
auto hovering_connectors = app.gm.nodes | RANGE_FILTER([&mouse_pos](SPTR_Node n) { return CheckCollisionPointCircle(mouse_pos, (n->position - ((n->position - mouse_pos)).norm() * (n->radius + 14.0f)), 8.0f);});
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);});
for(auto &n : app.gm.nodes ){
if(hovering_nodes.empty()) n->hovering = false;
if(hovering_connectors.empty()) n->hover_connect = false;
}
if(!mouse_down && !mouse_right_down && dragging_nodes.empty() && connecting_nodes.empty() && hovering_nodes.empty() && hovering_connectors.empty()) istate = InteractionState::HOVER_CANVAS;
if((mouse_clicked || mouse_down || mouse_released) && !mouse_right_down && dragging_nodes.empty() && connecting_nodes.empty() && hovering_nodes.empty() && hovering_connectors.empty()) istate = InteractionState::DRAW_SELECTION;
if((mouse_clicked || mouse_down || mouse_released) && !mouse_right_down && dragging_nodes.empty() && connecting_nodes.empty() && hovering_nodes.empty() && hovering_connectors.empty() && istate != InteractionState::SCALE_SELECTION) istate = InteractionState::DRAW_SELECTION;
if(!hovering_nodes.empty() && !mouse_down && !mouse_right_down) istate = InteractionState::HOVER_NODE;
if(!dragging_nodes.empty()) istate = InteractionState::DRAG_NODE;
if(!hovering_connectors.empty() && istate == InteractionState::HOVER_CANVAS) istate = InteractionState::HOVER_CONNECTOR;
if(!hovering_connectors.empty() && !mouse_right_down && istate == InteractionState::HOVER_CANVAS) istate = InteractionState::HOVER_CONNECTOR;
if(!connecting_nodes.empty()) istate = InteractionState::CONNECT_NODE;
if(!selected_nodes.empty() && IsKeyPressed(KEY_DELETE)) istate = InteractionState::DELETE_SELECTED_NODES;
@@ -140,11 +213,63 @@ int main(){
if(!copied_nodes.empty() && IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V)) istate = InteractionState::PASTE_SELECTED_NODES;
if(IsKeyPressed(KEY_TAB)) {
app.settings.mode = (app.settings.mode + 1) % 3;
app.gm.setNodeMode(app.settings.mode);
}
if(!selected_nodes.empty()){
float offset = 30;
Vec2Df corner_tl = {50000, 50000};
Vec2Df corner_br = {-50000, -50000};
std::shared_ptr<Node> node_tl;
std::shared_ptr<Node> node_br;
for(auto n : selected_nodes){
float x = n->position.x;
float y = n->position.y;
if(x < corner_tl.x && y < corner_tl.y) node_tl = n;
if(x > corner_br.x && y > corner_br.y) node_br = n;
if(x <= corner_tl.x) corner_tl.x = x;
if(x >= corner_br.x) corner_br.x = x;
if(y <= corner_tl.y) corner_tl.y = y;
if(y >= corner_br.y) corner_br.y = y;
}
corner_tl -= Vec2Df(offset, offset);
corner_br += Vec2Df(offset, offset);
DrawRectangleLines(corner_tl.x, corner_tl.y, (corner_br.x - corner_tl.x), (corner_br.y - corner_tl.y), VS_COLOR_LIGHTGREY);
DrawCircleV(corner_br, 5, YELLOW);
if(istate == InteractionState::SCALE_SELECTION && mouse_released){
istate = InteractionState::HOVER_CANVAS;
}
if(mouse_clicked && corner_br.dist(mouse_pos) < 10){
scale_start = (corner_br - corner_tl);
for(auto n : selected_nodes){
n->drag_offset = n->position - node_tl->position;
}
istate = InteractionState::SCALE_SELECTION;
}
if(istate == InteractionState::SCALE_SELECTION && mouse_down){
Vec2Df scale = (mouse_pos - corner_tl) / scale_start;
if(IsKeyDown(KEY_LEFT_SHIFT)) scale.y = scale.x;
DrawText(std::format("{:.2f} {:.2f}", scale.x, scale.y).c_str(), corner_br.x+5, corner_br.y+5, 10, RED);
for(auto n : selected_nodes){
n->position = node_tl->position + (n->drag_offset * scale);
}
istate = InteractionState::SCALE_SELECTION;
}
}
switch(istate){
case InteractionState::SCALE_SELECTION:
case InteractionState::HOVER_CANVAS:
{
for(auto &n : app.nm.nodes | std::ranges::views::values){
n.hover_connect = false;
for(auto &n : app.gm.nodes){
n->hover_connect = false;
}
if(mouse_right_down) {
camera.target = Vector2Add(camera.target, delta);
@@ -157,29 +282,32 @@ int main(){
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};
Vec2Df select1 = {mouse_pos.x > select_start.x ? select_start.x : mouse_pos.x, mouse_pos.y > select_start.y ? select_start.y : mouse_pos.y};
Vec2Df select2 = {mouse_pos.x > select_start.x ? mouse_pos.x - select_start.x : select_start.x - mouse_pos.x, mouse_pos.y > select_start.y ? mouse_pos.y - select_start.y : select_start.y - mouse_pos.y};
DrawRectangleLines(select1.x, select1.y, select2.x, select2.y, (Color){150,150,150,150});
selection = (Rectangle){select1.x, select1.y, select2.x, select2.y};
for(auto& n : app.nm.nodes | std::views::values){
if(CheckCollisionPointRec(n.position, selection)) {
n.in_rect = true;
for(auto& n : app.gm.nodes){
if(CheckCollisionPointRec(n->position, selection)) {
n->in_rect = true;
} else {
n.in_rect = false;
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;
int n_selected = 0;
for(auto& n : app.gm.nodes){
if(CheckCollisionPointRec(n->position, selection)) {
n->selected = true;
n->in_rect = false;
n_selected++;
} else {
if(!IsKeyDown(KEY_LEFT_SHIFT)) n.selected = false;
if(!IsKeyDown(KEY_LEFT_SHIFT)) n->selected = false;
}
}
if(n_selected > 0) copied_nodes.clear();
selection = (Rectangle){0,0,0,0};
}
break;
@@ -188,41 +316,69 @@ int main(){
{
if(mouse_clicked) {
if(IsKeyDown(KEY_LEFT_SHIFT)) {
hovering_nodes.front().selected = true;
hovering_nodes.front()->selected = true;
} else {
hovering_nodes.front().dragging = true;
hovering_nodes.front().drag_offset = hovering_nodes.front().position - mouse_pos;
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;
if(mouse_right_clicked) hovering_nodes.front()->config = true;
break;
}
case InteractionState::DRAG_NODE:
{
if(!dragging_nodes.empty()){
SPTR_Node dragNode = dragging_nodes.front();
for(auto &n : selected_nodes){
if(!n.picked_up) n.drag_offset = n.position - dragging_nodes.front().position;
n.picked_up = true;
if(!n->picked_up) n->drag_offset = n->position - dragNode->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};
dragNode->position.x = round((mouse_pos.x + dragNode->drag_offset.x) / app.settings.grid) * app.settings.grid;
dragNode->position.y = round((mouse_pos.y + dragNode->drag_offset.y) / app.settings.grid) * app.settings.grid;
} else if(IsKeyDown(KEY_LEFT_SHIFT)){
float steps = round((dragNode->neighbors.front()->position.dist(mouse_pos)) / app.settings.grid) * app.settings.grid;
if(dragNode->neighbors.size() > 1){
Vec2Df dn1 = dragNode->neighbors[0]->position;
Vec2Df dn2 = dragNode->neighbors[1]->position;
Vec2Df v1_orth = dn1.center(dn2);
if(dn1.x < dn2.x) std::swap(dn1,dn2);
float dist = dn1.dist(dn2);
float dist_orth = std::sqrt(std::pow(steps,2) - std::pow(dist/2,2));
if(mouse_pos.y < v1_orth.y) dist_orth *= -1;
Vec2Df v1_orth_t = v1_orth + (dn1-dn2).norm().orth() * dist_orth;
DrawLineV(dn1, dn2, VS_COLOR_RED);
DrawLineV(v1_orth, v1_orth_t, VS_COLOR_RED);
DrawCircleV(v1_orth, 3.0f, VS_COLOR_RED);
dragNode->position = v1_orth_t;
} else if(!dragNode->neighbors.empty()){
float pol_r = steps;
float pol_theta = Vector2Angle(Vec2Df{1,0},(mouse_pos - dragNode->neighbors.front()->position));
Vec2Df polar = VISEQ::VMATH::polarToCartesian(pol_r, pol_theta);
dragNode->position = dragNode->neighbors.front()->position + polar;
}
} else {
dragging_nodes.front().position = mouse_pos + dragging_nodes.front().drag_offset;
dragNode->position = mouse_pos + dragNode->drag_offset;
}
for(auto &n : selected_nodes){
n.position = dragging_nodes.front().position + n.drag_offset;
n->position = dragNode->position + n->drag_offset;
}
if(mouse_released) {
for(auto &n : dragging_nodes){
n.dragging = false;
n.picked_up = false;
n->dragging = false;
n->picked_up = false;
}
for(auto &n : selected_nodes){
n.picked_up = false;
n.dragging = false;
n->picked_up = false;
n->dragging = false;
}
}
}
@@ -230,79 +386,141 @@ int main(){
}
case InteractionState::HOVER_CONNECTOR:
{
hovering_connectors.front().hover_connect = true;
if(mouse_clicked) hovering_connectors.front().connecting = true;
if(!hovering_connectors.empty()){
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);
Vec2Df connector_position = connecting_nodes.front()->position - ((connecting_nodes.front()->position - mouse_pos)).norm() * (connecting_nodes.front()->radius + 14.0f);
Vec2Df target_position = mouse_pos;
if(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(IsKeyDown(KEY_LEFT_CONTROL)) {
target_position.x = round((mouse_pos.x) / app.settings.grid) * app.settings.grid;
target_position.y = round((mouse_pos.y) / app.settings.grid) * app.settings.grid;
} else if(IsKeyDown(KEY_LEFT_SHIFT)){
std::shared_ptr<Node> closest = app.gm.getClosestNode(mouse_pos);
float steps = round((closest->position.dist(mouse_pos)) / app.settings.grid) * app.settings.grid;
float pol_r = steps;
float pol_theta = Vector2Angle(Vec2Df{1,0},(mouse_pos - closest->position));
Vec2Df polar = VISEQ::VMATH::polarToCartesian(pol_r, pol_theta);
target_position = closest->position + polar;
} else {
target_position = mouse_pos;
}
if(connecting_nodes.front()->connecting && mouse_down){
DrawLine(connector_position.x, connector_position.y, target_position.x, target_position.y, (Color){229, 181, 103, 255});
connecting_nodes.front()->hovering = true;
connecting_nodes.front()->hover_connect = true;
}
if(!hovering_connectors.empty()) {
hovering_connectors.front()->hovering = true;
hovering_connectors.front()->hover_connect = true;
}
if(!hovering_nodes.empty()) hovering_nodes.front()->hovering = true;
if(mouse_released) {
if(&hovering_nodes.front() != &connecting_nodes.front() && &connecting_nodes.front() != &hovering_connectors.front()){
if(!hovering_nodes.empty()){
app.cm.addConnection(connecting_nodes.front().id, hovering_nodes.front().id);
app.gm.addConnection(connecting_nodes.front(), hovering_nodes.front());
} else if(!hovering_connectors.empty()){
app.cm.addConnection(connecting_nodes.front().id, hovering_connectors.front().id);
app.gm.addConnection(connecting_nodes.front(), hovering_connectors.front());
} else {
int id = app.nm.addNode(mouse_pos.x, mouse_pos.y);
app.cm.addConnection(connecting_nodes.front().id, id);
SPTR_Node new_node = app.gm.copyNode(std::dynamic_pointer_cast<MidiNode>(connecting_nodes.front()), target_position);
new_node->connecting = false;
app.gm.addConnection(connecting_nodes.front(), new_node);
}
}
connecting_nodes.front().connecting = false;
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();
app.removeNodes(selected_nodes | std::ranges::to<VEC_SPTR_Node>());
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;
}
std::ranges::copy(selected_nodes.begin(), selected_nodes.end(), std::back_inserter(copied_nodes));
std::ranges::for_each(selected_nodes.begin(), selected_nodes.end(), [](SPTR_Node n){n->selected = false;});
selected_nodes.clear();
break;
}
case InteractionState::PASTE_SELECTED_NODES:
{
for(auto &n: copied_nodes){
n.drag_offset = (n.position - copied_nodes.front().position);
for(auto n: copied_nodes){
n->drag_offset = (n->position - copied_nodes.front()->position);
}
// app.nm.copyNodes(mouse_pos);
app.copyNodesAndConnections(mouse_pos);
app.copyNodesAndConnections(copied_nodes, mouse_pos);
//copied_nodes.clear();
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 {
if(!hovering_nodes.empty() && hovering_nodes.front()->getNodeType() == NodeType::MIDINODE) {
VEC_SPTR_Node modify_nodes;
std::copy_if(selected_nodes.begin(), selected_nodes.end(), std::back_inserter(modify_nodes), [](SPTR_Node n){ return n->getNodeType() == NodeType::MIDINODE;});
if(selected_nodes.empty()) std::copy_if(hovering_nodes.begin(), hovering_nodes.end(), std::back_inserter(modify_nodes), [modify_nodes](SPTR_Node n){ return n->getNodeType() == NodeType::MIDINODE && !std::ranges::contains(modify_nodes, n);});
for(auto n : modify_nodes){
std::shared_ptr<MidiNode> midiNode = std::dynamic_pointer_cast<MidiNode>(n);
if(IsKeyDown(KEY_LEFT_CONTROL) && !IsKeyDown(KEY_LEFT_ALT)){
switch(app.settings.mode){
case 0:{
midiNode->midi_note += wheel;
break;}
case 1:{
midiNode->midi_cc += wheel;
break;}
case 2:{
midiNode->midi_pitch_bend += wheel;
break;}
};
if(midiNode->midi_note < 0 ) midiNode->midi_note = VISEQ::MIDI::Notes.size()-1;
if(midiNode->midi_note >= VISEQ::MIDI::Notes.size() ) midiNode->midi_note = 0;
if(midiNode->midi_cc < 0 ) midiNode->midi_cc = 127;
if(midiNode->midi_cc > 127 ) midiNode->midi_cc = 0;
if(midiNode->midi_pitch_bend < 0 ) midiNode->midi_pitch_bend = 0;
if(midiNode->midi_pitch_bend > 16383 ) midiNode->midi_pitch_bend = 16383;
} else if(IsKeyDown(KEY_LEFT_SHIFT) && !IsKeyDown(KEY_LEFT_ALT)) {
midiNode->midi_velocity += wheel;
if(midiNode->midi_velocity < 0 ) midiNode->midi_velocity = 127;
if(midiNode->midi_velocity > 127 ) midiNode->midi_velocity = 0;
} else if(IsKeyDown(KEY_LEFT_ALT)) {
switch(app.settings.mode){
case 0:{
float incr = (float)wheel * (IsKeyDown(KEY_LEFT_SHIFT) ? 100.0f : IsKeyDown(KEY_LEFT_CONTROL) ? 1000.0f : 10.0f);
midiNode->midi_gate += incr;
if(midiNode->midi_gate < 0 ) midiNode->midi_gate = 0;
break;}
case 1:{
midiNode->midi_cc_value += wheel;
if(midiNode->midi_cc_value < 0 ) midiNode->midi_cc_value = 0;
if(midiNode->midi_cc_value > 127 ) midiNode->midi_cc_value = 127;
break;}
case 2:{
float incr = (float)wheel * (IsKeyDown(KEY_LEFT_SHIFT) ? 100.0f : IsKeyDown(KEY_LEFT_CONTROL) ? 1000.0f : 10.0f);
midiNode->midi_pitch_bend += incr;
if(midiNode->midi_pitch_bend < 0 ) midiNode->midi_pitch_bend = 0;
if(midiNode->midi_pitch_bend > 16383 ) midiNode->midi_pitch_bend = 16383;
break;}
};
}
}
}
if(!IsKeyDown(KEY_LEFT_SHIFT) && !IsKeyDown(KEY_LEFT_CONTROL) && !IsKeyDown(KEY_LEFT_ALT)){
Vector2 mouseWorldPos = GetScreenToWorld2D(GetMousePosition(), camera);
camera.offset = GetMousePosition();
camera.target = mouseWorldPos;
@@ -318,7 +536,7 @@ int main(){
}
}
app.draw(&mouse_pos, &camera);
app.draw(mouse_pos, camera);
EndMode2D();
@@ -327,6 +545,11 @@ int main(){
EndDrawing();
}
std::ofstream file("state.json");
file << json(app);
std::cout << std::setw(4) << json(app) << std::endl;
CloseWindow();
return 0;
+172
View File
@@ -0,0 +1,172 @@
#pragma once
#include "datatypes.h"
#include "node.h"
#include "raymath.h"
#include <map>
#define RADIUS 14.0f
namespace VISEQ::GRAPH::NODES {
class MidiNode : public Node{
public:
struct NodeMidiOut{
int i;
std::string name;
bool enabled;
};
MidiNode() {}
MidiNode(Vec2Df position) : Node(position) {}
NodeType nodeType = NodeType::MIDINODE;
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;
bool triggered = false;
int mode = 0;
int midi_note_channel = 1;
int midi_note = 48;
int midi_velocity = 60;
int midi_gate = 10; // gate time in milliseconds
int midi_cc_channel = 0;
int midi_cc = 0;
int midi_cc_value = 0;
int midi_pc_channel = 0;
int midi_pc = 0;
int midi_pc_value = 0;
int midi_pitch_bend_channel = 0;
int midi_pitch_bend = 8192;
float start = 0;
float fade = 0;
float time = 0;
float last = 0;
virtual NodeType getNodeType() const override {return nodeType;}
void setMidiPort(int midi_port, bool state){
midi_outs[midi_port].enabled = state;
}
void trigger() override {
triggered = true;
start = GetTime();
fade = midi_gate;
}
void SMode(int _mode) override{
mode = _mode;
}
int Ggate(){
return midi_gate;
}
void draw(Vec2Df mouse, Camera2D cam) override {
last = time;
time = GetTime();
if(time - start >= (float)midi_gate / 1000.0f){
triggered = false;
fade = 0;
} else {
fade -= (time - last)*1000.0f;
}
DrawRing(position, radius, radius+4, 0, 360, 24, (Color){60, 60, 60, 255});
DrawRing(position, radius, radius+4, 0.0f - 90.0f, (360.0f/127.0f) * midi_velocity - 90.0f, 24, (Color){158, 134, 200, 255});
DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255});
float midi_gate_map = round((255.f / midi_gate) * fade);
Color gate_color = VS_COLOR_GREEN;
switch(mode){
case 0:{
gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){180, 210, 115, (unsigned char)Clamp(255 - midi_gate_map, 0, 255)}, WHITE);
break;}
case 1:{
gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){229, 181, 103, (unsigned char)Clamp(255 - midi_gate_map, 0, 255)}, WHITE);
break;}
case 2:{
gate_color = ColorAlphaBlend(VS_COLOR_RED, (Color){232, 125, 62, (unsigned char)Clamp(255 - midi_gate_map, 0, 255)}, WHITE);
break;}
}
if(!dragging && !selected && !in_rect && !hovering) DrawCircle(position.x, position.y, radius, gate_color);
if(selected || dragging || in_rect || hovering) DrawCircle(position.x, position.y, radius, (Color){108, 153, 187, 255});
if(distributionMode == Distribution::RANDOM) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_PINK);
if(distributionMode == Distribution::ROUNDROBIN) DrawCircle(position.x, position.y, radius/2.0f, VS_COLOR_YELLOW);
if(selected) DrawCircleLinesV(position, radius+8, (Color){158, 255, 210, 255});
if(IsKeyDown(KEY_LEFT_ALT)){
float value = 0;
int offset_gate = 0;
switch(mode){
case 0:
value = (float)midi_gate/1000.0f;
offset_gate = MeasureText(std::format("{:.2f}", value).c_str(), 12);
DrawText(std::format("{:.2f}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, WHITE );
break;
case 1:
value = (float)midi_cc_value;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, WHITE );
break;
case 2:
value = (float)midi_pitch_bend;
offset_gate = MeasureText(std::format("{}", value).c_str(), 12);
DrawText(std::format("{}",value).c_str(), position.x-offset_gate/2.0f, position.y-5, 12, WHITE );
break;
}
} else {
switch(mode){
case 0:{
int offset = MeasureText(MIDI::Notes[(int)midi_note].c_str(), 12);
DrawText(MIDI::Notes[midi_note].c_str(), position.x-offset/2.0f, position.y-5, 12, WHITE );
break;}
case 1:{
int offset = MeasureText(std::format("{}", midi_cc).c_str(), 12);
DrawText(std::format("{}", midi_cc).c_str(), position.x-offset/2.0f, position.y-5, 12, WHITE );
break;}
case 2:{
int offset = MeasureText(std::format("{}", midi_pitch_bend).c_str(), 12);
DrawText(std::format("{}", midi_pitch_bend).c_str(), position.x-offset/2.0f, position.y-5, 12, WHITE );
break;}
}
}
Vec2Df connector = position - ((position - mouse)).norm() * (radius + RADIUS);
float opacity = 150.0f - Clamp(position.dist(mouse), 0.0f, 150.0f);
if(hover_connect){
DrawCircle(connector.x, connector.y, 8.0f, (Color){108, 153, 187, 255});
} else {
DrawCircle(connector.x, connector.y, 8.0f, (Color){108, 153, 187, (unsigned char)opacity});
}
if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200});
DrawText(std::format("{}", id).c_str(), position.x + 20.0f, position.y + 20.0f, 10, WHITE);
}
};
}
+85 -86
View File
@@ -10,104 +10,103 @@
#include <memory>
#include "datatypes.h"
struct NoteTimer{
int midi_out;
int channel;
int note;
int velocity;
float start;
int gate;
bool erase = false;
};
namespace VISEQ::MIDI {
class Midi {
struct NoteTimer{
int midi_out;
int channel;
int note;
int velocity;
float start;
int gate;
bool erase = false;
};
public:
Midi() {
enumerate();
}
class Midi {
std::vector<std::string> midiNotes = {
"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",
"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++;
public:
Midi() {
enumerate();
}
}
void open_midi_out(MidiOutPort *out_port){
if(!midi_outs.contains(out_port->number)){
auto out = std::make_shared<libremidi::midi_out>();
out->open_port(out_port->port);
midi_outs[out_port->number] = out;
}
}
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 close_midi_out(MidiOutPort *out_port){
if(midi_outs.contains(out_port->number)){
midi_outs[out_port->number]->close_port();
midi_outs.erase(out_port->number);
}
}
void note(int midi_out, int channel, int note, int velocity, int gate){
if(midi_outs.contains(midi_out)){
if(midi_outs[midi_out]->is_port_open()){
midi_outs[midi_out]->send_message(libremidi::channel_events::note_on(channel, note, velocity));
notes.emplace_back(midi_out, channel, note, velocity, GetTime(), gate);
void 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(){
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;
void open_midi_out(MidiOutPort *out_port){
if(!midi_outs.contains(out_port->number)){
auto out = std::make_shared<libremidi::midi_out>();
out->open_port(out_port->port);
midi_outs[out_port->number] = out;
}
}
std::erase_if(notes, [](const auto& item){return item.erase;});
for(auto& p : midi_out_ports){
if(p.enabled) {
open_midi_out(&p);
} else if(!p.enabled) {
close_midi_out(&p);
void close_midi_out(MidiOutPort *out_port){
if(midi_outs.contains(out_port->number)){
midi_outs[out_port->number]->close_port();
midi_outs.erase(out_port->number);
}
}
}
};
void note(int midi_out, int channel, int note, int velocity, int gate){
if(midi_outs.contains(midi_out)){
if(midi_outs[midi_out]->is_port_open()){
midi_outs[midi_out]->send_message(libremidi::channel_events::note_on(channel, note, velocity));
notes.emplace_back(midi_out, channel, note, velocity, GetTime(), gate);
}
}
}
void cc(int midi_out, int channel, int cc, int value){
if(midi_outs.contains(midi_out)){
if(midi_outs[midi_out]->is_port_open()){
midi_outs[midi_out]->send_message(libremidi::channel_events::control_change(channel, cc, value));
}
}
}
void pitch_bend(int midi_out, int channel, int pitch_value){
if(midi_outs.contains(midi_out)){
if(midi_outs[midi_out]->is_port_open()){
midi_outs[midi_out]->send_message(libremidi::channel_events::pitch_bend(channel, pitch_value));
}
}
}
void update(){
float time = GetTime();
for(auto &n : notes){
if(time - n.start >= (float)n.gate / 1000.0f){
if(midi_outs.contains(n.midi_out) && midi_outs[n.midi_out]->is_port_open()) midi_outs[n.midi_out]->send_message(libremidi::channel_events::note_off(n.channel, n.note, n.velocity));
n.erase = true;
}
}
std::erase_if(notes, [](const auto& item){return item.erase;});
for(auto& p : midi_out_ports){
if(p.enabled) {
open_midi_out(&p);
} else if(!p.enabled) {
close_midi_out(&p);
}
}
}
};
}
+33 -16
View File
@@ -1,25 +1,42 @@
#pragma once
#include "datatypes.h"
#include "raylib.h"
class Movable{
namespace VISEQ::BASE {
class Movable{
public:
Movable() {}
virtual ~Movable() {}
public:
Movable() {}
virtual ~Movable() {}
Movable(Vec2D<float> p, Vec2D<float> d, float s) : speed(s), position(p), direction(d) {}
Movable(Vec2D<float> p, float s) : speed(s), position(p) {}
Movable(Vec2Df p, Vec2Df d, float s) : speed(s), position(p), direction(d) {}
Movable(Vec2Df p, float s) : speed(s), position(p) {}
float speed = 1.0f;
Vec2D<float> position;
Vec2D<float> direction;
float speed = 960.0f;
Vec2Df position;
Vec2Df direction;
virtual void update(int fps) {};
virtual void update(double frametime) {};
virtual void update() {};
virtual void draw(Vec2D<float> *mouse, Camera2D *cam) {};
Vec2Df drag_offset;
bool in_rect = false;
bool picked_up = false;
bool dragging = false;
bool hover_connect = false;
bool hovering = false;
bool connecting = false;
bool selected = false;
bool highlighted = false;
bool copied = false;
bool erase = false;
};
virtual void setPosition(Vec2Df _position) {position = _position;}
virtual Vec2Df getPosition() {return position;};
virtual void update(double frametime) {};
virtual void update() {};
};
}
-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);
}
}
};
+28 -100
View File
@@ -1,117 +1,45 @@
#pragma once
#include <map>
#include <memory>
#include <raylib.h>
#include "datatypes.h"
#include "graph-item.h"
#include "moveable.h"
#include "raymath.h"
#define R 14.0f
namespace VISEQ::GRAPH::EDGES{
class Edge;
}
struct NodeMidiOut{
int i;
std::string name;
bool enabled;
};
namespace VISEQ::GRAPH::NODES {
class Node : public Movable{
class Node : public BASE::Movable, public GraphItem{
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) {}
public:
Node() {}
Node(float x, float y) : Movable(Vec2Df(x, y), 960.0f) {}
Node(Vec2Df p) : Movable(p, 960.0f) {}
NodeType nodeType;
float radius;
std::vector<std::shared_ptr<Node>> neighbors;
std::vector<std::shared_ptr<VISEQ::GRAPH::EDGES::Edge>> connections;
int id;
Distribution distributionMode = Distribution::GEOMETRIC;
bool picked_up = false;
bool dragging = false;
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;
float radius = 15.0f;
bool config = false;
int midi_out = -1;
virtual NodeType getNodeType() const {return nodeType;}
Vec2D<float> drag_offset;
virtual void SMode(int mode) {};
virtual int GGate() {return 0;};
virtual int GActive() {return 0;};
virtual void trigger() {};
void draw(Vec2Df mouse_pos, Camera2D cam) override {}
};
std::map<int, NodeMidiOut> midi_outs;
typedef std::shared_ptr<VISEQ::GRAPH::NODES::Node> SPTR_Node;
typedef std::vector<SPTR_Node> VEC_SPTR_Node;
bool gui_midi_channel_edit = false;
bool gui_midi_note_edit = false;
bool gui_midi_velocity_edit = false;
bool gui_midi_gate_edit = false;
int midi_channel = 1;
int midi_note = 48;
int midi_velocity = 60;
int midi_gate = 100;
int midi_cc = 0;
float start = 0;
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});
DrawRing(position, radius, radius+4, 0.0f - 90.0f, (360.0f/127.0f) * midi_velocity - 90.0f, 24, (Color){158, 134, 200, 255});
DrawCircleLinesV(position, radius + 4, (Color){100, 100, 100, 255});
float midi_gate_map = round((255.f / midi_gate) * fade);
Color gate_color = 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);
float opacity = 150.0f - Clamp(position.dist(mouse), 0.0f, 150.0f);
if(hover_connect){
DrawCircle(connector.x, connector.y, 8.0f, (Color){108, 153, 187, 255});
} else {
DrawCircle(connector.x, connector.y, 8.0f, (Color){108, 153, 187, (unsigned char)opacity});
}
if(highlighted) DrawCircleLinesV(position, radius+2, (Color){255, 0, 0, 200});
}
};
}
+104 -81
View File
@@ -1,114 +1,137 @@
#pragma once
#include "connection-manager.h"
#include "datatypes.h"
#include "node-manager.h"
#include "graph-manager.h"
#include "midi-node.h"
#include "signal.h"
#include "node.h"
#include "midi.h"
#include <chrono>
#include <locale>
#include <memory>
#include <thread>
class SignalManager{
using namespace VISEQ::GRAPH;
public:
SignalManager() {}
SignalManager(NodeManager *nm, ConnectionManager *cm, std::mutex *mutex) : mutex(mutex), cm(cm), nm(nm) {}
Midi midi;
std::mutex *mutex;
ConnectionManager *cm;
NodeManager *nm;
namespace VISEQ::SIGNAL {
std::vector<Signal> signals;
typedef std::shared_ptr<Signal> SPTR_Signal;
typedef std::vector<SPTR_Signal> VEC_SPTR_Signal;
bool loop = true;
bool play = false;
void pause(){
play = false;
}
class SignalManager{
void stop(){
play = false;
reset();
}
public:
SignalManager() {}
SignalManager(GraphManager *gm, std::mutex *mutex) : mutex(mutex), gm(gm) {}
VISEQ::MIDI::Midi midi;
std::mutex *mutex;
GraphManager *gm;
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;
VEC_SPTR_Signal signals;
VEC_SPTR_Signal erase_signals;
bool loop = true;
bool play = false;
void pause(){
play = false;
}
cm->reset();
}
void startSignalThread(){
std::thread t1(&SignalManager::updateSignals, this);
t1.detach();
}
void stop(){
play = false;
reset();
}
void stopSignalThread(){
loop = false;
}
void addSignalAtNode(int node_id){
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 start(){
play = true;
}
void reset(){
for(auto &s : signals){
s->position = s->start->position + Vec2Df{0.1, 0.1};
s->next = s->start;
s->last = s->start;
}
gm->reset();
}
void removeSignal(int n){
signals.erase(signals.begin() + n);
}
void startSignalThread(){
std::thread t1(&SignalManager::updateSignals, this);
t1.detach();
}
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 = (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();
void stopSignalThread(){
loop = false;
}
void addSignalAtNode(SPTR_Node node){
Color c = {(unsigned char)(20 + (rand() % 200)), 100, (unsigned char)(20 + (int)(rand() % 200)), 200};
signals.emplace_back(std::make_shared<Signal>(node->position + Vec2Df{0.5f, 0.5f}, 8.0f, 0, c)); // position + 1 or else it does not render. why? no idea!
signals.back()->next = node;
signals.back()->last = node;
signals.back()->start = node;
}
if(n != s.next && n != -1) {
for(auto &m : nm->nodes[s.next].midi_outs){
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 removeSignal(std::shared_ptr<Signal> s){
erase_signals.push_back(s);
}
void updateSignals(){
auto last = std::chrono::high_resolution_clock::now();
while(loop){
std::chrono::duration<double, std::micro> diff = std::chrono::high_resolution_clock::now() - last;
if((int)diff.count() >= 250){
mutex->lock();
if(play){
for(auto& s : signals){
s->direction = (s->next->position - s->position).norm();
s->update(diff.count());
if(s->position.dist(s->next->position) < 0.6f) {
SPTR_Node next_node = gm->next(s->next, s->last);
s->next->trigger();
if(next_node != s->next && next_node != SPTR_Node(nullptr) && s->next->getNodeType() == NodeType::MIDINODE) {
auto midinode = std::dynamic_pointer_cast<MidiNode>(s->next);
for(auto &m : midinode->midi_outs){
if(m.second.enabled) {
if(midinode->send_note) midi.note(m.second.i, midinode->midi_note_channel, (int)Clamp(midinode->midi_note + s->midi_note_offset, 0, 127), midinode->midi_velocity, midinode->midi_gate);
if(midinode->send_cc) midi.cc(m.second.i, midinode->midi_cc_channel, midinode->midi_cc, (int)Clamp(midinode->midi_cc_value + s->midi_cc_offset, 0, 127));
if(midinode->send_pitch_bend) midi.pitch_bend(m.second.i, midinode->midi_pitch_bend_channel, midinode->midi_pitch_bend);
}
}
s->last = s->next;
s->next = next_node;
}
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){
for(auto& s : signals){
void drawSignals(Vec2Df mouse_pos, Camera2D camera){
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();
}
}
};
};
inline void to_json(json& j, const SignalManager& G){
j = json{{"signals", G.signals}};
}
}
+36 -27
View File
@@ -1,41 +1,50 @@
#pragma once
#include <raylib.h>
#include "datatypes.h"
#include "moveable.h"
class Signal : public Movable{
public:
#include "node.h"
#include "edge.h"
Signal();
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;
using namespace VISEQ::GRAPH;
int next;
int last;
namespace VISEQ::SIGNAL {
class Signal : public BASE::Movable{
public:
int start;
Signal() {}
Signal(Vec2Df position, float r, float _speed, Color color) : Movable(position, Vec2Df(0,0), _speed), radius(r), color(color) {}
NODES::SPTR_Node next;
NODES::SPTR_Node last;
EDGES::SPTR_Edge edge;
bool at_target = false;
int target_node_id = 1;
int source_node_id = 0;
NODES::SPTR_Node start;
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){
// 120px / 16th = 1920px / whole note
// whole note = 2s at 120bpm
// 960px / s
position += direction.norm() * speed * multiplier * (float)t_delta / 1000000.0f;
}
Color color;
void draw(Vec2D<float> *mouse, Camera2D *cam) {
DrawCircle(position.x, position.y, radius, color);
}
};
bool ui_edit_speed = false;
int midi_note_offset = 0;
int midi_cc_offset = 0;
void update(double t_delta){
// 120px / 16th = 1920px / whole note
// whole note = 2s at 120bpm
// 960px / s
if(run) position += direction.norm() * speed * multiplier * (float)t_delta / 1000000.0f;
}
void draw(Vec2Df mouse, Camera2D cam) {
DrawCircle(position.x, position.y, radius, color);
}
};
}
+199
View File
@@ -0,0 +1,199 @@
#pragma once
#include "midi-node.h"
#include "graph-item.h"
#include "node.h"
#include "edge.h"
//#include "graph-manager.h"
#include "datatypes.h"
#include "signal.h"
#include <iostream>
#include <memory>
/*using namespace VISEQ::BASE;*/
/*using namespace VISEQ::SIGNAL;*/
/*using namespace VISEQ::GRAPH::NODES;*/
/*using namespace VISEQ::GRAPH::EDGES;*/
typedef std::shared_ptr<VISEQ::GRAPH::GraphItem> SPTR_GraphItem;
typedef std::vector<SPTR_GraphItem> VEC_SPTR_GraphItem;
namespace VISEQ::GRAPH::EDGES {
using namespace VISEQ::GRAPH::NODES;
inline void to_json(json& j, const VISEQ::GRAPH::EDGES::Edge& E){
j = json{{"id",E.id}, {"start", E.start->id}, {"end",E.end->id}, {"pass_forward",E.pass_forward}, {"pass_backward", E.pass_backward}};
}
inline void from_json(const json& j, Edge& E){
j.at("id").get_to(E.id);
j.at("pass_forward").get_to(E.pass_forward);
j.at("pass_backward").get_to(E.pass_backward);
}
inline void to_json(json& j, const VISEQ::GRAPH::EDGES::SPTR_Edge& E){
j = *E;
}
inline void from_json(const json& j, VISEQ::GRAPH::EDGES::SPTR_Edge& E){
E.reset(new Edge(j.get<Edge>()));
}
}
namespace VISEQ::GRAPH::NODES {
/* ------------------- MIDINODE ---------------------- */
inline void to_json(json& j, const MidiNode& N){
std::vector<int> conns;
std::ranges::transform(N.connections.begin(), N.connections.end(), std::back_inserter(conns), [](VISEQ::GRAPH::EDGES::SPTR_Edge e){ return e->id;});
j = json{
{"id", N.id},
{"position", N.position},
{"distributionMode", N.distributionMode},
{"nodeType", N.nodeType},
{"midi_note_channel",N.midi_note_channel},
{"midi_note",N.midi_note},
{"midi_velocity",N.midi_velocity},
{"midi_gate",N.midi_gate},
{"midi_cc_channel",N.midi_cc_channel},
{"midi_cc",N.midi_cc},
{"midi_cc_value",N.midi_cc_value},
{"midi_pc_channel",N.midi_pc_channel},
{"midi_pc_value",N.midi_pc_value},
{"midi_pitch_bend_channel",N.midi_pitch_bend_channel},
{"midi_pitch_bend",N.midi_pitch_bend},
{"connections",conns}};
}
inline void from_json(const json& j, MidiNode& N){
std::cout << "MidiNode" << std::endl;
j.at("id").get_to(N.id);
j.at("position").get_to<Vec2Df>(N.position);
j.at("distributionMode").get_to(N.distributionMode);
j.at("nodeType").get_to(N.nodeType);
j.at("midi_note_channel").get_to(N.midi_note_channel);
j.at("midi_note").get_to(N.midi_note);
j.at("midi_velocity").get_to(N.midi_velocity);
j.at("midi_gate").get_to(N.midi_gate);
j.at("midi_cc_channel").get_to(N.midi_cc_channel);
j.at("midi_cc").get_to(N.midi_cc);
j.at("midi_cc_value").get_to(N.midi_cc_value);
j.at("midi_pc_channel").get_to(N.midi_pc_channel);
j.at("midi_pc_value").get_to(N.midi_pc_value);
j.at("midi_pitch_bend_channel").get_to(N.midi_pitch_bend_channel);
j.at("midi_pitch_bend").get_to(N.midi_pitch_bend);
//j.at("connections").get_to(N.connections);
}
inline void to_json(json& j, const std::shared_ptr<MidiNode>& N){
j = *N;
}
inline void from_json(const json& j, std::shared_ptr<MidiNode>& N){
N.reset(new MidiNode(j.get<MidiNode>()));
}
inline void to_json(json& j, const MidiNode::NodeMidiOut& MO){
j = json{{"port_id", MO.i}, {"enabled", MO.enabled}, {"name", MO.name}};
}
/* ------------------- NODE ---------------------- */
inline void to_json(json& j, const Node& N){
if(N.getNodeType() == NodeType::MIDINODE){
j = dynamic_cast<const MidiNode&>(N);
}
}
inline void from_json(const json& j, Node& N){
if(j.at("nodeType") == NodeType::MIDINODE){
std::cout << j.at("nodeType") << std::endl;
N = j.template get<MidiNode>();
}
}
inline void to_json(json& j, const SPTR_Node& N){
j = *N;
}
inline void from_json(const json& j, std::shared_ptr<Node>& N){
std::cout << "SPTR_Node" << std::endl;
N.reset(new Node(j.get<Node>()));
}
}
namespace VISEQ::GRAPH {
inline void to_json(json& j, const GraphItem& G){
std::cout << "Movable" << std::endl;
j = json{{"id", G.id}, {"type", G.graphItemType}};
}
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 = json{{"position", 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->id},{"next",G.next->id},{"last",G.last->id},{"speed",G.speed},{"midi_note_offset",G.midi_note_offset},{"midi_cc_offset",G.midi_cc_offset},{"multiplier",G.multiplier}};
}
inline void from_json(const json& j, Signal& S){
Color c;
c.r = j.at("color")["r"].get<int>();
c.g = j.at("color")["g"].get<int>();
c.b = j.at("color")["b"].get<int>();
c.a = j.at("color")["a"].get<int>();
j.at("position").get_to<Vec2Df>(S.position);
j.at("speed").get_to<float>(S.speed);
j.at("radius").get_to<float>(S.radius);
j.at("midi_note_offset").get_to(S.midi_note_offset);
j.at("midi_cc_offset").get_to(S.midi_cc_offset);
S.color = c;
}
inline void to_json(json& j, const std::shared_ptr<Signal>& G){
j = *G;
}
inline void from_json(const json& j, std::shared_ptr<Signal>& S){
S.reset(new Signal(j.get<Signal>()));
}
}
+123 -106
View File
@@ -1,137 +1,154 @@
#pragma once
#include "connection.h"
#include "datatypes.h"
#include "node-manager.h"
#include "connection-manager.h"
#include "graph-manager.h"
#include "midi-node.h"
#include "signal-manager.h"
#include "ui.h"
#include <algorithm>
#include <iterator>
#include <memory>
#include <ranges>
#include <vector>
class AppState{
public:
namespace VISEQ::APP {
AppState() : cm(&nm), sm(&nm, &cm, &mutex), ui(&settings, &nm, &cm, &sm) {}
class AppState{
public:
AppState() : sm(&gm, &mutex), ui(&settings, &sm, &gm) {}
enum States {IDLE, CONFIG};
AppSettings settings;
enum States {IDLE, CONFIG};
AppSettings settings;
States state;
States state;
std::mutex mutex;
std::mutex mutex;
ConnectionManager cm;
SignalManager sm;
NodeManager nm;
VISEQ::GRAPH::GraphManager gm;
VISEQ::SIGNAL::SignalManager sm;
AppUI ui;
UI::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 updateMidiPorts(){
for(auto&n : gm.nodes | RANGE_FILTER([](VISEQ::GRAPH::NODES::SPTR_Node n){return n->getNodeType() == NodeType::MIDINODE;})){
auto nc = std::dynamic_pointer_cast<MidiNode>(n);
for(auto& mo : sm.midi.midi_out_ports){
if(!nc->midi_outs.contains(mo.number) && mo.enabled){
nc->midi_outs[mo.number] = {mo.number, mo.port.port_name, false};
}
if(nc->midi_outs.contains(mo.number) && !mo.enabled){
nc->midi_outs.erase(mo.number);
}
}
}
}
}
void copyNodesAndConnections(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>>();
void copyNodesAndConnections(VEC_SPTR_Node nodes, Vec2Df position){
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;
VEC_SPTR_Edge copy_connections;
for(auto c : gm.edges){
if(std::ranges::contains(nodes, c->start) && std::ranges::contains(nodes, c->end)) {
copy_connections.push_back(std::make_shared<Edge>(*c));
}
}
s.next = next_id;
s.last = next_id;
std::cout << copy_connections.size() << std::endl;
for(auto old_node : nodes){
SPTR_Node new_node;
if(old_node->getNodeType() == NodeType::MIDINODE) {
new_node = gm.copyNode(std::dynamic_pointer_cast<MidiNode>(old_node), position + old_node->drag_offset);
} else {
new_node = gm.copyNode(old_node, position + old_node->drag_offset);
}
new_node->copied = false;
new_node->selected = true;
for(auto c : copy_connections){
if(c->start == old_node) {
c->start = new_node;
c->start->connections.push_back(c);
}
if(c->end == old_node) {
c->end = new_node;
c->end->connections.push_back(c);
}
}
}
std::ranges::copy(copy_connections.begin(), copy_connections.end(), std::back_inserter(gm.edges));
}
void removeNodes(VEC_SPTR_Node nodes){
for(auto n : gm.nodes){
n->highlighted = false;
}
auto erase_connections = gm.edges | RANGE_FILTER([nodes](SPTR_Edge c){ return std::ranges::contains(nodes, c->start) || std::ranges::contains(nodes, c->end);});
VEC_SPTR_Node adjacent_nodes;
for(auto c : erase_connections){
if(!c->start->selected) adjacent_nodes.push_back(c->start);
if(!c->end->selected) adjacent_nodes.push_back(c->end);
}
auto reroute_signals = sm.signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->next);});
for(auto s : reroute_signals){
float dist = 10000000;
SPTR_Node next_node;
for(auto n : adjacent_nodes){
if(n->position.dist(s->position) < dist) {
dist = n->position.dist(s->position);
next_node = n;
}
}
s->next = next_node;
s->last = next_node;
}
auto rebase_signals = sm.signals | RANGE_FILTER([nodes](SIGNAL::SPTR_Signal s){return std::ranges::contains(nodes, s->start);});
for(auto s : rebase_signals){
s->start = s->next;
}
for(auto n : adjacent_nodes){
std::erase_if(n->connections, [n, nodes](VISEQ::GRAPH::EDGES::SPTR_Edge c){ return std::ranges::contains(nodes, c->start) || std::ranges::contains(nodes, c->end);});
}
for(auto n : nodes){
std::erase_if(gm.edges, [n](SPTR_Edge c){ return c->start == n || c->end == n;});
}
std::erase_if(gm.nodes, [nodes](SPTR_Node n){ return std::ranges::contains(nodes, n);});
}
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;
void draw(Vec2Df mouse_pos, Camera2D camera){
mutex.lock();
updateMidiPorts();
mutex.unlock();
gm.drawNodes(mouse_pos, camera);
gm.drawEdges(mouse_pos, camera);
sm.drawSignals(mouse_pos, camera);
}
for(auto &n : erase_nodes){
std::erase_if(cm.connections, [n](Connection c){ return c.v1 == n.id || c.v2 == n.id;});
void drawUI(Vec2Df *mouse_pos, Camera2D *camera){
mutex.lock();
ui.draw(mouse_pos, camera);
mutex.unlock();
}
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();
}
};
inline void to_json(json& j, const AppState& A){
j = json{{"graphmanager", A.gm}, {"signalmanager", A.sm}, {"settings",A.settings}};
}
inline void from_json(const json& j, AppState& A){
j.at("graphmanager").get_to(A.gm); // = json{{"graphmanager", A.gm}, {"signalmanager", A.sm}};
}
}
+147 -90
View File
@@ -1,50 +1,54 @@
#pragma once
#include "midi-node.h"
#include "raylib.h"
#include "raygui/raygui.h"
#include "raygui/dark/style_dark.h"
#include "datatypes.h"
#include "connection-manager.h"
#include "node-manager.h"
#include "raylib.h"
#include "graph-manager.h"
#include "signal-manager.h"
#include <memory>
#include <ranges>
#include <set>
class AppUI{
public:
namespace VISEQ::APP::UI{
class AppUI{
public:
AppUI() {}
AppUI(AppSettings *settings, SIGNAL::SignalManager *sm, GraphManager *gm) : settings(settings), gm(gm), sm(sm) {}
AppUI(AppSettings *settings, NodeManager *nm, ConnectionManager *cm, SignalManager *sm) : settings(settings), nm(nm), sm(sm), cm(cm) {}
AppSettings *settings;
GraphManager *gm;
SIGNAL::SignalManager *sm;
AppSettings *settings;
NodeManager *nm;
SignalManager *sm;
ConnectionManager *cm;
bool bpm_spinner_edit_mode = false;
void init(){
GuiLoadStyleDark();
}
void init(){
GuiLoadStyleDark();
}
void drawSideBar(){
void drawSideBar(){
GuiPanel((Rectangle){-1,-30,280,(float)settings->height + 31}, "");
{
const int s_controls = 20;
GuiPanel((Rectangle){-1,-30,280,(float)settings->height + 31}, "");
const int s_controls = 25;
//GuiSetStyle(BUTTON, STATE_FOCUSED, 0xFF0000FF);
if(GuiButton((Rectangle){20, (float)s_controls, 80, 30}, "PLAY")) sm->play ? sm->pause() : sm->start();
if(GuiButton((Rectangle){120, (float)s_controls, 60, 30}, "STOP")) sm->stop();
if(GuiButton((Rectangle){200, (float)s_controls, 60, 30}, "RESET")) sm->reset();
const int s_bpm = s_controls + 35;
const int s_bpm = s_controls + 45;
GuiLabel((Rectangle){20, (float)s_bpm, 120, 20}, "TIMING");
GuiLabel((Rectangle){200, (float)s_bpm, 40, 20}, std::format("{}",settings->bpm).c_str());
float _bpm = (float)settings->bpm;
GuiSlider((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", "", &_bpm, 1, 300);
int _bpm = settings->bpm;
// GuiSlider((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", "", &_bpm, 1, 300);
if(GuiSpinner((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", &_bpm, 1, 300, bpm_spinner_edit_mode)) bpm_spinner_edit_mode = !bpm_spinner_edit_mode;
settings->bpm = _bpm;
int s_midi = s_bpm + 55;
int s_midi = s_bpm + 75;
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;
for(auto& mo : sm->midi.midi_out_ports){
@@ -55,97 +59,150 @@ class AppUI{
if(GuiButton((Rectangle){20, (float)s_midi + 45 + i*20.0f, 240, 20}, "ENABLE FOR ALL")) {
for(auto &midis : sm->midi.midi_out_ports){
if(midis.enabled){
for(auto &n : nm->nodes){
n.second.setMidiPort(midis.number, true);
for(auto n : gm->nodes){
if(n->getNodeType() == NodeType::MIDINODE) std::dynamic_pointer_cast<MidiNode>(n)->setMidiPort(midis.number, true);
}
}
}
}
int s_signal = s_midi + 90 + i*20;
int s_signal = s_midi + 100 + i*20;
GuiLabel((Rectangle){20, (float)s_signal, 240, 20}, "SIGNALS");
if(GuiButton((Rectangle){20, (float)s_signal + 35, 240, 20}, "ADD RANDOM SIGNAL")){
int ni = nm->getRandomNode();
std::shared_ptr<Node> ni = gm->getRandomNode();
sm->addSignalAtNode(ni);
}
int signal_configs = 0;
for(auto &s : sm->signals){
if(GuiButton((Rectangle){20,(float)s_signal+70 + signal_configs*20, 20, 20},"X")) sm->removeSignal(signal_configs);
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;
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 = s_multiplier;
s.speed = settings->speed;
if(GuiButton((Rectangle){20,(float)s_signal+70 + signal_configs*20, 20, 20},"X")) sm->removeSignal(s);
DrawCircle(55, s_signal+80 + signal_configs*20, 6.0F, s->color);
int s_speed = round(s->speed);
s->multiplier = settings->bpm / 120.0f;
float signal_bpm = round(settings->bpm * s->multiplier * (s->speed / settings->speed));
if(GuiSpinner((Rectangle){70, (float)s_signal+70 + signal_configs*20, 100, 20}, "", &s_speed, 1, 16 * settings->grid * 4, s->ui_edit_speed)) s->ui_edit_speed = !s->ui_edit_speed;
if(GuiButton((Rectangle){200,(float)s_signal+70 + signal_configs*20, 30, 20}, "1/2")) s_speed = settings->speed / 2;
//if(GuiButton((Rectangle){200,(float)s_signal+70 + signal_configs*20, 30, 20},"1/4")) s_speed = settings->speed / 4;
GuiLabel((Rectangle){195, (float)s_signal+70 + signal_configs*20, 30, 20}, std::format("{}",signal_bpm).c_str());
GuiToggle((Rectangle){240, (float)s_signal+70 + signal_configs*20, 20, 20},"P", &s->run);
s->speed = s_speed;
signal_configs++;
}
int s_nodes = s_signal + 60 + signal_configs*20;
int s_nodes = s_signal + 70 + signal_configs*20;
GuiLabel((Rectangle){20, (float)s_nodes + 30, 240, 20}, "NODES");
if(GuiButton((Rectangle){20, (float)s_nodes + 60, 240, 20}, "ADD RANDOM NODE")){
nm->addNode(200.0f, 200.0f);
gm->addMidiNode(Vec2Df{200.0f, 200.0f});
}
DrawText(std::format("{}", nm->nodes.size()).c_str(), 20, settings->height - 70, 15, VS_COLOR_WHITE);
DrawText(std::format("{}", gm->nodes.size()).c_str(), 20, settings->height - 70, 15, VS_COLOR_WHITE);
DrawFPS(20, settings->height - 30);
}
}
void drawPerNodeConfig(Camera2D *camera){
for(auto& n : nm->nodes | std::ranges::views::values | std::views::filter([](Node n){ return n.config; })){
Vector2 np = GetWorldToScreen2D((Vector2){n.position.x, n.position.y}, *camera);
n.config = !GuiWindowBox((Rectangle){np.x + 10, np.y + 10, 240, 440}, "config");
int option = n.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);
}
void drawPropertiesBar(){
int y_midi = 120;
GuiLabel((Rectangle){np.x + 20, np.y + y_midi, 220, 20}, "MIDI OUT");
float left = settings->width - 280.f;
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++;
}
GuiPanel((Rectangle){left,-30,280,(float)settings->height + 31}, "");
const int s_controls = 25;
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){
drawSideBar();
drawPerNodeConfig(camera);
}
void drawPerNodeConfig(Camera2D *camera){
for(auto& n : gm->nodes | std::views::filter([](std::shared_ptr<Node> n){ return n->config; })){
Vector2 np = GetWorldToScreen2D((Vector2){n->position.x, n->position.y}, *camera);
};
n->config = !GuiWindowBox((Rectangle){np.x + 10, np.y + 10, 240, 440}, "config");
int option = n->distributionMode;
GuiToggleGroup((Rectangle){ np.x + 20, np.y + 40, 73, 20 }, "GEO;RR;RAND", &option);
n->distributionMode = (Distribution)option;
GuiLabel((Rectangle){np.x + 20, np.y + 65, 220, 20}, "SIGNALS");
if(GuiButton((Rectangle){np.x + 20, np.y + 90, 220, 20}, "ADD SIGNAL")){
sm->addSignalAtNode(n);
}
int y_midi = 120;
GuiLabel((Rectangle){np.x + 20, np.y + y_midi, 220, 20}, "MIDI OUT");
int i = 0;
if(n->getNodeType() == NodeType::MIDINODE){
std::shared_ptr<MidiNode> mn = std::dynamic_pointer_cast<MidiNode>(n);
for(auto& mo : mn->midi_outs){
GuiCheckBox((Rectangle){np.x + 20, np.y + y_midi + 20 + i*20.0f, 20, 20}, mo.second.name.c_str(), &mo.second.enabled);
i++;
}
int y_channel = y_midi + 30 + i*20;
GuiCheckBox((Rectangle){np.x + 20, np.y + y_channel, 20, 20}, "NOTE", &mn->send_note);
GuiCheckBox((Rectangle){np.x + 90, np.y + y_channel, 20, 20}, "CC", &mn->send_cc);
GuiCheckBox((Rectangle){np.x + 160, np.y + y_channel, 20, 20}, "PITCH", &mn->send_pitch_bend);
int y_cc_note = y_channel + 120;
switch(settings->mode){
case 0:{
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "NOTE CHANNEL");
GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midi_note_channel);
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "NOTE");
float v = mn->midi_note;
GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note , 20, 20}, MIDI::Notes[(int)round(v)].c_str());
GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midi_note).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 127);
mn->midi_note = round(v);
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 60, 100, 20}, "VELOCITY");
v = mn->midi_velocity;
GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 60, 20, 20}, std::format("{}",mn->midi_velocity).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 90, 220, 20}, "", "", &v, 0, 127);
mn->midi_velocity = round(v);
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 120, 100, 20}, "GATE");
v = mn->midi_gate;
GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 120, 20, 20}, std::format("{}",(float)mn->midi_gate/1000.0f).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 150, 220, 20}, "", "", &v, 0, 30000);
mn->midi_gate = round(v);
break;}
case 1:
{
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "CC CHANNEL");
GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midi_cc_channel);
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "CC");
float v = mn->midi_cc;
GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midi_cc).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 127);
mn->midi_cc = round(v);
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note + 60, 100, 20}, "CC VALUE");
v = mn->midi_cc_value;
GuiLabel((Rectangle){np.x + 210, np.y + y_cc_note + 60, 20, 20}, std::format("{}",mn->midi_cc_value).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 90, 220, 20}, "", "", &v, 0, 127);
mn->midi_cc_value = round(v);
break;}
case 2:
{
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note - 75, 220, 20}, "PITCH BEND CHANNEL");
GuiToggleGroup((Rectangle){np.x + 20, np.y + y_cc_note - 50, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &mn->midi_pitch_bend_channel);
GuiLabel((Rectangle){np.x + 20, np.y + y_cc_note , 100, 20}, "CC");
float v = mn->midi_pitch_bend;
GuiLabel((Rectangle){np.x + 160, np.y + y_cc_note , 40, 20}, std::format("{}",mn->midi_pitch_bend).c_str());
GuiSlider((Rectangle){np.x + 20, np.y + y_cc_note + 30, 220, 20}, "", "", &v, 0, 16383);
mn->midi_pitch_bend = round(v);
break;}
}
}
}
}
void draw(Vec2Df *mouse_pos, Camera2D *camera){
drawSideBar();
drawPropertiesBar();
drawPerNodeConfig(camera);
}
};
}
+167
View File
@@ -0,0 +1,167 @@
#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& 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>;
BIN
View File
Binary file not shown.