Skip to content

Fix/dag preset connectivity and layout#202

Merged
sudip-mondal-2002 merged 40 commits into
sudip-mondal-2002:mainfrom
MohitBareja16:fix/dag-preset-connectivity-and-layout
May 26, 2026
Merged

Fix/dag preset connectivity and layout#202
sudip-mondal-2002 merged 40 commits into
sudip-mondal-2002:mainfrom
MohitBareja16:fix/dag-preset-connectivity-and-layout

Conversation

@MohitBareja16

@MohitBareja16 MohitBareja16 commented May 22, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Resolves a critical backend/frontend synchronization bug preventing modular Directed Acyclic Graph (DAG) presets from correctly loading into the audio engine. Specifically, this PR:

  • Restores engine dummy effects state: Synchronizes dummy_effects_ immediately after parsing preset.nodes via engine.restore_effects_state(). This prevents the GUI sync loop from aggressively treating active loaded pedals as orphaned nodes and deleting them on load.
  • Fixes Graph Endpoint Reachability: Explicitly anchors "Input", "Output", and "Amp Sim" nodes by flagging them as is_graph_input and is_graph_output during reconstruction. This ensures the AudioGraphExecutor recognizes the pathways and correctly compiles the audio topology without defaulting to an unreachable DISCONNECTED state.
  • Resolves String Mappings: Assigns proper node layout display names using pedal->name() to ensure accurate string mapping so pedals render properly as interactive widgets rather than generic utility blocks.
  • Corrects Legacy Auto-Layout: Unblocks standard Input and Output dummy nodes during legacy linear preset loading, assigning consistent 2D coordinate spacing so they no longer overlap dynamically generated pedals visually.

Related Issue

Fixes #148

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • ♻️ Refactor / code cleanup
  • 📝 Documentation
  • ✅ Tests
  • 🔧 Build / CI / Configuration

How Was This Tested?

  • Platform(s) tested on: Linux
  • Test command run: make -j4 && ./tests/test_amplitron
  • Manual test steps (if applicable):
    1. Booted Amplitron, created a custom rig utilizing "Splitter", "Looper", and "Amp Sim" wired securely.
    2. Hit Save and verified that structural links safely preserved internally in .json.
    3. Reloaded the preset via GUI and confirmed 1) graph correctly drew structural widgets, 2) cables successfully matched endpoints natively, and 3) none of the nodes crashed to a DISCONNECTED state.
    4. Loaded an older legacy linear preset file and ensured all sequential pedals, Input block, and Amp block snapped to a grid evenly across the horizontal canvas axis without overlapping.

Checklist

  • Code compiles and builds successfully on my platform
  • All existing tests pass (./tests/test_amplitron)
  • New tests added for new functionality (if applicable)
  • Documentation updated (README, code comments) if applicable
  • No blocking calls on the audio thread (no std::mutex::lock() on the hot path)
  • Tested on: Linux

Summary by CodeRabbit

  • New Features

    • Save/load full graph-style presets (format v2) with nodes, links, positions, Cabinet IR metadata, and configurable mixer/merge inputs.
  • Bug Fixes

    • Stronger validation rejects malformed graph presets, avoids partial/invalid restores, preserves legacy migration, and ensures MIDI mappings are consistently serialized.
  • Tests

    • Large expansion of graph, preset, migration, serialization, error-handling, and persistence tests.
  • UI

    • Improved node placement: prefer backend coordinates when available and use consistent horizontal spacing per routing type.

Review Change Stack

MohitBareja16 and others added 25 commits May 19, 2026 17:41
- Synchronize loaded graph nodes with engine dummy_effects_ so UI widgets persist.
- Dynamically parse pedal->name() to ensure accurate UI string matching.
- Anchor 'Input' and 'Output' nodes explicitly during preset load to ensure graph reachability.
- Map legacy preset UI coordinates consistently across all nodes.
@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@sudip-mondal-2002, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 37 minutes and 32 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4bf08ce-7d04-4883-8758-55aafc3024d7

📥 Commits

Reviewing files that changed from the base of the PR and between a22d5a9 and dde7888.

📒 Files selected for processing (9)
  • src/audio/audio_graph.cpp
  • src/audio/audio_graph.h
  • src/gui/pedal_board_chain.cpp
  • src/preset_json.cpp
  • src/preset_manager_io.cpp
  • tests/test_audio_graph.cpp
  • tests/test_framework.h
  • tests/test_json_serialization.cpp
  • tests/test_preset_manager.cpp
📝 Walkthrough

Walkthrough

Adds format-v2 graph-aware preset model and JSON converters; implements PresetManager graph_to_json/graph_from_json and switches save/load to persist full AudioGraph (nodes, links, positions); syncs GUI positions, adds AudioGraph num_inputs, and expands tests for roundtrips and error cases.

Changes

Graph Persistence and Serialization

Layer / File(s) Summary
Preset data model and API
src/preset_manager.h
PresetData gains routing, nested NodeData/LinkData, and nodes/links; PresetManager adds graph_to_json() and graph_from_json() APIs.
Ordered and nlohmann JSON converters
src/preset_json.cpp
Add ordered_json/ADL converters for EffectData/NodeData/LinkData/MidiMapping; set format_version = 2; always emit midi_mappings; emit effects for linear routing or nodes+links for graph routing; validate required arrays/fields when routing == "graph".
PresetManager graph I/O and load/save branching
src/preset_manager_io.cpp
save_preset() now serializes the routed AudioGraph with routing="graph" capturing positions, params, and link endpoints. load_preset() branches on routing: graph path uses graph_from_json() and restores engine effect state; legacy path rebuilds linear chain (including IR Cabinet→Cabinet migration) and repositions nodes.

UI State Synchronization and Rendering

Layer / File(s) Summary
Canvas position defaults
src/gui/pedal_board_chain.cpp
Assign default UI positions preferring backend node.x/node.y when non-zero; otherwise compute insertion X by scanning existing positioned nodes and extending right using routing-type widths.
Render cleanup and include removal
src/gui/pedal_board_chain.cpp
Remove unused <algorithm> include and minor rendering simplifications.

AudioGraph API

Layer / File(s) Summary
Variable mixer inputs
src/audio/audio_graph.h, src/audio/audio_graph.cpp
AudioGraph::add_node gains a num_inputs parameter (default 0). Mixer/MergeSum node creation now makes num_inputs input pins (fallback to 2 when non-positive).

Tests: serialization, graph behavior, integration

Layer / File(s) Summary
Audio graph test helpers and core behavior
tests/test_audio_graph.cpp
Update mock_save_graph/mock_load_graph to new format; export effect.type as effect.name(); add tests for link enforcement, automatic link cleanup on node removal, mixer input sizing, and invalid query handling.
JSON node/link roundtrip and error tests
tests/test_json_serialization.cpp
Roundtrip test for NodeData/LinkData and negative tests ensuring from_json_ext/from_json fail when required graph arrays are missing; tests for skipping invalid-typed params/metadata and defaulting behavior.
PresetManager integration and migration tests
tests/test_preset_manager.cpp
Update save/load tests to assert nodes presence and lowercase effect type names; add graph roundtrip and parallel-amp integration tests verifying node/link counts and positions; add legacy IR Cabinet→Cabinet migration and graph-cabinet loading tests; add widened mixer and config/preset-dir robustness tests.
Test framework helper
tests/test_framework.h
Add ASSERT_THROW(expr, ExceptionType) macro for exception-assertion tests.

Sequence Diagram

sequenceDiagram
  participant UI as Gui (PedalBoard / GuiGraphState)
  participant PM as PresetManager
  participant AG as AudioGraph
  participant FS as Filesystem

  UI->>PM: save_preset(graph) -> graph_to_json(nodes, links, positions)
  PM->>AG: read node positions and topology
  PM->>FS: write JSON (format_version=2, routing="graph")
  FS-->>PM: file stored
  PM-->>UI: confirm saved

  UI->>PM: load_preset(file)
  PM->>FS: read JSON
  FS-->>PM: JSON content
  PM->>AG: graph_from_json(recreate nodes, pins, links, positions)
  AG-->>PM: graph recreated (or false on failure)
  PM-->>UI: restore GUI positions and effects state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested labels

type:bug, type:testing, level:intermediate, quality:clean, mentor:MohitBareja16

Suggested reviewers

  • sudip-mondal-2002

"I hopped through nodes and wires with glee,
Saved every pin and the positions I see,
Links snug as carrots, topology neat,
Presets now travel — roundtrip is sweet!
🐇✨"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix/dag preset connectivity and layout' directly addresses the PR's main objective of fixing backend/frontend synchronization bugs in DAG preset loading and node layout display.
Linked Issues check ✅ Passed The PR comprehensively implements all key objectives from issue #148: graph preset serialization with nodes/links, position persistence, routing field support, legacy preset backwards compatibility, and graph_to_json/graph_from_json APIs.
Out of Scope Changes check ✅ Passed All changes directly support graph preset serialization and DAG loading: JSON serialization/deserialization, preset persistence, GUI state synchronization, node layout positioning, and comprehensive test coverage—no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
tests/test_json_serialization.cpp (1)

231-232: ⚡ Quick win

Avoid order-dependent endpoint assertions (front() / back()).

At Line 231 and Line 232, this assumes node storage order equals semantic graph order. Prefer counting flagged endpoints so valid reorderings don’t create false failures.

Suggested fix
-    ASSERT_TRUE(graph.get_nodes().front().is_graph_input);
-    ASSERT_TRUE(graph.get_nodes().back().is_graph_output);
+    size_t input_count = 0, output_count = 0;
+    for (const auto& n : graph.get_nodes()) {
+        if (n.is_graph_input)  ++input_count;
+        if (n.is_graph_output) ++output_count;
+    }
+    ASSERT_EQ(input_count, 1u);
+    ASSERT_EQ(output_count, 1u);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_json_serialization.cpp` around lines 231 - 232, The assertions
using graph.get_nodes().front() and .back() are order-dependent; instead iterate
over graph.get_nodes() and count nodes where is_graph_input is true and where
is_graph_output is true (using the get_nodes() vector and the is_graph_input /
is_graph_output flags) then assert the expected counts (e.g., at least one input
and one output or exact expected numbers) so tests pass regardless of node
ordering.
tests/test_preset_manager.cpp (1)

365-367: ⚡ Quick win

Use a real Amp Sim effect here to validate the intended load path.

Using Overdrive as an “Amp Sim” proxy means this test does not actually exercise the Amp Sim-specific mapping/output-endpoint behavior in preset load logic. Prefer constructing the real Amp Sim effect type in this integration test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_preset_manager.cpp` around lines 365 - 367, The test currently
adds amp nodes using Overdrive as a proxy (graph.add_node(...,
std::make_shared<Overdrive>())), which won't exercise Amp Sim-specific mapping;
replace the proxy with the real amp-sim effect type (e.g.,
std::make_shared<AmpSim>() or the actual Amp Sim class used in production) for
the nodes created by add_node ("Amp Sim") so the preset load logic routes and
output endpoints for Amp Sim are actually validated; update the two instances
where Overdrive is passed to add_node to construct the real Amp Sim effect
instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/preset_json.cpp`:
- Around line 395-400: The ADL JSON serializers omit PresetData::NodeData::mix,
causing mix to be lost on round-trips; update the ADL path by adding the "mix"
field to the node object in to_json(nlohmann::json&, const PresetData&) (e.g.,
include {"mix", node.mix} alongside id/type/position/enabled when building jn)
and read it back in from_json(const nlohmann::json&, PresetData&) by extracting
node["mix"] into NodeData::mix so the ADL (to_json/from_json) graph
serialization preserves mix just like the ext path does.

In `@src/preset_manager_io.cpp`:
- Around line 425-430: The parse_pin logic calls std::stoi on substrings for
"out..." and "in..." without guarding against invalid input; wrap the std::stoi
calls inside a try/catch (catch std::invalid_argument and std::out_of_range)
around the parsing in the branches that handle "out" and "in" so that on any
exception you return -1 (or the same sentinel used for failure) instead of
letting the exception propagate, updating the branches that reference
node->output_pin_ids and node->input_pin_ids so they only access by idx after
successful parsing.
- Around line 293-296: In PresetManager::graph_to_json, the pedal mix value is
not being serialized (nd.mix is never set) so roundtripping loses the mix; set
nd.mix from the pedal's current mix (e.g., call the pedal's mix getter such as
mix() or get_mix()) alongside nd.enabled and nd.params so that graph_from_json
can restore it via pedal->set_mix(node.mix).

In `@tests/test_preset_manager.cpp`:
- Around line 313-314: The test currently clears
GuiGraphState::get_instance().node_positions only at the start; to avoid leaking
singleton state add symmetric teardown cleanup (or a RAII/test fixture) that
clears GuiGraphState::get_instance().node_positions at the end of the test (and
do the same for the other test around lines 354-355); implement this by adding a
tearDown/fixture destructor or an explicit clear call after assertions so
node_positions is reset for subsequent tests.
- Around line 126-130: The loop counting graph nodes is using a loose predicate
(n.routing_type != NodeRoutingType::StandardEffect || n.pedal != nullptr) which
also counts splitters/mixers; change the condition that increments
graph_node_count to only count real effect nodes, e.g. require n.routing_type ==
NodeRoutingType::StandardEffect && n.pedal != nullptr (update the increment site
that references n.routing_type and n.pedal and keep the ASSERT_EQ against
orig_effects_count).

---

Nitpick comments:
In `@tests/test_json_serialization.cpp`:
- Around line 231-232: The assertions using graph.get_nodes().front() and
.back() are order-dependent; instead iterate over graph.get_nodes() and count
nodes where is_graph_input is true and where is_graph_output is true (using the
get_nodes() vector and the is_graph_input / is_graph_output flags) then assert
the expected counts (e.g., at least one input and one output or exact expected
numbers) so tests pass regardless of node ordering.

In `@tests/test_preset_manager.cpp`:
- Around line 365-367: The test currently adds amp nodes using Overdrive as a
proxy (graph.add_node(..., std::make_shared<Overdrive>())), which won't exercise
Amp Sim-specific mapping; replace the proxy with the real amp-sim effect type
(e.g., std::make_shared<AmpSim>() or the actual Amp Sim class used in
production) for the nodes created by add_node ("Amp Sim") so the preset load
logic routes and output endpoints for Amp Sim are actually validated; update the
two instances where Overdrive is passed to add_node to construct the real Amp
Sim effect instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 35dc5030-0f23-4d28-bc36-a4017a6a35be

📥 Commits

Reviewing files that changed from the base of the PR and between 11deae0 and 37b90d2.

📒 Files selected for processing (11)
  • src/audio/audio_engine.h
  • src/audio/audio_engine_chain.cpp
  • src/audio/audio_graph.cpp
  • src/audio/audio_graph.h
  • src/gui/pedal_board_chain.cpp
  • src/preset_json.cpp
  • src/preset_manager.h
  • src/preset_manager_io.cpp
  • tests/test_audio_graph.cpp
  • tests/test_json_serialization.cpp
  • tests/test_preset_manager.cpp

Comment thread src/preset_json.cpp Outdated
Comment thread src/preset_manager_io.cpp Outdated
Comment thread src/preset_manager_io.cpp Outdated
Comment thread tests/test_preset_manager.cpp Outdated
Comment thread tests/test_preset_manager.cpp Outdated
Comment on lines +313 to +314
GuiGraphState::get_instance().node_positions.clear();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reset GuiGraphState::node_positions at test end as well.

These tests mutate global singleton state; clearing only at start can leak state into later tests in the suite. Add end-of-test cleanup (or a small guard fixture) for isolation.

Also applies to: 354-355

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_preset_manager.cpp` around lines 313 - 314, The test currently
clears GuiGraphState::get_instance().node_positions only at the start; to avoid
leaking singleton state add symmetric teardown cleanup (or a RAII/test fixture)
that clears GuiGraphState::get_instance().node_positions at the end of the test
(and do the same for the other test around lines 354-355); implement this by
adding a tearDown/fixture destructor or an explicit clear call after assertions
so node_positions is reset for subsequent tests.

@github-actions

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Report 📊

Line Coverage: 85.2%

✅ Coverage meets threshold: 85.2% >= 60%

Full Coverage Summary
Summary coverage rate:
  lines......: 85.2% (3916 of 4595 lines)
  functions..: 93.3% (502 of 538 functions)
  branches...: no data found

@github-actions

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

PR Preview Removed

The GitHub Pages preview for this PR has been removed because the PR was closed.

@sudip-mondal-2002 sudip-mondal-2002 added High Impact Significant user-facing improvement Audio Processing Related to DSP, effects, or signal processing level:advanced Advanced task · 45 GSSoC points DAG Related to DAG-based modular signal routing architecture labels May 24, 2026
@MohitBareja16 MohitBareja16 added level:critical and removed level:advanced Advanced task · 45 GSSoC points labels May 25, 2026

@sudip-mondal-2002 sudip-mondal-2002 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but from next time Add comments for long code blocks so i can just dont have to go through everything

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Audio Processing Related to DSP, effects, or signal processing DAG Related to DAG-based modular signal routing architecture gssoc:approved GSSoC 2026 contribution High Impact Significant user-facing improvement level:critical mentor:sudip-mondal-2002 quality:exceptional type:feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Graph preset serialization — save/load node positions and cable topology as JSON

2 participants