/* * * VISEQ - Visual Sequencer * * */ /*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 "datatypes.h" #include "signal-manager.h" #include #include #include #include #include #include #include #define RAYGUI_IMPLEMENTATION #include "raygui/raygui.h" #include "raygui/dark/style_dark.h" #include #include #include #include "midi.h" #include "node.h" #include "connection-manager.h" #include "signal-manager.h" AppState app; std::vector nodes; ConnectionManager cm; Midi midi; SignalManager sm(&app, &cm, &midi); Vec2D mouse_pos; Vec2D drag_start; bool mouse_clicked = false; bool mouse_touched = false; bool mouse_dragging = false; int main(){ srand(time(NULL)); InitWindow(2560, 1440, "VISEQ"); GuiLoadStyleDark(); int monitor = 0; //GetCurrentMonitor(); SetWindowMonitor(monitor); app.fps = 1.0f; app.bpm = 120.0f; app.grid = 120.0f; //round(((app.fps / (app.bpm / 60.0f)) * app.speed) / 4.0f); // from quarter notes to 16th notes = div by 4 // app.speed = round((((app.fps / (app.bpm / 60.0f)) / app.grid) / 4.0f) / 10.0f) * 10.0f; // 16th note = 120px (festgelegt) // ganze note = 1920px // ganze note bei 120bpm = 2s // 120bpm = 960px/s app.speed = (app.grid * 8.0f) * (120.0f / app.bpm); // 120 fps // 120bpm = 120 1/4 Noten pro Minute = 2 1/4 Noten / s // 2px / frame bei 120fps = 240px / s = 14400 px / minute // 1/4 Note / 120 pixel // 1/16 Note alle 30 pixel // 60 fps bei 120 bpm // 2px / frame bei 60fps = 120px / s = 7200 px / minute // 1/16 Note alle 15 pixel // 1/8 Note alle 30 pixel // 1/4 Note alle 60 pixel // 1/2 Note alle 120 pixel // 1/1 Note alle 240 pixel app.width = GetMonitorWidth(monitor); app.height = GetMonitorHeight(monitor); SetTargetFPS(60.0f); Camera2D camera = { {0} }; camera.target = (Vector2){ app.width/2.0f, app.height/2.0f }; camera.offset = (Vector2){ app.width/2.0f, app.height/2.0f }; camera.rotation = 0.0f; camera.zoom = 1.0f; nodes.reserve(1000); for(int i = 0; i < 4; ++i){ nodes.emplace_back((float)i * app.grid + 10 * app.grid, 5 * app.grid, 15.0f); } for(int i = 3; i >= 0; --i){ nodes.emplace_back((float)i * app.grid + 10 * app.grid, 6 * app.grid, 15.0f); } for(int i = 0; i < 8; ++i){ cm.addConnection(&nodes[i], &nodes[(i+1) % 8]); } sm.addSignalAtNode(&nodes.front()); sm.startSignalThread(); Vec2D drag_offset; Vec2D cam_drag_offset; Vec2D cam_mouse_drag_offset; double drag_timer = GetTime(); enum InteractionState {HOVER_CANVAS, HOVER_NODE, HOVER_CONNECTOR, DRAG_NODE, CONNECT_NODE, DRAW_SELECTION}; InteractionState istate = HOVER_CANVAS; Vec2D select_start; Rectangle selection; ToggleFullscreen(); while(!WindowShouldClose()){ BeginDrawing(); ClearBackground((Color){46,46,46}); BeginMode2D(camera); //app.grid = round(((app.fps / (app.bpm / 60.0f)) * app.speed) / 4.0f); // from quarter notes to 16th notes = div by 4 app.speed = (app.grid * 8.0f) * (120.0f / app.bpm); Vector2 mp = GetScreenToWorld2D(GetMousePosition(), camera); Vec2D mouse_pos = {(float)mp.x, (float)mp.y}; Vec2D cam_pos = {camera.offset.x, camera.offset.y}; bool mouse_down = IsMouseButtonDown(MOUSE_BUTTON_LEFT); bool mouse_clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); bool mouse_released = IsMouseButtonReleased(MOUSE_BUTTON_LEFT); bool mouse_right_down = IsMouseButtonDown(MOUSE_BUTTON_RIGHT); bool mouse_right_clicked = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); bool mouse_right_released = IsMouseButtonReleased(MOUSE_BUTTON_RIGHT); Vec2D mouse_delta = {GetMouseDelta().x, GetMouseDelta().y}; Vec2D delta = mouse_delta * (-1.0f/camera.zoom); auto dragging_nodes = nodes | std::ranges::views::filter([](Node n) { return n.dragging;}); auto connecting_nodes = nodes | std::ranges::views::filter([](Node n) { return n.connecting;}); auto selected_nodes = nodes | std::ranges::views::filter([](Node n) { return n.selected && !n.dragging;}); auto hovering_nodes = nodes | std::ranges::views::filter([&mouse_pos](Node n) { return CheckCollisionPointCircle(mouse_pos, n.position, n.radius);}); auto hovering_connectors = nodes | std::ranges::views::filter([&mouse_pos](Node n) { return CheckCollisionPointCircle(mouse_pos, (n.position - ((n.position - mouse_pos)).norm() * (n.radius + 14.0f)), 8.0f);}); if(!mouse_down && !mouse_right_down && dragging_nodes.empty() && connecting_nodes.empty() && hovering_nodes.empty() && hovering_connectors.empty()) istate = InteractionState::HOVER_CANVAS; if((mouse_clicked || mouse_down || mouse_released) && !mouse_right_down && dragging_nodes.empty() && connecting_nodes.empty() && hovering_nodes.empty() && hovering_connectors.empty()) istate = InteractionState::DRAW_SELECTION; if(!hovering_nodes.empty() && !mouse_down && !mouse_right_down) istate = InteractionState::HOVER_NODE; if(!dragging_nodes.empty()) istate = InteractionState::DRAG_NODE; if(!hovering_connectors.empty() && istate == InteractionState::HOVER_CANVAS) istate = InteractionState::HOVER_CONNECTOR; if(!connecting_nodes.empty()) istate = InteractionState::CONNECT_NODE; switch(istate){ case InteractionState::HOVER_CANVAS: if(mouse_right_down) { camera.target = Vector2Add(camera.target, delta); } break; case InteractionState::DRAW_SELECTION: if(mouse_clicked){ select_start = mouse_pos; } if(mouse_down){ Vec2D 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 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 : nodes){ if(CheckCollisionPointRec(n.position, selection)) { n.in_rect = true; } else { n.in_rect = false; } } } if(mouse_released){ for(auto& n : nodes){ if(CheckCollisionPointRec(n.position, selection)) { n.selected = true; n.in_rect = false; } else { if(!IsKeyDown(KEY_LEFT_SHIFT)) n.selected = false; } } selection = (Rectangle){0,0,0,0}; } break; case InteractionState::HOVER_NODE: if(mouse_clicked) { if(IsKeyDown(KEY_LEFT_SHIFT)) { hovering_nodes.front().selected = true; } else { hovering_nodes.front().dragging = true; hovering_nodes.front().drag_offset = hovering_nodes.front().position - mouse_pos; } } if(mouse_right_clicked) hovering_nodes.front().config = true; break; case InteractionState::DRAG_NODE: if(IsKeyDown(KEY_LEFT_CONTROL)) { dragging_nodes.front().position = (Vec2D){round((mouse_pos.x+dragging_nodes.front().drag_offset.x)/app.grid)*app.grid, round((mouse_pos.y+dragging_nodes.front().drag_offset.y)/app.grid)*app.grid}; } else { dragging_nodes.front().position = mouse_pos + dragging_nodes.front().drag_offset; } for(auto &n : selected_nodes){ if(!n.picked_up) n.drag_offset = n.position - dragging_nodes.front().position; n.picked_up = true; n.position = dragging_nodes.front().position + n.drag_offset; } if(mouse_released) { dragging_nodes.front().dragging = false; for(auto &n : selected_nodes){ n.picked_up = false; } } break; case InteractionState::HOVER_CONNECTOR: if(mouse_clicked) hovering_connectors.front().connecting = true; break; case InteractionState::CONNECT_NODE: Vec2D connector_position = connecting_nodes.front().position - ((connecting_nodes.front().position - mouse_pos)).norm() * (connecting_nodes.front().radius + 14.0f); if(connecting_nodes.front().connecting && mouse_down){ DrawLine(connector_position.x, connector_position.y, mouse_pos.x, mouse_pos.y, (Color){229, 181, 103, 255}); } if(mouse_released) { if(!hovering_nodes.empty()){ cm.addConnection(&connecting_nodes.front(), &hovering_nodes.front()); } else if(!hovering_connectors.empty()){ cm.addConnection(&connecting_nodes.front(), &hovering_connectors.front()); } else { nodes.emplace_back(mouse_pos.x, mouse_pos.y, 15.0f); cm.addConnection(&connecting_nodes.front(), &nodes.back()); } connecting_nodes.front().connecting = false; } break; }; float wheel = GetMouseWheelMove(); if(wheel != 0){ if(!hovering_nodes.empty() && IsKeyDown(KEY_LEFT_CONTROL)){ hovering_nodes.front().midi_note += wheel; if(hovering_nodes.front().midi_note < 0 ) hovering_nodes.front().midi_note = midi.midiNotes.size()-1; if(hovering_nodes.front().midi_note >= midi.midiNotes.size() ) hovering_nodes.front().midi_note = 0; } else { Vector2 mouseWorldPos = GetScreenToWorld2D(GetMousePosition(), camera); camera.offset = GetMousePosition(); camera.target = mouseWorldPos; float scaleFactor = 1.0f + (0.25f*fabsf(wheel)); if (wheel < 0) scaleFactor = 1.0f/scaleFactor; camera.zoom = Clamp(camera.zoom*scaleFactor, 0.125f, 64.0f); } } for(int x = -app.grid*100; x < app.grid*100; x+=app.grid){ for(int y = -app.grid*100; y < app.grid*100; y+=app.grid){ DrawPixel(x, y, (Color){130,130,130,200}); } } for(auto& n : nodes){ n.draw(&mouse_pos, &camera); } cm.drawConnections(&mouse_pos, &camera); sm.drawSignals(&mouse_pos, &camera); midi.update(); EndMode2D(); // Main Config Window GuiWindowBox((Rectangle){0,0,280,(float)app.height}, "config"); { const int s_controls = 55; if(GuiButton((Rectangle){20, (float)s_controls, 60, 30}, "PLAY")) sm.play ? sm.pause() : sm.start(); if(GuiButton((Rectangle){100, (float)s_controls, 60, 30}, "STOP")) sm.stop(); if(GuiButton((Rectangle){180, (float)s_controls, 60, 30}, "RESET")) sm.reset(); const int s_bpm = s_controls + 35; GuiLabel((Rectangle){20, (float)s_bpm, 120, 20}, "TIMING"); GuiLabel((Rectangle){200, (float)s_bpm, 40, 20}, std::format("{}",app.bpm).c_str()); float _bpm = (float)app.bpm; GuiSlider((Rectangle){20, (float)s_bpm + 25, 240, 20}, "", "", &_bpm, 1, 300); app.bpm = _bpm; int s_midi = s_bpm + 55; GuiLabel((Rectangle){20, (float)s_midi, 240, 20}, "MIDI OUT"); int i = 0; for(auto& mo : midi.midi_out_ports){ GuiCheckBox((Rectangle){20, (float)s_midi + 25 + i*20.0f, 20, 20}, mo.port.port_name.c_str(), &mo.enabled); i++; } if(GuiButton((Rectangle){20, (float)s_midi + 45 + i*20.0f, 240, 20}, "ENABLE FOR ALL")) { for(auto &midis : midi.midi_out_ports){ if(midis.enabled){ for(auto &n : nodes){ n.midi_outs[midis.number].enabled = true; } } } } int s_signal = s_midi + 90 + 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 = rand() % nodes.size(); sm.addSignalAtNode(&nodes[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 = app.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, 2); s.multiplier = s_multiplier; signal_configs++; } int s_nodes = s_signal + 60 + 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")){ nodes.emplace_back(200.0f, 200.0f, 15.0f); } DrawFPS(20, app.height - 30); } // Per Node Config Windows for(auto& n : nodes){ Vector2 np = GetWorldToScreen2D((Vector2){n.position.x, n.position.y}, camera); for(auto& mo : 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); } } if(n.config){ 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); } int y_midi = 120; GuiLabel((Rectangle){np.x + 20, np.y + y_midi, 220, 20}, "MIDI OUT"); int i = 0; for(auto& mo : n.midi_outs){ GuiCheckBox((Rectangle){np.x + 20, np.y + y_midi + 20 + i*20.0f, 20, 20}, mo.second.name.c_str(), &mo.second.enabled); i++; } int y_channel = y_midi + 30 + i*20; GuiLabel((Rectangle){np.x + 20, np.y + y_channel, 220, 20}, "CHANNEL"); GuiToggleGroup((Rectangle){np.x + 20, np.y + y_channel + 30, 25, 20}, "1;2;3;4;5;6;7;8\n9;10;11;12;13;14;15;16", &n.midi_channel); GuiLabel((Rectangle){np.x + 20, np.y + y_channel + 90, 100, 20}, "NOTE"); float v = n.midi_note; GuiLabel((Rectangle){np.x + 210, np.y + y_channel + 90, 20, 20}, 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, 5000); 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; } } for(auto& c : cm.connections){ if(c.config){ Vec2D config_handle = c.v2->position - ((c.v2->position - c.v1->position))*0.75f; c.config = !GuiWindowBox((Rectangle){config_handle.x + 10, config_handle.y + 10, 200, 300}, "connection"); GuiToggle((Rectangle){config_handle.x + 10, config_handle.y + 50, 80, 20}, "v1v2", &c.v1_v2); GuiToggle((Rectangle){config_handle.x + 100, config_handle.y + 50, 80, 20}, "v2v1", &c.v2_v1); } } EndDrawing(); } CloseWindow(); return 0; }