diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c9989d..09e9d7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -249,6 +249,16 @@ target_include_directories(tail_control PUBLIC src) target_link_libraries(tail_control PUBLIC render_settings) 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 # (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. # 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_link_libraries(bank_book PUBLIC bank_model) +target_link_libraries(bank_book PUBLIC slot_map) 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 # sample_map (which pulls bank_book/wav_trim/sampler_core transitively) and # 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 # the container and decodes back through the instrument's own reader. Mirror of # 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) 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) target_link_libraries(wav_trim_tests PRIVATE wav_trim) add_test(NAME wav_trim_tests COMMAND wav_trim_tests) diff --git a/src/actions.h b/src/actions.h index 492f0e5..936b381 100644 --- a/src/actions.h +++ b/src/actions.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // 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 // a mode, tag/untag/show-both the current track selection. Each action mutates the diff --git a/src/bank_panel.h b/src/bank_panel.h index 0139852..1d5d52b 100644 --- a/src/bank_panel.h +++ b/src/bank_panel.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // 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 // bank as a grid of LICE-drawn waveform thumbnails. The panel itself NEVER inserts diff --git a/src/core/audio/peaks.cpp b/src/core/audio/peaks.cpp index 3c4f03a..1d79625 100644 --- a/src/core/audio/peaks.cpp +++ b/src/core/audio/peaks.cpp @@ -123,4 +123,4 @@ std::size_t lastFrameAboveThreshold(const std::vector& interleaved, return kNoFrameAboveThreshold; } -} // namespace reasampler::audio \ No newline at end of file +} // namespace reasampler::audio diff --git a/src/core/audio/peaks.h b/src/core/audio/peaks.h index f1e1ee9..6f3d5b6 100644 --- a/src/core/audio/peaks.h +++ b/src/core/audio/peaks.h @@ -119,4 +119,4 @@ std::size_t lastFrameAboveThreshold(const std::vector& interleaved, std::size_t frameCount, AudioSample linearThreshold); -} // namespace reasampler::audio \ No newline at end of file +} // namespace reasampler::audio diff --git a/src/core/capture/batch_capture.cpp b/src/core/capture/batch_capture.cpp index 1d85add..3f5ff1d 100644 --- a/src/core/capture/batch_capture.cpp +++ b/src/core/capture/batch_capture.cpp @@ -73,4 +73,4 @@ std::string BatchOutcome::summaryLine(const std::string& noun) const { return line; } -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/batch_capture.h b/src/core/capture/batch_capture.h index 9ad5084..e1353a7 100644 --- a/src/core/capture/batch_capture.h +++ b/src/core/capture/batch_capture.h @@ -97,4 +97,4 @@ private: std::vector results_; }; -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/capture_paths.cpp b/src/core/capture/capture_paths.cpp index f7dc629..175f40a 100644 --- a/src/core/capture/capture_paths.cpp +++ b/src/core/capture/capture_paths.cpp @@ -284,4 +284,4 @@ ProjectTransition classifyProjectTransition(bool sameProjectObject, return ProjectTransition::NoOp; } -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/capture_paths.h b/src/core/capture/capture_paths.h index 8c92268..1cb9f6c 100644 --- a/src/core/capture/capture_paths.h +++ b/src/core/capture/capture_paths.h @@ -212,4 +212,4 @@ ProjectTransition classifyProjectTransition(bool sameProjectObject, const std::string& currentGuid, const std::string& currentPath); -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/insert_plan.cpp b/src/core/capture/insert_plan.cpp index d9590de..b58d56c 100644 --- a/src/core/capture/insert_plan.cpp +++ b/src/core/capture/insert_plan.cpp @@ -46,4 +46,4 @@ int computeInsertMode(const InsertOptions& opts) { return mode; } -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/insert_plan.h b/src/core/capture/insert_plan.h index a3b9770..2bb45e9 100644 --- a/src/core/capture/insert_plan.h +++ b/src/core/capture/insert_plan.h @@ -72,4 +72,4 @@ int computeInsertMode(const InsertOptions& opts); // any computed mode (the "no silent time-stretch" invariant, made checkable). inline constexpr int kStretchToTimeSelBit = 4; -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/realtime_record.cpp b/src/core/capture/realtime_record.cpp index 8a42296..9a754c4 100644 --- a/src/core/capture/realtime_record.cpp +++ b/src/core/capture/realtime_record.cpp @@ -124,4 +124,4 @@ bool isTerminalPhase(RecordPhase phase) { return phase == RecordPhase::Done || phase == RecordPhase::Failed; } -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/realtime_record.h b/src/core/capture/realtime_record.h index 3ab269b..76e551a 100644 --- a/src/core/capture/realtime_record.h +++ b/src/core/capture/realtime_record.h @@ -235,4 +235,4 @@ bool isStopRequested(RecordPhase phase); // Only Done and Failed are terminal; Recording and Finalizing are live. bool isTerminalPhase(RecordPhase phase); -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/render_settings.cpp b/src/core/capture/render_settings.cpp index d73ff32..a83c5ca 100644 --- a/src/core/capture/render_settings.cpp +++ b/src/core/capture/render_settings.cpp @@ -221,4 +221,4 @@ const std::vector& captureActionTable() { return table; } -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/render_settings.h b/src/core/capture/render_settings.h index 75f8e30..4c88323 100644 --- a/src/core/capture/render_settings.h +++ b/src/core/capture/render_settings.h @@ -262,4 +262,4 @@ struct CaptureActionDef { // capture applies is read from the docked-panel setting, not baked into the row. const std::vector& captureActionTable(); -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/tail_control.cpp b/src/core/capture/tail_control.cpp index 1ee105d..01b9ad8 100644 --- a/src/core/capture/tail_control.cpp +++ b/src/core/capture/tail_control.cpp @@ -128,4 +128,4 @@ std::optional deserializeTailSetting(const std::string& blob) { return out; } -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/tail_control.h b/src/core/capture/tail_control.h index 2ea3855..9430dba 100644 --- a/src/core/capture/tail_control.h +++ b/src/core/capture/tail_control.h @@ -67,4 +67,4 @@ std::string tailToggleLabel(const TailSetting& setting); std::string serializeTailSetting(const TailSetting& setting); std::optional deserializeTailSetting(const std::string& json); -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/wav_trim.cpp b/src/core/capture/wav_trim.cpp index 5c4d89b..3fbe945 100644 --- a/src/core/capture/wav_trim.cpp +++ b/src/core/capture/wav_trim.cpp @@ -157,4 +157,4 @@ WavTruncatePlan planWavTruncate(const WavLayout& layout, std::size_t keptFrames) return plan; } -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/capture/wav_trim.h b/src/core/capture/wav_trim.h index f47eea2..497790d 100644 --- a/src/core/capture/wav_trim.h +++ b/src/core/capture/wav_trim.h @@ -100,4 +100,4 @@ struct WavTruncatePlan { // truncate the file to newFileByteLength. WavTruncatePlan planWavTruncate(const WavLayout& layout, std::size_t keptFrames); -} // namespace reasampler::capture \ No newline at end of file +} // namespace reasampler::capture diff --git a/src/core/instrument/engine/master_gain.cpp b/src/core/instrument/engine/master_gain.cpp index 4fac41a..02957dc 100644 --- a/src/core/instrument/engine/master_gain.cpp +++ b/src/core/instrument/engine/master_gain.cpp @@ -48,4 +48,4 @@ void formatMasterGainLabel(double norm, char* buf, std::size_t len) { std::snprintf(buf, len, "%+.1fdB", db); } -} // namespace reasampler::instrument::engine \ No newline at end of file +} // namespace reasampler::instrument::engine diff --git a/src/core/instrument/engine/master_gain.h b/src/core/instrument/engine/master_gain.h index dce0d67..42e2b3b 100644 --- a/src/core/instrument/engine/master_gain.h +++ b/src/core/instrument/engine/master_gain.h @@ -55,4 +55,4 @@ double masterGainNormFromLinear(double linear); // including the terminator. Pure. void formatMasterGainLabel(double norm, char* buf, std::size_t len); -} // namespace reasampler::instrument::engine \ No newline at end of file +} // namespace reasampler::instrument::engine diff --git a/src/core/instrument/engine/pitch_shift.cpp b/src/core/instrument/engine/pitch_shift.cpp index 96df91f..6bcdae3 100644 --- a/src/core/instrument/engine/pitch_shift.cpp +++ b/src/core/instrument/engine/pitch_shift.cpp @@ -431,4 +431,4 @@ AudioSample PitchShifter::processImpl(AudioSample in, const SpliceEvent* linked) return static_cast(out); } -} // namespace reasampler::instrument::engine \ No newline at end of file +} // namespace reasampler::instrument::engine diff --git a/src/core/instrument/engine/pitch_shift.h b/src/core/instrument/engine/pitch_shift.h index d866826..2a73b8d 100644 --- a/src/core/instrument/engine/pitch_shift.h +++ b/src/core/instrument/engine/pitch_shift.h @@ -227,4 +227,4 @@ private: // live fade by that rate. }; -} // namespace reasampler::instrument::engine \ No newline at end of file +} // namespace reasampler::instrument::engine diff --git a/src/core/instrument/engine/velocity_curve.cpp b/src/core/instrument/engine/velocity_curve.cpp index 868073a..50af883 100644 --- a/src/core/instrument/engine/velocity_curve.cpp +++ b/src/core/instrument/engine/velocity_curve.cpp @@ -269,4 +269,4 @@ bool VelocityCurve::equals(const VelocityCurve& other, double eps) const { return true; } -} // namespace reasampler::instrument::engine \ No newline at end of file +} // namespace reasampler::instrument::engine diff --git a/src/core/instrument/engine/velocity_curve.h b/src/core/instrument/engine/velocity_curve.h index 5d1bb27..a2de2dc 100644 --- a/src/core/instrument/engine/velocity_curve.h +++ b/src/core/instrument/engine/velocity_curve.h @@ -168,4 +168,4 @@ private: std::vector points_; }; -} // namespace reasampler::instrument::engine \ No newline at end of file +} // namespace reasampler::instrument::engine diff --git a/src/core/instrument/map/bank_sync.cpp b/src/core/instrument/map/bank_sync.cpp index b88eea7..10532e9 100644 --- a/src/core/instrument/map/bank_sync.cpp +++ b/src/core/instrument/map/bank_sync.cpp @@ -60,4 +60,4 @@ AssignConsumeDecision consumeDecision(const std::optional& re return d; } -} // namespace reasampler::instrument::map \ No newline at end of file +} // namespace reasampler::instrument::map diff --git a/src/core/instrument/map/bank_sync.h b/src/core/instrument/map/bank_sync.h index acaf1ad..856bfe5 100644 --- a/src/core/instrument/map/bank_sync.h +++ b/src/core/instrument/map/bank_sync.h @@ -104,4 +104,4 @@ AssignConsumeDecision consumeDecision(const std::optional& re std::int64_t lastConsumed, bool resolves, bool isFocusedTarget); -} // namespace reasampler::instrument::map \ No newline at end of file +} // namespace reasampler::instrument::map diff --git a/src/core/instrument/map/bridge_marshal.cpp b/src/core/instrument/map/bridge_marshal.cpp index 3414c34..2163b55 100644 --- a/src/core/instrument/map/bridge_marshal.cpp +++ b/src/core/instrument/map/bridge_marshal.cpp @@ -13,4 +13,4 @@ std::optional decodeGetProjExtState(int apiReturn, return buffer; } -} // namespace reasampler::instrument::map \ No newline at end of file +} // namespace reasampler::instrument::map diff --git a/src/core/instrument/map/bridge_marshal.h b/src/core/instrument/map/bridge_marshal.h index 3e432dd..9e918f4 100644 --- a/src/core/instrument/map/bridge_marshal.h +++ b/src/core/instrument/map/bridge_marshal.h @@ -34,4 +34,4 @@ namespace reasampler::instrument::map { std::optional decodeGetProjExtState(int apiReturn, const std::string& buffer); -} // namespace reasampler::instrument::map \ No newline at end of file +} // namespace reasampler::instrument::map diff --git a/src/core/instrument/map/note_entry.cpp b/src/core/instrument/map/note_entry.cpp index 3e936e3..3a3e2a1 100644 --- a/src/core/instrument/map/note_entry.cpp +++ b/src/core/instrument/map/note_entry.cpp @@ -110,4 +110,4 @@ std::optional parseNoteEntry(const std::string& text) { return parseNoteName(s); } -} // namespace reasampler::instrument::map \ No newline at end of file +} // namespace reasampler::instrument::map diff --git a/src/core/instrument/map/note_entry.h b/src/core/instrument/map/note_entry.h index 07dbaf0..b1e909d 100644 --- a/src/core/instrument/map/note_entry.h +++ b/src/core/instrument/map/note_entry.h @@ -30,4 +30,4 @@ namespace reasampler::instrument::map { // into [0,127]; empty or unparseable input returns nullopt (no change). Pure — no host types. std::optional parseNoteEntry(const std::string& text); -} // namespace reasampler::instrument::map \ No newline at end of file +} // namespace reasampler::instrument::map diff --git a/src/core/instrument/map/trigger_seam.cpp b/src/core/instrument/map/trigger_seam.cpp index 3bcc81b..812fb3b 100644 --- a/src/core/instrument/map/trigger_seam.cpp +++ b/src/core/instrument/map/trigger_seam.cpp @@ -24,4 +24,4 @@ std::int64_t fadeFractionToFrames(double fadeFraction, std::int64_t playLength) return static_cast(fadeFraction * static_cast(playLength) + 0.5); } -} // namespace reasampler::instrument::map \ No newline at end of file +} // namespace reasampler::instrument::map diff --git a/src/core/instrument/map/trigger_seam.h b/src/core/instrument/map/trigger_seam.h index 483bb38..6539589 100644 --- a/src/core/instrument/map/trigger_seam.h +++ b/src/core/instrument/map/trigger_seam.h @@ -48,4 +48,4 @@ double framesToFadeFraction(std::int64_t fadeFrames, std::int64_t playLength); // Rounds to nearest integer frame. Returns 0 when playLength == 0. std::int64_t fadeFractionToFrames(double fadeFraction, std::int64_t playLength); -} // namespace reasampler::instrument::map \ No newline at end of file +} // namespace reasampler::instrument::map diff --git a/src/core/instrument/ui/browser_scroll.cpp b/src/core/instrument/ui/browser_scroll.cpp index d643f1a..3a7f9ad 100644 --- a/src/core/instrument/ui/browser_scroll.cpp +++ b/src/core/instrument/ui/browser_scroll.cpp @@ -155,4 +155,4 @@ std::vector filterNameIndices(const std::vector& names, return out; } -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/browser_scroll.h b/src/core/instrument/ui/browser_scroll.h index f04e88c..641d409 100644 --- a/src/core/instrument/ui/browser_scroll.h +++ b/src/core/instrument/ui/browser_scroll.h @@ -104,4 +104,4 @@ bool nameMatchesQuery(const std::string& name, const std::string& query); std::vector filterNameIndices(const std::vector& names, const std::string& query); -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/capture_browser.cpp b/src/core/instrument/ui/capture_browser.cpp index 969b540..5ed9517 100644 --- a/src/core/instrument/ui/capture_browser.cpp +++ b/src/core/instrument/ui/capture_browser.cpp @@ -93,4 +93,4 @@ int filterTabHitTest(const BrowserLayout& layout, int tabCount, int x, int y) { return -1; } -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/capture_browser.h b/src/core/instrument/ui/capture_browser.h index a6ea38e..73ecbe7 100644 --- a/src/core/instrument/ui/capture_browser.h +++ b/src/core/instrument/ui/capture_browser.h @@ -89,4 +89,4 @@ Rect filterTabRect(const BrowserLayout& layout, int tabCount, int index); // tab strip. Pure. int filterTabHitTest(const BrowserLayout& layout, int tabCount, int x, int y); -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/curve_popup.cpp b/src/core/instrument/ui/curve_popup.cpp index c01b1b0..29451e1 100644 --- a/src/core/instrument/ui/curve_popup.cpp +++ b/src/core/instrument/ui/curve_popup.cpp @@ -38,4 +38,4 @@ bool popupOutsideSheet(const CurvePopupLayout& layout, int x, int y) { return !contains(layout.sheet, x, y); } -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/curve_popup.h b/src/core/instrument/ui/curve_popup.h index 4abbf63..3213ec2 100644 --- a/src/core/instrument/ui/curve_popup.h +++ b/src/core/instrument/ui/curve_popup.h @@ -45,4 +45,4 @@ CurvePopupLayout computeCurvePopup(int w, int h); // The shell additionally gates on "no drag in flight" (spec). Pure. bool popupOutsideSheet(const CurvePopupLayout& layout, int x, int y); -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/editor_geometry.cpp b/src/core/instrument/ui/editor_geometry.cpp index cce57f7..90ccfe5 100644 --- a/src/core/instrument/ui/editor_geometry.cpp +++ b/src/core/instrument/ui/editor_geometry.cpp @@ -159,4 +159,4 @@ bool addZoneHitTest(const KeymapEditorLayout& layout, int x, int y) { return contains(layout.addZoneButton, x, y); } -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/editor_geometry.h b/src/core/instrument/ui/editor_geometry.h index 50fdaee..a0c7ff9 100644 --- a/src/core/instrument/ui/editor_geometry.h +++ b/src/core/instrument/ui/editor_geometry.h @@ -139,4 +139,4 @@ ZoneHit zoneHitTest(const KeymapEditorLayout& layout, int zoneCount, int x, int // True if (x, y) lands on the "Add Zone" button. Pure. bool addZoneHitTest(const KeymapEditorLayout& layout, int x, int y); -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/embed_strip.cpp b/src/core/instrument/ui/embed_strip.cpp index 3f1a02e..7b8d827 100644 --- a/src/core/instrument/ui/embed_strip.cpp +++ b/src/core/instrument/ui/embed_strip.cpp @@ -83,4 +83,4 @@ Rect levelFillRect(const EmbedLayout& layout, double level) { return Rect::ltrb(band.x, band.y, band.x + fillW, band.bottom()); } -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/embed_strip.h b/src/core/instrument/ui/embed_strip.h index df32b24..1c28193 100644 --- a/src/core/instrument/ui/embed_strip.h +++ b/src/core/instrument/ui/embed_strip.h @@ -73,4 +73,4 @@ int zoneAtPoint(const EmbedLayout& layout, const EmbedZone* zones, int zoneCount // (rounded down). level <= 0 -> empty rect; level >= 1 -> the whole band. Pure. Rect levelFillRect(const EmbedLayout& layout, double level); -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/envelope_edit.cpp b/src/core/instrument/ui/envelope_edit.cpp index 68ab989..9b050f1 100644 --- a/src/core/instrument/ui/envelope_edit.cpp +++ b/src/core/instrument/ui/envelope_edit.cpp @@ -177,4 +177,4 @@ AmpEnvelope resolveNodeDrag(const AmpEnvelope& grabEnv, EnvNode node, const Rect return out; } -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/envelope_edit.h b/src/core/instrument/ui/envelope_edit.h index 808667d..7d7699c 100644 --- a/src/core/instrument/ui/envelope_edit.h +++ b/src/core/instrument/ui/envelope_edit.h @@ -105,4 +105,4 @@ AmpEnvelope resolveNodeDrag(const AmpEnvelope& grabEnv, EnvNode node, const Rect double totalSeconds, const EnvClampBounds& bounds, int dxPixels, int dyPixels); -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/envelope_overlay.cpp b/src/core/instrument/ui/envelope_overlay.cpp index 2876f51..a4bf59d 100644 --- a/src/core/instrument/ui/envelope_overlay.cpp +++ b/src/core/instrument/ui/envelope_overlay.cpp @@ -55,7 +55,6 @@ int levelToY(const Rect& area, double level) { namespace { - EnvVertex vtx(EnvNode node, const Rect& area, double totalSeconds, double t, double level) { EnvVertex v; v.node = node; @@ -172,4 +171,4 @@ std::vector buildEnvelopePolyline(const AmpEnvelope& env, const Rect& : triggerPolyline(env, area, totalSeconds); } -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/envelope_overlay.h b/src/core/instrument/ui/envelope_overlay.h index ee839cf..0d06945 100644 --- a/src/core/instrument/ui/envelope_overlay.h +++ b/src/core/instrument/ui/envelope_overlay.h @@ -226,4 +226,4 @@ int timeToX(const Rect& area, double totalSeconds, double t); // and the node hit-test share. int levelToY(const Rect& area, double level); -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/keyboard_strip.cpp b/src/core/instrument/ui/keyboard_strip.cpp index f6795bb..7b64af4 100644 --- a/src/core/instrument/ui/keyboard_strip.cpp +++ b/src/core/instrument/ui/keyboard_strip.cpp @@ -146,4 +146,4 @@ int resolveDragNote(const StripLayout& layout, int startNote, int dxPixels) { return clampNote(startNote + shift); } -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/keyboard_strip.h b/src/core/instrument/ui/keyboard_strip.h index 2a6902b..2f843a3 100644 --- a/src/core/instrument/ui/keyboard_strip.h +++ b/src/core/instrument/ui/keyboard_strip.h @@ -128,4 +128,4 @@ int resolveDragNote(const StripLayout& layout, int startNote, int dxPixels); // pastel spectral fill (S-VIEW-7). Pure — no layout required, no host types. bool isNaturalKey(int note); -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/knob_deck.cpp b/src/core/instrument/ui/knob_deck.cpp index 06be9b4..6eae276 100644 --- a/src/core/instrument/ui/knob_deck.cpp +++ b/src/core/instrument/ui/knob_deck.cpp @@ -153,4 +153,4 @@ DeckHit hitTestDeck(const DeckLayout& layout, int x, int y) { return {}; } -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/knob_deck.h b/src/core/instrument/ui/knob_deck.h index 5ffa3fc..2d07b70 100644 --- a/src/core/instrument/ui/knob_deck.h +++ b/src/core/instrument/ui/knob_deck.h @@ -130,4 +130,4 @@ struct DeckHit { // Pure — the shell's routing entry point. DeckHit hitTestDeck(const DeckLayout& layout, int x, int y); -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/param_slider.cpp b/src/core/instrument/ui/param_slider.cpp index bc2b663..6aa17cc 100644 --- a/src/core/instrument/ui/param_slider.cpp +++ b/src/core/instrument/ui/param_slider.cpp @@ -99,7 +99,6 @@ double normDeg(double deg) { return deg; } - } // namespace KnobGeometry computeKnob(const Rect& cell) { @@ -157,4 +156,4 @@ int controlAtPoint(const std::vector& rows, int x, int y) { return -1; } -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/param_slider.h b/src/core/instrument/ui/param_slider.h index 1cddbf7..0720388 100644 --- a/src/core/instrument/ui/param_slider.h +++ b/src/core/instrument/ui/param_slider.h @@ -177,4 +177,4 @@ double knobDragValue(double startValue, int dyPixels, // toggleSegmentHitTest / knobDragValue over the ensuing drag) and commits. int controlAtPoint(const std::vector& rows, int x, int y); -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/waveform_view.cpp b/src/core/instrument/ui/waveform_view.cpp index 0d566d6..fc004b9 100644 --- a/src/core/instrument/ui/waveform_view.cpp +++ b/src/core/instrument/ui/waveform_view.cpp @@ -96,4 +96,4 @@ std::int64_t nearestZeroCrossing(const AudioSample* pcm, std::int64_t frames, return t; // no sign change in the whole buffer -> keep the raw (clamped) target } -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/instrument/ui/waveform_view.h b/src/core/instrument/ui/waveform_view.h index 34e6948..171b39a 100644 --- a/src/core/instrument/ui/waveform_view.h +++ b/src/core/instrument/ui/waveform_view.h @@ -82,4 +82,4 @@ std::int64_t resolveDragFrame(const Rect& area, std::int64_t frameCount, std::in std::int64_t nearestZeroCrossing(const AudioSample* pcm, std::int64_t frames, std::int64_t target); -} // namespace reasampler::instrument::ui \ No newline at end of file +} // namespace reasampler::instrument::ui diff --git a/src/core/model/bank_model.cpp b/src/core/model/bank_model.cpp index 27c8ae8..dee8731 100644 --- a/src/core/model/bank_model.cpp +++ b/src/core/model/bank_model.cpp @@ -458,4 +458,4 @@ std::optional BankModel::deserialize(const std::string& blob) { return idx; } -} // namespace reasampler::model \ No newline at end of file +} // namespace reasampler::model diff --git a/src/core/model/bank_model.h b/src/core/model/bank_model.h index 01cf8af..c7612bc 100644 --- a/src/core/model/bank_model.h +++ b/src/core/model/bank_model.h @@ -214,4 +214,4 @@ private: std::vector samples_; // insertion order preserved }; -} // namespace reasampler::model \ No newline at end of file +} // namespace reasampler::model diff --git a/src/core/model/owned_manifest.cpp b/src/core/model/owned_manifest.cpp index 2dd68af..e2ae900 100644 --- a/src/core/model/owned_manifest.cpp +++ b/src/core/model/owned_manifest.cpp @@ -110,4 +110,4 @@ std::optional OwnedFileManifest::deserialize(const std::strin return m; } -} // namespace reasampler::model \ No newline at end of file +} // namespace reasampler::model diff --git a/src/core/model/owned_manifest.h b/src/core/model/owned_manifest.h index 629f67e..9135b75 100644 --- a/src/core/model/owned_manifest.h +++ b/src/core/model/owned_manifest.h @@ -88,4 +88,4 @@ private: std::vector paths_; // insertion order; deduplicated }; -} // namespace reasampler::model \ No newline at end of file +} // namespace reasampler::model diff --git a/src/core/model/provenance.cpp b/src/core/model/provenance.cpp index cd91e7b..329b66a 100644 --- a/src/core/model/provenance.cpp +++ b/src/core/model/provenance.cpp @@ -159,4 +159,4 @@ std::optional detectParent( return parent; } -} // namespace reasampler::model \ No newline at end of file +} // namespace reasampler::model diff --git a/src/core/model/provenance.h b/src/core/model/provenance.h index 1dd2ca0..cd6255d 100644 --- a/src/core/model/provenance.h +++ b/src/core/model/provenance.h @@ -146,4 +146,4 @@ std::optional detectParent( const std::vector& sourceItemFiles, const std::vector& bankFiles); -} // namespace reasampler::model \ No newline at end of file +} // namespace reasampler::model diff --git a/src/core/reclaim/prune_reconcile.cpp b/src/core/reclaim/prune_reconcile.cpp index a9b009b..464c6b9 100644 --- a/src/core/reclaim/prune_reconcile.cpp +++ b/src/core/reclaim/prune_reconcile.cpp @@ -83,4 +83,4 @@ std::vector pruneDeletePlan(const std::vector& confirm return plan; } -} // namespace reasampler::reclaim \ No newline at end of file +} // namespace reasampler::reclaim diff --git a/src/core/reclaim/prune_reconcile.h b/src/core/reclaim/prune_reconcile.h index 5d3809d..e744827 100644 --- a/src/core/reclaim/prune_reconcile.h +++ b/src/core/reclaim/prune_reconcile.h @@ -175,4 +175,4 @@ PruneReport buildPruneReport(const std::vector& orphans, std::vector pruneDeletePlan(const std::vector& confirmed, const std::vector& freshOrphans); -} // namespace reasampler::reclaim \ No newline at end of file +} // namespace reasampler::reclaim diff --git a/src/core/ui/action_bar.cpp b/src/core/ui/action_bar.cpp index 65f197d..36da30b 100644 --- a/src/core/ui/action_bar.cpp +++ b/src/core/ui/action_bar.cpp @@ -151,4 +151,4 @@ int hitTestActionBar(int px, int py, const ActionBarRect& bar, return -1; // inter-button/cluster gap or the overflow dead-zone — a clean miss } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/action_bar.h b/src/core/ui/action_bar.h index 286d928..bb754c2 100644 --- a/src/core/ui/action_bar.h +++ b/src/core/ui/action_bar.h @@ -148,4 +148,4 @@ std::vector computeBarSlots(const ActionBarRect& bar, int hitTestActionBar(int px, int py, const ActionBarRect& bar, const std::vector& clusters, const ActionBarSpec& spec); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/bank_grid.cpp b/src/core/ui/bank_grid.cpp index 371296b..2522527 100644 --- a/src/core/ui/bank_grid.cpp +++ b/src/core/ui/bank_grid.cpp @@ -224,4 +224,4 @@ float compressAmplitudeForDisplay(float linear) { return linear < 0.0f ? -clamped : clamped; } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/bank_grid.h b/src/core/ui/bank_grid.h index c744208..6c4dd00 100644 --- a/src/core/ui/bank_grid.h +++ b/src/core/ui/bank_grid.h @@ -175,4 +175,4 @@ constexpr float kDisplayFloorDb = -60.0f; // (stays on the midline). Full-scale (|linear| == 1.0f) returns exactly ±1.0f. float compressAmplitudeForDisplay(float linear); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/card_drag.cpp b/src/core/ui/card_drag.cpp index 6289d64..6f14fd9 100644 --- a/src/core/ui/card_drag.cpp +++ b/src/core/ui/card_drag.cpp @@ -93,4 +93,4 @@ int hitTestSlot(int px, int py, const std::vector& rects) { return -1; } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/card_drag.h b/src/core/ui/card_drag.h index 7ce7a9d..e64ce05 100644 --- a/src/core/ui/card_drag.h +++ b/src/core/ui/card_drag.h @@ -144,4 +144,4 @@ std::vector computeSlotRectsForDrop(int maxSlot, int panelWidth, // callers reason in model slots. int hitTestSlot(int px, int py, const std::vector& rects); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/card_meta.cpp b/src/core/ui/card_meta.cpp index a52c4e1..8987dbf 100644 --- a/src/core/ui/card_meta.cpp +++ b/src/core/ui/card_meta.cpp @@ -61,4 +61,4 @@ std::string formatSecondsMs(double lengthSeconds) { return buf; } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/card_meta.h b/src/core/ui/card_meta.h index 22cec45..a561cd3 100644 --- a/src/core/ui/card_meta.h +++ b/src/core/ui/card_meta.h @@ -52,4 +52,4 @@ std::string formatBarsBeats(const MusicalLength& m); // * negative length is clamped to "0.000" (a length is never negative; defensive). std::string formatSecondsMs(double lengthSeconds); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/component_geometry.cpp b/src/core/ui/component_geometry.cpp index 6d13d75..3e9d521 100644 --- a/src/core/ui/component_geometry.cpp +++ b/src/core/ui/component_geometry.cpp @@ -105,4 +105,4 @@ int waveformColumnCount(const KitBox& box) { return w > 0 ? w : 0; } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/component_geometry.h b/src/core/ui/component_geometry.h index af0419b..7455c47 100644 --- a/src/core/ui/component_geometry.h +++ b/src/core/ui/component_geometry.h @@ -126,4 +126,4 @@ int hitTestListRow(int px, int py, const KitBox& list, int rowHeight, int rowCou // identical whether bins == columns or bins == k*columns) and wastes memory and CPU. int waveformColumnCount(const KitBox& box); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/drag_out.cpp b/src/core/ui/drag_out.cpp index 54018a7..06a10e3 100644 --- a/src/core/ui/drag_out.cpp +++ b/src/core/ui/drag_out.cpp @@ -51,4 +51,4 @@ PathList assemblePathList(const std::vector& resolved) { return out; } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/drag_out.h b/src/core/ui/drag_out.h index 919284b..a24dd2c 100644 --- a/src/core/ui/drag_out.h +++ b/src/core/ui/drag_out.h @@ -129,4 +129,4 @@ struct PathList { // case-insensitive dedup on Windows — the pure layer does not guess a platform rule). PathList assemblePathList(const std::vector& resolved); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/footer_bar.cpp b/src/core/ui/footer_bar.cpp index ca8145a..e4b683f 100644 --- a/src/core/ui/footer_bar.cpp +++ b/src/core/ui/footer_bar.cpp @@ -66,4 +66,4 @@ FooterHit hitTestFooterBar(int px, int py, const FooterBarLayout& layout) { return FooterHit::None; } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/footer_bar.h b/src/core/ui/footer_bar.h index 8f4e15e..4d1e58f 100644 --- a/src/core/ui/footer_bar.h +++ b/src/core/ui/footer_bar.h @@ -103,4 +103,4 @@ FooterBarLayout computeFooterBar(const FooterRect& footer, const FooterBarSpec& // mode_switch over the toggle box), then the Tail hit; this returns which region was struck. FooterHit hitTestFooterBar(int px, int py, const FooterBarLayout& layout); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/mode_enable.cpp b/src/core/ui/mode_enable.cpp index fc03d7b..0f31bd8 100644 --- a/src/core/ui/mode_enable.cpp +++ b/src/core/ui/mode_enable.cpp @@ -18,4 +18,4 @@ bool tagButtonEnabled(const std::string& activeModeId, TagTarget target) { return activeModeId != targetId; } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/mode_enable.h b/src/core/ui/mode_enable.h index be0be30..0132f5e 100644 --- a/src/core/ui/mode_enable.h +++ b/src/core/ui/mode_enable.h @@ -36,4 +36,4 @@ enum class TagTarget { // disable an action the user can still reach), so a future added mode never dead-locks the bar. bool tagButtonEnabled(const std::string& activeModeId, TagTarget target); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/overflow_menu.cpp b/src/core/ui/overflow_menu.cpp index 71c0446..e6dd9b6 100644 --- a/src/core/ui/overflow_menu.cpp +++ b/src/core/ui/overflow_menu.cpp @@ -39,4 +39,4 @@ bool hitTestMenuButton(int px, int py, const MenuButtonRect& button) { py >= button.y && py < button.y + button.height; } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/overflow_menu.h b/src/core/ui/overflow_menu.h index 64bcfcb..31972ee 100644 --- a/src/core/ui/overflow_menu.h +++ b/src/core/ui/overflow_menu.h @@ -65,4 +65,4 @@ MenuButtonRect computeMenuButton(const MenuBarRect& bar, const MenuButtonSpec& s // hit-test agree on the same pixels. An empty button never claims a point (always false). bool hitTestMenuButton(int px, int py, const MenuButtonRect& button); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/prune_button.cpp b/src/core/ui/prune_button.cpp index 6ac2777..71db3a2 100644 --- a/src/core/ui/prune_button.cpp +++ b/src/core/ui/prune_button.cpp @@ -36,4 +36,4 @@ bool hitTestPruneButton(int px, int py, const ButtonRect& button) { py >= button.y && py < button.y + button.height; } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/prune_button.h b/src/core/ui/prune_button.h index f482f01..fbd5b3a 100644 --- a/src/core/ui/prune_button.h +++ b/src/core/ui/prune_button.h @@ -79,4 +79,4 @@ ButtonRect computePruneButton(const FooterRect& footer, const PruneButtonSpec& s // so a suppressed button cannot be accidentally clicked. bool hitTestPruneButton(int px, int py, const ButtonRect& button); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/tab_strip.cpp b/src/core/ui/tab_strip.cpp index f3b48d0..c0d4db7 100644 --- a/src/core/ui/tab_strip.cpp +++ b/src/core/ui/tab_strip.cpp @@ -109,4 +109,4 @@ TabHit hitTestTabStrip(int px, int py, const TabStripRect& strip, int tabCount, return miss; // track dead space (no tab under the point) } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/tab_strip.h b/src/core/ui/tab_strip.h index c58809c..4f05ee9 100644 --- a/src/core/ui/tab_strip.h +++ b/src/core/ui/tab_strip.h @@ -124,4 +124,4 @@ struct TabHit { TabHit hitTestTabStrip(int px, int py, const TabStripRect& strip, int tabCount, const TabStripSpec& spec, int scrollOffset); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/theme.cpp b/src/core/ui/theme.cpp index d947d5c..e80ee62 100644 --- a/src/core/ui/theme.cpp +++ b/src/core/ui/theme.cpp @@ -190,4 +190,4 @@ double textFloor(TextClass cls) { return cls == TextClass::Body ? 4.5 : 3.0; } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/theme.h b/src/core/ui/theme.h index 4e47916..0d6f5ba 100644 --- a/src/core/ui/theme.h +++ b/src/core/ui/theme.h @@ -115,4 +115,4 @@ double contrastRatio(const KitColor& a, const KitColor& b); // pair the kit actually draws. double textFloor(TextClass cls); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/tooltip.cpp b/src/core/ui/tooltip.cpp index d47848e..5f09110 100644 --- a/src/core/ui/tooltip.cpp +++ b/src/core/ui/tooltip.cpp @@ -50,4 +50,4 @@ TooltipBox computeTooltip(int anchorX, int anchorY, int anchorW, int anchorH, return box; } -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/ui/tooltip.h b/src/core/ui/tooltip.h index 74c5f5b..1682597 100644 --- a/src/core/ui/tooltip.h +++ b/src/core/ui/tooltip.h @@ -59,4 +59,4 @@ TooltipBox computeTooltip(int anchorX, int anchorY, int anchorW, int anchorH, int textW, int textH, int clientW, int clientH, const TooltipSpec& spec); -} // namespace reasampler::ui \ No newline at end of file +} // namespace reasampler::ui diff --git a/src/core/util/file_bytes.cpp b/src/core/util/file_bytes.cpp index 3a7b879..b66608c 100644 --- a/src/core/util/file_bytes.cpp +++ b/src/core/util/file_bytes.cpp @@ -18,4 +18,4 @@ std::vector readFileBytes(const std::string& path) { return bytes; } -} // namespace reasampler::util \ No newline at end of file +} // namespace reasampler::util diff --git a/src/core/util/file_bytes.h b/src/core/util/file_bytes.h index 16fe9d4..5214850 100644 --- a/src/core/util/file_bytes.h +++ b/src/core/util/file_bytes.h @@ -16,4 +16,4 @@ namespace reasampler::util { // "nothing to work with" branch. std::vector readFileBytes(const std::string& path); -} // namespace reasampler::util \ No newline at end of file +} // namespace reasampler::util diff --git a/src/core/version/app_version.cpp b/src/core/version/app_version.cpp index c84d212..568f02d 100644 --- a/src/core/version/app_version.cpp +++ b/src/core/version/app_version.cpp @@ -166,4 +166,4 @@ WritingVersion classifyWritingVersion(const std::string& rawStamp) { return wv; } -} // namespace reasampler::version \ No newline at end of file +} // namespace reasampler::version diff --git a/src/core/version/app_version.h b/src/core/version/app_version.h index ea04abc..603dcf1 100644 --- a/src/core/version/app_version.h +++ b/src/core/version/app_version.h @@ -199,4 +199,4 @@ struct WritingVersion { // with the raw ext-state read and never has to reason about the cases itself. WritingVersion classifyWritingVersion(const std::string& rawStamp); -} // namespace reasampler::version \ No newline at end of file +} // namespace reasampler::version diff --git a/src/core/view/guid_diff.cpp b/src/core/view/guid_diff.cpp index be4a2af..303f94e 100644 --- a/src/core/view/guid_diff.cpp +++ b/src/core/view/guid_diff.cpp @@ -41,4 +41,4 @@ void GuidBaseline::reset() { primed_ = false; // next observe() re-baselines (first-poll guard re-armed) } -} // namespace reasampler::view \ No newline at end of file +} // namespace reasampler::view diff --git a/src/core/view/guid_diff.h b/src/core/view/guid_diff.h index 2c7bfc3..5026ec2 100644 --- a/src/core/view/guid_diff.h +++ b/src/core/view/guid_diff.h @@ -59,4 +59,4 @@ private: bool primed_ = false; // false ⇒ next observe() sets the baseline }; -} // namespace reasampler::view \ No newline at end of file +} // namespace reasampler::view diff --git a/src/core/view/lane_keys.cpp b/src/core/view/lane_keys.cpp index b241ff7..b003925 100644 --- a/src/core/view/lane_keys.cpp +++ b/src/core/view/lane_keys.cpp @@ -48,4 +48,4 @@ bool isOnManualLane(bool isFixedLaneTrack, const std::string& laneName) { return !hasManagedPrefix(laneName); } -} // namespace reasampler::view \ No newline at end of file +} // namespace reasampler::view diff --git a/src/core/view/lane_keys.h b/src/core/view/lane_keys.h index 6799704..36cb133 100644 --- a/src/core/view/lane_keys.h +++ b/src/core/view/lane_keys.h @@ -82,4 +82,4 @@ std::optional modeIdFromLaneName(const std::string& laneName); // inputs (I_FREEMODE result, P_LANENAME string) and never re-derives this logic. bool isOnManualLane(bool isFixedLaneTrack, const std::string& laneName); -} // namespace reasampler::view \ No newline at end of file +} // namespace reasampler::view diff --git a/src/core/view/mode_switch.cpp b/src/core/view/mode_switch.cpp index 881af49..6bc8c57 100644 --- a/src/core/view/mode_switch.cpp +++ b/src/core/view/mode_switch.cpp @@ -61,4 +61,4 @@ int hitTestSegment(int px, int py, const HeaderRect& header, int segmentCount) { return -1; } -} // namespace reasampler::view \ No newline at end of file +} // namespace reasampler::view diff --git a/src/core/view/mode_switch.h b/src/core/view/mode_switch.h index 07c9ce7..44b8250 100644 --- a/src/core/view/mode_switch.h +++ b/src/core/view/mode_switch.h @@ -46,4 +46,4 @@ std::vector computeSegmentRects(const HeaderRect& header, // the panel drew there. int hitTestSegment(int px, int py, const HeaderRect& header, int segmentCount); -} // namespace reasampler::view \ No newline at end of file +} // namespace reasampler::view diff --git a/src/core/view/view_tree.cpp b/src/core/view/view_tree.cpp index 65b239d..6356ec0 100644 --- a/src/core/view/view_tree.cpp +++ b/src/core/view/view_tree.cpp @@ -38,4 +38,4 @@ FolderTree buildFolderTree(const std::vector& entries) { return tree; } -} // namespace reasampler::view \ No newline at end of file +} // namespace reasampler::view diff --git a/src/core/view/view_tree.h b/src/core/view/view_tree.h index ad63a91..7d2d968 100644 --- a/src/core/view/view_tree.h +++ b/src/core/view/view_tree.h @@ -30,4 +30,4 @@ struct TrackFolderEntry { // is clamped to empty) so a corrupt/stale project can never fault the shell. FolderTree buildFolderTree(const std::vector& entries); -} // namespace reasampler::view \ No newline at end of file +} // namespace reasampler::view diff --git a/src/core/wire/assignment_request.cpp b/src/core/wire/assignment_request.cpp index 5ca645d..ab05c53 100644 --- a/src/core/wire/assignment_request.cpp +++ b/src/core/wire/assignment_request.cpp @@ -40,4 +40,4 @@ std::optional decodeAssignmentRequest(const std::string& wire return req; } -} // namespace reasampler::wire \ No newline at end of file +} // namespace reasampler::wire diff --git a/src/core/wire/assignment_request.h b/src/core/wire/assignment_request.h index 5847405..12e469a 100644 --- a/src/core/wire/assignment_request.h +++ b/src/core/wire/assignment_request.h @@ -86,4 +86,4 @@ std::string encodeAssignmentRequest(const AssignmentRequest& req); // silently, never crashing or selecting a nonexistent entry. std::optional decodeAssignmentRequest(const std::string& wire); -} // namespace reasampler::wire \ No newline at end of file +} // namespace reasampler::wire diff --git a/src/core/wire/instrument_drop.cpp b/src/core/wire/instrument_drop.cpp index 36dfd9d..e475830 100644 --- a/src/core/wire/instrument_drop.cpp +++ b/src/core/wire/instrument_drop.cpp @@ -1,12 +1,12 @@ // instrument_drop — pure implementation. See instrument_drop.h. // 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 -#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) namespace reasampler::wire { @@ -106,4 +106,4 @@ bool infoNamesFxHotspot(const std::string& info) { return startsWith("fx_") || startsWith("tcp.fx") || startsWith("mcp.fx"); } -} // namespace reasampler::wire \ No newline at end of file +} // namespace reasampler::wire diff --git a/src/core/wire/instrument_drop.h b/src/core/wire/instrument_drop.h index d44de6d..25fec92 100644 --- a/src/core/wire/instrument_drop.h +++ b/src/core/wire/instrument_drop.h @@ -3,7 +3,7 @@ // // 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 -// 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. // // -- 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 // (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. -// 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 // writes presets only the beta VST class accepts, preserving the S18 pairing invariant. std::string vstClassIdHex(); @@ -111,4 +111,4 @@ bool infoNamesFxHotspot(const std::string& info); // its setState expects. Not called by the shell (which uses the .vstpreset image). std::vector instrumentDropStateBytes(const std::string& sampleId); -} // namespace reasampler::wire \ No newline at end of file +} // namespace reasampler::wire diff --git a/src/shell/instrument/reasampler_uid.h b/src/core/wire/reasampler_uid.h similarity index 100% rename from src/shell/instrument/reasampler_uid.h rename to src/core/wire/reasampler_uid.h diff --git a/src/core/wire/sample_usage.cpp b/src/core/wire/sample_usage.cpp index 3f45708..0489650 100644 --- a/src/core/wire/sample_usage.cpp +++ b/src/core/wire/sample_usage.cpp @@ -230,4 +230,4 @@ bool identityMatches(const std::string& identity, const std::string& uidHexUpper return !nameUpper.empty() && up.find(nameUpper) != std::string::npos; } -} // namespace reasampler::wire \ No newline at end of file +} // namespace reasampler::wire diff --git a/src/core/wire/sample_usage.h b/src/core/wire/sample_usage.h index 1bef5eb..8553bf0 100644 --- a/src/core/wire/sample_usage.h +++ b/src/core/wire/sample_usage.h @@ -250,4 +250,4 @@ bool identityMatches(const std::string& identity, const std::string& uidHexUpper // ASCII-only uppercase (shared by the matcher and the shell's needle preparation). std::string toUpperAscii(const std::string& s); -} // namespace reasampler::wire \ No newline at end of file +} // namespace reasampler::wire diff --git a/src/ext_keys.h b/src/ext_keys.h index 7fcb56d..71d6953 100644 --- a/src/ext_keys.h +++ b/src/ext_keys.h @@ -1,4 +1,3 @@ -#include "core/namespaces.h" #pragma once // 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 @@ -27,7 +26,7 @@ namespace reasampler { // 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 // 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 key the VST3 instrument reads to see the live bank (read-only, S4). persist.h diff --git a/src/ingest.h b/src/ingest.h index 3fcd40d..6c74d57 100644 --- a/src/ingest.h +++ b/src/ingest.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // ingest — the S8 "ingest through the bank" shell (EXTENSION side). // // Compiled into the reaper_reasampler MODULE. REAPER-facing (PCM_Source metadata reads, diff --git a/src/persist.h b/src/persist.h index 1c9cbb2..74a3d37 100644 --- a/src/persist.h +++ b/src/persist.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // persist — the REAPER-facing bridge between the in-memory BankModel and project // ext state (CLAUDE.md §load-bearing split; CONTEXT.md §Persistence & paths). // diff --git a/src/shell/actions/drag_out_win.h b/src/shell/actions/drag_out_win.h index 4382c82..1a5781b 100644 --- a/src/shell/actions/drag_out_win.h +++ b/src/shell/actions/drag_out_win.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // 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 // shell that hands a resolved, existing-file path list to the operating system's drag-drop diff --git a/src/shell/actions/instrument_drop_win.h b/src/shell/actions/instrument_drop_win.h index 161b900..f9aa016 100644 --- a/src/shell/actions/instrument_drop_win.h +++ b/src/shell/actions/instrument_drop_win.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // 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 // in instrument_drop; THIS is the platform shell that (a) resolves a screen point to a track diff --git a/src/shell/capture/capture.h b/src/shell/capture/capture.h index 48190f8..fd9a54d 100644 --- a/src/shell/capture/capture.h +++ b/src/shell/capture/capture.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // capture — the REAPER-facing capture shell (CLAUDE.md §load-bearing split). // // This header declares the capture *seam* the later milestones fill: diff --git a/src/shell/capture/insert.h b/src/shell/capture/insert.h index 5c6ea59..18aa2c7 100644 --- a/src/shell/capture/insert.h +++ b/src/shell/capture/insert.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // 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 // file, and drops it into the arrange at the edit cursor via InsertMedia, wrapped diff --git a/src/shell/capture/item_read.h b/src/shell/capture/item_read.h index 1aec24a..4c8da44 100644 --- a/src/shell/capture/item_read.h +++ b/src/shell/capture/item_read.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // 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 // bank_panel.cpp each carried a near-identical private itemGuid / itemLaneName pair diff --git a/src/shell/capture/provenance_shell.h b/src/shell/capture/provenance_shell.h index 594002e..1a1dab5 100644 --- a/src/shell/capture/provenance_shell.h +++ b/src/shell/capture/provenance_shell.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // provenance_shell — the REAPER-facing reads Milestone 10 needs, in one place. // // The PURE provenance module (provenance.h) owns the fingerprint encoding, the diff --git a/src/shell/capture/track_guid.h b/src/shell/capture/track_guid.h index 0bbadd5..ec28cae 100644 --- a/src/shell/capture/track_guid.h +++ b/src/shell/capture/track_guid.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // 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 // the actions layer (actions.cpp) key membership on this exact string, so the key diff --git a/src/shell/instrument/reaper_bridge.h b/src/shell/instrument/reaper_bridge.h index 004c54a..ef7183b 100644 --- a/src/shell/instrument/reaper_bridge.h +++ b/src/shell/instrument/reaper_bridge.h @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // 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 // "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. #pragma once +#include "core/namespaces.h" #include #include diff --git a/src/shell/instrument/reasampler_embed.h b/src/shell/instrument/reasampler_embed.h index bf4f52f..23e9b56 100644 --- a/src/shell/instrument/reasampler_embed.h +++ b/src/shell/instrument/reasampler_embed.h @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // reasampler_embed.h — the S6 embedded TCP/MCP UI shell. Implements REAPER's // 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 @@ -32,6 +31,7 @@ // REAPER's messages to/from it and draws with the same LICE idiom as reasampler_editor. #pragma once +#include "core/namespaces.h" #include #include diff --git a/src/shell/instrument/reasampler_vst.h b/src/shell/instrument/reasampler_vst.h index 7e252ea..5e99b76 100644 --- a/src/shell/instrument/reasampler_vst.h +++ b/src/shell/instrument/reasampler_vst.h @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // 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 // processor, factory, and editor agree. @@ -19,10 +18,11 @@ // binary UID identity — the string identity lives in the pure module). #pragma once +#include "core/namespaces.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 { diff --git a/src/shell/panel/draw_kit.h b/src/shell/panel/draw_kit.h index 594f3e0..c8696d0 100644 --- a/src/shell/panel/draw_kit.h +++ b/src/shell/panel/draw_kit.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // 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 // editor + embed strip at L3) fills, buttons, rows, sliders, waveforms, and — above all — diff --git a/src/shell/persist/usage_scan.h b/src/shell/persist/usage_scan.h index 21db288..c1bfe0b 100644 --- a/src/shell/persist/usage_scan.h +++ b/src/shell/persist/usage_scan.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // 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 // answers ONE question: which project-relative bank paths are held by a LIVE ReaSampler diff --git a/src/shell/view/view.h b/src/shell/view/view.h index 1bf8fa6..4ad8c58 100644 --- a/src/shell/view/view.h +++ b/src/shell/view/view.h @@ -1,5 +1,5 @@ -#include "core/namespaces.h" #pragma once +#include "core/namespaces.h" // 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/ // membership/snapshot state and emits the toggle plan; this shell reads the live diff --git a/src/vst/reasampler_editor.h b/src/vst/reasampler_editor.h index 03938d8..18a26ea 100644 --- a/src/vst/reasampler_editor.h +++ b/src/vst/reasampler_editor.h @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // 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 // 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. #pragma once +#include "core/namespaces.h" #include #include diff --git a/src/vst/reasampler_processor.h b/src/vst/reasampler_processor.h index 29b3b6c..e339048 100644 --- a/src/vst/reasampler_processor.h +++ b/src/vst/reasampler_processor.h @@ -1,4 +1,3 @@ -#include "core/namespaces.h" // 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 // 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. #pragma once +#include "core/namespaces.h" #include #include diff --git a/tests/test_bank_book.cpp b/tests/test_bank_book.cpp index f72b69f..c25ddaf 100644 --- a/tests/test_bank_book.cpp +++ b/tests/test_bank_book.cpp @@ -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() { BankBook book; // 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 // =========================================================================== - -// --- 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{"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{"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{"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{"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 -} +// +// Pure SlotMap-only unit behaviour (add/remove/query, reorder gap-preservation, +// resetDense/reconcile, equality/fromEntries, serialize golden literal + round +// trip) now lives in test_slot_map.cpp (Q-W1 follow-up), extracted per the house +// every-pure-module-has-a-_tests rule. This file keeps the BankBook-level +// integration coverage below: reorderSample / reconcileSlots / JSON round-trip +// WITH a full book. // --- BankBook L7: JSON round-trip WITH positions ----------------------------- @@ -1081,6 +994,7 @@ static void testReplaceSampleInPoolPassesGuard() { } int main() { + testSerializeGoldenLiteral(); testPoolSeededAndDefaults(); testPoolPrivileges(); testCreateRenameReorder(); @@ -1117,18 +1031,8 @@ int main() { testRemoveAllBanksLatentScope(); testUpdateSampleInPlace(); - // L7 — SlotMap + ordering/reorder/replace + slot round-trip/migration. - testSlotMapDenseAppend(); - testSlotMapRemoveLeavesGap(); - testSlotMapAppendAfterGapGoesToFrontier(); - testSlotMapReorderIntoEmpty(); - testSlotMapReorderOntoOccupiedInsertsAndShifts(); - testSlotMapReorderPreservesInteriorGapAboveTarget(); - testSlotMapReorderUnmappedIsNoOp(); - testSlotMapNegativeTargetClampsToZero(); - testSlotMapResetDenseSkipsDupesAndEmpties(); - testSlotMapReconcileDropsStaleAppendsNew(); - testSlotMapEqualityAndFromEntries(); + // L7 — BankBook ordering/reorder/replace + slot round-trip/migration. + // (Pure SlotMap-only unit behaviour lives in slot_map_tests.) testBankBookSlotsRoundTrip(); testMigrationDefaultsToInsertionOrderDense(); testOrderedSampleIdsReconcilesLazily(); diff --git a/tests/test_bank_model.cpp b/tests/test_bank_model.cpp index 8111e91..299aef0 100644 --- a/tests/test_bank_model.cpp +++ b/tests/test_bank_model.cpp @@ -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() { BankModel idx; Sample a = fullSample("x"); @@ -551,6 +575,7 @@ static void testSeamFieldsAdditiveInvariant() { int main() { testFullFieldRoundTrip(); + testSerializeGoldenLiteral(); testDedupByHash(); testTierFilterAndMove(); testRelativePathInvariant(); diff --git a/tests/test_owned_manifest.cpp b/tests/test_owned_manifest.cpp index e5f27f8..d850004 100644 --- a/tests/test_owned_manifest.cpp +++ b/tests/test_owned_manifest.cpp @@ -36,6 +36,18 @@ static void testEmptyManifest() { 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 -------------------------------------------------- static void testAddAndContains() { @@ -159,6 +171,7 @@ static void testMalformedParse() { } int main() { + testSerializeGoldenLiteral(); testEmptyManifest(); testAddAndContains(); testDedupRepeatedAdds(); diff --git a/tests/test_slot_map.cpp b/tests/test_slot_map.cpp new file mode 100644 index 0000000..72fce65 --- /dev/null +++ b/tests/test_slot_map.cpp @@ -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 +#include +#include +#include + +#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{"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{"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{"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{"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>& 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> 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; +} diff --git a/tests/test_view_mode_model.cpp b/tests/test_view_mode_model.cpp index 34bb1b3..bb57611 100644 --- a/tests/test_view_mode_model.cpp +++ b/tests/test_view_mode_model.cpp @@ -56,6 +56,20 @@ static int flagValue(const TrackPlan& p, Flag f) { 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 -------------------------------------------------------- static void testNModeRegistryAndMembership() { @@ -1787,6 +1801,7 @@ static void testLaneMalformedJson() { } int main() { + testSerializeGoldenLiteral(); testNModeRegistryAndMembership(); testParentDerivationMultiMode(); testParentOwnMembershipVisibility();