Phase 23: refine routing inspection, signal-flow layout and atomic wire insertion
This commit is contained in:
parent
01d8c1d661
commit
ba5c2503a7
15 changed files with 533 additions and 22 deletions
|
|
@ -63,6 +63,15 @@ SequencerWidget *timeline(Fl_Group *group)
|
|||
}
|
||||
return nullptr;
|
||||
}
|
||||
GraphWidget *graph_canvas(Fl_Group *group)
|
||||
{
|
||||
for (int i = 0; i < group->children(); ++i) {
|
||||
if (auto *canvas = dynamic_cast<GraphWidget *>(group->child(i))) return canvas;
|
||||
if (auto *nested = dynamic_cast<Fl_Group *>(group->child(i)))
|
||||
if (auto *canvas = graph_canvas(nested)) return canvas;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
void gesture(Fl_Widget *s, int left, int top, int dx, int dy)
|
||||
{
|
||||
int old_x = Fl::e_x, old_y = Fl::e_y, old_key = Fl::e_keysym;
|
||||
|
|
@ -549,6 +558,50 @@ int main(int argc, char **argv)
|
|||
CHECK(xtc_project_get_track(s->project(),0,&t) == XTC_OK && !std::strcmp(t.name,"Keys"));
|
||||
window.do_callback();
|
||||
}
|
||||
{
|
||||
reset();
|
||||
MainWindow window(false);
|
||||
auto *canvas = graph_canvas(&window);
|
||||
CHECK(canvas != nullptr);
|
||||
CHECK(canvas->add_node(XTC_NODE_OSCILLATOR) == XTC_OK);
|
||||
CHECK(canvas->add_node(XTC_NODE_OUTPUT) == XTC_OK);
|
||||
CHECK(canvas->connect_ports({0,1,1,0}) == XTC_OK);
|
||||
CHECK(canvas->select_connection(0,1) && contains(&window, "Wire: #0 audio out -> #1 sum in"));
|
||||
const xtc_graph *original = canvas->graph();
|
||||
unsigned int submitted = submissions;
|
||||
submit_result = XTC_ERR_CAPACITY;
|
||||
CHECK(canvas->insert_gain() == XTC_ERR_CAPACITY && canvas->graph() == original && submissions == submitted+1);
|
||||
submit_result = XTC_OK;
|
||||
CHECK(canvas->insert_gain() == XTC_OK);
|
||||
size_t inserted = canvas->selected_node(), nodes, edges;
|
||||
CHECK(inserted == 2);
|
||||
CHECK(xtc_graph_get_counts(canvas->graph(), &nodes, &edges) == XTC_OK && nodes == 3 && edges == 2);
|
||||
xtc_routing_info route{};
|
||||
CHECK(canvas->routing(inserted,route) && route.level == 1 && route.reaches_output && route.inputs == 1);
|
||||
submitted = submissions;
|
||||
CHECK(canvas->auto_layout() && submissions == submitted);
|
||||
int sx = 0, sy = 0, gx = 0, gy = 0, ox = 0, oy = 0;
|
||||
CHECK(canvas->node_position(0,sx,sy) && canvas->node_position(inserted,gx,gy) && canvas->node_position(1,ox,oy));
|
||||
CHECK(sx < gx && gx < ox);
|
||||
CHECK(canvas->select_node(inserted) && contains(&window,"1 incoming, 1 outgoing"));
|
||||
CHECK(canvas->select_connection(0,inserted) && canvas->delete_selected() == XTC_OK);
|
||||
CHECK(canvas->select_node(inserted) && contains(&window,"unwired input uses external audio"));
|
||||
CHECK(window.undo() == XTC_OK); // Disconnect is independently undoable.
|
||||
CHECK(window.undo() == XTC_OK); // Whole insertion is one undo step.
|
||||
CHECK(xtc_graph_get_counts(canvas->graph(), &nodes, &edges) == XTC_OK && nodes == 2 && edges == 1);
|
||||
CHECK(window.undo(true) == XTC_OK);
|
||||
CHECK(xtc_graph_get_counts(canvas->graph(), &nodes, &edges) == XTC_OK && nodes == 3 && edges == 2);
|
||||
CHECK(canvas->select_node(inserted));
|
||||
int key = Fl::e_keysym, state = Fl::e_state;
|
||||
Fl::e_keysym = FL_Right; Fl::e_state = 0;
|
||||
CHECK(canvas->handle(FL_KEYDOWN) == 1 && canvas->selected_node() == 0);
|
||||
Fl::e_keysym = key; Fl::e_state = state;
|
||||
// Whole-project binding must give every new node a distinct position.
|
||||
canvas->reset_layout(); canvas->set_graph(canvas->graph());
|
||||
CHECK(canvas->node_position(0,sx,sy) && canvas->node_position(inserted,gx,gy) && canvas->node_position(1,ox,oy));
|
||||
CHECK((sx != gx || sy != gy) && (sx != ox || sy != oy) && (gx != ox || gy != oy));
|
||||
window.do_callback();
|
||||
}
|
||||
if (const char *module = std::getenv("XTC_TEST_PLUGIN")) {
|
||||
reset();
|
||||
MainWindow window(false);
|
||||
|
|
|
|||
|
|
@ -72,6 +72,22 @@ int main(int argc, char **argv)
|
|||
const xtc_graph *unchanged = canvas->graph();
|
||||
CHECK(canvas->connect_ports({1U, 1U, 1U, 0U}) == XTC_ERR_GRAPH_CYCLE);
|
||||
CHECK(canvas->graph() == unchanged);
|
||||
// Replace a live wire by a complete unity-gain chain at one publication.
|
||||
CHECK(canvas->select_connection(1,2));
|
||||
CHECK(canvas->insert_gain() == XTC_OK);
|
||||
size_t inserted = canvas->selected_node();
|
||||
pump();
|
||||
CHECK(find(window, "Graph: active in engine") != nullptr);
|
||||
CHECK(find(window, "running — modular graph") != nullptr);
|
||||
xtc_routing_info route{};
|
||||
CHECK(canvas->routing(inserted, route) && route.inputs == 1 && route.outputs == 1 && route.reaches_output);
|
||||
CHECK(canvas->connect_ports({inserted,1,1,0}) == XTC_ERR_GRAPH_CYCLE);
|
||||
CHECK(canvas->select_connection(1,inserted) && canvas->delete_selected() == XTC_OK); pump();
|
||||
CHECK(canvas->select_connection(inserted,2) && canvas->delete_selected() == XTC_OK); pump();
|
||||
CHECK(canvas->connect_ports({1,1,2,0}) == XTC_OK); pump();
|
||||
CHECK(canvas->select_node(inserted) && canvas->delete_selected() == XTC_OK);
|
||||
pump();
|
||||
CHECK(find(window, "Graph: active in engine") != nullptr);
|
||||
CHECK(canvas->add_node(XTC_NODE_METER) == XTC_OK);
|
||||
pump();
|
||||
CHECK(find(window, "Graph: active in engine") != nullptr);
|
||||
|
|
|
|||
84
tests/unit/test_routing.c
Normal file
84
tests/unit/test_routing.c
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#include "xtc/xtc_engine.h"
|
||||
#include "xtc/xtc_realtime.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
xtc_graph *graph = NULL, *inserted = NULL, *bad = NULL, *legacy = NULL, *full = NULL;
|
||||
xtc_plan *old = NULL, *fresh = NULL;
|
||||
xtc_realtime *engine = NULL;
|
||||
xtc_graph_edit edit = {0};
|
||||
xtc_routing_info info[256] = {{0}}, sentinel[256];
|
||||
xtc_realtime_status status;
|
||||
size_t source, gain, output, unused, added = SIZE_MAX, count = 999, nodes, edges, i;
|
||||
float a[32], b[32];
|
||||
assert(xtc_graph_create(8, &graph) == XTC_OK);
|
||||
assert(xtc_graph_require_output_nodes(graph, 1) == XTC_OK);
|
||||
assert(xtc_graph_add_oscillator(graph, 12000, &source) == XTC_OK);
|
||||
assert(xtc_graph_add_gain(graph, 0.25f, &gain) == XTC_OK);
|
||||
assert(xtc_graph_add_output(graph, &output) == XTC_OK);
|
||||
assert(xtc_graph_add_meter(graph, &unused) == XTC_OK);
|
||||
assert(xtc_graph_connect(graph, source, gain) == XTC_OK && xtc_graph_connect(graph, gain, output) == XTC_OK);
|
||||
assert(xtc_graph_analyze(graph, info, 256, &count) == XTC_OK && count == 4);
|
||||
assert(info[0].id == source && info[0].inputs == 0 && info[0].outputs == 1 && info[0].level == 0 && info[0].reaches_output && !info[0].external_input);
|
||||
assert(info[1].inputs == 1 && info[1].outputs == 1 && info[1].level == 1 && info[1].reaches_output && !info[1].external_input);
|
||||
assert(info[2].level == 2 && info[2].reaches_output && !info[2].external_input);
|
||||
assert(!info[3].reaches_output && info[3].external_input);
|
||||
memcpy(sentinel, info, sizeof(info)); count = 999;
|
||||
assert(xtc_graph_analyze(graph, info, 3, &count) == XTC_ERR_CAPACITY && count == 999 && !memcmp(info, sentinel, sizeof(info)));
|
||||
assert(xtc_graph_analyze(NULL, info, 256, &count) == XTC_ERR_INVALID_ARGUMENT);
|
||||
assert(xtc_graph_analyze(graph, NULL, 256, &count) == XTC_ERR_INVALID_ARGUMENT);
|
||||
edit.kind = XTC_EDIT_INSERT_GAIN;
|
||||
edit.data.connection = (xtc_connection_info){source, 1, gain, 0};
|
||||
assert(xtc_graph_apply_edit(graph, &edit, &inserted, &added) == XTC_OK && added > unused);
|
||||
assert(xtc_graph_get_counts(graph, &nodes, &edges) == XTC_OK && nodes == 4 && edges == 2);
|
||||
assert(xtc_graph_get_counts(inserted, &nodes, &edges) == XTC_OK && nodes == 5 && edges == 3);
|
||||
assert(xtc_graph_analyze(inserted, info, 256, &count) == XTC_OK && count == 5);
|
||||
assert(info[4].id == added && info[4].level == 1 && info[1].level == 2 && info[2].level == 3);
|
||||
assert(xtc_graph_compile(graph, 48000, 32, &old) == XTC_OK);
|
||||
assert(xtc_graph_compile(inserted, 48000, 32, &fresh) == XTC_OK);
|
||||
assert(xtc_plan_process(old, NULL, a, 32) == XTC_OK && xtc_plan_process(fresh, NULL, b, 32) == XTC_OK && !memcmp(a, b, sizeof(a)));
|
||||
xtc_plan_destroy(old); xtc_plan_destroy(fresh);
|
||||
/* Missing/invalid wire never consumes IDs or returns a partial graph. */
|
||||
size_t inserted_id = added;
|
||||
added = 999;
|
||||
edit.data.connection.destination_node = unused;
|
||||
assert(xtc_graph_apply_edit(graph, &edit, &bad, &added) == XTC_ERR_NOT_FOUND && !bad && added == 999);
|
||||
edit.data.connection = (xtc_connection_info){source, 0, gain, 0};
|
||||
assert(xtc_graph_apply_edit(graph, &edit, &bad, &added) != XTC_OK && !bad && added == 999);
|
||||
assert(xtc_graph_create(2, &full) == XTC_OK);
|
||||
assert(xtc_graph_add_gain(full, 1, &source) == XTC_OK && xtc_graph_add_output(full, &output) == XTC_OK);
|
||||
assert(xtc_graph_connect(full, source, output) == XTC_OK);
|
||||
edit.data.connection = (xtc_connection_info){source, 1, output, 0};
|
||||
assert(xtc_graph_apply_edit(full, &edit, &bad, &added) == XTC_ERR_CAPACITY && !bad && added == 999);
|
||||
assert(xtc_graph_get_counts(full, &nodes, &edges) == XTC_OK && nodes == 2 && edges == 1);
|
||||
xtc_graph_destroy(full);
|
||||
/* Rejected cycle and disconnect leave an independently compiled plan usable. */
|
||||
edit.kind = XTC_EDIT_CONNECT;
|
||||
edit.data.connection = (xtc_connection_info){gain, 1, inserted_id, 0};
|
||||
assert(xtc_graph_apply_edit(inserted, &edit, &bad, NULL) == XTC_ERR_GRAPH_CYCLE && !bad);
|
||||
/* Pending/active plan handoff publishes the entire inserted chain at once. */
|
||||
assert(xtc_realtime_create(48000, 32, &engine) == XTC_OK);
|
||||
assert(xtc_realtime_submit(engine, graph) == XTC_OK);
|
||||
assert(xtc_realtime_process(engine, NULL, a, 32) == XTC_OK);
|
||||
for (i = 0; i < 100; ++i) {
|
||||
assert(xtc_realtime_submit(engine, i % 2 ? graph : inserted) == XTC_OK);
|
||||
assert(xtc_realtime_process(engine, NULL, b, 32) == XTC_OK && !memcmp(a, b, sizeof(a)));
|
||||
xtc_realtime_collect(engine);
|
||||
assert(xtc_realtime_get_status(engine, &status) == XTC_OK && status.active_revision == status.submitted_revision);
|
||||
}
|
||||
xtc_realtime_destroy(engine);
|
||||
/* Legacy terminal fallback is disabled whenever an explicit output exists. */
|
||||
assert(xtc_graph_create(4, &legacy) == XTC_OK);
|
||||
assert(xtc_graph_add_gain(legacy, 1, &gain) == XTC_OK);
|
||||
assert(xtc_graph_analyze(legacy, info, 256, &count) == XTC_OK && info[0].reaches_output && info[0].external_input);
|
||||
assert(xtc_graph_add_output(legacy, &output) == XTC_OK);
|
||||
assert(xtc_graph_analyze(legacy, info, 256, &count) == XTC_OK && !info[0].reaches_output && info[1].external_input);
|
||||
assert(xtc_graph_connect(legacy, gain, output) == XTC_OK);
|
||||
assert(xtc_graph_analyze(legacy, info, 256, &count) == XTC_OK && info[0].reaches_output && !info[1].external_input);
|
||||
xtc_graph_destroy(legacy); xtc_graph_destroy(graph); xtc_graph_destroy(inserted);
|
||||
puts("Routing reachability/levels/external input, atomic wire insertion/rollback and publication passed.");
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue