/* * * VISEQ - Visual Sequencer * 2024 Sebastian Kujas * */ #include "graph-manager.h" #include "node.h" #include "vmath.h" #include #include #define RAYGUI_IMPLEMENTATION #include #include #include "state-manager.h" #include "datatypes.h" #include "midi-node.h" #include "nlohmann/json.hpp" #include #include #include #include #include #include #include #include "state-io.h" using json = nlohmann::json; VISEQ::APP::AppState app; Vec2Df mouse_pos; Vec2Df drag_start; Camera2D camera = { {0} }; bool mouse_clicked = false; bool mouse_touched = false; bool mouse_dragging = false; int main(){ srand(time(NULL)); InitWindow(1920, 1080, "VISEQ - The Visual Sequencer"); int current_monitor = GetCurrentMonitor(); int monitor_width = GetMonitorWidth(current_monitor); int monitor_height = GetMonitorHeight(current_monitor); SetWindowMonitor(current_monitor); SetWindowState(FLAG_WINDOW_RESIZABLE); SetWindowSize(monitor_width - 300, monitor_height - 300); //ToggleFullscreen(); app.ui.init(); app.settings.fps = 60.0f; app.settings.bpm = 120.0f; app.settings.grid = 60.0f; // 120px for a 16th note app.settings.speed = (app.settings.grid * 16.0f) * (app.settings.bpm / 120.f); app.settings.width = monitor_width - 300; app.settings.height = monitor_height - 300; SetTargetFPS(app.settings.fps); camera.target = (Vector2){ app.settings.width/2.0f, app.settings.height/2.0f }; camera.offset = (Vector2){ app.settings.width/2.0f, app.settings.height/2.0f }; camera.rotation = 0.0f; camera.zoom = 1.0f; if(std::filesystem::exists("state.json")){ std::ifstream f("state.json"); json graph = json::parse(f); app.settings = graph.at("settings"); // app.gm = graph["graphmanager"].get(); int highest_id = 0; std::shared_ptr hid; for(auto n : graph.at("graphmanager").at("nodes")){ std::shared_ptr mn = n; app.gm.nodes.push_back(mn); if(mn->id > highest_id) { highest_id = mn->id; hid = 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 end_id = e.at("end").get(); 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()) { 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}); } for(int i = 3; i >= 0; --i){ app.gm.addMidiNode(Vec2Df{(float)i * app.settings.grid*4 + 7 * app.settings.grid, 3 * app.settings.grid*4}); } for(int i = 0; i < 8; ++i){ app.gm.addConnection(app.gm.nodes[i], app.gm.nodes[(i+1) % 8]); } app.sm.addSignalAtNode(app.gm.nodes.front()); } app.sm.startSignalThread(); enum InteractionState {HOVER_CANVAS, HOVER_NODE, HOVER_CONNECTOR, DRAG_NODE, CONNECT_NODE, DRAW_SELECTION, DELETE_SELECTED_NODES, COPY_SELECTED_NODES, PASTE_SELECTED_NODES}; InteractionState istate = HOVER_CANVAS; Vec2Df select_start; Rectangle selection; VEC_SPTR_Node copied_nodes; while(!WindowShouldClose()){ BeginDrawing(); ClearBackground((Color){46,46,46}); BeginMode2D(camera); app.settings.speed = (app.settings.grid * 16.0f) * (app.settings.bpm / 120.f); Vector2 mp = GetScreenToWorld2D(GetMousePosition(), camera); Vec2Df mouse_pos = {(float)mp.x, (float)mp.y}; Vec2Df cam_pos = {camera.offset.x, camera.offset.y}; bool mouse_down = IsMouseButtonDown(MOUSE_BUTTON_LEFT); bool mouse_clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); bool mouse_released = IsMouseButtonReleased(MOUSE_BUTTON_LEFT); bool mouse_right_down = IsMouseButtonDown(MOUSE_BUTTON_RIGHT); bool mouse_right_clicked = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); bool mouse_right_released = IsMouseButtonReleased(MOUSE_BUTTON_RIGHT); Vec2Df mouse_delta = {GetMouseDelta().x, GetMouseDelta().y}; Vec2Df delta = mouse_delta * (-1.0f/camera.zoom); auto dragging_nodes = app.gm.nodes | RANGE_FILTER([](SPTR_Node n) { return n->dragging;}); auto connecting_nodes = app.gm.nodes | RANGE_FILTER([](SPTR_Node n) { return n->connecting;}); auto selected_nodes = app.gm.nodes | RANGE_FILTER([](SPTR_Node n) { return n->selected && !n->dragging;}) | std::ranges::to(); auto hovering_nodes = app.gm.nodes | RANGE_FILTER([&mouse_pos](SPTR_Node n) { return CheckCollisionPointCircle(mouse_pos, n->position, n->radius);}); auto hovering_connectors = app.gm.nodes | RANGE_FILTER([&mouse_pos](SPTR_Node n) { return CheckCollisionPointCircle(mouse_pos, (n->position - ((n->position - mouse_pos)).norm() * (n->radius + 14.0f)), 8.0f);}); for(auto &n : app.gm.nodes ){ if(hovering_nodes.empty()) n->hovering = false; if(hovering_connectors.empty()) n->hover_connect = false; } if(!mouse_down && !mouse_right_down && dragging_nodes.empty() && connecting_nodes.empty() && hovering_nodes.empty() && hovering_connectors.empty()) istate = InteractionState::HOVER_CANVAS; if((mouse_clicked || mouse_down || mouse_released) && !mouse_right_down && dragging_nodes.empty() && connecting_nodes.empty() && hovering_nodes.empty() && hovering_connectors.empty()) istate = InteractionState::DRAW_SELECTION; if(!hovering_nodes.empty() && !mouse_down && !mouse_right_down) istate = InteractionState::HOVER_NODE; if(!dragging_nodes.empty()) istate = InteractionState::DRAG_NODE; if(!hovering_connectors.empty() && !mouse_right_down && istate == InteractionState::HOVER_CANVAS) istate = InteractionState::HOVER_CONNECTOR; if(!connecting_nodes.empty()) istate = InteractionState::CONNECT_NODE; if(!selected_nodes.empty() && IsKeyPressed(KEY_DELETE)) istate = InteractionState::DELETE_SELECTED_NODES; if(!selected_nodes.empty() && IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) istate = InteractionState::COPY_SELECTED_NODES; if(!copied_nodes.empty() && IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V)) istate = InteractionState::PASTE_SELECTED_NODES; if(IsKeyPressed(KEY_TAB)) { app.settings.mode = (app.settings.mode + 1) % 3; app.gm.setNodeMode(app.settings.mode); } switch(istate){ case InteractionState::HOVER_CANVAS: { for(auto &n : app.gm.nodes){ n->hover_connect = false; } if(mouse_right_down) { camera.target = Vector2Add(camera.target, delta); } break; } case InteractionState::DRAW_SELECTION: { if(mouse_clicked){ select_start = mouse_pos; } if(mouse_down){ Vec2Df select1 = {mouse_pos.x > select_start.x ? select_start.x : mouse_pos.x, mouse_pos.y > select_start.y ? select_start.y : mouse_pos.y}; Vec2Df select2 = {mouse_pos.x > select_start.x ? mouse_pos.x - select_start.x : select_start.x - mouse_pos.x, mouse_pos.y > select_start.y ? mouse_pos.y - select_start.y : select_start.y - mouse_pos.y}; DrawRectangleLines(select1.x, select1.y, select2.x, select2.y, (Color){150,150,150,150}); selection = (Rectangle){select1.x, select1.y, select2.x, select2.y}; for(auto& n : app.gm.nodes){ if(CheckCollisionPointRec(n->position, selection)) { n->in_rect = true; } else { n->in_rect = false; } } } if(mouse_released){ int n_selected = 0; for(auto& n : app.gm.nodes){ if(CheckCollisionPointRec(n->position, selection)) { n->selected = true; n->in_rect = false; n_selected++; } else { if(!IsKeyDown(KEY_LEFT_SHIFT)) n->selected = false; } } if(n_selected > 0) copied_nodes.clear(); selection = (Rectangle){0,0,0,0}; } break; } case InteractionState::HOVER_NODE: { if(mouse_clicked) { if(IsKeyDown(KEY_LEFT_SHIFT)) { hovering_nodes.front()->selected = true; } else { hovering_nodes.front()->dragging = true; hovering_nodes.front()->drag_offset = hovering_nodes.front()->position - mouse_pos; } } if(mouse_right_clicked) hovering_nodes.front()->config = true; break; } case InteractionState::DRAG_NODE: { if(!dragging_nodes.empty()){ SPTR_Node dragNode = dragging_nodes.front(); for(auto &n : selected_nodes){ if(!n->picked_up) n->drag_offset = n->position - dragNode->position; n->picked_up = true; } if(IsKeyDown(KEY_LEFT_CONTROL)) { dragNode->position.x = round((mouse_pos.x + dragNode->drag_offset.x) / app.settings.grid) * app.settings.grid; dragNode->position.y = round((mouse_pos.y + dragNode->drag_offset.y) / app.settings.grid) * app.settings.grid; } else if(IsKeyDown(KEY_LEFT_SHIFT)){ float steps = round((dragNode->neighbor->position.dist(mouse_pos)) / app.settings.grid) * app.settings.grid; float pol_r = steps; float pol_theta = Vector2Angle(Vec2Df{1,0},(mouse_pos - dragNode->neighbor->position)); Vec2Df polar = VISEQ::VMATH::polarToCartesian(pol_r, pol_theta); dragNode->position = dragNode->neighbor->position + polar; } else { dragNode->position = mouse_pos + dragNode->drag_offset; } for(auto &n : selected_nodes){ n->position = dragNode->position + n->drag_offset; } if(mouse_released) { for(auto &n : dragging_nodes){ n->dragging = false; n->picked_up = false; } for(auto &n : selected_nodes){ n->picked_up = false; n->dragging = false; } } } break; } case InteractionState::HOVER_CONNECTOR: { if(!hovering_connectors.empty()){ hovering_connectors.front()->hover_connect = true; if(mouse_clicked) hovering_connectors.front()->connecting = true; } break; } case InteractionState::CONNECT_NODE: { Vec2Df connector_position = connecting_nodes.front()->position - ((connecting_nodes.front()->position - mouse_pos)).norm() * (connecting_nodes.front()->radius + 14.0f); Vec2Df target_position = mouse_pos; if(IsKeyDown(KEY_LEFT_CONTROL)) { target_position.x = round((mouse_pos.x) / app.settings.grid) * app.settings.grid; target_position.y = round((mouse_pos.y) / app.settings.grid) * app.settings.grid; } else if(IsKeyDown(KEY_LEFT_SHIFT)){ std::shared_ptr closest = app.gm.getClosestNode(mouse_pos); float steps = round((closest->position.dist(mouse_pos)) / app.settings.grid) * app.settings.grid; float pol_r = steps; float pol_theta = Vector2Angle(Vec2Df{1,0},(mouse_pos - closest->position)); Vec2Df polar = VISEQ::VMATH::polarToCartesian(pol_r, pol_theta); target_position = closest->position + polar; } else { target_position = mouse_pos; } if(connecting_nodes.front()->connecting && mouse_down){ DrawLine(connector_position.x, connector_position.y, target_position.x, target_position.y, (Color){229, 181, 103, 255}); connecting_nodes.front()->hovering = true; connecting_nodes.front()->hover_connect = true; } if(!hovering_connectors.empty()) { hovering_connectors.front()->hovering = true; hovering_connectors.front()->hover_connect = true; } if(!hovering_nodes.empty()) hovering_nodes.front()->hovering = true; if(mouse_released) { if(&hovering_nodes.front() != &connecting_nodes.front() && &connecting_nodes.front() != &hovering_connectors.front()){ if(!hovering_nodes.empty()){ app.gm.addConnection(connecting_nodes.front(), hovering_nodes.front()); } else if(!hovering_connectors.empty()){ app.gm.addConnection(connecting_nodes.front(), hovering_connectors.front()); } else { SPTR_Node new_node = app.gm.copyNode(std::dynamic_pointer_cast(connecting_nodes.front()), target_position); new_node->connecting = false; app.gm.addConnection(connecting_nodes.front(), new_node); } } connecting_nodes.front()->connecting = false; istate = InteractionState::HOVER_CANVAS; } break; } case InteractionState::DELETE_SELECTED_NODES: { app.removeNodes(selected_nodes | std::ranges::to()); break; } case InteractionState::COPY_SELECTED_NODES: { std::ranges::copy(selected_nodes.begin(), selected_nodes.end(), std::back_inserter(copied_nodes)); std::ranges::for_each(selected_nodes.begin(), selected_nodes.end(), [](SPTR_Node n){n->selected = false;}); selected_nodes.clear(); break; } case InteractionState::PASTE_SELECTED_NODES: { for(auto n: copied_nodes){ n->drag_offset = (n->position - copied_nodes.front()->position); } app.copyNodesAndConnections(copied_nodes, mouse_pos); //copied_nodes.clear(); break; } }; float wheel = GetMouseWheelMove(); if(wheel != 0){ if(!hovering_nodes.empty() && hovering_nodes.front()->getNodeType() == NodeType::MIDINODE) { VEC_SPTR_Node modify_nodes; std::copy_if(selected_nodes.begin(), selected_nodes.end(), std::back_inserter(modify_nodes), [](SPTR_Node n){ return n->getNodeType() == NodeType::MIDINODE;}); if(selected_nodes.empty()) std::copy_if(hovering_nodes.begin(), hovering_nodes.end(), std::back_inserter(modify_nodes), [modify_nodes](SPTR_Node n){ return n->getNodeType() == NodeType::MIDINODE && !std::ranges::contains(modify_nodes, n);}); for(auto n : modify_nodes){ std::shared_ptr midiNode = std::dynamic_pointer_cast(n); if(IsKeyDown(KEY_LEFT_CONTROL) && !IsKeyDown(KEY_LEFT_ALT)){ switch(app.settings.mode){ case 0:{ midiNode->midi_note += wheel; break;} case 1:{ midiNode->midi_cc += wheel; break;} case 2:{ midiNode->midi_pitch_bend += wheel; break;} }; if(midiNode->midi_note < 0 ) midiNode->midi_note = VISEQ::MIDI::Notes.size()-1; if(midiNode->midi_note >= VISEQ::MIDI::Notes.size() ) midiNode->midi_note = 0; if(midiNode->midi_cc < 0 ) midiNode->midi_cc = 127; if(midiNode->midi_cc > 127 ) midiNode->midi_cc = 0; if(midiNode->midi_pitch_bend < 0 ) midiNode->midi_pitch_bend = 0; if(midiNode->midi_pitch_bend > 16383 ) midiNode->midi_pitch_bend = 16383; } else if(IsKeyDown(KEY_LEFT_SHIFT) && !IsKeyDown(KEY_LEFT_ALT)) { midiNode->midi_velocity += wheel; if(midiNode->midi_velocity < 0 ) midiNode->midi_velocity = 127; if(midiNode->midi_velocity > 127 ) midiNode->midi_velocity = 0; } else if(IsKeyDown(KEY_LEFT_ALT)) { switch(app.settings.mode){ case 0:{ float incr = (float)wheel * (IsKeyDown(KEY_LEFT_SHIFT) ? 100.0f : IsKeyDown(KEY_LEFT_CONTROL) ? 1000.0f : 10.0f); midiNode->midi_gate += incr; if(midiNode->midi_gate < 0 ) midiNode->midi_gate = 0; break;} case 1:{ midiNode->midi_cc_value += wheel; if(midiNode->midi_cc_value < 0 ) midiNode->midi_cc_value = 0; if(midiNode->midi_cc_value > 127 ) midiNode->midi_cc_value = 127; break;} case 2:{ float incr = (float)wheel * (IsKeyDown(KEY_LEFT_SHIFT) ? 100.0f : IsKeyDown(KEY_LEFT_CONTROL) ? 1000.0f : 10.0f); midiNode->midi_pitch_bend += incr; if(midiNode->midi_pitch_bend < 0 ) midiNode->midi_pitch_bend = 0; if(midiNode->midi_pitch_bend > 16383 ) midiNode->midi_pitch_bend = 16383; break;} }; } } } if(!IsKeyDown(KEY_LEFT_SHIFT) && !IsKeyDown(KEY_LEFT_CONTROL) && !IsKeyDown(KEY_LEFT_ALT)){ Vector2 mouseWorldPos = GetScreenToWorld2D(GetMousePosition(), camera); camera.offset = GetMousePosition(); camera.target = mouseWorldPos; float scaleFactor = 1.0f + (0.25f*fabsf(wheel)); if (wheel < 0) scaleFactor = 1.0f/scaleFactor; camera.zoom = Clamp(camera.zoom*scaleFactor, 0.125f, 64.0f); } } for(int x = -app.settings.grid*100; x < app.settings.grid*100; x+=app.settings.grid){ for(int y = -app.settings.grid*100; y < app.settings.grid*100; y+=app.settings.grid){ DrawPixel(x, y, (Color){130,130,130,200}); } } app.draw(mouse_pos, camera); EndMode2D(); app.drawUI(&mouse_pos, &camera); EndDrawing(); } std::ofstream file("state.json"); file << json(app); std::cout << std::setw(4) << json(app) << std::endl; CloseWindow(); return 0; }