diff --git a/src/datatypes.h b/src/datatypes.h index fcd9cdf..f708a10 100644 --- a/src/datatypes.h +++ b/src/datatypes.h @@ -44,6 +44,7 @@ namespace VISEQ::APP{ 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{ diff --git a/src/graph-item.h b/src/graph-item.h index dbba9e8..fd4768d 100644 --- a/src/graph-item.h +++ b/src/graph-item.h @@ -9,11 +9,23 @@ namespace VISEQ::GRAPH { class GraphItem{ public: - GraphItem() {static int _id; id=_id++;} + GraphItem() {newId();} + GraphItem(const GraphItem& G) { + newId(); + graphItemType = G.graphItemType; + neighbors = G.neighbors; + } std::vector> neighbors; - int id = 0; GraphItemType graphItemType; + + int id = 0; + void newId(){ + static int _id; + id = _id; + _id++; + } + virtual void draw(Vec2Df mouse, Camera2D cam) {}; virtual GraphItemType getType() {return graphItemType;} virtual ~GraphItem() = default; diff --git a/src/graph-manager.h b/src/graph-manager.h index 72aa4ff..aac1e15 100644 --- a/src/graph-manager.h +++ b/src/graph-manager.h @@ -159,7 +159,7 @@ namespace VISEQ::GRAPH { void splitEdges(){ for(auto s : split_edges){ Vec2Df center = s->end->position.center(s->start->position); - SPTR_Node cn = addMidiNode(center); + SPTR_Node cn = copyNode(std::dynamic_pointer_cast(s->start), center); addConnection(s->start, cn); addConnection(cn, s->end); remove_edges.push_back(s); diff --git a/src/main.cpp b/src/main.cpp index 13b7091..dcec8a3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -83,12 +83,13 @@ int main(){ std::ifstream f("state.json"); json graph = json::parse(f); + app.settings = graph.at("settings"); + // app.gm = graph["graphmanager"].get(); for(auto n : graph.at("graphmanager").at("nodes")){ std::shared_ptr mn = n; app.gm.nodes.push_back(mn); - /*app.gm.addMidiNode(n.at("position").get());*/ - /*app.gm.nodes.back().midi_note_channel = n.at("midi_note_channel").get();*/ + std::cout << std::format("adding node {}", mn->id) << std::endl; } for(auto e : graph.at("graphmanager").at("edges")){ @@ -98,10 +99,30 @@ int main(){ 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 end = app.gm.nodes | RANGE_FILTER([end_id](SPTR_Node n){return n->id == end_id;}) | std::ranges::to(); - if(!start.empty() && !end.empty()) app.gm.addConnection(start.front(), end.front()); + if(!start.empty() && !end.empty()) { + std::cout << std::format("connection {} to {}", start_id, end_id) << std::endl; + app.gm.addConnection(start.front(), end.front()); + } } + for(auto s : graph.at("signalmanager").at("signals")){ + int start_id = s.at("start").get(); + int next_id = s.at("next").get(); + int last_id = s.at("last").get(); + std::shared_ptr ms = s; + + VEC_SPTR_Node start = app.gm.nodes | RANGE_FILTER([start_id](SPTR_Node n){return n->id == start_id;}) | std::ranges::to(); + 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 next = app.gm.nodes | RANGE_FILTER([next_id](SPTR_Node n){return n->id == next_id;}) | std::ranges::to(); + 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}); @@ -211,14 +232,17 @@ int main(){ } if(mouse_released){ + int n_selected = 0; for(auto& n : app.gm.nodes){ if(CheckCollisionPointRec(n->position, selection)) { n->selected = true; n->in_rect = false; + n_selected++; } else { if(!IsKeyDown(KEY_LEFT_SHIFT)) n->selected = false; } } + if(n_selected > 0) copied_nodes.clear(); selection = (Rectangle){0,0,0,0}; } break; @@ -308,7 +332,9 @@ int main(){ } else if(!hovering_connectors.empty()){ app.gm.addConnection(connecting_nodes.front(), hovering_connectors.front()); } else { - SPTR_Node new_node = app.gm.addMidiNode(mouse_pos); + //SPTR_Node new_node = app.gm.addMidiNode(mouse_pos); + SPTR_Node new_node = app.gm.copyNode(std::dynamic_pointer_cast(connecting_nodes.front()), mouse_pos); + new_node->connecting = false; app.gm.addConnection(connecting_nodes.front(), new_node); } } @@ -421,8 +447,8 @@ int main(){ EndDrawing(); } - //std::ofstream file("state.json"); - //file << json(app); + std::ofstream file("state.json"); + file << json(app); std::cout << std::setw(4) << json(app) << std::endl; diff --git a/src/midi-node.h b/src/midi-node.h index 6057eeb..2f77609 100644 --- a/src/midi-node.h +++ b/src/midi-node.h @@ -166,6 +166,7 @@ namespace VISEQ::GRAPH::NODES { 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); } }; } diff --git a/src/signal-manager.h b/src/signal-manager.h index 5dd31fb..33427c5 100644 --- a/src/signal-manager.h +++ b/src/signal-manager.h @@ -7,6 +7,7 @@ #include "midi.h" #include +#include #include #include @@ -30,6 +31,8 @@ namespace VISEQ::SIGNAL { VEC_SPTR_Signal signals; + VEC_SPTR_Signal erase_signals; + bool loop = true; bool play = false; @@ -72,8 +75,8 @@ namespace VISEQ::SIGNAL { signals.back()->start = node; } - void removeSignal(int n){ - signals.erase(signals.begin() + n); + void removeSignal(std::shared_ptr s){ + erase_signals.push_back(s); } void updateSignals(){ @@ -94,8 +97,8 @@ namespace VISEQ::SIGNAL { auto midinode = std::dynamic_pointer_cast(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, midinode->midi_note, midinode->midi_velocity, midinode->midi_gate); - if(midinode->send_cc) midi.cc(m.second.i, midinode->midi_cc_channel, midinode->midi_cc, midinode->midi_cc_value); + 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); } } @@ -113,11 +116,17 @@ namespace VISEQ::SIGNAL { } void drawSignals(Vec2Df mouse_pos, Camera2D camera){ - for(auto s : signals){ - mutex->lock(); - s->draw(mouse_pos, camera); - mutex->unlock(); + mutex->lock(); + if(!erase_signals.empty()){ + for(auto s: erase_signals){ + std::erase(signals, s); + } + erase_signals.clear(); } + for(auto s : signals){ + s->draw(mouse_pos, camera); + } + mutex->unlock(); } }; diff --git a/src/signal.h b/src/signal.h index 262454d..d2d231b 100644 --- a/src/signal.h +++ b/src/signal.h @@ -12,7 +12,7 @@ namespace VISEQ::SIGNAL { class Signal : public BASE::Movable{ public: - Signal(); + Signal() {} Signal(Vec2Df position, float r, float _speed, Color color) : Movable(position, Vec2Df(0,0), _speed), radius(r), color(color) {} NODES::SPTR_Node next; @@ -22,18 +22,22 @@ namespace VISEQ::SIGNAL { NODES::SPTR_Node start; bool at_target = false; + bool run = true; - float radius; + float radius = 8.0f; float multiplier = 1.0f; Color color; + 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 - position += direction.norm() * speed * multiplier * (float)t_delta / 1000000.0f; + if(run) position += direction.norm() * speed * multiplier * (float)t_delta / 1000000.0f; } void draw(Vec2Df mouse, Camera2D cam) { diff --git a/src/state-io.h b/src/state-io.h index f2ac1ab..1f61366 100644 --- a/src/state-io.h +++ b/src/state-io.h @@ -56,6 +56,7 @@ namespace VISEQ::GRAPH::NODES { {"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}, @@ -76,6 +77,7 @@ namespace VISEQ::GRAPH::NODES { 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); @@ -164,10 +166,34 @@ namespace VISEQ::BASE{ namespace VISEQ::SIGNAL{ inline void to_json(json& j, const Signal& G){ - j = json{{"position", G.position},{"start",G.start->id},{"speed",G.speed}}; + std::map fc = {{"r",G.color.r},{"g",G.color.g},{"b",G.color.b},{"a",G.color.a}}; + j = json{{"color",fc},{"position", G.position},{"radius",G.radius},{"start",G.start->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(); + c.g = j.at("color")["g"].get(); + c.b = j.at("color")["b"].get(); + c.a = j.at("color")["a"].get(); + + j.at("position").get_to(S.position); + j.at("speed").get_to(S.speed); + j.at("radius").get_to(S.radius); + j.at("midi_note_offset").get_to(S.midi_note_offset); + j.at("midi_cc_offset").get_to(S.midi_cc_offset); + + S.color = c; } inline void to_json(json& j, const std::shared_ptr& G){ j = *G; } + + inline void from_json(const json& j, std::shared_ptr& S){ + S.reset(new Signal(j.get())); + } } + + diff --git a/src/state-manager.h b/src/state-manager.h index 826bcd5..ad8773e 100644 --- a/src/state-manager.h +++ b/src/state-manager.h @@ -147,7 +147,7 @@ namespace VISEQ::APP { }; inline void to_json(json& j, const AppState& A){ - j = json{{"graphmanager", A.gm}, {"signalmanager", A.sm}}; + j = json{{"graphmanager", A.gm}, {"signalmanager", A.sm}, {"settings",A.settings}}; } inline void from_json(const json& j, AppState& A){ diff --git a/src/ui.h b/src/ui.h index 8b03244..aed7cb1 100644 --- a/src/ui.h +++ b/src/ui.h @@ -11,6 +11,7 @@ #include #include +#include namespace VISEQ::APP::UI{ class AppUI{ @@ -22,6 +23,8 @@ namespace VISEQ::APP::UI{ GraphManager *gm; SIGNAL::SignalManager *sm; + bool bpm_spinner_edit_mode = false; + void init(){ GuiLoadStyleDark(); } @@ -38,8 +41,9 @@ namespace VISEQ::APP::UI{ 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 + 75; @@ -71,13 +75,15 @@ namespace VISEQ::APP::UI{ 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); + 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); - 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; + float s_speed = round(s->speed); + s->multiplier = settings->bpm / 120.0f; + float signal_bpm = round(settings->bpm * s->multiplier * (s->speed / settings->speed)); + GuiSlider((Rectangle){70, (float)s_signal+70 + signal_configs*20, 120, 20}, "", "", &s_speed, 1.0f, 16.0f * settings->grid * 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++; }