Skip to content

Commit 9cb3ce6

Browse files
authored
Merge pull request #445 from stoermelder/v2.4.1
V2.4.1
2 parents 0f2d414 + 9991f20 commit 9cb3ce6

16 files changed

Lines changed: 653 additions & 375 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 2.4.1
2+
3+
- [8FACE mk2](./docs/eightface/EightFaceMk2.md)
4+
- Fixed crash patch autosave and on preset-loading
5+
16
## 2.4.0
27

38
### New modules

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# stoermelder PackOne
22

33
<!-- Version and License Badges -->
4-
![Version](https://img.shields.io/badge/Version-2.4.0-green.svg?style=flat-square)
4+
![Version](https://img.shields.io/badge/Version-2.4.1-green.svg?style=flat-square)
55
![Rack](https://img.shields.io/badge/VCV_Rack-v2-red.svg?style=flat-square)
66
![MetaModule](https://img.shields.io/badge/MetaModule-v2-orange.svg?style=flat-square)
77
![License](https://img.shields.io/badge/License-GPLv3+-blue.svg?style=flat-square)

docs/eightface/EightFace.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,6 @@ With the option _Autoload first preset_ on the context menu you can autoload the
114114
- Added stability mode setting and _Safe-mode_ and use as new default setting
115115
- Fixed broken function on some modules (only 8FACEx2)
116116
- v2.4.0
117-
- Fixed broken processing in VCV Rack-plugin on closed plugin-window (#424)
117+
- Fixed broken processing in VCV Rack-plugin on closed plugin-window (#424)
118+
- v2.4.1
119+
- Fixed crash patch autosave and on preset-loading

plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"slug": "Stoermelder-P1",
3-
"version": "2.4.0",
3+
"version": "2.4.1",
44
"license": "GPL-3.0-or-later",
55
"author": "Benjamin Dill",
66
"name": "PackOne",

src/modules/ahab/AhabSim.cpp

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -684,8 +684,7 @@ void AhabSim::reset() {
684684
oevent_list_clear(&oevent_list_);
685685
tick_number_.store(0);
686686

687-
auto cb1 = std::atomic_load(&ui_reset_callback_ptr_);
688-
if (cb1 && *cb1) (*cb1)();
687+
if (ui_reset_callback_ptr) ui_reset_callback_ptr();
689688
}
690689

691690
// UI thread operation - enqueue an setGlyph command
@@ -802,14 +801,8 @@ void AhabSim::step() {
802801
}
803802

804803
// Call the callback without holding any locks
805-
auto cb1 = std::atomic_load(&ui_tick_callback_ptr_);
806-
if (cb1 && *cb1) {
807-
(*cb1)(&field_);
808-
}
809-
auto cb2 = std::atomic_load(&dsp_tick_callback_ptr_);
810-
if (cb2 && *cb2) {
811-
(*cb2)(&oevent_list_);
812-
}
804+
if (ui_tick_callback_ptr) ui_tick_callback_ptr(&field_);
805+
if (dsp_tick_callback_ptr) dsp_tick_callback_ptr(&oevent_list_);
813806
}
814807

815808
// Process pending UI requests. Must be called from DSP thread.
@@ -877,10 +870,7 @@ void AhabSim::process() {
877870
delete cmd;
878871
}
879872

880-
auto cb1 = std::atomic_load(&ui_tick_callback_ptr_);
881-
if (cb1 && *cb1) {
882-
(*cb1)(&field_);
883-
}
873+
if (ui_tick_callback_ptr) ui_tick_callback_ptr(&field_);
884874
}
885875

886876
// UDP helper: ensure device, send, destroy
@@ -981,8 +971,7 @@ extern "C" Usz custom_vcvin(void* ptr, Usz port_num, Usz a, Usz b) {
981971
}
982972

983973
float AhabSim::readDspInput(size_t port_num) const {
984-
auto cb = std::atomic_load(&dsp_input_reader_ptr_);
985-
if (cb && *cb) return (*cb)(port_num);
974+
if (dsp_input_reader_ptr) return dsp_input_reader_ptr(port_num);
986975
return 0;
987976
}
988977

@@ -1015,8 +1004,7 @@ extern "C" void custom_vcvout(void* ptr, Usz port_index, Usz a, Usz b, Usz value
10151004
}
10161005

10171006
void AhabSim::writeDspOutput(size_t port_num, float value, int gateTicks) {
1018-
auto cb = std::atomic_load(&dsp_output_writer_ptr_);
1019-
if (cb && *cb) (*cb)(port_num, value, gateTicks);
1007+
if (dsp_output_writer_ptr) dsp_output_writer_ptr(port_num, value, gateTicks);
10201008
}
10211009

10221010
} // namespace Ahab

src/modules/ahab/AhabSim.hpp

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -84,38 +84,23 @@ class AhabSim {
8484
Usz getRandomSeed() const { return random_seed_; }
8585

8686
void setUiTickCallback(UiTickCallback cb) {
87-
if (cb)
88-
std::atomic_store(&ui_tick_callback_ptr_, std::make_shared<UiTickCallback>(std::move(cb)));
89-
else
90-
std::atomic_store(&ui_tick_callback_ptr_, std::shared_ptr<UiTickCallback>());
87+
ui_tick_callback_ptr = cb ? std::move(cb) : 0;
9188
}
9289

9390
void setDspTickCallback(TickDspCallback cb) {
94-
if (cb)
95-
std::atomic_store(&dsp_tick_callback_ptr_, std::make_shared<TickDspCallback>(std::move(cb)));
96-
else
97-
std::atomic_store(&dsp_tick_callback_ptr_, std::shared_ptr<TickDspCallback>());
91+
dsp_tick_callback_ptr = cb ? std::move(cb) : 0;
9892
}
9993

10094
void setDspInputReader(DspInputReader cb) {
101-
if (cb)
102-
std::atomic_store(&dsp_input_reader_ptr_, std::make_shared<DspInputReader>(std::move(cb)));
103-
else
104-
std::atomic_store(&dsp_input_reader_ptr_, std::shared_ptr<DspInputReader>());
95+
dsp_input_reader_ptr = cb ? std::move(cb) : 0;
10596
}
10697

10798
void setDspOutputWriter(DspOutputWriter cb) {
108-
if (cb)
109-
std::atomic_store(&dsp_output_writer_ptr_, std::make_shared<DspOutputWriter>(std::move(cb)));
110-
else
111-
std::atomic_store(&dsp_output_writer_ptr_, std::shared_ptr<DspOutputWriter>());
99+
dsp_output_writer_ptr = cb ? std::move(cb) : 0;
112100
}
113101

114102
void setUiResetCallback(UiResetCallback cb) {
115-
if (cb)
116-
std::atomic_store(&ui_reset_callback_ptr_, std::make_shared<UiResetCallback>(std::move(cb)));
117-
else
118-
std::atomic_store(&ui_reset_callback_ptr_, std::shared_ptr<UiResetCallback>());
103+
ui_reset_callback_ptr = cb ? std::move(cb) : 0;
119104
}
120105

121106
// Write output via the registered DspOutputWriter (safe to call from C callbacks)
@@ -210,15 +195,15 @@ class AhabSim {
210195
// Tick callback stored as a shared_ptr. We use the free functions
211196
// std::atomic_load / std::atomic_store for atomic access without needing
212197
// an std::atomic wrapper (these overloads are provided for shared_ptr).
213-
std::shared_ptr<UiTickCallback> ui_tick_callback_ptr_;
198+
UiTickCallback ui_tick_callback_ptr;
214199
// Reset callback (stored atomically as shared_ptr)
215-
std::shared_ptr<UiResetCallback> ui_reset_callback_ptr_;
200+
UiResetCallback ui_reset_callback_ptr;
216201
// Callback for into DSP class
217-
std::shared_ptr<TickDspCallback> dsp_tick_callback_ptr_;
202+
TickDspCallback dsp_tick_callback_ptr;
218203
// Input reader callback (stored atomically as shared_ptr)
219-
std::shared_ptr<DspInputReader> dsp_input_reader_ptr_;
204+
DspInputReader dsp_input_reader_ptr;
220205
// Output writer callback (stored atomically as shared_ptr)
221-
std::shared_ptr<DspOutputWriter> dsp_output_writer_ptr_;
206+
DspOutputWriter dsp_output_writer_ptr;
222207

223208
// Undo / Redo history
224209
struct UndoNode { Field f; Usz tick; };

src/modules/eightface/EightFaceMk2.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,6 @@ struct EightFaceMk2Module : EightFaceMk2Base<NUM_PRESETS>, ExpanderChangeListene
578578
} else {
579579
mw->fromJson(vJ);
580580
}
581-
json_decref(vJ);
582581
}
583582
}
584583

src/modules/eightface/EightFaceMk2.test.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,35 @@ TEST_CASE("EightFaceMk2Ex Construction and initialization", "[EightFaceMk2]") {
3131

3232
Test::destroyWidget(mw);
3333
Test::destroyModule(m);
34+
}
35+
36+
TEST_CASE("processGui does not decrement refcount of slot-owned json objects", "[EightFaceMk2]") {
37+
// Regression test: commit 84866bc incorrectly added json_decref(vJ) inside processGui().
38+
// vJ pointers in workerGuiQueue are owned by slot->preset — processGui must not
39+
// touch the refcount or the preset slot's json_t* becomes a dangling pointer.
40+
41+
EightFaceMk2Module<8>* m = Test::createModule<EightFaceMk2Module<8>>("EightFaceMk2");
42+
// A second module instance acts as the "bound" module whose preset is being loaded.
43+
EightFaceMk2Module<8>* boundM = Test::createModule<EightFaceMk2Module<8>>("EightFaceMk2");
44+
EightFaceMk2Widget<8>* boundMw = Test::createWidget<EightFaceMk2Widget<8>>(boundM);
45+
46+
// Use GUI mode: processGui calls boundMw->module->fromJson(vJ).
47+
m->guiSafeMode = GUISAFEMODE::GUI;
48+
49+
// Build a valid preset JSON matching the bound module's plugin/model slugs.
50+
json_t* vJ = m->toJson();
51+
size_t refcount = vJ->refcount;
52+
53+
m->workerGuiQueue.push(std::make_tuple(boundMw, vJ));
54+
m->processGui();
55+
56+
REQUIRE(m->workerGuiQueue.empty());
57+
REQUIRE(json_typeof(vJ) == JSON_OBJECT);
58+
REQUIRE(vJ->refcount == refcount);
59+
60+
json_decref(vJ);
61+
62+
Test::destroyWidget(boundMw);
63+
Test::destroyModule(boundM);
64+
Test::destroyModule(m);
3465
}

src/modules/strip/Strip.cpp

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
#include "../../utils/digital.hpp"
33
#include "../../utils/TaskWorker.hpp"
44
#include "../../utils/TaskProcessor.hpp"
5+
#include "../../utils/SpscLatestValue.hpp"
56
#include <atomic>
6-
#include <memory>
77

88
namespace StoermelderPackOne {
99
namespace Strip {
@@ -55,12 +55,12 @@ struct StripModule : StripModuleBase, StripIdFixModule {
5555
std::atomic<bool> lastBypassState{false};
5656

5757
/** [Stored to JSON] */
58-
/* This is owned be the engine thread */
58+
/* This is owned by the engine thread */
5959
std::set<std::tuple<int64_t, int>> excludedParams;
60-
/* Snapshot published for UI thread: shared_ptr to immutable set. Use std::atomic_load/store for atomic access. */
61-
std::shared_ptr<const std::set<std::tuple<int64_t, int>>> excludedParamsPtr;
62-
/* Snapshot published by UI Thread, only used for loading presets */
63-
std::shared_ptr<const std::set<std::tuple<int64_t, int>>> excludedParamsPtrUi;
60+
/* Snapshot published for UI thread (engine writes, UI reads). */
61+
SpscLatestValue<std::set<std::tuple<int64_t, int>>> excludedParamsPtr;
62+
/* Snapshot published by UI thread for engine to consume once (UI writes, engine reads). */
63+
SpscLatestValue<std::set<std::tuple<int64_t, int>>> excludedParamsPtrUi;
6464

6565
/* Indicates that learn mode is ready - only used for LED */
6666
std::atomic<bool> excludeLearn{false};
@@ -108,7 +108,8 @@ struct StripModule : StripModuleBase, StripIdFixModule {
108108
presetLoadReplace = false;
109109
// Initialize snapshot to empty set so UI can read safely immediately
110110
excludedParams.clear();
111-
std::atomic_store(&excludedParamsPtr, std::make_shared<const std::set<std::tuple<int64_t, int>>>());
111+
excludedParamsPtr.store({});
112+
excludedParamsPtrUi.store({});
112113
Module::onReset(e);
113114
}
114115

@@ -414,7 +415,7 @@ struct StripModule : StripModuleBase, StripIdFixModule {
414415
excludedParams.erase(it);
415416
}
416417
// Publish updated snapshot for UI readers
417-
std::atomic_store(&excludedParamsPtr, std::make_shared<const std::set<std::tuple<int64_t, int>>>(excludedParams));
418+
excludedParamsPtr.store(excludedParams);
418419
}
419420
}
420421

@@ -432,7 +433,7 @@ struct StripModule : StripModuleBase, StripIdFixModule {
432433
*/
433434
void groupExcludeClear() {
434435
excludedParams.clear();
435-
std::atomic_store(&excludedParamsPtr, std::make_shared<const std::set<std::tuple<int64_t, int>>>());
436+
excludedParamsPtr.store({});
436437
}
437438

438439
/**
@@ -458,7 +459,7 @@ struct StripModule : StripModuleBase, StripIdFixModule {
458459
if (mNext->getId() == moduleId) {
459460
excludedParams.insert(std::make_tuple(moduleId, paramId));
460461
// Publish updated snapshot for UI readers
461-
std::atomic_store(&excludedParamsPtr, std::make_shared<const std::set<std::tuple<int64_t, int>>>(excludedParams));
462+
excludedParamsPtr.store(excludedParams);
462463
return;
463464
}
464465
m = mNext;
@@ -475,7 +476,7 @@ struct StripModule : StripModuleBase, StripIdFixModule {
475476
if (mNext->getId() == moduleId) {
476477
excludedParams.insert(std::make_tuple(moduleId, paramId));
477478
// Publish updated snapshot for UI readers
478-
std::atomic_store(&excludedParamsPtr, std::make_shared<const std::set<std::tuple<int64_t, int>>>(excludedParams));
479+
excludedParamsPtr.store(excludedParams);
479480
return;
480481
}
481482
m = mNext;
@@ -497,7 +498,7 @@ struct StripModule : StripModuleBase, StripIdFixModule {
497498
*/
498499
void groupExcludeRemove(int64_t moduleId, int paramId) {
499500
excludedParams.erase(std::make_tuple(moduleId, paramId));
500-
std::atomic_store(&excludedParamsPtr, std::make_shared<const std::set<std::tuple<int64_t, int>>>(excludedParams));
501+
excludedParamsPtr.store(excludedParams);
501502
}
502503

503504
/**
@@ -513,12 +514,9 @@ struct StripModule : StripModuleBase, StripIdFixModule {
513514
* To be called from engine-thread only.
514515
*/
515516
void groupExcludeLoad() {
516-
auto snapUi = std::atomic_load(&excludedParamsPtrUi);
517-
if (snapUi) {
518-
excludedParams = *snapUi;
519-
// Publish updated snapshot for UI readers
520-
std::atomic_store(&excludedParamsPtr, std::make_shared<const std::set<std::tuple<int64_t, int>>>(excludedParams));
521-
std::atomic_store(&excludedParamsPtrUi, std::shared_ptr<const std::set<std::tuple<int64_t, int>>>{});
517+
if (excludedParamsPtrUi.load_if_new(excludedParams)) {
518+
// Publish updated snapshot for UI reader
519+
excludedParamsPtr.store(excludedParams);
522520
}
523521
}
524522

@@ -532,16 +530,13 @@ struct StripModule : StripModuleBase, StripIdFixModule {
532530

533531
json_object_set_new(rootJ, "onMode", json_integer((int)onMode));
534532

535-
// Use atomic snapshot published by the engine thread
536-
auto snap = std::atomic_load(&excludedParamsPtr);
533+
// Use snapshot published by the engine thread
537534
json_t* excludedParamsJ = json_array();
538-
if (snap) {
539-
for (auto t : *snap) {
540-
json_t* excludedParamJ = json_object();
541-
json_object_set_new(excludedParamJ, "moduleId", json_integer(std::get<0>(t)));
542-
json_object_set_new(excludedParamJ, "paramId", json_integer(std::get<1>(t)));
543-
json_array_append_new(excludedParamsJ, excludedParamJ);
544-
}
535+
for (auto t : excludedParamsPtr.peek()) {
536+
json_t* excludedParamJ = json_object();
537+
json_object_set_new(excludedParamJ, "moduleId", json_integer(std::get<0>(t)));
538+
json_object_set_new(excludedParamJ, "paramId", json_integer(std::get<1>(t)));
539+
json_array_append_new(excludedParamsJ, excludedParamJ);
545540
}
546541
json_object_set_new(rootJ, "excludedParams", excludedParamsJ);
547542

@@ -578,7 +573,7 @@ struct StripModule : StripModuleBase, StripIdFixModule {
578573
snap.insert(std::make_tuple(moduleId, paramId));
579574
}
580575
// Publish snapshot for engine thread
581-
std::atomic_store(&excludedParamsPtrUi, std::make_shared<const std::set<std::tuple<int64_t, int>>>(snap));
576+
excludedParamsPtrUi.store(snap);
582577
groupExcludeLoadRequest();
583578
}
584579

@@ -690,14 +685,13 @@ struct ExcludeButton : TL1105 {
690685
module->groupExcludeClearRequest();
691686
}));
692687

693-
// Use atomic snapshot published by the engine thread to avoid racing with engine mutations
694-
auto snap = std::atomic_load(&module->excludedParamsPtr);
688+
const auto& snap = module->excludedParamsPtr.peek();
695689

696-
if (!snap || snap->size() == 0)
690+
if (snap.empty())
697691
return;
698692

699693
menu->addChild(new MenuSeparator());
700-
for (auto it : *snap) {
694+
for (auto it : snap) {
701695
int64_t moduleId = std::get<0>(it);
702696
int paramId = std::get<1>(it);
703697

0 commit comments

Comments
 (0)