From f2e68f143e05a205da39d4ba7339ab076ddca016 Mon Sep 17 00:00:00 2001 From: Jaco du Preez Date: Fri, 7 Aug 2026 20:59:04 +0200 Subject: [PATCH 1/2] Fix S-param mode not reloading on project deserialize (issue #44) --- amplifier/src/amplifier_engine.cpp | 4 +- attenuator/src/attenuator_engine.cpp | 4 +- combiner/src/combiner_engine.cpp | 4 +- equalizer/src/equalizer_engine.cpp | 8 ++- ideal_filter/src/ideal_filter_engine.cpp | 4 +- tests/test_project_file.cpp | 74 ++++++++++++++++++++++++ 6 files changed, 92 insertions(+), 6 deletions(-) diff --git a/amplifier/src/amplifier_engine.cpp b/amplifier/src/amplifier_engine.cpp index 0e6841e..5f7a980 100644 --- a/amplifier/src/amplifier_engine.cpp +++ b/amplifier/src/amplifier_engine.cpp @@ -227,8 +227,10 @@ void AmplifierEngine::deserialize(const nlohmann::json &j) { if (!j.contains("enable_nonlinear") && (j.contains("oip2_dBm") || j.contains("oip3_dBm") || j.contains("p1db_dBm"))) m_nonlinear.setEnabled(true); - m_sparam_mode = j.value("sparam_mode", false); m_sparam_filepath = j.value("sparam_filepath", ""); + if (!m_sparam_filepath.empty()) + m_sparam_data.load(m_sparam_filepath); + m_sparam_mode = j.value("sparam_mode", false) && m_sparam_data.loaded(); m_sparam_fwd_idx = j.value("sparam_fwd_idx", 0); m_dirty = true; } diff --git a/attenuator/src/attenuator_engine.cpp b/attenuator/src/attenuator_engine.cpp index 7e4da45..86f9877 100644 --- a/attenuator/src/attenuator_engine.cpp +++ b/attenuator/src/attenuator_engine.cpp @@ -183,8 +183,10 @@ nlohmann::json AttenuatorEngine::serialize() const { void AttenuatorEngine::deserialize(const nlohmann::json &j) { m_atten_dB = j.contains("atten_dB") ? j["atten_dB"].get() : j.value("attenuation_dB", 0.0); - m_sparam_mode = j.value("sparam_mode", false); m_sparam_path = j.value("sparam_path", ""); + if (!m_sparam_path.empty()) + m_sparam.load(m_sparam_path); + m_sparam_mode = j.value("sparam_mode", false) && m_sparam.loaded(); m_dirty = true; } diff --git a/combiner/src/combiner_engine.cpp b/combiner/src/combiner_engine.cpp index 27480d0..1103621 100644 --- a/combiner/src/combiner_engine.cpp +++ b/combiner/src/combiner_engine.cpp @@ -63,8 +63,10 @@ nlohmann::json CombinerEngine::serialize() const { void CombinerEngine::deserialize(const nlohmann::json &j) { m_manual_mode = j.value("manual_mode", true); - m_sparam_mode = j.value("sparam_mode", false); m_sparam_path = j.value("sparam_path", ""); + if (!m_sparam_path.empty()) + m_sparam.load(m_sparam_path); + m_sparam_mode = j.value("sparam_mode", false) && m_sparam.loaded(); m_dirty = true; } diff --git a/equalizer/src/equalizer_engine.cpp b/equalizer/src/equalizer_engine.cpp index 91eaf94..1ccd203 100644 --- a/equalizer/src/equalizer_engine.cpp +++ b/equalizer/src/equalizer_engine.cpp @@ -161,14 +161,18 @@ nlohmann::json EqualizerEngine::serialize() const { {"ref_freq_Hz", m_ref_freq_Hz}, {"slope_dB_per_decade", m_slope_dB_per_decade}, {"sparam_mode", m_sparam_mode}, - {"sparam_filepath", m_sparam_filepath}}; + {"sparam_filepath", m_sparam_filepath}, + {"sparam_fwd_idx", m_sparam_fwd_idx}}; } void EqualizerEngine::deserialize(const nlohmann::json &j) { m_ref_gain_dB = j.value("ref_gain_dB", 0.0); m_ref_freq_Hz = j.value("ref_freq_Hz", 1e9); m_slope_dB_per_decade = j.value("slope_dB_per_decade", 0.0); - m_sparam_mode = j.value("sparam_mode", false); m_sparam_filepath = j.value("sparam_filepath", ""); + if (!m_sparam_filepath.empty()) + m_sparam_data.load(m_sparam_filepath); + m_sparam_mode = j.value("sparam_mode", false) && m_sparam_data.loaded(); + m_sparam_fwd_idx = j.value("sparam_fwd_idx", 0); m_dirty = true; } diff --git a/ideal_filter/src/ideal_filter_engine.cpp b/ideal_filter/src/ideal_filter_engine.cpp index 21133bf..f57682f 100644 --- a/ideal_filter/src/ideal_filter_engine.cpp +++ b/ideal_filter/src/ideal_filter_engine.cpp @@ -200,8 +200,10 @@ void IdealFilterEngine::deserialize(const nlohmann::json &j) { } else if (j.contains("fc_low_Hz")) { setCutoff_Hz(j["fc_low_Hz"].get()); } - m_sparam_mode = j.value("sparam_mode", false); m_sparam_filepath = j.value("sparam_filepath", ""); + if (!m_sparam_filepath.empty()) + m_sparam_data.load(m_sparam_filepath); + m_sparam_mode = j.value("sparam_mode", false) && m_sparam_data.loaded(); m_sparam_fwd_idx = j.value("sparam_fwd_idx", 0); m_dirty = true; } diff --git a/tests/test_project_file.cpp b/tests/test_project_file.cpp index 05dd836..0cae55f 100644 --- a/tests/test_project_file.cpp +++ b/tests/test_project_file.cpp @@ -438,3 +438,77 @@ TEST_CASE_METHOD(ImGuiFixture, "Round-trip: default component positions are (0,0 } std::remove(path.c_str()); } + +// --------------------------------------------------------------------------- +// 13 — Issue #44: S-param mode survives save/load for amplifier, ideal filter, +// equalizer, attenuator, and combiner. Previously deserialize() restored +// sparam_mode/sparam_filepath but never reloaded the Touchstone file, so +// a reloaded project silently fell back to ideal/manual mode. +// --------------------------------------------------------------------------- +static std::string sparamFixturePath() { + return std::string(PROJECT_SOURCE_DIR) + + "/component_data/amplifiers/adm-3844psm/ADM-8344PSM_SM_A_25C_De_5V_5V_102mA.s2p"; +} + +TEST_CASE_METHOD(ImGuiFixture, "Round-trip: S-param mode survives save/load (issue #44)", + "[project_file][sparam]") { + auto path = tempPath(); + std::remove(path.c_str()); + const std::string s2p = sparamFixturePath(); + { + RfSimulatorApp app; + app.newProject(); + + auto & = app.testComponents().add(10001, app.testGraphEngine()); + amp.setSParamFilepath(s2p); + REQUIRE(amp.sparamLoaded()); + + auto &flt = app.testComponents().add(10002, app.testGraphEngine()); + flt.setSParamFilepath(s2p); + REQUIRE(flt.sparamLoaded()); + + auto &eq = app.testComponents().add(10003, app.testGraphEngine()); + eq.setSParamFilepath(s2p); + REQUIRE(eq.sparamLoaded()); + + auto &atten = app.testComponents().add(10004, app.testGraphEngine()); + atten.setSParamFile(s2p); + REQUIRE(atten.sParamMode()); + + auto &comb = app.testComponents().add(10005, app.testGraphEngine()); + comb.setSParamFile(s2p); + REQUIRE(comb.sParamMode()); + + REQUIRE(app.componentCount() == 5); + app.saveProject(path); + } + { + RfSimulatorApp app; + app.loadProject(path); + REQUIRE(app.componentCount() == 5); + + auto amps = app.testComponents().byType(); + REQUIRE(amps.size() == 1); + CHECK(amps[0]->sparamMode() == true); + CHECK(amps[0]->sparamLoaded() == true); + + auto flts = app.testComponents().byType(); + REQUIRE(flts.size() == 1); + CHECK(flts[0]->sparamMode() == true); + CHECK(flts[0]->sparamLoaded() == true); + + auto eqs = app.testComponents().byType(); + REQUIRE(eqs.size() == 1); + CHECK(eqs[0]->sparamMode() == true); + CHECK(eqs[0]->sparamLoaded() == true); + + auto attens = app.testComponents().byType(); + REQUIRE(attens.size() == 1); + CHECK(attens[0]->sParamMode() == true); + + auto combs = app.testComponents().byType(); + REQUIRE(combs.size() == 1); + CHECK(combs[0]->sParamMode() == true); + } + std::remove(path.c_str()); +} \ No newline at end of file From 35aef61d4fce7e3a1268ad6bfede7b5e257c10fe Mon Sep 17 00:00:00 2001 From: Jaco du Preez Date: Fri, 7 Aug 2026 21:06:52 +0200 Subject: [PATCH 2/2] style: fix clang-format-18 violations in app.cpp and issue42 test The repo-wide format job (clang-format-18) flagged pre-existing violations carried from #42; no PR could pass until these were resolved. --- app/src/app.cpp | 8 ++++---- tests/test_issue42_multi_output.cpp | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/app.cpp b/app/src/app.cpp index 57f2290..2cb5262 100644 --- a/app/src/app.cpp +++ b/app/src/app.cpp @@ -542,10 +542,10 @@ void RfSimulatorApp::update_dsp() { for (auto *node : m_view_manager.nodes()) { if (node) { - node->view_enabled = - std::find_if(probed_sources.begin(), probed_sources.end(), - [node](const SignalSource &ps) { return ps.node == node; }) != - probed_sources.end(); + node->view_enabled = std::find_if(probed_sources.begin(), probed_sources.end(), + [node](const SignalSource &ps) { + return ps.node == node; + }) != probed_sources.end(); } } diff --git a/tests/test_issue42_multi_output.cpp b/tests/test_issue42_multi_output.cpp index dfc5d85..414bb3c 100644 --- a/tests/test_issue42_multi_output.cpp +++ b/tests/test_issue42_multi_output.cpp @@ -49,8 +49,7 @@ static int outputPinFor(NodeGraphEngine &graph, SignalNode *node, int port) { return -1; } -TEST_CASE_METHOD(ImGuiFixture, - "Splitter OUT2 routes to Combiner IN1 with outputs[1] (issue #42)", +TEST_CASE_METHOD(ImGuiFixture, "Splitter OUT2 routes to Combiner IN1 with outputs[1] (issue #42)", "[app][issue42]") { RfSimulatorApp app; app.newProject();