Q-W1 code-review follow-ups: restore 104 trailing newlines, relocate reasampler_uid.h to core/wire, drop ext_keys namespaces shim, add slot_map_tests, golden serialize literals for 4 modules, namespaces.h/pragma-once ordering sweep. 60/60 green.
This commit is contained in:
+17
-2
@@ -249,6 +249,16 @@ target_include_directories(tail_control PUBLIC src)
|
|||||||
target_link_libraries(tail_control PUBLIC render_settings)
|
target_link_libraries(tail_control PUBLIC render_settings)
|
||||||
target_link_libraries(tail_control PRIVATE json)
|
target_link_libraries(tail_control PRIVATE json)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2g''') Pure slot_map library — NO REAPER, NO SWELL. The L7 gap-preserving
|
||||||
|
# display-position carrier for one bank (extracted from bank_book, Q-W1
|
||||||
|
# T4-05): sample id -> slot, gap-preserving append/remove/reorder/reconcile,
|
||||||
|
# JSON round-trip. Mirror of bank_model; wrapped (not merged) by bank_book.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
add_library(slot_map STATIC src/core/model/slot_map.cpp)
|
||||||
|
target_include_directories(slot_map PUBLIC src)
|
||||||
|
target_link_libraries(slot_map PRIVATE json)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 2g') Pure bank_book library — NO REAPER, NO SWELL. The multi-bank phase heart
|
# 2g') Pure bank_book library — NO REAPER, NO SWELL. The multi-bank phase heart
|
||||||
# (Phase B1): an ordered registry of banks (pool seeded as bank-zero + named
|
# (Phase B1): an ordered registry of banks (pool seeded as bank-zero + named
|
||||||
@@ -257,9 +267,10 @@ target_link_libraries(tail_control PRIVATE json)
|
|||||||
# sample between banks, JSON round-trip + legacy-bank_index→pool migration.
|
# sample between banks, JSON round-trip + legacy-bank_index→pool migration.
|
||||||
# Mirror of bank_model / view_mode_model; wraps BankIndex (bank_model untouched).
|
# Mirror of bank_model / view_mode_model; wraps BankIndex (bank_model untouched).
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
add_library(bank_book STATIC src/core/model/bank_book.cpp src/core/model/slot_map.cpp)
|
add_library(bank_book STATIC src/core/model/bank_book.cpp)
|
||||||
target_include_directories(bank_book PUBLIC src)
|
target_include_directories(bank_book PUBLIC src)
|
||||||
target_link_libraries(bank_book PUBLIC bank_model)
|
target_link_libraries(bank_book PUBLIC bank_model)
|
||||||
|
target_link_libraries(bank_book PUBLIC slot_map)
|
||||||
target_link_libraries(bank_book PRIVATE json)
|
target_link_libraries(bank_book PRIVATE json)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -404,7 +415,7 @@ target_include_directories(drag_out PUBLIC src)
|
|||||||
# parallel byte writer — so the cross-artifact contract cannot drift; links
|
# parallel byte writer — so the cross-artifact contract cannot drift; links
|
||||||
# sample_map (which pulls bank_book/wav_trim/sampler_core transitively) and
|
# sample_map (which pulls bank_book/wav_trim/sampler_core transitively) and
|
||||||
# NEITHER SDK. The class-ID string derives from the FROZEN UID macros
|
# NEITHER SDK. The class-ID string derives from the FROZEN UID macros
|
||||||
# (src/shell/instrument/reasampler_uid.h, SDK-free), channel-selected via the generated
|
# (src/core/wire/reasampler_uid.h, SDK-free), channel-selected via the generated
|
||||||
# version header — hence the generated include dir. The round-trip test parses
|
# version header — hence the generated include dir. The round-trip test parses
|
||||||
# the container and decodes back through the instrument's own reader. Mirror of
|
# the container and decodes back through the instrument's own reader. Mirror of
|
||||||
# assignment_request.
|
# assignment_request.
|
||||||
@@ -645,6 +656,10 @@ add_executable(bank_book_tests tests/test_bank_book.cpp)
|
|||||||
target_link_libraries(bank_book_tests PRIVATE bank_book)
|
target_link_libraries(bank_book_tests PRIVATE bank_book)
|
||||||
add_test(NAME bank_book_tests COMMAND bank_book_tests)
|
add_test(NAME bank_book_tests COMMAND bank_book_tests)
|
||||||
|
|
||||||
|
add_executable(slot_map_tests tests/test_slot_map.cpp)
|
||||||
|
target_link_libraries(slot_map_tests PRIVATE slot_map json)
|
||||||
|
add_test(NAME slot_map_tests COMMAND slot_map_tests)
|
||||||
|
|
||||||
add_executable(wav_trim_tests tests/test_wav_trim.cpp)
|
add_executable(wav_trim_tests tests/test_wav_trim.cpp)
|
||||||
target_link_libraries(wav_trim_tests PRIVATE wav_trim)
|
target_link_libraries(wav_trim_tests PRIVATE wav_trim)
|
||||||
add_test(NAME wav_trim_tests COMMAND wav_trim_tests)
|
add_test(NAME wav_trim_tests COMMAND wav_trim_tests)
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// actions — the Design View action family (Phase D4). Registers the bindable
|
// actions — the Design View action family (Phase D4). Registers the bindable
|
||||||
// actions that drive the mode workflow and wires them end-to-end: toggle/activate
|
// actions that drive the mode workflow and wires them end-to-end: toggle/activate
|
||||||
// a mode, tag/untag/show-both the current track selection. Each action mutates the
|
// a mode, tag/untag/show-both the current track selection. Each action mutates the
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// bank_panel — the docked grid window (M5, Wave A). REAPER-facing shell: it owns
|
// bank_panel — the docked grid window (M5, Wave A). REAPER-facing shell: it owns
|
||||||
// a SWELL dialog docked via DockWindowAddEx, and paints the current project's
|
// a SWELL dialog docked via DockWindowAddEx, and paints the current project's
|
||||||
// bank as a grid of LICE-drawn waveform thumbnails. The panel itself NEVER inserts
|
// bank as a grid of LICE-drawn waveform thumbnails. The panel itself NEVER inserts
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ int levelToY(const Rect& area, double level) {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|
||||||
EnvVertex vtx(EnvNode node, const Rect& area, double totalSeconds, double t, double level) {
|
EnvVertex vtx(EnvNode node, const Rect& area, double totalSeconds, double t, double level) {
|
||||||
EnvVertex v;
|
EnvVertex v;
|
||||||
v.node = node;
|
v.node = node;
|
||||||
|
|||||||
@@ -99,7 +99,6 @@ double normDeg(double deg) {
|
|||||||
return deg;
|
return deg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
KnobGeometry computeKnob(const Rect& cell) {
|
KnobGeometry computeKnob(const Rect& cell) {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// instrument_drop — pure implementation. See instrument_drop.h.
|
// instrument_drop — pure implementation. See instrument_drop.h.
|
||||||
// NO REAPER / SWELL / VST3 SDK / vendor. Reuses sample_map's ComponentState serializer and
|
// NO REAPER / SWELL / VST3 SDK / vendor. Reuses sample_map's ComponentState serializer and
|
||||||
// the SDK-free UID macros (vst/reasampler_uid.h).
|
// the SDK-free UID macros (core/wire/reasampler_uid.h).
|
||||||
|
|
||||||
#include "core/wire/instrument_drop.h"
|
#include "core/wire/instrument_drop.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
#include "shell/instrument/reasampler_uid.h" // REASAMPLER_ACTIVE_UID_* — the FROZEN, channel-selected class UID
|
#include "core/wire/reasampler_uid.h" // REASAMPLER_ACTIVE_UID_* — the FROZEN, channel-selected class UID
|
||||||
#include "core/instrument/map/sample_map.h" // ComponentState + serializeComponentState (the SHARED writer)
|
#include "core/instrument/map/sample_map.h" // ComponentState + serializeComponentState (the SHARED writer)
|
||||||
|
|
||||||
namespace reasampler::wire {
|
namespace reasampler::wire {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//
|
//
|
||||||
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO VST3 SDK,
|
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO VST3 SDK,
|
||||||
// NO vendor/ includes. Standard library only (+ the pure sample_map it reuses and the
|
// NO vendor/ includes. Standard library only (+ the pure sample_map it reuses and the
|
||||||
// SDK-free UID macros in vst/reasampler_uid.h). Unit-tested outside the DAW — the same
|
// SDK-free UID macros in core/wire/reasampler_uid.h). Unit-tested outside the DAW — the same
|
||||||
// "small pure builder + round-trip proof" pattern as assignment_request / provenance.
|
// "small pure builder + round-trip proof" pattern as assignment_request / provenance.
|
||||||
//
|
//
|
||||||
// -- What it is (the S17 seam, extension side) --------------------------------
|
// -- What it is (the S17 seam, extension side) --------------------------------
|
||||||
@@ -43,7 +43,7 @@ namespace reasampler::wire {
|
|||||||
// header carries (public.sdk vstpresetfile: "ASCII-encoded FUID"). On both COM-compatible
|
// header carries (public.sdk vstpresetfile: "ASCII-encoded FUID"). On both COM-compatible
|
||||||
// (Windows GUID byte order) and plain layouts, FUID::toString reduces to the four
|
// (Windows GUID byte order) and plain layouts, FUID::toString reduces to the four
|
||||||
// INLINE_UID uint32 words printed "%08X" in order, so this derivation is platform-stable.
|
// INLINE_UID uint32 words printed "%08X" in order, so this derivation is platform-stable.
|
||||||
// Sourced from the FROZEN macros in vst/reasampler_uid.h (the same constants the factory
|
// Sourced from the FROZEN macros in core/wire/reasampler_uid.h (the same constants the factory
|
||||||
// registers), channel-selected by the one REASAMPLER_CHANNEL_IS_BETA bit — a beta extension
|
// registers), channel-selected by the one REASAMPLER_CHANNEL_IS_BETA bit — a beta extension
|
||||||
// writes presets only the beta VST class accepts, preserving the S18 pairing invariant.
|
// writes presets only the beta VST class accepts, preserving the S18 pairing invariant.
|
||||||
std::string vstClassIdHex();
|
std::string vstClassIdHex();
|
||||||
|
|||||||
+1
-2
@@ -1,4 +1,3 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
// ext_keys — the SINGLE SOURCE OF TRUTH for the "reasampler" project ext-state
|
// ext_keys — the SINGLE SOURCE OF TRUTH for the "reasampler" project ext-state
|
||||||
// namespace + key names, shared by the extension (writer, via persist.h) and the
|
// namespace + key names, shared by the extension (writer, via persist.h) and the
|
||||||
@@ -27,7 +26,7 @@ namespace reasampler {
|
|||||||
// because the value is fixed by the channel bit at build time. This is the wire-contract
|
// because the value is fixed by the channel bit at build time. This is the wire-contract
|
||||||
// reconciliation between S4 (shared ext_keys) and V4 (channel-isolated namespace): without
|
// reconciliation between S4 (shared ext_keys) and V4 (channel-isolated namespace): without
|
||||||
// it a beta instrument would read the stable namespace and see empty state.
|
// it a beta instrument would read the stable namespace and see empty state.
|
||||||
inline const char* kProjExtNamespace() { return extStateNamespace().c_str(); }
|
inline const char* kProjExtNamespace() { return version::extStateNamespace().c_str(); }
|
||||||
|
|
||||||
// The multi-bank key: the whole serialized BankBook (pool + named banks). This is
|
// The multi-bank key: the whole serialized BankBook (pool + named banks). This is
|
||||||
// the key the VST3 instrument reads to see the live bank (read-only, S4). persist.h
|
// the key the VST3 instrument reads to see the live bank (read-only, S4). persist.h
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// ingest — the S8 "ingest through the bank" shell (EXTENSION side).
|
// ingest — the S8 "ingest through the bank" shell (EXTENSION side).
|
||||||
//
|
//
|
||||||
// Compiled into the reaper_reasampler MODULE. REAPER-facing (PCM_Source metadata reads,
|
// Compiled into the reaper_reasampler MODULE. REAPER-facing (PCM_Source metadata reads,
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// persist — the REAPER-facing bridge between the in-memory BankModel and project
|
// persist — the REAPER-facing bridge between the in-memory BankModel and project
|
||||||
// ext state (CLAUDE.md §load-bearing split; CONTEXT.md §Persistence & paths).
|
// ext state (CLAUDE.md §load-bearing split; CONTEXT.md §Persistence & paths).
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// drag_out_win — the OS/COM initiation half of native OS drag-out (Milestone 11). The pure
|
// drag_out_win — the OS/COM initiation half of native OS drag-out (Milestone 11). The pure
|
||||||
// gesture-boundary decision and path-list assembly live in drag_out.*; THIS is the platform
|
// gesture-boundary decision and path-list assembly live in drag_out.*; THIS is the platform
|
||||||
// shell that hands a resolved, existing-file path list to the operating system's drag-drop
|
// shell that hands a resolved, existing-file path list to the operating system's drag-drop
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// instrument_drop_win — the REAPER-facing shell half of S17 drop-and-load. The pure gesture
|
// instrument_drop_win — the REAPER-facing shell half of S17 drop-and-load. The pure gesture
|
||||||
// decision lives in drag_out (DragGesture::InstrumentDrop) and the pure payload construction
|
// decision lives in drag_out (DragGesture::InstrumentDrop) and the pure payload construction
|
||||||
// in instrument_drop; THIS is the platform shell that (a) resolves a screen point to a track
|
// in instrument_drop; THIS is the platform shell that (a) resolves a screen point to a track
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// capture — the REAPER-facing capture shell (CLAUDE.md §load-bearing split).
|
// capture — the REAPER-facing capture shell (CLAUDE.md §load-bearing split).
|
||||||
//
|
//
|
||||||
// This header declares the capture *seam* the later milestones fill:
|
// This header declares the capture *seam* the later milestones fill:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// insert — placement of bank samples into the arrange (M6). REAPER-facing shell:
|
// insert — placement of bank samples into the arrange (M6). REAPER-facing shell:
|
||||||
// it reads the bank_panel's current selection, resolves each selected sample's
|
// it reads the bank_panel's current selection, resolves each selected sample's
|
||||||
// file, and drops it into the arrange at the edit cursor via InsertMedia, wrapped
|
// file, and drops it into the arrange at the edit cursor via InsertMedia, wrapped
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// item_read — the ONE place a MediaItem* is read for its canonical GUID string and for
|
// item_read — the ONE place a MediaItem* is read for its canonical GUID string and for
|
||||||
// the durable P_LANENAME of the fixed lane it sits on. Before this seam, view.cpp and
|
// the durable P_LANENAME of the fixed lane it sits on. Before this seam, view.cpp and
|
||||||
// bank_panel.cpp each carried a near-identical private itemGuid / itemLaneName pair
|
// bank_panel.cpp each carried a near-identical private itemGuid / itemLaneName pair
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// provenance_shell — the REAPER-facing reads Milestone 10 needs, in one place.
|
// provenance_shell — the REAPER-facing reads Milestone 10 needs, in one place.
|
||||||
//
|
//
|
||||||
// The PURE provenance module (provenance.h) owns the fingerprint encoding, the
|
// The PURE provenance module (provenance.h) owns the fingerprint encoding, the
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// track_guid — the ONE place a MediaTrack* is formatted into the canonical GUID
|
// track_guid — the ONE place a MediaTrack* is formatted into the canonical GUID
|
||||||
// string used as a membership-index key. Both the Design View shell (view.cpp) and
|
// string used as a membership-index key. Both the Design View shell (view.cpp) and
|
||||||
// the actions layer (actions.cpp) key membership on this exact string, so the key
|
// the actions layer (actions.cpp) key membership on this exact string, so the key
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
// reaper_bridge.h — the REAPER VST-host bridge (Phase S1 read spike). THIN shell:
|
// reaper_bridge.h — the REAPER VST-host bridge (Phase S1 read spike). THIN shell:
|
||||||
// resolves REAPER API functions by name over the host context and reads the live
|
// resolves REAPER API functions by name over the host context and reads the live
|
||||||
// "reasampler" project ext-state. The fiddly decode lives in bridge_marshal (pure).
|
// "reasampler" project ext-state. The fiddly decode lives in bridge_marshal (pure).
|
||||||
@@ -17,6 +16,7 @@
|
|||||||
// reaper_vst3_interfaces.h + reaper_plugin_functions.h at the spike.
|
// reaper_vst3_interfaces.h + reaper_plugin_functions.h at the spike.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
// reasampler_embed.h — the S6 embedded TCP/MCP UI shell. Implements REAPER's
|
// reasampler_embed.h — the S6 embedded TCP/MCP UI shell. Implements REAPER's
|
||||||
// IReaperUIEmbedInterface (vendor/reaper-sdk/sdk/reaper_plugin_fx_embed.h +
|
// IReaperUIEmbedInterface (vendor/reaper-sdk/sdk/reaper_plugin_fx_embed.h +
|
||||||
// reaper_vst3_interfaces.h) so the instrument draws a compact keymap/level strip INLINE in
|
// reaper_vst3_interfaces.h) so the instrument draws a compact keymap/level strip INLINE in
|
||||||
@@ -32,6 +31,7 @@
|
|||||||
// REAPER's messages to/from it and draws with the same LICE idiom as reasampler_editor.
|
// REAPER's messages to/from it and draws with the same LICE idiom as reasampler_editor.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
// reasampler_vst.h — shared identity constants for the ReaSampler VST3 instrument
|
// reasampler_vst.h — shared identity constants for the ReaSampler VST3 instrument
|
||||||
// (Phase S). One place for the plugin's class UID, name, vendor, and version so the
|
// (Phase S). One place for the plugin's class UID, name, vendor, and version so the
|
||||||
// processor, factory, and editor agree.
|
// processor, factory, and editor agree.
|
||||||
@@ -19,10 +18,11 @@
|
|||||||
// binary UID identity — the string identity lives in the pure module).
|
// binary UID identity — the string identity lives in the pure module).
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
|
|
||||||
#include "pluginterfaces/base/funknown.h"
|
#include "pluginterfaces/base/funknown.h"
|
||||||
|
|
||||||
#include "shell/instrument/reasampler_uid.h" // the FROZEN UID macros + channel selection (SDK-free values)
|
#include "core/wire/reasampler_uid.h" // the FROZEN UID macros + channel selection (SDK-free values)
|
||||||
|
|
||||||
namespace reasampler::vst {
|
namespace reasampler::vst {
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// draw_kit — the LICE-facing SHELL half of the shared drawing kit (Phase L, L1). This is
|
// draw_kit — the LICE-facing SHELL half of the shared drawing kit (Phase L, L1). This is
|
||||||
// the ONE source of drawing for the whole system: every surface (bank_panel now; the VST
|
// the ONE source of drawing for the whole system: every surface (bank_panel now; the VST
|
||||||
// editor + embed strip at L3) fills, buttons, rows, sliders, waveforms, and — above all —
|
// editor + embed strip at L3) fills, buttons, rows, sliders, waveforms, and — above all —
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// usage_scan — the EXTENSION-side shell of the pS-usage seam (see sample_usage.h for
|
// usage_scan — the EXTENSION-side shell of the pS-usage seam (see sample_usage.h for
|
||||||
// the pure core, the fail-safe folds, and the full design note). At prune-scan time it
|
// the pure core, the fail-safe folds, and the full design note). At prune-scan time it
|
||||||
// answers ONE question: which project-relative bank paths are held by a LIVE ReaSampler
|
// answers ONE question: which project-relative bank paths are held by a LIVE ReaSampler
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
// view — the REAPER-facing shell of the Design View feature (Phase D2). It is the
|
// view — the REAPER-facing shell of the Design View feature (Phase D2). It is the
|
||||||
// mirror of the capture shell: the ViewModeModel (pure, D1) holds the mode/
|
// mirror of the capture shell: the ViewModeModel (pure, D1) holds the mode/
|
||||||
// membership/snapshot state and emits the toggle plan; this shell reads the live
|
// membership/snapshot state and emits the toggle plan; this shell reads the live
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
// reasampler_editor.h — the VST3 IPlugView LICE editor for the ReaSampler 9000
|
// reasampler_editor.h — the VST3 IPlugView LICE editor for the ReaSampler 9000
|
||||||
// capture-first UI (Phase S10). THIN shell: hosts a LICE-drawn child window inside the
|
// capture-first UI (Phase S10). THIN shell: hosts a LICE-drawn child window inside the
|
||||||
// host's IPlugView seat and routes host paint/mouse into the pure geometry modules
|
// host's IPlugView seat and routes host paint/mouse into the pure geometry modules
|
||||||
@@ -23,6 +22,7 @@
|
|||||||
// to create/destroy the child window and onSize to resize it.
|
// to create/destroy the child window and onSize to resize it.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include "core/namespaces.h"
|
|
||||||
// reasampler_processor.h — the VST3 SingleComponentEffect (Phase S4, Tier 0). Wires the
|
// reasampler_processor.h — the VST3 SingleComponentEffect (Phase S4, Tier 0). Wires the
|
||||||
// pure S3 sampler core into a real VSTi: it declares an event-input bus + a stereo audio
|
// pure S3 sampler core into a real VSTi: it declares an event-input bus + a stereo audio
|
||||||
// output bus, marshals host MIDI note-on/off into the VoiceEngine, and renders the
|
// output bus, marshals host MIDI note-on/off into the VoiceEngine, and renders the
|
||||||
@@ -26,6 +25,7 @@
|
|||||||
// single atomic pointer swap. See the LoadedInstrument handoff below.
|
// single atomic pointer swap. See the LoadedInstrument handoff below.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/namespaces.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|||||||
+33
-129
@@ -42,6 +42,29 @@ static Sample sampleWith(const std::string& seed) { return sampleWith(seed, "has
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Golden byte-literal (Q-W1 follow-up): pins the EXACT serialized bytes for a
|
||||||
|
// small fixture (a freshly-seeded book: pool only, one sample), not just
|
||||||
|
// self-consistent re-serialization — a format drift that both writer and
|
||||||
|
// reader agree on would slip past the round-trip tests but not this. The
|
||||||
|
// format is frozen as-shipped; the literal below is the captured current
|
||||||
|
// output.
|
||||||
|
static void testSerializeGoldenLiteral() {
|
||||||
|
BankBook book;
|
||||||
|
CHECK(book.pool().index.add(sampleWith("g1")) == AddResult::Added);
|
||||||
|
CHECK(book.serialize() ==
|
||||||
|
"{\"version\":1,\"activeBank\":\"pool\",\"banks\":[{\"id\":\"pool\","
|
||||||
|
"\"displayName\":\"Pool\",\"ordinal\":0,\"index\":{\"version\":1,"
|
||||||
|
"\"samples\":[{\"id\":\"id-g1\",\"displayName\":\"sample g1\","
|
||||||
|
"\"relativePath\":\"bank/g1.wav\",\"sourceMode\":0,\"sourceRange\":{"
|
||||||
|
"\"startSeconds\":0,\"endSeconds\":0,\"startPpq\":0,\"endPpq\":0},"
|
||||||
|
"\"trackGuids\":[],\"wetDry\":1,\"channelCount\":2,\"sampleRate\":48000,"
|
||||||
|
"\"lengthSeconds\":0,\"lengthBeats\":0,\"captureTempo\":0,"
|
||||||
|
"\"captureTimeSigNum\":0,\"captureTimeSigDenom\":0,\"key\":null,"
|
||||||
|
"\"rootNote\":null,\"loop\":null,\"levels\":{\"peakDb\":0,\"rmsDb\":0,"
|
||||||
|
"\"lufs\":0},\"clipped\":false,\"tier\":0,\"contentHash\":\"hash-g1\","
|
||||||
|
"\"provenance\":null,\"createdTimestamp\":1753080000}]},\"slots\":[]}]}");
|
||||||
|
}
|
||||||
|
|
||||||
static void testPoolSeededAndDefaults() {
|
static void testPoolSeededAndDefaults() {
|
||||||
BankBook book;
|
BankBook book;
|
||||||
// Pool present as bank-zero with fixed id + name + ordinal 0.
|
// Pool present as bank-zero with fixed id + name + ordinal 0.
|
||||||
@@ -790,123 +813,13 @@ static void testUpdateSampleInPlace() {
|
|||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// L7 — SlotMap (gap-preserving display positions) + BankBook ordering/reorder/replace
|
// L7 — SlotMap (gap-preserving display positions) + BankBook ordering/reorder/replace
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
//
|
||||||
// --- SlotMap unit behaviour --------------------------------------------------
|
// Pure SlotMap-only unit behaviour (add/remove/query, reorder gap-preservation,
|
||||||
|
// resetDense/reconcile, equality/fromEntries, serialize golden literal + round
|
||||||
static void testSlotMapDenseAppend() {
|
// trip) now lives in test_slot_map.cpp (Q-W1 follow-up), extracted per the house
|
||||||
SlotMap m;
|
// every-pure-module-has-a-_tests rule. This file keeps the BankBook-level
|
||||||
m.append("a");
|
// integration coverage below: reorderSample / reconcileSlots / JSON round-trip
|
||||||
m.append("b");
|
// WITH a full book.
|
||||||
m.append("c");
|
|
||||||
CHECK(m.slotOf("a") == 0);
|
|
||||||
CHECK(m.slotOf("b") == 1);
|
|
||||||
CHECK(m.slotOf("c") == 2);
|
|
||||||
CHECK(m.maxSlot() == 2);
|
|
||||||
CHECK((m.orderedIds() == std::vector<std::string>{"a", "b", "c"}));
|
|
||||||
CHECK(m.idAt(1) == "b");
|
|
||||||
CHECK(m.slotOf("nope") == -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testSlotMapRemoveLeavesGap() {
|
|
||||||
SlotMap m;
|
|
||||||
m.append("a"); m.append("b"); m.append("c"); // 0,1,2
|
|
||||||
CHECK(m.remove("b")); // slot 1 now EMPTY (no re-pack)
|
|
||||||
CHECK(m.slotOf("a") == 0);
|
|
||||||
CHECK(m.slotOf("c") == 2); // c did NOT shift down
|
|
||||||
CHECK(m.idAt(1).empty()); // gap preserved
|
|
||||||
CHECK((m.orderedIds() == std::vector<std::string>{"a", "c"}));
|
|
||||||
CHECK(!m.remove("b")); // already gone
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testSlotMapAppendAfterGapGoesToFrontier() {
|
|
||||||
SlotMap m;
|
|
||||||
m.append("a"); m.append("b"); m.append("c"); // 0,1,2
|
|
||||||
m.remove("a"); // slot 0 empty
|
|
||||||
m.append("d"); // append goes AFTER last occupied (2) -> 3
|
|
||||||
CHECK(m.slotOf("d") == 3); // did NOT fill the slot-0 gap
|
|
||||||
CHECK(m.idAt(0).empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testSlotMapReorderIntoEmpty() {
|
|
||||||
SlotMap m;
|
|
||||||
m.append("a"); m.append("b"); m.append("c"); // 0,1,2
|
|
||||||
m.remove("b"); // slot 1 empty
|
|
||||||
CHECK(m.reorder("c", 1)); // c -> empty slot 1; its slot 2 empties
|
|
||||||
CHECK(m.slotOf("c") == 1);
|
|
||||||
CHECK(m.idAt(2).empty());
|
|
||||||
CHECK(m.slotOf("a") == 0); // untouched
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testSlotMapReorderOntoOccupiedInsertsAndShifts() {
|
|
||||||
SlotMap m;
|
|
||||||
m.append("a"); m.append("b"); m.append("c"); m.append("d"); // 0,1,2,3
|
|
||||||
CHECK(m.reorder("d", 1)); // d onto occupied slot 1 -> insert-before, shift b,c up
|
|
||||||
CHECK(m.slotOf("a") == 0); // before the target: unchanged
|
|
||||||
CHECK(m.slotOf("d") == 1); // took the target slot
|
|
||||||
CHECK(m.slotOf("b") == 2); // shifted +1
|
|
||||||
CHECK(m.slotOf("c") == 3); // shifted +1
|
|
||||||
CHECK((m.orderedIds() == std::vector<std::string>{"a", "d", "b", "c"}));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testSlotMapReorderPreservesInteriorGapAboveTarget() {
|
|
||||||
SlotMap m;
|
|
||||||
m.append("a"); m.append("b"); m.append("c"); // 0,1,2
|
|
||||||
m.remove("b"); // gap at 1: a@0, c@2
|
|
||||||
m.append("d"); // d@3
|
|
||||||
CHECK(m.reorder("d", 0)); // d onto occupied slot 0 -> a shifts to 1, c shifts to 3
|
|
||||||
CHECK(m.slotOf("d") == 0);
|
|
||||||
CHECK(m.slotOf("a") == 1); // shifted from 0 -> 1
|
|
||||||
CHECK(m.slotOf("c") == 3); // shifted from 2 -> 3 (gap at 2 preserved as a +1 of its own)
|
|
||||||
CHECK(m.idAt(2).empty()); // interior gap above the target survives
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testSlotMapReorderUnmappedIsNoOp() {
|
|
||||||
SlotMap m;
|
|
||||||
m.append("a");
|
|
||||||
CHECK(!m.reorder("ghost", 0)); // not mapped -> false, no mutation
|
|
||||||
CHECK(m.slotOf("a") == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testSlotMapNegativeTargetClampsToZero() {
|
|
||||||
SlotMap m;
|
|
||||||
m.append("a"); m.append("b"); // 0,1
|
|
||||||
CHECK(m.reorder("b", -3)); // clamp to 0 -> insert-before a
|
|
||||||
CHECK(m.slotOf("b") == 0);
|
|
||||||
CHECK(m.slotOf("a") == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testSlotMapResetDenseSkipsDupesAndEmpties() {
|
|
||||||
SlotMap m;
|
|
||||||
m.resetDense({"a", "", "b", "a", "c"}); // "" and the second "a" dropped
|
|
||||||
CHECK((m.orderedIds() == std::vector<std::string>{"a", "b", "c"}));
|
|
||||||
CHECK(m.slotOf("a") == 0);
|
|
||||||
CHECK(m.slotOf("c") == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testSlotMapReconcileDropsStaleAppendsNew() {
|
|
||||||
SlotMap m;
|
|
||||||
m.append("a"); m.append("b"); m.append("c"); // 0,1,2
|
|
||||||
m.reconcile({"a", "c", "d"}); // b left the index (drop), d is new (append)
|
|
||||||
CHECK(m.slotOf("a") == 0); // kept at its slot
|
|
||||||
CHECK(m.slotOf("c") == 2); // kept at its slot (gap where b was)
|
|
||||||
CHECK(m.slotOf("b") == -1); // stale marker dropped
|
|
||||||
CHECK(m.slotOf("d") == 3); // appended after the frontier
|
|
||||||
CHECK(m.idAt(1).empty()); // b's slot stays empty
|
|
||||||
}
|
|
||||||
|
|
||||||
static void testSlotMapEqualityAndFromEntries() {
|
|
||||||
SlotMap a;
|
|
||||||
a.append("x"); a.append("y");
|
|
||||||
SlotMap b = SlotMap::fromEntries({{"x", 0}, {"y", 1}});
|
|
||||||
CHECK(a == b);
|
|
||||||
// Defensive repair: duplicate id (first wins), slot conflict (later dropped),
|
|
||||||
// empty id / negative slot dropped.
|
|
||||||
SlotMap c = SlotMap::fromEntries({{"x", 0}, {"x", 5}, {"y", 0}, {"", 9}, {"z", -1}, {"w", 2}});
|
|
||||||
CHECK(c.slotOf("x") == 0); // first x wins
|
|
||||||
CHECK(c.slotOf("y") == -1); // slot 0 already taken -> dropped
|
|
||||||
CHECK(c.slotOf("w") == 2); // valid
|
|
||||||
CHECK(c.slotOf("z") == -1); // negative slot dropped
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- BankBook L7: JSON round-trip WITH positions -----------------------------
|
// --- BankBook L7: JSON round-trip WITH positions -----------------------------
|
||||||
|
|
||||||
@@ -1081,6 +994,7 @@ static void testReplaceSampleInPoolPassesGuard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
testSerializeGoldenLiteral();
|
||||||
testPoolSeededAndDefaults();
|
testPoolSeededAndDefaults();
|
||||||
testPoolPrivileges();
|
testPoolPrivileges();
|
||||||
testCreateRenameReorder();
|
testCreateRenameReorder();
|
||||||
@@ -1117,18 +1031,8 @@ int main() {
|
|||||||
testRemoveAllBanksLatentScope();
|
testRemoveAllBanksLatentScope();
|
||||||
testUpdateSampleInPlace();
|
testUpdateSampleInPlace();
|
||||||
|
|
||||||
// L7 — SlotMap + ordering/reorder/replace + slot round-trip/migration.
|
// L7 — BankBook ordering/reorder/replace + slot round-trip/migration.
|
||||||
testSlotMapDenseAppend();
|
// (Pure SlotMap-only unit behaviour lives in slot_map_tests.)
|
||||||
testSlotMapRemoveLeavesGap();
|
|
||||||
testSlotMapAppendAfterGapGoesToFrontier();
|
|
||||||
testSlotMapReorderIntoEmpty();
|
|
||||||
testSlotMapReorderOntoOccupiedInsertsAndShifts();
|
|
||||||
testSlotMapReorderPreservesInteriorGapAboveTarget();
|
|
||||||
testSlotMapReorderUnmappedIsNoOp();
|
|
||||||
testSlotMapNegativeTargetClampsToZero();
|
|
||||||
testSlotMapResetDenseSkipsDupesAndEmpties();
|
|
||||||
testSlotMapReconcileDropsStaleAppendsNew();
|
|
||||||
testSlotMapEqualityAndFromEntries();
|
|
||||||
testBankBookSlotsRoundTrip();
|
testBankBookSlotsRoundTrip();
|
||||||
testMigrationDefaultsToInsertionOrderDense();
|
testMigrationDefaultsToInsertionOrderDense();
|
||||||
testOrderedSampleIdsReconcilesLazily();
|
testOrderedSampleIdsReconcilesLazily();
|
||||||
|
|||||||
@@ -104,6 +104,30 @@ static void testFullFieldRoundTrip() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Golden byte-literal (Q-W1 T?-05 follow-up): pins the EXACT serialized bytes for
|
||||||
|
// a small fixture, not just self-consistent re-serialization — a format drift
|
||||||
|
// that round-trips losslessly (e.g. a renamed key both writer and reader agree
|
||||||
|
// on) would slip past testFullFieldRoundTrip but not this. The format is frozen
|
||||||
|
// as-shipped; the literal below is the captured current output.
|
||||||
|
static void testSerializeGoldenLiteral() {
|
||||||
|
BankModel idx;
|
||||||
|
Sample s;
|
||||||
|
s.id = "g1";
|
||||||
|
s.relativePath = "bank/g1.wav";
|
||||||
|
s.contentHash = "hash-g1";
|
||||||
|
CHECK(idx.add(s) == AddResult::Added);
|
||||||
|
CHECK(idx.serialize() ==
|
||||||
|
"{\"version\":1,\"samples\":[{\"id\":\"g1\",\"displayName\":\"\","
|
||||||
|
"\"relativePath\":\"bank/g1.wav\",\"sourceMode\":0,\"sourceRange\":{"
|
||||||
|
"\"startSeconds\":0,\"endSeconds\":0,\"startPpq\":0,\"endPpq\":0},"
|
||||||
|
"\"trackGuids\":[],\"wetDry\":1,\"channelCount\":0,\"sampleRate\":0,"
|
||||||
|
"\"lengthSeconds\":0,\"lengthBeats\":0,\"captureTempo\":0,"
|
||||||
|
"\"captureTimeSigNum\":0,\"captureTimeSigDenom\":0,\"key\":null,"
|
||||||
|
"\"rootNote\":null,\"loop\":null,\"levels\":{\"peakDb\":0,\"rmsDb\":0,"
|
||||||
|
"\"lufs\":0},\"clipped\":false,\"tier\":0,\"contentHash\":\"hash-g1\","
|
||||||
|
"\"provenance\":null,\"createdTimestamp\":0}]}");
|
||||||
|
}
|
||||||
|
|
||||||
static void testDedupByHash() {
|
static void testDedupByHash() {
|
||||||
BankModel idx;
|
BankModel idx;
|
||||||
Sample a = fullSample("x");
|
Sample a = fullSample("x");
|
||||||
@@ -551,6 +575,7 @@ static void testSeamFieldsAdditiveInvariant() {
|
|||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
testFullFieldRoundTrip();
|
testFullFieldRoundTrip();
|
||||||
|
testSerializeGoldenLiteral();
|
||||||
testDedupByHash();
|
testDedupByHash();
|
||||||
testTierFilterAndMove();
|
testTierFilterAndMove();
|
||||||
testRelativePathInvariant();
|
testRelativePathInvariant();
|
||||||
|
|||||||
@@ -36,6 +36,18 @@ static void testEmptyManifest() {
|
|||||||
CHECK(back->empty());
|
CHECK(back->empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Golden byte-literal (Q-W1 follow-up): pins the EXACT serialized bytes for a
|
||||||
|
// small fixture (two paths), not just self-consistent re-serialization — a
|
||||||
|
// format drift that both writer and reader agree on would slip past the
|
||||||
|
// round-trip tests but not this. The format is frozen as-shipped; the literal
|
||||||
|
// below is the captured current output.
|
||||||
|
static void testSerializeGoldenLiteral() {
|
||||||
|
OwnedFileManifest m;
|
||||||
|
m.add("reasampler_bank/a.wav");
|
||||||
|
m.add("reasampler_bank/b.wav");
|
||||||
|
CHECK(m.serialize() == "{\"owned\":[\"reasampler_bank/a.wav\",\"reasampler_bank/b.wav\"]}");
|
||||||
|
}
|
||||||
|
|
||||||
// --- add / contains / order --------------------------------------------------
|
// --- add / contains / order --------------------------------------------------
|
||||||
|
|
||||||
static void testAddAndContains() {
|
static void testAddAndContains() {
|
||||||
@@ -159,6 +171,7 @@ static void testMalformedParse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
testSerializeGoldenLiteral();
|
||||||
testEmptyManifest();
|
testEmptyManifest();
|
||||||
testAddAndContains();
|
testAddAndContains();
|
||||||
testDedupRepeatedAdds();
|
testDedupRepeatedAdds();
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
// Standalone tests for reasampler::model::SlotMap — no REAPER, no test framework.
|
||||||
|
// SlotMap is the L7 gap-preserving display-position carrier for one bank, extracted
|
||||||
|
// from bank_book (Q-W1, T4-05). These are the pure SlotMap-only assertions that
|
||||||
|
// previously lived inline in test_bank_book.cpp (the L7 "SlotMap unit behaviour"
|
||||||
|
// block); test_bank_book.cpp keeps its BankBook-level integration coverage
|
||||||
|
// (reorderSample / reconcileSlots / JSON round-trip WITH a full book), this file
|
||||||
|
// owns the module's own contract: add/remove/query, reorder gap-preservation,
|
||||||
|
// resetDense/reconcile, equality/fromEntries, and the serialize wire shape.
|
||||||
|
|
||||||
|
#include "../src/core/model/slot_map.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../src/core/json/json.h"
|
||||||
|
|
||||||
|
using namespace reasampler::model;
|
||||||
|
namespace json = reasampler::json;
|
||||||
|
|
||||||
|
static int g_fail = 0;
|
||||||
|
#define CHECK(cond) do { if(!(cond)) { \
|
||||||
|
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
||||||
|
|
||||||
|
// --- SlotMap unit behaviour --------------------------------------------------
|
||||||
|
|
||||||
|
static void testSlotMapDenseAppend() {
|
||||||
|
SlotMap m;
|
||||||
|
m.append("a");
|
||||||
|
m.append("b");
|
||||||
|
m.append("c");
|
||||||
|
CHECK(m.slotOf("a") == 0);
|
||||||
|
CHECK(m.slotOf("b") == 1);
|
||||||
|
CHECK(m.slotOf("c") == 2);
|
||||||
|
CHECK(m.maxSlot() == 2);
|
||||||
|
CHECK((m.orderedIds() == std::vector<std::string>{"a", "b", "c"}));
|
||||||
|
CHECK(m.idAt(1) == "b");
|
||||||
|
CHECK(m.slotOf("nope") == -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSlotMapRemoveLeavesGap() {
|
||||||
|
SlotMap m;
|
||||||
|
m.append("a"); m.append("b"); m.append("c"); // 0,1,2
|
||||||
|
CHECK(m.remove("b")); // slot 1 now EMPTY (no re-pack)
|
||||||
|
CHECK(m.slotOf("a") == 0);
|
||||||
|
CHECK(m.slotOf("c") == 2); // c did NOT shift down
|
||||||
|
CHECK(m.idAt(1).empty()); // gap preserved
|
||||||
|
CHECK((m.orderedIds() == std::vector<std::string>{"a", "c"}));
|
||||||
|
CHECK(!m.remove("b")); // already gone
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSlotMapAppendAfterGapGoesToFrontier() {
|
||||||
|
SlotMap m;
|
||||||
|
m.append("a"); m.append("b"); m.append("c"); // 0,1,2
|
||||||
|
m.remove("a"); // slot 0 empty
|
||||||
|
m.append("d"); // append goes AFTER last occupied (2) -> 3
|
||||||
|
CHECK(m.slotOf("d") == 3); // did NOT fill the slot-0 gap
|
||||||
|
CHECK(m.idAt(0).empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSlotMapReorderIntoEmpty() {
|
||||||
|
SlotMap m;
|
||||||
|
m.append("a"); m.append("b"); m.append("c"); // 0,1,2
|
||||||
|
m.remove("b"); // slot 1 empty
|
||||||
|
CHECK(m.reorder("c", 1)); // c -> empty slot 1; its slot 2 empties
|
||||||
|
CHECK(m.slotOf("c") == 1);
|
||||||
|
CHECK(m.idAt(2).empty());
|
||||||
|
CHECK(m.slotOf("a") == 0); // untouched
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSlotMapReorderOntoOccupiedInsertsAndShifts() {
|
||||||
|
SlotMap m;
|
||||||
|
m.append("a"); m.append("b"); m.append("c"); m.append("d"); // 0,1,2,3
|
||||||
|
CHECK(m.reorder("d", 1)); // d onto occupied slot 1 -> insert-before, shift b,c up
|
||||||
|
CHECK(m.slotOf("a") == 0); // before the target: unchanged
|
||||||
|
CHECK(m.slotOf("d") == 1); // took the target slot
|
||||||
|
CHECK(m.slotOf("b") == 2); // shifted +1
|
||||||
|
CHECK(m.slotOf("c") == 3); // shifted +1
|
||||||
|
CHECK((m.orderedIds() == std::vector<std::string>{"a", "d", "b", "c"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSlotMapReorderPreservesInteriorGapAboveTarget() {
|
||||||
|
SlotMap m;
|
||||||
|
m.append("a"); m.append("b"); m.append("c"); // 0,1,2
|
||||||
|
m.remove("b"); // gap at 1: a@0, c@2
|
||||||
|
m.append("d"); // d@3
|
||||||
|
CHECK(m.reorder("d", 0)); // d onto occupied slot 0 -> a shifts to 1, c shifts to 3
|
||||||
|
CHECK(m.slotOf("d") == 0);
|
||||||
|
CHECK(m.slotOf("a") == 1); // shifted from 0 -> 1
|
||||||
|
CHECK(m.slotOf("c") == 3); // shifted from 2 -> 3 (gap at 2 preserved as a +1 of its own)
|
||||||
|
CHECK(m.idAt(2).empty()); // interior gap above the target survives
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSlotMapReorderUnmappedIsNoOp() {
|
||||||
|
SlotMap m;
|
||||||
|
m.append("a");
|
||||||
|
CHECK(!m.reorder("ghost", 0)); // not mapped -> false, no mutation
|
||||||
|
CHECK(m.slotOf("a") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSlotMapNegativeTargetClampsToZero() {
|
||||||
|
SlotMap m;
|
||||||
|
m.append("a"); m.append("b"); // 0,1
|
||||||
|
CHECK(m.reorder("b", -3)); // clamp to 0 -> insert-before a
|
||||||
|
CHECK(m.slotOf("b") == 0);
|
||||||
|
CHECK(m.slotOf("a") == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSlotMapResetDenseSkipsDupesAndEmpties() {
|
||||||
|
SlotMap m;
|
||||||
|
m.resetDense({"a", "", "b", "a", "c"}); // "" and the second "a" dropped
|
||||||
|
CHECK((m.orderedIds() == std::vector<std::string>{"a", "b", "c"}));
|
||||||
|
CHECK(m.slotOf("a") == 0);
|
||||||
|
CHECK(m.slotOf("c") == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSlotMapReconcileDropsStaleAppendsNew() {
|
||||||
|
SlotMap m;
|
||||||
|
m.append("a"); m.append("b"); m.append("c"); // 0,1,2
|
||||||
|
m.reconcile({"a", "c", "d"}); // b left the index (drop), d is new (append)
|
||||||
|
CHECK(m.slotOf("a") == 0); // kept at its slot
|
||||||
|
CHECK(m.slotOf("c") == 2); // kept at its slot (gap where b was)
|
||||||
|
CHECK(m.slotOf("b") == -1); // stale marker dropped
|
||||||
|
CHECK(m.slotOf("d") == 3); // appended after the frontier
|
||||||
|
CHECK(m.idAt(1).empty()); // b's slot stays empty
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSlotMapEqualityAndFromEntries() {
|
||||||
|
SlotMap a;
|
||||||
|
a.append("x"); a.append("y");
|
||||||
|
SlotMap b = SlotMap::fromEntries({{"x", 0}, {"y", 1}});
|
||||||
|
CHECK(a == b);
|
||||||
|
// Defensive repair: duplicate id (first wins), slot conflict (later dropped),
|
||||||
|
// empty id / negative slot dropped.
|
||||||
|
SlotMap c = SlotMap::fromEntries({{"x", 0}, {"x", 5}, {"y", 0}, {"", 9}, {"z", -1}, {"w", 2}});
|
||||||
|
CHECK(c.slotOf("x") == 0); // first x wins
|
||||||
|
CHECK(c.slotOf("y") == -1); // slot 0 already taken -> dropped
|
||||||
|
CHECK(c.slotOf("w") == 2); // valid
|
||||||
|
CHECK(c.slotOf("z") == -1); // negative slot dropped
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- serialize: golden byte-literal + round-trip -----------------------------
|
||||||
|
|
||||||
|
// Pins the exact wire shape (an array of {"id":..,"slot":..} objects, ascending
|
||||||
|
// slot, no whitespace) so a future format drift is caught here rather than only
|
||||||
|
// as a downstream bank_book diff. Mirrors the pre-extraction bank_book writer
|
||||||
|
// byte-for-byte (core/json emit helpers are shared, not reimplemented).
|
||||||
|
static void testSlotMapSerializeGoldenLiteral() {
|
||||||
|
SlotMap empty;
|
||||||
|
CHECK(empty.serialize() == "[]");
|
||||||
|
|
||||||
|
SlotMap m;
|
||||||
|
m.append("a");
|
||||||
|
m.append("b");
|
||||||
|
CHECK(m.serialize() == "[{\"id\":\"a\",\"slot\":0},{\"id\":\"b\",\"slot\":1}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// A local mirror of bank_book's private parseSlots (the "slots" array grammar):
|
||||||
|
// [{id, slot}, ...]. slot_map.cpp itself only emits — JSON parsing is a consumer
|
||||||
|
// concern (see slot_map.h) — so the round-trip proof below parses the emitted
|
||||||
|
// text back into pairs the same way bank_book does, then rebuilds via
|
||||||
|
// SlotMap::fromEntries and checks equality against the original.
|
||||||
|
static bool parseSlotsArray(json::Reader& r, std::vector<std::pair<std::string, int>>& out) {
|
||||||
|
out.clear();
|
||||||
|
if (!r.consume('[')) return false;
|
||||||
|
r.skipWs();
|
||||||
|
if (r.consume(']')) return true; // empty array
|
||||||
|
do {
|
||||||
|
if (!r.consume('{')) return false;
|
||||||
|
std::string id;
|
||||||
|
int slot = 0;
|
||||||
|
bool haveId = false, haveSlot = false;
|
||||||
|
do {
|
||||||
|
std::string k;
|
||||||
|
if (!r.parseKey(k)) return false;
|
||||||
|
if (k == "id") { if (!r.parseString(id)) return false; haveId = true; }
|
||||||
|
else if (k == "slot") { if (!r.parseInt(slot)) return false; haveSlot = true; }
|
||||||
|
else { if (!r.skipValue()) return false; }
|
||||||
|
} while (r.consume(','));
|
||||||
|
if (!r.consume('}')) return false;
|
||||||
|
if (!haveId || !haveSlot) return false;
|
||||||
|
out.emplace_back(std::move(id), slot);
|
||||||
|
} while (r.consume(','));
|
||||||
|
return r.consume(']');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testSlotMapSerializeRoundTrip() {
|
||||||
|
SlotMap m;
|
||||||
|
m.append("a"); m.append("b"); m.append("c");
|
||||||
|
m.remove("b"); // leave a gap: a@0, c@2
|
||||||
|
m.append("d"); // d@3
|
||||||
|
|
||||||
|
const std::string blob = m.serialize();
|
||||||
|
json::Reader r(blob);
|
||||||
|
std::vector<std::pair<std::string, int>> pairs;
|
||||||
|
CHECK(parseSlotsArray(r, pairs));
|
||||||
|
|
||||||
|
SlotMap round = SlotMap::fromEntries(pairs);
|
||||||
|
CHECK(round == m);
|
||||||
|
CHECK(round.slotOf("a") == 0);
|
||||||
|
CHECK(round.idAt(1).empty()); // gap survives the round trip
|
||||||
|
CHECK(round.slotOf("c") == 2);
|
||||||
|
CHECK(round.slotOf("d") == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
testSlotMapDenseAppend();
|
||||||
|
testSlotMapRemoveLeavesGap();
|
||||||
|
testSlotMapAppendAfterGapGoesToFrontier();
|
||||||
|
testSlotMapReorderIntoEmpty();
|
||||||
|
testSlotMapReorderOntoOccupiedInsertsAndShifts();
|
||||||
|
testSlotMapReorderPreservesInteriorGapAboveTarget();
|
||||||
|
testSlotMapReorderUnmappedIsNoOp();
|
||||||
|
testSlotMapNegativeTargetClampsToZero();
|
||||||
|
testSlotMapResetDenseSkipsDupesAndEmpties();
|
||||||
|
testSlotMapReconcileDropsStaleAppendsNew();
|
||||||
|
testSlotMapEqualityAndFromEntries();
|
||||||
|
testSlotMapSerializeGoldenLiteral();
|
||||||
|
testSlotMapSerializeRoundTrip();
|
||||||
|
|
||||||
|
if (g_fail == 0) {
|
||||||
|
std::printf("slot_map_tests: all passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::printf("slot_map_tests: %d failure(s)\n", g_fail);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@@ -56,6 +56,20 @@ static int flagValue(const TrackPlan& p, Flag f) {
|
|||||||
return -999; // sentinel: flag absent
|
return -999; // sentinel: flag absent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Golden byte-literal (Q-W1 follow-up): pins the EXACT serialized bytes for the
|
||||||
|
// default-seeded model (Arrange + Design, no membership), not just self-
|
||||||
|
// consistent re-serialization — a format drift that both writer and reader
|
||||||
|
// agree on would slip past the round-trip tests but not this. The format is
|
||||||
|
// frozen as-shipped; the literal below is the captured current output.
|
||||||
|
static void testSerializeGoldenLiteral() {
|
||||||
|
ViewModeModel vm;
|
||||||
|
CHECK(vm.serialize() ==
|
||||||
|
"{\"version\":1,\"activeMode\":\"arrange\",\"modes\":[{\"id\":\"arrange\","
|
||||||
|
"\"displayName\":\"Arrange\",\"ordinal\":0},{\"id\":\"design\","
|
||||||
|
"\"displayName\":\"Design\",\"ordinal\":1}],\"membership\":[],"
|
||||||
|
"\"snapshots\":[],\"lanes\":[]}");
|
||||||
|
}
|
||||||
|
|
||||||
// -- 1. N-mode proven --------------------------------------------------------
|
// -- 1. N-mode proven --------------------------------------------------------
|
||||||
|
|
||||||
static void testNModeRegistryAndMembership() {
|
static void testNModeRegistryAndMembership() {
|
||||||
@@ -1787,6 +1801,7 @@ static void testLaneMalformedJson() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
testSerializeGoldenLiteral();
|
||||||
testNModeRegistryAndMembership();
|
testNModeRegistryAndMembership();
|
||||||
testParentDerivationMultiMode();
|
testParentDerivationMultiMode();
|
||||||
testParentOwnMembershipVisibility();
|
testParentOwnMembershipVisibility();
|
||||||
|
|||||||
Reference in New Issue
Block a user