#include "../../src/gui/main_window.hpp" #include "../../src/gui/graph_widget.hpp" #include "../../src/gui/sequencer_widget.hpp" #include "../../src/gui/piano_roll.hpp" #include #include #include #include #include #include namespace { Fl_Widget *find(Fl_Group &group, const char *label) { for (int i = 0; i < group.children(); ++i) { Fl_Widget *child = group.child(i); if (child->label() != nullptr && std::strstr(child->label(), label) != nullptr) return child; if (auto *nested = dynamic_cast(child)) if (auto *match = find(*nested, label)) return match; } return nullptr; } GraphWidget *canvas_in(Fl_Group &group) { for (int i = 0; i < group.children(); ++i) { if (auto *canvas = dynamic_cast(group.child(i))) return canvas; if (auto *nested = dynamic_cast(group.child(i))) if (auto *canvas = canvas_in(*nested)) return canvas; } return nullptr; } SequencerWidget *timeline_in(Fl_Group &group) { for (int i = 0; i < group.children(); ++i) { if (auto *s = dynamic_cast(group.child(i))) return s; if (auto *g = dynamic_cast(group.child(i))) if (auto *s = timeline_in(*g)) return s; } return nullptr; } void pump() { const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(400); while (std::chrono::steady_clock::now() < end) Fl::wait(0.02); } } #define CHECK(c) do { if (!(c)) { std::fprintf(stderr,"FAIL %s:%d: %s\n",__FILE__,__LINE__,#c); return 1; } } while (0) int main(int argc, char **argv) { // Real JACK backend and real FLTK event loop. No routes, no playback data. for (int iteration = 0; iteration < 3; ++iteration) { MainWindow window(false); window.show(argc, argv); CHECK(find(window, "ready / stopped") != nullptr); auto *play = find(window, "Play"); auto *stop = find(window, "Stop"); auto *reconnect = find(window, "Reconnect"); CHECK(play != nullptr && stop != nullptr && reconnect != nullptr); auto *canvas = canvas_in(window); CHECK(canvas != nullptr); auto *timeline = timeline_in(window); CHECK(timeline != nullptr); CHECK(canvas->add_node(XTC_NODE_OSCILLATOR) == XTC_OK); CHECK(canvas->add_node(XTC_NODE_GAIN) == XTC_OK); CHECK(canvas->add_node(XTC_NODE_OUTPUT) == XTC_OK); CHECK(canvas->connect_ports({0U, 1U, 1U, 0U}) == XTC_OK); CHECK(canvas->connect_ports({1U, 1U, 2U, 0U}) == XTC_OK); for (int restart = 0; restart < 3; ++restart) { uint64_t previous = timeline->position(); CHECK(play->active()); play->do_callback(); pump(); CHECK(find(window, "running — modular graph") != nullptr); CHECK(timeline->position() > previous); CHECK(find(window, "Graph: active in engine") != nullptr); const xtc_graph *unchanged = canvas->graph(); CHECK(canvas->connect_ports({1U, 1U, 1U, 0U}) == XTC_ERR_GRAPH_CYCLE); CHECK(canvas->graph() == unchanged); CHECK(canvas->add_node(XTC_NODE_METER) == XTC_OK); pump(); CHECK(find(window, "Graph: active in engine") != nullptr); CHECK(canvas->delete_selected() == XTC_OK); pump(); CHECK(find(window, "Graph: active in engine") != nullptr); auto *status = find(window, "JACK |"); CHECK(status != nullptr); unsigned int rate = 0U, frames = 0U, blocks = 0U; CHECK(std::sscanf(status->label(), "JACK | %u Hz | %u frames | stereo | %u blocks", &rate, &frames, &blocks) == 3); CHECK(rate > 0U && frames > 0U && blocks > 0U); stop->do_callback(); CHECK(find(window, "ready / stopped") != nullptr); uint64_t paused = timeline->position(); pump(); CHECK(timeline->position() == paused); } reconnect->do_callback(); CHECK(find(window, "ready / stopped") != nullptr); play->do_callback(); pump(); CHECK(find(window, "running — modular graph") != nullptr); stop->do_callback(); char directory[] = "/tmp/xtc-live-record-XXXXXX"; CHECK(mkdtemp(directory) != nullptr); std::string take = std::string(directory) + "/take.wav"; std::string song = std::string(directory) + "/song.xtc"; CHECK(window.record_audio(take.c_str()) == XTC_OK); pump(); CHECK(find(window, "recording JACK input") != nullptr); stop->do_callback(); size_t tracks, clips; CHECK(xtc_project_get_counts(timeline->project(), &tracks, &clips) == XTC_OK && clips == 1); CHECK(window.save_project(song.c_str()) == XTC_OK); CHECK(window.open_project(song.c_str()) == XTC_OK); play->do_callback(); pump(); CHECK(find(window, "running — modular graph") != nullptr); auto *add_midi = find(window, "Add MIDI clip"); CHECK(add_midi != nullptr); add_midi->do_callback(); CHECK(window.open_piano_roll() == XTC_OK); CHECK(window.piano_roll()->add_note(0, 960, 69) == XTC_OK); CHECK(window.save_project(song.c_str()) == XTC_OK); CHECK(window.open_project(song.c_str()) == XTC_OK); size_t notes; CHECK(xtc_project_note_count(timeline->project(), ¬es) == XTC_OK && notes == 1); play->do_callback(); pump(); CHECK(find(window, "Graph: active in engine") != nullptr); CHECK(unlink(take.c_str()) == 0 && unlink(song.c_str()) == 0 && rmdir(directory) == 0); window.do_callback(); // Close with an active client; synchronizes callbacks. CHECK(!window.shown()); pump(); // No timer may access the closed client. } std::puts("Live GUI/JACK graph edits/revision acknowledgements/start/stop/reconnect/active-close tests passed."); return 0; }