Q-W1 pt2: core/shell/app relocation + sub-namespaces; one concrete ui::Rect (LTRB fork retired); slot_map split from bank_book; BankIndex→BankModel; 59/59 green
This commit is contained in:
+120
-117
@@ -69,7 +69,7 @@ endif()
|
||||
# Regenerated at configure time whenever either changes; the exact string is substituted
|
||||
# verbatim and the channel bit fans out through app_version.
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/version_generated.h.in
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/core/version/version_generated.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/generated/version_generated.h
|
||||
@ONLY)
|
||||
|
||||
@@ -107,7 +107,7 @@ target_include_directories(file_bytes PUBLIC src)
|
||||
# 1) Pure model library — NO REAPER, NO SWELL. Builds & tests anywhere.
|
||||
# The sampler's heart: Sample metadata + BankIndex (Milestone 1).
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(bank_model STATIC src/bank_model.cpp)
|
||||
add_library(bank_model STATIC src/core/model/bank_model.cpp)
|
||||
target_include_directories(bank_model PUBLIC src)
|
||||
target_link_libraries(bank_model PRIVATE json)
|
||||
|
||||
@@ -115,7 +115,7 @@ target_link_libraries(bank_model PRIVATE json)
|
||||
# 2) Pure peaks library — NO REAPER, NO SWELL. Waveform min/max thumbnails from
|
||||
# raw PCM (Milestone 2). A sibling pure lib, kept distinct from bank_model.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(peaks STATIC src/peaks.cpp)
|
||||
add_library(peaks STATIC src/core/audio/peaks.cpp)
|
||||
target_include_directories(peaks PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -123,7 +123,7 @@ target_include_directories(peaks PUBLIC src)
|
||||
# file-name / project-relative path derivation for the capture shell (M3).
|
||||
# Split out so the fiddly path logic is unit-tested outside the DAW.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(capture_paths STATIC src/capture_paths.cpp)
|
||||
add_library(capture_paths STATIC src/core/capture/capture_paths.cpp)
|
||||
target_include_directories(capture_paths PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -132,7 +132,7 @@ target_include_directories(capture_paths PUBLIC src)
|
||||
# cache key for the docked bank_panel (M5). Split out so the layout logic is
|
||||
# unit-tested outside the DAW; the panel shell (SWELL/LICE/PCM) is DAW-verified.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(bank_grid STATIC src/bank_grid.cpp)
|
||||
add_library(bank_grid STATIC src/core/ui/bank_grid.cpp)
|
||||
target_include_directories(bank_grid PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -142,7 +142,7 @@ target_include_directories(bank_grid PUBLIC src)
|
||||
# unit-tested outside the DAW; the bank_panel header strip that draws it and
|
||||
# routes clicks to view::applyMode is DAW-verified. Mirror of bank_grid.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(mode_switch STATIC src/mode_switch.cpp)
|
||||
add_library(mode_switch STATIC src/core/view/mode_switch.cpp)
|
||||
target_include_directories(mode_switch PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -153,7 +153,7 @@ target_include_directories(mode_switch PUBLIC src)
|
||||
# overflow/scroll math is unit-tested outside the DAW; the bank_panel region
|
||||
# that draws it and routes clicks is DAW-verified. Mirror of mode_switch.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(tab_strip STATIC src/tab_strip.cpp)
|
||||
add_library(tab_strip STATIC src/core/ui/tab_strip.cpp)
|
||||
target_include_directories(tab_strip PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -162,7 +162,7 @@ target_include_directories(tab_strip PUBLIC src)
|
||||
# visibility derivation + parking/restore planner + JSON round-trip. Mirror of
|
||||
# bank_model; the folder tree is an INPUT supplied by the D2 shell.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(view_mode_model STATIC src/view_mode_model.cpp)
|
||||
add_library(view_mode_model STATIC src/core/view/view_mode_model.cpp)
|
||||
target_include_directories(view_mode_model PUBLIC src)
|
||||
target_link_libraries(view_mode_model PRIVATE json)
|
||||
# The pure lane-minting decision (planLaneMinting) names managed lanes via the ONE
|
||||
@@ -177,7 +177,7 @@ target_link_libraries(view_mode_model PUBLIC lane_keys)
|
||||
# view.cpp; this fiddly folder-depth walk is unit-tested here (mirrors
|
||||
# capture_paths splitting the path math out of the capture shell).
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(view_tree STATIC src/view_tree.cpp)
|
||||
add_library(view_tree STATIC src/core/view/view_tree.cpp)
|
||||
target_include_directories(view_tree PUBLIC src)
|
||||
target_link_libraries(view_tree PUBLIC view_mode_model)
|
||||
|
||||
@@ -189,7 +189,7 @@ target_link_libraries(view_tree PUBLIC view_mode_model)
|
||||
# live track/item GUID set and applies the tags is DAW-verified. Mirror of
|
||||
# view_tree splitting the folder-depth walk out of view.cpp.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(guid_diff STATIC src/guid_diff.cpp)
|
||||
add_library(guid_diff STATIC src/core/view/guid_diff.cpp)
|
||||
target_include_directories(guid_diff PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -200,7 +200,7 @@ target_include_directories(guid_diff PUBLIC src)
|
||||
# exemption) and #2 (name-keyed identity survives ordinal renumber). Split out
|
||||
# so the prefix rule is unit-tested; view.cpp reads the names from REAPER.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(lane_keys STATIC src/lane_keys.cpp)
|
||||
add_library(lane_keys STATIC src/core/view/lane_keys.cpp)
|
||||
target_include_directories(lane_keys PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -209,7 +209,7 @@ target_include_directories(lane_keys PUBLIC src)
|
||||
# load-bearing bit computation (no silent stretch, opt-in conform) is
|
||||
# unit-tested outside the DAW; the InsertMedia call itself is DAW-verified.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(insert_plan STATIC src/insert_plan.cpp)
|
||||
add_library(insert_plan STATIC src/core/capture/insert_plan.cpp)
|
||||
target_include_directories(insert_plan PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -220,7 +220,7 @@ target_include_directories(insert_plan PUBLIC src)
|
||||
# the DAW; the render-driving + selection reads stay in capture.cpp / main.cpp.
|
||||
# Depends on bank_model for the pure SourceMode enum.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(render_settings STATIC src/render_settings.cpp)
|
||||
add_library(render_settings STATIC src/core/capture/render_settings.cpp)
|
||||
target_include_directories(render_settings PUBLIC src)
|
||||
target_link_libraries(render_settings PUBLIC bank_model)
|
||||
|
||||
@@ -233,7 +233,7 @@ target_link_libraries(render_settings PUBLIC bank_model)
|
||||
# the selection read, transient re-selection, and render loop stay in main.cpp.
|
||||
# No dependency on bank_model — it takes plain ranges/values at its boundary.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(batch_capture STATIC src/batch_capture.cpp)
|
||||
add_library(batch_capture STATIC src/core/capture/batch_capture.cpp)
|
||||
target_include_directories(batch_capture PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -244,7 +244,7 @@ target_include_directories(batch_capture PUBLIC src)
|
||||
# outside the DAW; the bank_panel footer that draws it + routes clicks is
|
||||
# DAW-verified. Depends on render_settings for the pure TailMode enum + caps.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(tail_control STATIC src/tail_control.cpp)
|
||||
add_library(tail_control STATIC src/core/capture/tail_control.cpp)
|
||||
target_include_directories(tail_control PUBLIC src)
|
||||
target_link_libraries(tail_control PUBLIC render_settings)
|
||||
target_link_libraries(tail_control PRIVATE json)
|
||||
@@ -257,7 +257,7 @@ 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/bank_book.cpp)
|
||||
add_library(bank_book STATIC src/core/model/bank_book.cpp src/core/model/slot_map.cpp)
|
||||
target_include_directories(bank_book PUBLIC src)
|
||||
target_link_libraries(bank_book PUBLIC bank_model)
|
||||
target_link_libraries(bank_book PRIVATE json)
|
||||
@@ -271,7 +271,7 @@ target_link_libraries(bank_book PRIVATE json)
|
||||
# pure type + JSON round-trip; mirror of wav_trim / tab_strip. B-cap writes +
|
||||
# persists it; Phase R (R1/R2) consumes it — no prune logic here.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(owned_manifest STATIC src/owned_manifest.cpp)
|
||||
add_library(owned_manifest STATIC src/core/model/owned_manifest.cpp)
|
||||
target_include_directories(owned_manifest PUBLIC src)
|
||||
target_link_libraries(owned_manifest PRIVATE json)
|
||||
|
||||
@@ -284,7 +284,7 @@ target_link_libraries(owned_manifest PRIVATE json)
|
||||
# pure function; R2/R3 wrap the two ends (folder enumeration + deletion) in the
|
||||
# shell. Standalone — depends only on the standard library.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(prune_reconcile STATIC src/prune_reconcile.cpp)
|
||||
add_library(prune_reconcile STATIC src/core/reclaim/prune_reconcile.cpp)
|
||||
target_include_directories(prune_reconcile PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -295,7 +295,7 @@ target_include_directories(prune_reconcile PUBLIC src)
|
||||
# outside the DAW; the bank_panel footer that draws it and dispatches the
|
||||
# "Prune bank folder" command is DAW-verified. Mirror of mode_switch / tab_strip.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(prune_button STATIC src/prune_button.cpp)
|
||||
add_library(prune_button STATIC src/core/ui/prune_button.cpp)
|
||||
target_include_directories(prune_button PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -306,7 +306,7 @@ target_include_directories(prune_button PUBLIC src)
|
||||
# the DAW; the transport/temp-track/send/file-move recipe stays in capture.cpp.
|
||||
# Depends on bank_model for the pure Sample / SourceMode types.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(realtime_record STATIC src/realtime_record.cpp)
|
||||
add_library(realtime_record STATIC src/core/capture/realtime_record.cpp)
|
||||
target_include_directories(realtime_record PUBLIC src)
|
||||
target_link_libraries(realtime_record PUBLIC bank_model)
|
||||
|
||||
@@ -320,7 +320,7 @@ target_link_libraries(realtime_record PUBLIC bank_model)
|
||||
# patched RIFF/data size fields). The file read/write/truncate I/O stays in the
|
||||
# realtime shell. Depends on peaks for the AudioSample float alias.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(wav_trim STATIC src/wav_trim.cpp)
|
||||
add_library(wav_trim STATIC src/core/capture/wav_trim.cpp)
|
||||
target_include_directories(wav_trim PUBLIC src)
|
||||
target_link_libraries(wav_trim PUBLIC peaks)
|
||||
|
||||
@@ -334,7 +334,7 @@ target_link_libraries(wav_trim PUBLIC peaks)
|
||||
# DAW; the ext-state write/read (persist) and the show-version action (main) are shell.
|
||||
# Depends on the generated header in the build tree (PUBLIC so every consumer sees it).
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(app_version STATIC src/app_version.cpp)
|
||||
add_library(app_version STATIC src/core/version/app_version.cpp)
|
||||
target_include_directories(app_version PUBLIC src ${CMAKE_CURRENT_BINARY_DIR}/generated)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -347,7 +347,7 @@ target_include_directories(app_version PUBLIC src ${CMAKE_CURRENT_BINARY_DIR}/ge
|
||||
# registration stay in the shell (main.cpp / actions.cpp). No dependency on
|
||||
# bank_model — it takes plain strings/values at its boundary.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(provenance STATIC src/provenance.cpp)
|
||||
add_library(provenance STATIC src/core/model/provenance.cpp)
|
||||
target_include_directories(provenance PUBLIC src)
|
||||
target_link_libraries(provenance PRIVATE wire)
|
||||
|
||||
@@ -361,7 +361,7 @@ target_link_libraries(provenance PRIVATE wire)
|
||||
# depend on is unit-tested outside the DAW; the reader lands in a separate artifact,
|
||||
# so the round-trip test is the contract guard. No dependency — plain strings + int64.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(assignment_request STATIC src/assignment_request.cpp)
|
||||
add_library(assignment_request STATIC src/core/wire/assignment_request.cpp)
|
||||
target_include_directories(assignment_request PUBLIC src)
|
||||
target_link_libraries(assignment_request PRIVATE wire)
|
||||
|
||||
@@ -375,7 +375,7 @@ target_link_libraries(assignment_request PRIVATE wire)
|
||||
# matcher.
|
||||
# Linked by BOTH artifacts — the mirror of assignment_request, reversed direction.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(sample_usage STATIC src/sample_usage.cpp)
|
||||
add_library(sample_usage STATIC src/core/wire/sample_usage.cpp)
|
||||
target_include_directories(sample_usage PUBLIC src)
|
||||
target_link_libraries(sample_usage PRIVATE wire)
|
||||
|
||||
@@ -390,7 +390,7 @@ target_link_libraries(sample_usage PRIVATE wire)
|
||||
# initiation (drag_out_win) and the bank_panel gesture hook are DAW-verified. Mirror
|
||||
# of mode_switch.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(drag_out STATIC src/drag_out.cpp)
|
||||
add_library(drag_out STATIC src/core/ui/drag_out.cpp)
|
||||
target_include_directories(drag_out PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -404,13 +404,13 @@ 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/vst/reasampler_uid.h, SDK-free), channel-selected via the generated
|
||||
# (src/shell/instrument/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.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(instrument_drop STATIC src/instrument_drop.cpp)
|
||||
target_include_directories(instrument_drop PUBLIC src src/vst ${CMAKE_CURRENT_BINARY_DIR}/generated)
|
||||
add_library(instrument_drop STATIC src/core/wire/instrument_drop.cpp)
|
||||
target_include_directories(instrument_drop PUBLIC src ${CMAKE_CURRENT_BINARY_DIR}/generated)
|
||||
target_link_libraries(instrument_drop PUBLIC sample_map)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -423,7 +423,7 @@ target_link_libraries(instrument_drop PUBLIC sample_map)
|
||||
# switching the visual direction is a one-file edit. The draw shell (draw_kit) turns
|
||||
# a KitColor into a LICE_pixel at the boundary. Mirror of mode_switch — pure.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(theme STATIC src/theme.cpp)
|
||||
add_library(theme STATIC src/core/ui/theme.cpp)
|
||||
target_include_directories(theme PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -436,7 +436,7 @@ target_include_directories(theme PUBLIC src)
|
||||
# SliderGeometry/ListRowBox avoid the existing ButtonRect/CellRect collisions.
|
||||
# Mirror of prune_button — pure, CTest-covered.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(component_geometry STATIC src/component_geometry.cpp)
|
||||
add_library(component_geometry STATIC src/core/ui/component_geometry.cpp)
|
||||
target_include_directories(component_geometry PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -451,7 +451,7 @@ target_include_directories(component_geometry PUBLIC src)
|
||||
# Main_OnCommand dispatch + kbd_getTextFromCmd query are DAW-verified. Mirror of
|
||||
# mode_switch / prune_button.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(action_bar STATIC src/action_bar.cpp)
|
||||
add_library(action_bar STATIC src/core/ui/action_bar.cpp)
|
||||
target_include_directories(action_bar PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -465,7 +465,7 @@ target_include_directories(action_bar PUBLIC src)
|
||||
# DAW-verified. Reuses prune_button's FooterRect input type. Mirror of action_bar /
|
||||
# mode_switch / prune_button.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(footer_bar STATIC src/footer_bar.cpp)
|
||||
add_library(footer_bar STATIC src/core/ui/footer_bar.cpp)
|
||||
target_include_directories(footer_bar PUBLIC src)
|
||||
target_link_libraries(footer_bar PUBLIC prune_button)
|
||||
|
||||
@@ -478,7 +478,7 @@ target_link_libraries(footer_bar PUBLIC prune_button)
|
||||
# L1-kit draw + TrackPopupMenu popup + NamedCommandLookup/Main_OnCommand dispatch are
|
||||
# DAW-verified. Mirror of prune_button / mode_switch.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(overflow_menu STATIC src/overflow_menu.cpp)
|
||||
add_library(overflow_menu STATIC src/core/ui/overflow_menu.cpp)
|
||||
target_include_directories(overflow_menu PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -490,7 +490,7 @@ target_include_directories(overflow_menu PUBLIC src)
|
||||
# draws disabled buttons in the kit Disabled state. Depends on view_mode_model for the
|
||||
# seed mode-id constants (kArrangeModeId / kDesignModeId — ONE home for the ids).
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(mode_enable STATIC src/mode_enable.cpp)
|
||||
add_library(mode_enable STATIC src/core/ui/mode_enable.cpp)
|
||||
target_include_directories(mode_enable PUBLIC src)
|
||||
target_link_libraries(mode_enable PUBLIC view_mode_model)
|
||||
|
||||
@@ -502,7 +502,7 @@ target_link_libraries(mode_enable PUBLIC view_mode_model)
|
||||
# prefix strip are unit-tested outside the DAW; the bank_panel hover timer + LICE overlay
|
||||
# draw are DAW-verified. Mirror of prune_button / component_geometry.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(tooltip STATIC src/tooltip.cpp)
|
||||
add_library(tooltip STATIC src/core/ui/tooltip.cpp)
|
||||
target_include_directories(tooltip PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -513,7 +513,7 @@ target_include_directories(tooltip PUBLIC src)
|
||||
# edge cases) is unit-tested outside the DAW; the bank_panel kit-text overlay draw is
|
||||
# DAW-verified. Mirror of tooltip's prefix-strip helper — pure, CTest-covered.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(card_meta STATIC src/card_meta.cpp)
|
||||
add_library(card_meta STATIC src/core/ui/card_meta.cpp)
|
||||
target_include_directories(card_meta PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -526,7 +526,7 @@ target_include_directories(card_meta PUBLIC src)
|
||||
# are DAW-verified. Reuses drag_out's PanelClientRect/DragState + bank_grid's CellRect/
|
||||
# GridSpec. Mirror of drag_out::decideGesture / bank_grid.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(card_drag STATIC src/card_drag.cpp)
|
||||
add_library(card_drag STATIC src/core/ui/card_drag.cpp)
|
||||
target_include_directories(card_drag PUBLIC src)
|
||||
target_link_libraries(card_drag PUBLIC drag_out bank_grid)
|
||||
|
||||
@@ -547,8 +547,8 @@ target_link_libraries(card_drag PUBLIC drag_out bank_grid)
|
||||
# because that header drags <windows.h> (via wdltypes.h) into any TU that includes it, which
|
||||
# cannot enter the pure sampler_core. Links only peaks (the AudioSample alias). sampler_core
|
||||
# depends on it (Voice owns two PitchShifters).
|
||||
add_library(pitch_shift STATIC src/vst/pitch_shift.cpp)
|
||||
target_include_directories(pitch_shift PUBLIC src src/vst)
|
||||
add_library(pitch_shift STATIC src/core/instrument/engine/pitch_shift.cpp)
|
||||
target_include_directories(pitch_shift PUBLIC src)
|
||||
target_link_libraries(pitch_shift PUBLIC peaks)
|
||||
|
||||
# velocity_curve (S-VIEW-9) — the pure velocity->amp transfer curve (eval + editing/clamp/inverse
|
||||
@@ -556,11 +556,11 @@ target_link_libraries(pitch_shift PUBLIC peaks)
|
||||
# explicit pixel box, not a Rect) so the engine can depend on it WITHOUT gaining a transitive
|
||||
# dependency on the editor's layout types. sampler_core depends on it (KeyZone carries a
|
||||
# VelocityCurve; Voice::start eval's it). Mirror of pitch_shift's role, one layer below the engine.
|
||||
add_library(velocity_curve STATIC src/vst/velocity_curve.cpp)
|
||||
target_include_directories(velocity_curve PUBLIC src/vst)
|
||||
add_library(velocity_curve STATIC src/core/instrument/engine/velocity_curve.cpp)
|
||||
target_include_directories(velocity_curve PUBLIC src)
|
||||
|
||||
add_library(sampler_core STATIC src/vst/sampler_core.cpp)
|
||||
target_include_directories(sampler_core PUBLIC src src/vst)
|
||||
add_library(sampler_core STATIC src/core/instrument/engine/sampler_core.cpp)
|
||||
target_include_directories(sampler_core PUBLIC src)
|
||||
target_link_libraries(sampler_core PUBLIC peaks pitch_shift velocity_curve)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -671,7 +671,7 @@ add_test(NAME app_version_tests COMMAND app_version_tests)
|
||||
# threaded verbatim or reconstructed from numeric components, at any version, padded or
|
||||
# not — the live suite cannot detect a reconstruct-from-components regression at all.
|
||||
# So: run the SAME version_generated.h.in template through configure_file a second time
|
||||
# with a SYNTHETIC padded version, and compile the SAME src/app_version.cpp against that
|
||||
# with a SYNTHETIC padded version, and compile the SAME src/core/version/app_version.cpp against that
|
||||
# header (include-dir substitution — the canary target never sees the live generated/
|
||||
# dir). The "0.9.01" here is a permanent test fixture, NOT the shipped version (see also
|
||||
# the source-of-truth comment at the top of this file for the live-version invariant); it
|
||||
@@ -688,20 +688,20 @@ function(_configure_padding_canary)
|
||||
# and CMAKE_CURRENT_SOURCE_DIR / CMAKE_CURRENT_BINARY_DIR are inherited read-only.
|
||||
set(REASAMPLER_VERSION "0.9.01")
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/version_generated.h.in
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/core/version/version_generated.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/generated_padding_canary/version_generated.h
|
||||
@ONLY)
|
||||
endfunction()
|
||||
_configure_padding_canary()
|
||||
|
||||
# NOTE: app_version_padding_tests deliberately recompiles src/app_version.cpp rather
|
||||
# NOTE: app_version_padding_tests deliberately recompiles src/core/version/app_version.cpp rather
|
||||
# than linking the app_version library target. This is required for the include-dir
|
||||
# substitution to work — the canary needs to see generated_padding_canary/ instead of
|
||||
# the live generated/ dir. If app_version ever gains a link dependency (e.g. a new
|
||||
# pure-module link), the canary target_link_libraries must mirror it here.
|
||||
add_executable(app_version_padding_tests
|
||||
tests/test_app_version_padding.cpp
|
||||
src/app_version.cpp)
|
||||
src/core/version/app_version.cpp)
|
||||
target_include_directories(app_version_padding_tests PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/generated_padding_canary src)
|
||||
add_test(NAME app_version_padding_tests COMMAND app_version_padding_tests)
|
||||
@@ -800,19 +800,19 @@ add_test(NAME velocity_curve_tests COMMAND velocity_curve_tests)
|
||||
# reader (mirror of capture_paths/wav_trim). Both are unit-tested outside the DAW;
|
||||
# the VST3 shell (src/vst/*) that draws/routes/invokes is DAW-verified.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(editor_geometry STATIC src/vst/editor_geometry.cpp)
|
||||
target_include_directories(editor_geometry PUBLIC src/vst)
|
||||
add_library(editor_geometry STATIC src/core/instrument/ui/editor_geometry.cpp)
|
||||
target_include_directories(editor_geometry PUBLIC src)
|
||||
|
||||
add_library(bridge_marshal STATIC src/vst/bridge_marshal.cpp)
|
||||
target_include_directories(bridge_marshal PUBLIC src/vst)
|
||||
add_library(bridge_marshal STATIC src/core/instrument/map/bridge_marshal.cpp)
|
||||
target_include_directories(bridge_marshal PUBLIC src)
|
||||
|
||||
# embed_strip (Phase S6) — PURE layout + hit-test for the embedded TCP/MCP strip: the
|
||||
# 128-key span -> zone-segment rects, point -> zone selection, and the level-band fill.
|
||||
# The mirror of editor_geometry (whose Rect + contains() it reuses); unit-tested outside
|
||||
# the DAW, while the embed shell (src/vst/reasampler_embed.cpp) marshals REAPER's embed
|
||||
# the DAW, while the embed shell (src/shell/instrument/reasampler_embed.cpp) marshals REAPER's embed
|
||||
# messages (paint bitmap + mouse coords) into it. Links editor_geometry for the shared Rect.
|
||||
add_library(embed_strip STATIC src/vst/embed_strip.cpp)
|
||||
target_include_directories(embed_strip PUBLIC src/vst)
|
||||
add_library(embed_strip STATIC src/core/instrument/ui/embed_strip.cpp)
|
||||
target_include_directories(embed_strip PUBLIC src)
|
||||
target_link_libraries(embed_strip PUBLIC editor_geometry)
|
||||
|
||||
# sample_map (Phase S4) — PURE mapping logic for the Tier-0 instrument: the live bank
|
||||
@@ -823,8 +823,8 @@ target_link_libraries(embed_strip PUBLIC editor_geometry)
|
||||
# sampler_core (the Keymap/SampleData it yields) — and NEITHER SDK. The VST3 shell
|
||||
# (reasampler_processor.cpp) does the bridge read + file I/O off the audio thread, then
|
||||
# calls these; the process callback stays allocation-free.
|
||||
add_library(sample_map STATIC src/vst/sample_map.cpp)
|
||||
target_include_directories(sample_map PUBLIC src/vst src)
|
||||
add_library(sample_map STATIC src/core/instrument/map/sample_map.cpp)
|
||||
target_include_directories(sample_map PUBLIC src)
|
||||
# master_gain: the v8 component-state master-gain field validates against the pure taper's
|
||||
# linear cap at the (de)serialization boundary (one cap, shared with the knob + the processor).
|
||||
target_link_libraries(sample_map PUBLIC bank_book wav_trim sampler_core master_gain)
|
||||
@@ -834,16 +834,16 @@ target_link_libraries(sample_map PUBLIC bank_book wav_trim sampler_core master_g
|
||||
# grid/tab arithmetic lives here, unit-tested outside the DAW; the editor shell draws each
|
||||
# card's peak thumbnail + name + badge and routes clicks into it. Links editor_geometry for
|
||||
# the shared Rect + contains(). NEITHER SDK.
|
||||
add_library(capture_browser STATIC src/vst/capture_browser.cpp)
|
||||
target_include_directories(capture_browser PUBLIC src/vst)
|
||||
add_library(capture_browser STATIC src/core/instrument/ui/capture_browser.cpp)
|
||||
target_include_directories(capture_browser PUBLIC src)
|
||||
target_link_libraries(capture_browser PUBLIC editor_geometry)
|
||||
|
||||
# keyboard_strip (Phase S10) — PURE key-span<->pixel mapping, root marker, zone-bar rects +
|
||||
# edge-grab hit regions, and the drag-delta note resolver for the capture-first editor's
|
||||
# keyboard strip (single-capture root-set) and the opt-in Zones panel (S10-Z). The mirror of
|
||||
# embed_strip; links editor_geometry for the shared Rect. NEITHER SDK.
|
||||
add_library(keyboard_strip STATIC src/vst/keyboard_strip.cpp)
|
||||
target_include_directories(keyboard_strip PUBLIC src/vst)
|
||||
add_library(keyboard_strip STATIC src/core/instrument/ui/keyboard_strip.cpp)
|
||||
target_include_directories(keyboard_strip PUBLIC src)
|
||||
target_link_libraries(keyboard_strip PUBLIC editor_geometry)
|
||||
|
||||
# waveform_view (Phase S11) — PURE frame<->pixel mapping, marker grab regions, drag-delta
|
||||
@@ -851,8 +851,8 @@ target_link_libraries(keyboard_strip PUBLIC editor_geometry)
|
||||
# (draggable start + loop markers over the picked capture's decoded PCM). The mirror of
|
||||
# keyboard_strip; links editor_geometry for the shared Rect and peaks for the AudioSample
|
||||
# alias the snap scans. NEITHER SDK.
|
||||
add_library(waveform_view STATIC src/vst/waveform_view.cpp)
|
||||
target_include_directories(waveform_view PUBLIC src/vst src)
|
||||
add_library(waveform_view STATIC src/core/instrument/ui/waveform_view.cpp)
|
||||
target_include_directories(waveform_view PUBLIC src)
|
||||
target_link_libraries(waveform_view PUBLIC editor_geometry peaks)
|
||||
|
||||
# bank_sync (Phase S9/S8 reader) — PURE decision logic for the instrument's off-audio-thread
|
||||
@@ -862,8 +862,8 @@ target_link_libraries(waveform_view PUBLIC editor_geometry peaks)
|
||||
# setSelectedSampleId, component-state marker); this owns only the yes/no maths, unit-tested
|
||||
# outside the DAW. Links assignment_request for the decoded AssignmentRequest it consumes.
|
||||
# NEITHER SDK.
|
||||
add_library(bank_sync STATIC src/vst/bank_sync.cpp)
|
||||
target_include_directories(bank_sync PUBLIC src/vst src)
|
||||
add_library(bank_sync STATIC src/core/instrument/map/bank_sync.cpp)
|
||||
target_include_directories(bank_sync PUBLIC src)
|
||||
target_link_libraries(bank_sync PUBLIC assignment_request)
|
||||
target_link_libraries(bank_sync PRIVATE wire)
|
||||
|
||||
@@ -872,15 +872,15 @@ target_link_libraries(bank_sync PRIVATE wire)
|
||||
# thumb-drag<->offset mapping, and the name-substring filter that composes with the bank
|
||||
# filter. The mirror of capture_browser; links capture_browser (for BrowserLayout + the card
|
||||
# metrics/cell rect) which pulls editor_geometry transitively. NEITHER SDK.
|
||||
add_library(browser_scroll STATIC src/vst/browser_scroll.cpp)
|
||||
target_include_directories(browser_scroll PUBLIC src/vst)
|
||||
add_library(browser_scroll STATIC src/core/instrument/ui/browser_scroll.cpp)
|
||||
target_include_directories(browser_scroll PUBLIC src)
|
||||
target_link_libraries(browser_scroll PUBLIC capture_browser)
|
||||
|
||||
# note_entry (Phase S12) — PURE text->clamped-MIDI-note parse for the direct numeric entry of
|
||||
# a zone's low/high/root (decimal integer OR note name under the C4==60 convention, clamped to
|
||||
# [0,127]). No dependency beyond the standard library. NEITHER SDK.
|
||||
add_library(note_entry STATIC src/vst/note_entry.cpp)
|
||||
target_include_directories(note_entry PUBLIC src/vst)
|
||||
add_library(note_entry STATIC src/core/instrument/map/note_entry.cpp)
|
||||
target_include_directories(note_entry PUBLIC src)
|
||||
|
||||
# param_slider (Phase S12 + the S15/S16 control surfaces deferred here) — PURE control-surface
|
||||
# layout + hit-test + normalized value<->pixel mapping for the editor parameter panel (the
|
||||
@@ -888,16 +888,16 @@ target_include_directories(note_entry PUBLIC src/vst)
|
||||
# mirror of keyboard_strip; links editor_geometry for the shared Rect. Deliberately engine-free
|
||||
# (no sampler_core types) — the shell owns the control-id -> param binding + the value DOMAIN
|
||||
# mapping. NEITHER SDK.
|
||||
add_library(param_slider STATIC src/vst/param_slider.cpp)
|
||||
target_include_directories(param_slider PUBLIC src/vst)
|
||||
add_library(param_slider STATIC src/core/instrument/ui/param_slider.cpp)
|
||||
target_include_directories(param_slider PUBLIC src)
|
||||
target_link_libraries(param_slider PUBLIC editor_geometry)
|
||||
|
||||
# trigger_seam (Phase S-VIEW-3) — PURE Trigger-mode frames<->fraction converter for the TRIGGER
|
||||
# SEAM documented in envelope_overlay.h: triggerPlayLength / framesToFadeFraction /
|
||||
# fadeFractionToFrames. Owns the one formula so pack (draw) and unpack (commit) are provably
|
||||
# consistent. No shell/LICE/REAPER types — only <cstdint>. NEITHER SDK.
|
||||
add_library(trigger_seam STATIC src/vst/trigger_seam.cpp)
|
||||
target_include_directories(trigger_seam PUBLIC src/vst)
|
||||
add_library(trigger_seam STATIC src/core/instrument/map/trigger_seam.cpp)
|
||||
target_include_directories(trigger_seam PUBLIC src)
|
||||
|
||||
# envelope_overlay (Phase S-VIEW-3) — PURE amp-envelope -> polyline geometry for the Sample-view
|
||||
# envelope overlay: AHDSR (Gate) / fade+%-length (Trigger) params + the sample's wall-clock
|
||||
@@ -905,16 +905,16 @@ target_include_directories(trigger_seam PUBLIC src/vst)
|
||||
# The mirror of waveform_view / param_slider; links editor_geometry for the shared Rect.
|
||||
# Deliberately engine-free (no sample_map / sampler_core) — the shell packs the zone's stored
|
||||
# AdsrSeconds / TriggerParams into the small AmpEnvelope view struct. NEITHER SDK.
|
||||
add_library(envelope_overlay STATIC src/vst/envelope_overlay.cpp)
|
||||
target_include_directories(envelope_overlay PUBLIC src/vst)
|
||||
add_library(envelope_overlay STATIC src/core/instrument/ui/envelope_overlay.cpp)
|
||||
target_include_directories(envelope_overlay PUBLIC src)
|
||||
target_link_libraries(envelope_overlay PUBLIC editor_geometry)
|
||||
|
||||
# envelope_edit (Phase S-VIEW-3) — PURE node hit-test + pixel-delta -> clamped-param inverse map
|
||||
# for the draggable envelope nodes: monotonic-in-time + range-clamped (against caller-supplied
|
||||
# slider maxima) so a drag can never produce a param a slider couldn't. The mirror of card_drag;
|
||||
# links envelope_overlay for the shared node vocabulary + the timeToX/levelToY maps. NEITHER SDK.
|
||||
add_library(envelope_edit STATIC src/vst/envelope_edit.cpp)
|
||||
target_include_directories(envelope_edit PUBLIC src/vst)
|
||||
add_library(envelope_edit STATIC src/core/instrument/ui/envelope_edit.cpp)
|
||||
target_include_directories(envelope_edit PUBLIC src)
|
||||
target_link_libraries(envelope_edit PUBLIC envelope_overlay)
|
||||
|
||||
# knob_deck (Wave B FB1, r11) — PURE knob-deck layout + hit-test for the recomposed Sample face:
|
||||
@@ -922,23 +922,23 @@ target_link_libraries(envelope_edit PUBLIC envelope_overlay)
|
||||
# toggle), deterministic whole-group wrap, point -> control-id hit-test. The mirror of
|
||||
# action_bar / param_slider; links editor_geometry for the shared Rect. Engine-free — cells and
|
||||
# toggles carry opaque shell-owned control ids. NEITHER SDK.
|
||||
add_library(knob_deck STATIC src/vst/knob_deck.cpp)
|
||||
target_include_directories(knob_deck PUBLIC src/vst)
|
||||
add_library(knob_deck STATIC src/core/instrument/ui/knob_deck.cpp)
|
||||
target_include_directories(knob_deck PUBLIC src)
|
||||
target_link_libraries(knob_deck PUBLIC editor_geometry)
|
||||
|
||||
# curve_popup (Wave B FB1, r11) — PURE centered-sheet geometry for the velocity-curve popup
|
||||
# editor: size clamps (60%/55% of window, 360..520 x 260..380), title row + close button, the
|
||||
# curve-box border rect, and the outside-sheet dismissal test. The mirror of overflow_menu;
|
||||
# links editor_geometry for the shared Rect. NEITHER SDK.
|
||||
add_library(curve_popup STATIC src/vst/curve_popup.cpp)
|
||||
target_include_directories(curve_popup PUBLIC src/vst)
|
||||
add_library(curve_popup STATIC src/core/instrument/ui/curve_popup.cpp)
|
||||
target_include_directories(curve_popup PUBLIC src)
|
||||
target_link_libraries(curve_popup PUBLIC editor_geometry)
|
||||
|
||||
# master_gain (Wave B FB1) — PURE dB<->linear<->knob-taper math for the post-mixer master gain
|
||||
# (-inf..+24 dB; norm 0 = TRUE zero linear). One formula shared by the editor's Gain knob, the
|
||||
# v8 component-state wire cap, and the processor's applied gain. Standard library only. NEITHER SDK.
|
||||
add_library(master_gain STATIC src/vst/master_gain.cpp)
|
||||
target_include_directories(master_gain PUBLIC src/vst)
|
||||
add_library(master_gain STATIC src/core/instrument/engine/master_gain.cpp)
|
||||
target_include_directories(master_gain PUBLIC src)
|
||||
|
||||
add_executable(editor_geometry_tests tests/test_editor_geometry.cpp)
|
||||
target_link_libraries(editor_geometry_tests PRIVATE editor_geometry)
|
||||
@@ -1048,40 +1048,40 @@ set(LICE_SRC
|
||||
)
|
||||
|
||||
add_library(reaper_reasampler MODULE
|
||||
src/main.cpp
|
||||
src/capture.cpp
|
||||
src/capture_realtime.cpp
|
||||
src/realtime_record.cpp
|
||||
src/app/main.cpp
|
||||
src/shell/capture/capture.cpp
|
||||
src/shell/capture/capture_realtime.cpp
|
||||
src/core/capture/realtime_record.cpp
|
||||
src/persist.cpp
|
||||
src/bank_panel.cpp
|
||||
src/draw_kit.cpp
|
||||
src/mode_switch.cpp
|
||||
src/tab_strip.cpp
|
||||
src/insert.cpp
|
||||
src/insert_plan.cpp
|
||||
src/shell/panel/draw_kit.cpp
|
||||
src/core/view/mode_switch.cpp
|
||||
src/core/ui/tab_strip.cpp
|
||||
src/shell/capture/insert.cpp
|
||||
src/core/capture/insert_plan.cpp
|
||||
${LICE_SRC}
|
||||
src/view_mode_model.cpp
|
||||
src/view_tree.cpp
|
||||
src/view.cpp
|
||||
src/track_guid.cpp
|
||||
src/provenance_shell.cpp
|
||||
src/guid_diff.cpp
|
||||
src/lane_keys.cpp
|
||||
src/item_read.cpp
|
||||
src/core/view/view_mode_model.cpp
|
||||
src/core/view/view_tree.cpp
|
||||
src/shell/view/view.cpp
|
||||
src/shell/capture/track_guid.cpp
|
||||
src/shell/capture/provenance_shell.cpp
|
||||
src/core/view/guid_diff.cpp
|
||||
src/core/view/lane_keys.cpp
|
||||
src/shell/capture/item_read.cpp
|
||||
src/actions.cpp
|
||||
src/ingest.cpp
|
||||
src/bank_book.cpp
|
||||
src/owned_manifest.cpp
|
||||
src/drag_out_win.cpp
|
||||
src/instrument_drop_win.cpp
|
||||
src/action_bar.cpp
|
||||
src/footer_bar.cpp
|
||||
src/overflow_menu.cpp
|
||||
src/mode_enable.cpp
|
||||
src/tooltip.cpp
|
||||
src/card_meta.cpp
|
||||
src/card_drag.cpp
|
||||
src/usage_scan.cpp
|
||||
src/core/model/bank_book.cpp
|
||||
src/core/model/owned_manifest.cpp
|
||||
src/shell/actions/drag_out_win.cpp
|
||||
src/shell/actions/instrument_drop_win.cpp
|
||||
src/core/ui/action_bar.cpp
|
||||
src/core/ui/footer_bar.cpp
|
||||
src/core/ui/overflow_menu.cpp
|
||||
src/core/ui/mode_enable.cpp
|
||||
src/core/ui/tooltip.cpp
|
||||
src/core/ui/card_meta.cpp
|
||||
src/core/ui/card_drag.cpp
|
||||
src/shell/persist/usage_scan.cpp
|
||||
)
|
||||
target_link_libraries(reaper_reasampler PRIVATE json wire file_bytes bank_model capture_paths peaks bank_grid mode_switch tab_strip view_mode_model insert_plan render_settings batch_capture tail_control realtime_record bank_book wav_trim owned_manifest prune_reconcile prune_button app_version provenance drag_out instrument_drop theme component_geometry action_bar footer_bar overflow_menu mode_enable tooltip card_meta card_drag assignment_request bank_sync sample_usage)
|
||||
target_include_directories(reaper_reasampler PRIVATE ${SDK_INC} ${WDL_INC})
|
||||
@@ -1186,16 +1186,16 @@ if(WIN32 AND EXISTS "${VST3_SDK}/public.sdk/source/main/pluginfactory.cpp")
|
||||
|
||||
# --- 5b) The VST3 module (loadable .vst3 DLL). -------------------------------
|
||||
add_library(reasampler_vst MODULE
|
||||
src/vst/vst_entry.cpp
|
||||
src/shell/instrument/vst_entry.cpp
|
||||
src/vst/reasampler_processor.cpp
|
||||
src/vst/reasampler_editor.cpp
|
||||
src/vst/reasampler_embed.cpp
|
||||
src/vst/reaper_bridge.cpp
|
||||
src/shell/instrument/reasampler_embed.cpp
|
||||
src/shell/instrument/reaper_bridge.cpp
|
||||
# The Phase L (L1) draw kit — the ONE source of drawing the editor + embed shells
|
||||
# now consume (L3). Compiled into the MODULE (not a static lib) for the same reason
|
||||
# bank_panel does: it is the only kit TU touching LICE, and its cached-font engine
|
||||
# (LICE_CachedFont) needs the LICE_SRC TUs below linked into this artifact.
|
||||
src/draw_kit.cpp
|
||||
src/shell/panel/draw_kit.cpp
|
||||
# SDK module entry — compiled into the module (not the static lib) so the
|
||||
# InitDll/ExitDll dll exports survive the link (see vst3_sdk note above).
|
||||
${VST3_SDK}/public.sdk/source/main/dllmain.cpp
|
||||
@@ -1248,7 +1248,10 @@ if(WIN32 AND EXISTS "${VST3_SDK}/public.sdk/source/main/pluginfactory.cpp")
|
||||
knob_deck curve_popup master_gain sample_usage file_bytes)
|
||||
# SDK_INC gives reaper_vst3_interfaces.h + reaper_plugin_functions.h for the bridge;
|
||||
# WDL_INC gives LICE for the editor. The VST3 SDK headers come from vst3_sdk PUBLIC.
|
||||
target_include_directories(reasampler_vst PRIVATE ${SDK_INC} ${WDL_INC})
|
||||
# src: the Q-W1 rooted include convention ("core/..." / "shell/..."). src/vst: the
|
||||
# two not-yet-split god TUs (reasampler_editor / reasampler_processor, Q-W2v) still
|
||||
# live there and are included flat by the shell TUs.
|
||||
target_include_directories(reasampler_vst PRIVATE src src/vst ${SDK_INC} ${WDL_INC})
|
||||
# A .vst3 is a DLL with a .vst3 extension and no lib-prefix. OUTPUT_NAME is the on-disk
|
||||
# product name, channel-forked (S18): reasampler_9000.vst3 (stable, byte-identical to
|
||||
# pre-S18) / reasampler_9000_beta.vst3 (beta) — driven by REASAMPLER_VST_OUTPUT_NAME set
|
||||
|
||||
+11
-10
@@ -1,3 +1,4 @@
|
||||
#include "core/namespaces.h"
|
||||
// actions.cpp — the Design View action family (Phase D4). See actions.h.
|
||||
//
|
||||
// Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h
|
||||
@@ -25,16 +26,16 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "app_version.h" // channelCommandId / channelActionName — one channel-identity point
|
||||
#include "core/version/app_version.h" // channelCommandId / channelActionName — one channel-identity point
|
||||
|
||||
#include "bank_book.h" // BankBook, nextBankId, TransferResult, kPoolBankId (B1)
|
||||
#include "core/model/bank_book.h" // BankBook, nextBankId, TransferResult, kPoolBankId (B1)
|
||||
#include "bank_panel.h" // selection seam + full-height toggles (B3/B4)
|
||||
#include "item_read.h" // shared MediaItem* -> GUID + fixed-lane-name reads (D2 W3-B)
|
||||
#include "lane_keys.h" // isOnManualLane — the single managed/manual predicate
|
||||
#include "shell/capture/item_read.h" // shared MediaItem* -> GUID + fixed-lane-name reads (D2 W3-B)
|
||||
#include "core/view/lane_keys.h" // isOnManualLane — the single managed/manual predicate
|
||||
#include "persist.h" // ReaSamplerSession (owns book() + view() model)
|
||||
#include "track_guid.h" // shared MediaTrack* -> canonical GUID key
|
||||
#include "view.h" // applyMode + mintManagedLanes (D2 shell)
|
||||
#include "view_mode_model.h"
|
||||
#include "shell/capture/track_guid.h" // shared MediaTrack* -> canonical GUID key
|
||||
#include "shell/view/view.h" // applyMode + mintManagedLanes (D2 shell)
|
||||
#include "core/view/view_mode_model.h"
|
||||
|
||||
#include "reaper_plugin.h" // reaper_plugin_info_t, gaccel_register_t (full defs)
|
||||
|
||||
@@ -431,7 +432,7 @@ void designViewUnregisterActions(reaper_plugin_info_t* rec) {
|
||||
//
|
||||
// REFERENCE-INVALIDATION GUARDRAIL (B2 review): book().activeIndex() / bank()->index
|
||||
// return a reference INTO the book's internal vector, which a create/delete can
|
||||
// reallocate. No handler here caches a BankIndex& (or a Bank*) across a structural
|
||||
// reallocate. No handler here caches a BankModel& (or a Bank*) across a structural
|
||||
// mutation — each resolves ids to strings up front and re-resolves after any
|
||||
// create/delete. Move/copy pass ids (not references) straight to moveSample/copySample.
|
||||
|
||||
@@ -720,7 +721,7 @@ void doBankTransferSelected(bool copy) {
|
||||
return;
|
||||
}
|
||||
// Source = the bank the selection lives in (the focused region's displayed bank).
|
||||
// Pass ids by value — no BankIndex& is cached across the loop's mutations.
|
||||
// Pass ids by value — no BankModel& is cached across the loop's mutations.
|
||||
const std::string srcId = bankPanelSelectedSourceBankId();
|
||||
if (srcId == destId) {
|
||||
ShowConsoleMsg("ReaSampler: source and destination are the same bank.\n");
|
||||
@@ -788,7 +789,7 @@ void doBankRemoveSelected() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform the removes (this-bank scope). Pass ids by value — no BankIndex& is cached
|
||||
// Perform the removes (this-bank scope). Pass ids by value — no BankModel& is cached
|
||||
// across the loop's mutations. Count real drops so the no-op guardrail can skip the
|
||||
// undo point when nothing was removed (every id was already absent).
|
||||
int removed = 0;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "core/namespaces.h"
|
||||
#pragma once
|
||||
// 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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "core/namespaces.h"
|
||||
// main.cpp — the SINGLE translation unit that OWNS the REAPER API pointers.
|
||||
//
|
||||
// This file is the entire contract between REAPER and the extension:
|
||||
@@ -27,19 +28,19 @@
|
||||
#include <vector>
|
||||
|
||||
#include "actions.h"
|
||||
#include "app_version.h"
|
||||
#include "bank_model.h"
|
||||
#include "core/version/app_version.h"
|
||||
#include "core/model/bank_model.h"
|
||||
#include "bank_panel.h"
|
||||
#include "batch_capture.h"
|
||||
#include "capture.h"
|
||||
#include "core/capture/batch_capture.h"
|
||||
#include "shell/capture/capture.h"
|
||||
#include "ingest.h"
|
||||
#include "insert.h"
|
||||
#include "shell/capture/insert.h"
|
||||
#include "persist.h"
|
||||
#include "provenance.h"
|
||||
#include "provenance_shell.h"
|
||||
#include "render_settings.h"
|
||||
#include "track_guid.h"
|
||||
#include "view.h"
|
||||
#include "core/model/provenance.h"
|
||||
#include "shell/capture/provenance_shell.h"
|
||||
#include "core/capture/render_settings.h"
|
||||
#include "shell/capture/track_guid.h"
|
||||
#include "shell/view/view.h"
|
||||
|
||||
#include <filesystem> // project-dir derivation for provenance parent resolution
|
||||
|
||||
@@ -175,7 +176,7 @@ static int g_cmdCancelRealtime = 0;
|
||||
// window). The user copies this line into a bug report.
|
||||
static int g_cmdShowVersion = 0;
|
||||
|
||||
// The persistence session (M4): owns the in-memory BankIndex and bridges it to
|
||||
// The persistence session (M4): owns the in-memory BankModel and bridges it to
|
||||
// project ext state. A timer tick drives g_session.poll() to detect project
|
||||
// load / Save-As; capture adds Samples to g_session.bank() — which (B2) resolves to
|
||||
// the ACTIVE bank's index inside the session's BankBook; after a capture we serialize
|
||||
+47
-46
@@ -1,3 +1,4 @@
|
||||
#include "core/namespaces.h"
|
||||
// bank_panel.cpp — REAPER-facing docked grid (M5 Wave A/B + Phase B4). See
|
||||
// bank_panel.h.
|
||||
//
|
||||
@@ -32,7 +33,7 @@
|
||||
//
|
||||
// REFERENCE-INVALIDATION GUARDRAIL (CONTEXT.md §Multi-bank): a bank-structural
|
||||
// mutation (create/delete/evacuate/activate/move) can reallocate the book's vector,
|
||||
// so a BankIndex& / Bank* must NEVER be cached across one. Every handler below
|
||||
// so a BankModel& / Bank* must NEVER be cached across one. Every handler below
|
||||
// resolves fresh AFTER any mutation and passes bank IDS (not references) into the
|
||||
// model ops.
|
||||
|
||||
@@ -47,39 +48,39 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "action_bar.h" // pure TASK-GROUPED action-bar layout + hit-test (L2)
|
||||
#include "core/ui/action_bar.h" // pure TASK-GROUPED action-bar layout + hit-test (L2)
|
||||
#include "actions.h" // persistBankOp — shared undo-block wrapper (R-B panel path)
|
||||
#include "drag_out.h" // pure gesture-boundary decision + path-list assembly (M11)
|
||||
#include "drag_out_win.h" // OLE / SWELL drag-out initiation seam (M11)
|
||||
#include "app_version.h" // channelCommandId — compose the named-command lookup string (M11)
|
||||
#include "bank_book.h"
|
||||
#include "bank_grid.h"
|
||||
#include "bank_model.h"
|
||||
#include "card_drag.h" // L7 pure gesture precedence + sparse slot layout/hit-test
|
||||
#include "card_meta.h" // L7 decorative overlay formatters: bars.beats + s.ms (pure)
|
||||
#include "capture_paths.h"
|
||||
#include "component_geometry.h" // KitBox — the kit text()'s draw box (L1)
|
||||
#include "draw_kit.h" // kit text() over cached AA fonts — retires GDI DrawText (L1)
|
||||
#include "footer_bar.h" // pure footer LEFT-group layout: toggle + count + Tail button (L4)
|
||||
#include "guid_diff.h" // GuidBaseline — new-content detection (D2 Wave 2)
|
||||
#include "core/ui/drag_out.h" // pure gesture-boundary decision + path-list assembly (M11)
|
||||
#include "shell/actions/drag_out_win.h" // OLE / SWELL drag-out initiation seam (M11)
|
||||
#include "core/version/app_version.h" // channelCommandId — compose the named-command lookup string (M11)
|
||||
#include "core/model/bank_book.h"
|
||||
#include "core/ui/bank_grid.h"
|
||||
#include "core/model/bank_model.h"
|
||||
#include "core/ui/card_drag.h" // L7 pure gesture precedence + sparse slot layout/hit-test
|
||||
#include "core/ui/card_meta.h" // L7 decorative overlay formatters: bars.beats + s.ms (pure)
|
||||
#include "core/capture/capture_paths.h"
|
||||
#include "core/ui/component_geometry.h" // KitBox — the kit text()'s draw box (L1)
|
||||
#include "shell/panel/draw_kit.h" // kit text() over cached AA fonts — retires GDI DrawText (L1)
|
||||
#include "core/ui/footer_bar.h" // pure footer LEFT-group layout: toggle + count + Tail button (L4)
|
||||
#include "core/view/guid_diff.h" // GuidBaseline — new-content detection (D2 Wave 2)
|
||||
#include "ingest.h" // ingestDroppedFiles — S8 drop-onto-panel ingest
|
||||
#include "instrument_drop.h" // pure buildInstrumentDropPreset — the .vstpreset payload (S17)
|
||||
#include "instrument_drop_win.h" // resolveFxDropTarget / performInstrumentDrop shell (S17)
|
||||
#include "item_read.h" // itemGuid / itemLaneName — shared item-read seam (D2 W3-B)
|
||||
#include "lane_keys.h" // managed/manual lane heuristic (D2 Wave 2)
|
||||
#include "mode_enable.h" // opposite-mode tag-button enablement predicate (pure, L5)
|
||||
#include "mode_switch.h"
|
||||
#include "overflow_menu.h" // top-toolbar More-button geometry + reserve (pure, L5)
|
||||
#include "peaks.h"
|
||||
#include "core/wire/instrument_drop.h" // pure buildInstrumentDropPreset — the .vstpreset payload (S17)
|
||||
#include "shell/actions/instrument_drop_win.h" // resolveFxDropTarget / performInstrumentDrop shell (S17)
|
||||
#include "shell/capture/item_read.h" // itemGuid / itemLaneName — shared item-read seam (D2 W3-B)
|
||||
#include "core/view/lane_keys.h" // managed/manual lane heuristic (D2 Wave 2)
|
||||
#include "core/ui/mode_enable.h" // opposite-mode tag-button enablement predicate (pure, L5)
|
||||
#include "core/view/mode_switch.h"
|
||||
#include "core/ui/overflow_menu.h" // top-toolbar More-button geometry + reserve (pure, L5)
|
||||
#include "core/audio/peaks.h"
|
||||
#include "persist.h"
|
||||
#include "prune_button.h" // footer prune-button layout + hit-test (pure, R3)
|
||||
#include "tooltip.h" // tooltip placement + prefix-strip (pure, L5)
|
||||
#include "render_settings.h" // captureActionTable — the table-driven button rows (M11)
|
||||
#include "tab_strip.h"
|
||||
#include "tail_control.h" // TailSetting, cycleTailMode, tailToggleLabel (pure)
|
||||
#include "track_guid.h" // guidString — canonical track GUID key (D2 Wave 2)
|
||||
#include "view.h" // applyMode — the D2/D4 mode-activation entrypoint the switch fires
|
||||
#include "view_mode_model.h" // autoTagNewContent / NewItem (D2 Wave 2)
|
||||
#include "core/ui/prune_button.h" // footer prune-button layout + hit-test (pure, R3)
|
||||
#include "core/ui/tooltip.h" // tooltip placement + prefix-strip (pure, L5)
|
||||
#include "core/capture/render_settings.h" // captureActionTable — the table-driven button rows (M11)
|
||||
#include "core/ui/tab_strip.h"
|
||||
#include "core/capture/tail_control.h" // TailSetting, cycleTailMode, tailToggleLabel (pure)
|
||||
#include "shell/capture/track_guid.h" // guidString — canonical track GUID key (D2 Wave 2)
|
||||
#include "shell/view/view.h" // applyMode — the D2/D4 mode-activation entrypoint the switch fires
|
||||
#include "core/view/view_mode_model.h" // autoTagNewContent / NewItem (D2 Wave 2)
|
||||
|
||||
// SWELL / LICE. On macOS/Linux SWELL is provided by the host (SWELL_PROVIDED_BY_APP);
|
||||
// on Windows we use native Win32 (windows.h first, then swell.h no-ops on _WIN32).
|
||||
@@ -390,10 +391,10 @@ std::string currentProjectDir() {
|
||||
|
||||
BankBook* book() { return g_panel.session ? &g_panel.session->book() : nullptr; }
|
||||
|
||||
// The BankIndex a region currently displays. Pool region -> the pool; banks region ->
|
||||
// The BankModel a region currently displays. Pool region -> the pool; banks region ->
|
||||
// the shown tab's bank (or nullptr when no named banks / the id went stale). Resolved
|
||||
// FRESH every call (never cached across a mutation).
|
||||
const BankIndex* indexForRegion(Region r) {
|
||||
const BankModel* indexForRegion(Region r) {
|
||||
BankBook* b = book();
|
||||
if (!b) return nullptr;
|
||||
if (r == Region::Pool) return &b->pool().index;
|
||||
@@ -1256,7 +1257,7 @@ RECT createBtnRect(const RECT& region) {
|
||||
// --- L7 slot-order display bridge ---------------------------------------------
|
||||
//
|
||||
// L7 re-maps cell index <-> sample identity: the grid draws in the bank's persisted
|
||||
// SlotMap order (sparse, gap-preserving), NOT BankIndex insertion order. This one helper
|
||||
// SlotMap order (sparse, gap-preserving), NOT BankModel insertion order. This one helper
|
||||
// is the single place that resolves a region's display, composed purely from bank_book's
|
||||
// slot order (orderedSampleIds) + card_drag's sparse slot rects (computeSlotRects) — the
|
||||
// shell adds no layout math of its own.
|
||||
@@ -1337,7 +1338,7 @@ RegionDisplay focusedDisplay() {
|
||||
// viewport. `selectionOwner` is true when this region holds the live selection, so
|
||||
// its cells show selection/focus chrome; the other region draws plain.
|
||||
void drawRegionGrid(LICE_IBitmap* bmp, const RECT& region, bool isBanks,
|
||||
const BankIndex* index, const std::string& emptyMsg,
|
||||
const BankModel* index, const std::string& emptyMsg,
|
||||
bool selectionOwner, const std::string& projectDir, Region reg) {
|
||||
const RECT grid = regionGridRect(region, isBanks);
|
||||
if (grid.bottom <= grid.top) return;
|
||||
@@ -1348,7 +1349,7 @@ void drawRegionGrid(LICE_IBitmap* bmp, const RECT& region, bool isBanks,
|
||||
}
|
||||
|
||||
// L7: iterate the bank's SPARSE slot layout (slot order, gaps included), not the dense
|
||||
// BankIndex insertion order. Selection/focus are keyed by the occupied-ordinal (selection
|
||||
// BankModel insertion order. Selection/focus are keyed by the occupied-ordinal (selection
|
||||
// space); a slot maps back to its ordinal via selectionForSlot.
|
||||
const RegionDisplay disp = regionDisplay(region, isBanks, reg);
|
||||
// FA3 gap-free: request one bin per drawn pixel column; drawWaveform's
|
||||
@@ -1697,7 +1698,7 @@ bool refreshFingerprint() {
|
||||
stopAudition();
|
||||
}
|
||||
reconcileShownBank();
|
||||
const BankIndex* idx = indexForRegion(g_panel.focusedRegion);
|
||||
const BankModel* idx = indexForRegion(g_panel.focusedRegion);
|
||||
g_panel.selItemCount = idx ? static_cast<int>(idx->size()) : 0;
|
||||
return true;
|
||||
}
|
||||
@@ -1926,11 +1927,11 @@ void deinitPreview() {
|
||||
|
||||
// Auditions the sample at selection ordinal `idx` of the FOCUSED region's displayed bank.
|
||||
// L7: `idx` is a DISPLAY-order (slot) ordinal, resolved through orderedIds, not a raw
|
||||
// BankIndex position.
|
||||
// BankModel position.
|
||||
void startAudition(int idx) {
|
||||
stopAudition();
|
||||
|
||||
const BankIndex* index = indexForRegion(g_panel.focusedRegion);
|
||||
const BankModel* index = indexForRegion(g_panel.focusedRegion);
|
||||
if (!index) return;
|
||||
const RegionDisplay disp = focusedDisplay();
|
||||
if (idx < 0 || idx >= disp.occupiedCount()) return;
|
||||
@@ -1978,7 +1979,7 @@ void invalidatePanel() {
|
||||
// exactly one occupied slot (gaps are empty slots, which the index never backs), so the
|
||||
// raw index size IS the dense selection-space extent.
|
||||
int focusedItemCount() {
|
||||
const BankIndex* idx = indexForRegion(g_panel.focusedRegion);
|
||||
const BankModel* idx = indexForRegion(g_panel.focusedRegion);
|
||||
return idx ? static_cast<int>(idx->size()) : 0;
|
||||
}
|
||||
|
||||
@@ -2006,7 +2007,7 @@ bool regionAt(int x, int y, Region& out) {
|
||||
// --- Bank management ops (id-keyed; drive the B1 model + persist) --------------
|
||||
//
|
||||
// Each op mutates g_session.book() then persists via persistBankOp(). After a
|
||||
// STRUCTURAL mutation (create/delete/evacuate) any Bank*/BankIndex& is invalid — we
|
||||
// STRUCTURAL mutation (create/delete/evacuate) any Bank*/BankModel& is invalid — we
|
||||
// resolve fresh, pass ids, and let the next refreshFingerprint repaint. On an
|
||||
// unsaved project the empty-close discard in persistBankOp ensures no stale state
|
||||
// survives (matches the capture/B3 quiet-persist idiom).
|
||||
@@ -2123,7 +2124,7 @@ void doActivateBank(const std::string& bankId) {
|
||||
}
|
||||
|
||||
// Move or copy `sampleIds` from `srcBankId` to `destBankId` (index-only). Both pass
|
||||
// ids straight to the model op (no BankIndex& cached across the loop's mutations).
|
||||
// ids straight to the model op (no BankModel& cached across the loop's mutations).
|
||||
//
|
||||
// NO-OP GUARDRAIL — VERB-AWARE (matches the action layer's doBankTransferSelected):
|
||||
// * MOVE collapse: the source entry WAS removed (bank_book removes unconditionally
|
||||
@@ -2166,7 +2167,7 @@ void transferSamples(const std::vector<std::string>& sampleIds,
|
||||
// the file: a last-reference remove leaves the file on disk, orphaned until Phase R
|
||||
// prune — remove NEVER deletes bytes (the manifest is untouched). Removes are silent
|
||||
// (no confirm dialog); recoverability is provided by the batched REAPER undo (R-B) —
|
||||
// one Ctrl-Z restores the index entry. Ids passed by value — no BankIndex& cached
|
||||
// one Ctrl-Z restores the index entry. Ids passed by value — no BankModel& cached
|
||||
// across the loop's mutations.
|
||||
void removeSamples(const std::vector<std::string>& sampleIds,
|
||||
const std::string& srcBankId) {
|
||||
@@ -2190,7 +2191,7 @@ void removeSamples(const std::vector<std::string>& sampleIds,
|
||||
// The selection's sample ids resolved against the FOCUSED region's bank (source of a
|
||||
// move/copy). Returns ids in bank order; empty when nothing selected.
|
||||
std::vector<std::string> focusedSelectionIds() {
|
||||
// L7: selection ordinals index the DISPLAY (slot) order, not BankIndex insertion order.
|
||||
// L7: selection ordinals index the DISPLAY (slot) order, not BankModel insertion order.
|
||||
// orderedIds[i] is the id at selection ordinal i.
|
||||
std::vector<std::string> ids;
|
||||
const RegionDisplay disp = focusedDisplay();
|
||||
@@ -2212,7 +2213,7 @@ std::vector<std::string> resolveDragPathsForOs() {
|
||||
std::vector<ResolvedSample> resolved;
|
||||
BankBook* b = book();
|
||||
if (!b) return {};
|
||||
const BankIndex* idx = b->index(g_panel.dragSourceBankId);
|
||||
const BankModel* idx = b->index(g_panel.dragSourceBankId);
|
||||
if (!idx) return {};
|
||||
|
||||
const std::string projectDir = currentProjectDir();
|
||||
@@ -3138,7 +3139,7 @@ void onLBtnUp(int x, int y) {
|
||||
// collapses the multi-selection to the pressed cell (standard behavior).
|
||||
// Release capture acquired at arm time (handleClick) — drag never started.
|
||||
if (GetCapture() == g_panel.hwnd) ReleaseCapture();
|
||||
const BankIndex* idx = indexForRegion(g_panel.focusedRegion);
|
||||
const BankModel* idx = indexForRegion(g_panel.focusedRegion);
|
||||
const int count = idx ? static_cast<int>(idx->size()) : 0;
|
||||
const int focus = g_panel.selection.focus;
|
||||
if (focus >= 0)
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
#include "core/namespaces.h"
|
||||
#pragma once
|
||||
// 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
|
||||
@@ -13,7 +14,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "tail_control.h" // TailSetting — the panel's tail-mode toggle state
|
||||
#include "core/capture/tail_control.h" // TailSetting — the panel's tail-mode toggle state
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "peaks.h"
|
||||
#include "core/audio/peaks.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
@@ -14,7 +14,7 @@
|
||||
// extra frames) with no rounding drift and no dropped tail — the last bin's end is
|
||||
// always exactly frameCount.
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::audio {
|
||||
|
||||
Envelope computeEnvelope(const std::vector<AudioSample>& interleaved,
|
||||
std::size_t channelCount,
|
||||
@@ -123,4 +123,4 @@ std::size_t lastFrameAboveThreshold(const std::vector<AudioSample>& interleaved,
|
||||
return kNoFrameAboveThreshold;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::audio
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::audio {
|
||||
|
||||
// Canonical in-memory audio-sample type. `float` is REAPER's native audio buffer
|
||||
// format (its render/PCM_source callbacks hand back interleaved 32-bit float), so
|
||||
@@ -119,4 +119,4 @@ std::size_t lastFrameAboveThreshold(const std::vector<AudioSample>& interleaved,
|
||||
std::size_t frameCount,
|
||||
AudioSample linearThreshold);
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::audio
|
||||
@@ -1,11 +1,11 @@
|
||||
// batch_capture.cpp — pure logic for M11 batch capture. See header.
|
||||
// NO REAPER types; unit-tested by tests/test_batch_capture.cpp.
|
||||
|
||||
#include "batch_capture.h"
|
||||
#include "core/capture/batch_capture.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
std::vector<CaptureUnit> planCaptureUnits(const std::vector<BatchRange>& ranges) {
|
||||
std::vector<CaptureUnit> units;
|
||||
@@ -73,4 +73,4 @@ std::string BatchOutcome::summaryLine(const std::string& noun) const {
|
||||
return line;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
// One capture in a batch: an exact source range plus its 1-based ordinal within the
|
||||
// KEPT set. The ordinal disambiguates per-unit file stems (the offline backend's
|
||||
@@ -97,4 +97,4 @@ private:
|
||||
std::vector<BatchUnitResult> results_;
|
||||
};
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "capture_paths.h"
|
||||
#include "core/capture/capture_paths.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
std::string hashBytes(const std::uint8_t* data, std::size_t len) {
|
||||
// FNV-1a 64-bit: deterministic, no dependencies, adequate for dedup identity.
|
||||
@@ -284,4 +284,4 @@ ProjectTransition classifyProjectTransition(bool sameProjectObject,
|
||||
return ProjectTransition::NoOp;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -17,7 +17,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
// The project-relative bank subfolder. All captured wavs live here so the bank
|
||||
// travels with the .rpp (CONTEXT.md §Settled decisions: per-project bank).
|
||||
@@ -25,7 +25,7 @@ inline constexpr const char* kBankSubfolder = "reasampler_bank";
|
||||
|
||||
// A resolved pair of paths for one capture: where REAPER must be told to write
|
||||
// (absolute, because RENDER_FILE wants a directory REAPER can create/open) and
|
||||
// what we store in the BankIndex (project-relative, because the index is
|
||||
// what we store in the BankModel (project-relative, because the index is
|
||||
// relative-paths-only — CLAUDE.md precision invariant).
|
||||
struct BankPaths {
|
||||
std::string absoluteDir; // <projectDir>/reasampler_bank (forward slash)
|
||||
@@ -87,7 +87,7 @@ std::string sanitizeStem(const std::string& baseName);
|
||||
// timestamp or counter) so repeated captures do not collide.
|
||||
// Also sanitized. May be empty.
|
||||
// Produces "<stem>[_<tag>].wav". The relativePath is always project-relative and
|
||||
// forward-slashed so it satisfies BankIndex::add's relative-only invariant.
|
||||
// forward-slashed so it satisfies BankModel::add's relative-only invariant.
|
||||
BankPaths deriveBankPaths(const std::string& projectDir,
|
||||
const std::string& baseName,
|
||||
const std::string& uniqueTag);
|
||||
@@ -212,4 +212,4 @@ ProjectTransition classifyProjectTransition(bool sameProjectObject,
|
||||
const std::string& currentGuid,
|
||||
const std::string& currentPath);
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -1,8 +1,8 @@
|
||||
// insert_plan.cpp — see insert_plan.h. Pure InsertMedia mode-bit arithmetic.
|
||||
|
||||
#include "insert_plan.h"
|
||||
#include "core/capture/insert_plan.h"
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -46,4 +46,4 @@ int computeInsertMode(const InsertOptions& opts) {
|
||||
return mode;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
// Where InsertMedia drops the item. Maps to the low bits of `mode` (mode&3).
|
||||
// We expose only the two placement targets M6 needs; "add as takes" (3) is a
|
||||
@@ -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
|
||||
} // namespace reasampler::capture
|
||||
@@ -1,9 +1,9 @@
|
||||
// realtime_record.cpp — pure logic for the realtime-record backend (M8). See header.
|
||||
// NO REAPER types; unit-tested by tests/test_realtime_record.cpp.
|
||||
|
||||
#include "realtime_record.h"
|
||||
#include "core/capture/realtime_record.h"
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
RecordModePlan recordModePlanFor(int channelCount, OutputTap tap) {
|
||||
RecordModePlan p;
|
||||
@@ -124,4 +124,4 @@ bool isTerminalPhase(RecordPhase phase) {
|
||||
return phase == RecordPhase::Done || phase == RecordPhase::Failed;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -24,9 +24,13 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "bank_model.h" // Sample, SourceMode (pure)
|
||||
#include "core/model/bank_model.h" // Sample, SourceMode (pure)
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
using model::Sample;
|
||||
using model::Tier;
|
||||
using model::SourceMode;
|
||||
|
||||
// --- I_RECMODE values (verbatim from SDK header ~2197) -----------------------
|
||||
//
|
||||
@@ -231,4 +235,4 @@ bool isStopRequested(RecordPhase phase);
|
||||
// Only Done and Failed are terminal; Recording and Finalizing are live.
|
||||
bool isTerminalPhase(RecordPhase phase);
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -1,13 +1,13 @@
|
||||
// render_settings.cpp — pure logic for the three-scope capture action family. See header.
|
||||
// NO REAPER types; unit-tested by tests/test_render_settings.cpp.
|
||||
|
||||
#include "render_settings.h"
|
||||
#include "core/capture/render_settings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
double autoTrimEndRatio() {
|
||||
// Amplitude ratio = 10^(dB/20). Derived from kAutoTrimThresholdDb so the dB is
|
||||
@@ -221,4 +221,4 @@ const std::vector<CaptureActionDef>& captureActionTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -25,9 +25,11 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "bank_model.h" // SourceMode (pure enum)
|
||||
#include "core/model/bank_model.h" // SourceMode (pure enum)
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
using model::SourceMode;
|
||||
|
||||
// --- RENDER_SETTINGS source/processing bits (verbatim from SDK header ~3041) --
|
||||
//
|
||||
@@ -260,4 +262,4 @@ struct CaptureActionDef {
|
||||
// capture applies is read from the docked-panel setting, not baked into the row.
|
||||
const std::vector<CaptureActionDef>& captureActionTable();
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -1,13 +1,13 @@
|
||||
// tail_control — pure implementation. See tail_control.h. NO REAPER / SWELL / vendor.
|
||||
|
||||
#include "tail_control.h"
|
||||
#include "core/capture/tail_control.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
#include "core/json/json.h"
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
TailMode cycleTailMode(TailMode current) {
|
||||
switch (current) {
|
||||
@@ -128,4 +128,4 @@ std::optional<TailSetting> deserializeTailSetting(const std::string& blob) {
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -12,9 +12,9 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "render_settings.h" // TailMode (pure enum) — the three-state tail contract
|
||||
#include "core/capture/render_settings.h" // TailMode (pure enum) — the three-state tail contract
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
// The Manual-mode starting length. 2 s is a musically useful default tail (a bar of
|
||||
// reverb throw at a moderate tempo) that is well under the 8 s cap. Also the value a
|
||||
@@ -67,4 +67,4 @@ std::string tailToggleLabel(const TailSetting& setting);
|
||||
std::string serializeTailSetting(const TailSetting& setting);
|
||||
std::optional<TailSetting> deserializeTailSetting(const std::string& json);
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -1,10 +1,10 @@
|
||||
// wav_trim — pure implementation. See wav_trim.h. NO REAPER / SWELL / vendor.
|
||||
|
||||
#include "wav_trim.h"
|
||||
#include "core/capture/wav_trim.h"
|
||||
|
||||
#include <cstring> // std::memcpy, std::memcmp
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -157,4 +157,4 @@ WavTruncatePlan planWavTruncate(const WavLayout& layout, std::size_t keptFrames)
|
||||
return plan;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -29,9 +29,11 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "peaks.h" // AudioSample (float)
|
||||
#include "core/audio/peaks.h" // AudioSample (float)
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::capture {
|
||||
|
||||
using audio::AudioSample;
|
||||
|
||||
// The parsed geometry of a canonical 32-bit-float WAV. `valid` is false when the
|
||||
// bytes are not a WAV we can safely trim (see FORMAT ASSUMPTION); every other field
|
||||
@@ -98,4 +100,4 @@ struct WavTruncatePlan {
|
||||
// truncate the file to newFileByteLength.
|
||||
WavTruncatePlan planWavTruncate(const WavLayout& layout, std::size_t keptFrames);
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::capture
|
||||
@@ -1,17 +1,17 @@
|
||||
// master_gain.cpp — see master_gain.h. Pure math; no LICE/VST3/REAPER includes.
|
||||
|
||||
#include "master_gain.h"
|
||||
#include "core/instrument/engine/master_gain.h"
|
||||
|
||||
#include "core/util/clamp01.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::engine {
|
||||
|
||||
namespace {
|
||||
double clamp01(double v) { return v < 0.0 ? 0.0 : (v > 1.0 ? 1.0 : v); }
|
||||
} // namespace
|
||||
using util::clamp01; // the ONE unit-interval clamp (Q-W1, T4-24)
|
||||
|
||||
double masterGainMaxLinear() { return std::pow(10.0, kMasterGainMaxDb / 20.0); }
|
||||
|
||||
@@ -48,4 +48,4 @@ void formatMasterGainLabel(double norm, char* buf, std::size_t len) {
|
||||
std::snprintf(buf, len, "%+.1fdB", db);
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::engine
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::engine {
|
||||
|
||||
// The dB taper endpoints. norm 0 is -inf (true zero); norm just above 0 starts at the
|
||||
// finite floor kMasterGainMinDb and sweeps linearly in dB to kMasterGainMaxDb at norm 1.
|
||||
@@ -55,4 +55,4 @@ double masterGainNormFromLinear(double linear);
|
||||
// including the terminator. Pure.
|
||||
void formatMasterGainLabel(double norm, char* buf, std::size_t len);
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::engine
|
||||
@@ -20,13 +20,13 @@
|
||||
// ratio the delay is frozen mid-band and no splice ever fires: a primed shifter passes the
|
||||
// stream through with ZERO added latency; a silence-warmed one is a clean window delay.
|
||||
|
||||
#include "pitch_shift.h"
|
||||
#include "core/instrument/engine/pitch_shift.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::instrument::engine {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -431,4 +431,4 @@ AudioSample PitchShifter::processImpl(AudioSample in, const SpliceEvent* linked)
|
||||
return static_cast<AudioSample>(out);
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::instrument::engine
|
||||
@@ -66,9 +66,11 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "peaks.h" // AudioSample (float)
|
||||
#include "core/audio/peaks.h" // AudioSample (float)
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::instrument::engine {
|
||||
|
||||
using audio::AudioSample;
|
||||
|
||||
// The splice decision made by the most recent process()/processLinked() call — the LINKED-LAG
|
||||
// stereo contract (Q-W0 T1-01). A stereo voice runs channel 0 as the MASTER (full correlation
|
||||
@@ -225,4 +227,4 @@ private:
|
||||
// live fade by that rate.
|
||||
};
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::instrument::engine
|
||||
@@ -2,7 +2,7 @@
|
||||
// contract and the design rationale (keymap resolution, pitch ratio, ADSR shape,
|
||||
// voice allocation + stealing policy). NO VST3 / REAPER / SWELL / vendor includes.
|
||||
|
||||
#include "sampler_core.h"
|
||||
#include "core/instrument/engine/sampler_core.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
@@ -270,7 +270,7 @@ bool Voice::sustainLoopUsable() const {
|
||||
}
|
||||
|
||||
void Voice::start(int note, int velocity, const SampleData& sample, int rootNote,
|
||||
double keyTrack, const vst::VelocityCurve& velocityCurve,
|
||||
double keyTrack, const VelocityCurve& velocityCurve,
|
||||
bool declickTakeover) {
|
||||
// Takeover declick (Phase S GA fix, rev 2): BEFORE any state reset, record the PRE-CUT
|
||||
// REFERENCE — the last rendered output — and mark the compensation PENDING iff this
|
||||
@@ -22,12 +22,19 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "peaks.h" // AudioSample (float)
|
||||
#include "pitch_shift.h" // PitchShifter (S16 Preserve engine DSP core)
|
||||
#include "velocity_curve.h" // VelocityCurve (S-VIEW-9 velocity->amp transfer curve; eval at start)
|
||||
#include "core/audio/peaks.h" // AudioSample (float)
|
||||
#include "core/instrument/engine/pitch_shift.h" // PitchShifter (S16 Preserve engine DSP core)
|
||||
#include "core/instrument/engine/velocity_curve.h" // VelocityCurve (S-VIEW-9 velocity->amp transfer curve; eval at start)
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
// Q-W1 interim: the engine deps live in their sub-namespace homes now; sampler_core
|
||||
// re-namespaces in its own split wave (Q-W2v).
|
||||
using audio::AudioSample;
|
||||
using instrument::engine::PitchShifter;
|
||||
using instrument::engine::VelocityCurve;
|
||||
using instrument::engine::VelocityPoint;
|
||||
|
||||
// The instrument's per-instance output channel mode (S7, D-E). MONO keeps the pre-S7
|
||||
// downmix path (one channel out); STEREO negotiates a 2-channel output bus and renders
|
||||
// per-channel. A PERFORMANCE choice the instrument owns (component state), never written
|
||||
@@ -232,7 +239,7 @@ struct KeyZone {
|
||||
// of keyTrack), carried from PerformanceZone by resolvePerformance and eval'd ONCE in
|
||||
// Voice::start (never per frame). DEFAULT flat y=1 (R10-F1 Option A) — every velocity plays at
|
||||
// unity, a deliberate behavior change from the pre-r10 linear map.
|
||||
vst::VelocityCurve velocityCurve = vst::VelocityCurve::flat();
|
||||
VelocityCurve velocityCurve = VelocityCurve::flat();
|
||||
std::size_t sampleIndex = 0; // index into Keymap::samples
|
||||
};
|
||||
|
||||
@@ -439,7 +446,7 @@ public:
|
||||
// kDeclickDecay). A fresh start never declicks.
|
||||
void start(int note, int velocity, const SampleData& sample, int rootNote,
|
||||
double keyTrack = 1.0,
|
||||
const vst::VelocityCurve& velocityCurve = vst::VelocityCurve::flat(),
|
||||
const VelocityCurve& velocityCurve = VelocityCurve::flat(),
|
||||
bool declickTakeover = false);
|
||||
|
||||
// MONO LEGATO takeover (Phase S): re-pitch this ACTIVE voice to `note` without touching the
|
||||
@@ -1,23 +1,18 @@
|
||||
// velocity_curve.cpp — see velocity_curve.h. Pure eval + editing/clamp/inverse map; no host types.
|
||||
|
||||
#include "velocity_curve.h"
|
||||
#include "core/instrument/engine/velocity_curve.h"
|
||||
|
||||
#include <algorithm> // std::max, std::min, std::abs, std::stable_sort
|
||||
#include <cmath> // std::fabs
|
||||
#include <utility> // std::move
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::engine {
|
||||
|
||||
namespace {
|
||||
|
||||
double clamp(double v, double lo, double hi) {
|
||||
if (v < lo) return lo;
|
||||
if (v > hi) return hi;
|
||||
return v;
|
||||
}
|
||||
|
||||
double clampVelocity(double v) { return clamp(v, kVelMin, kVelMax); }
|
||||
double clampAmp(double a) { return clamp(a, kAmpMin, kAmpMax); }
|
||||
double clampVelocity(double v) { return std::clamp(v, kVelMin, kVelMax); }
|
||||
double clampAmp(double a) { return std::clamp(a, kAmpMin, kAmpMax); }
|
||||
|
||||
// Pixel<->box maps (mirror of envelope_edit's timeToX/levelToY). X spans the width for [0,127]; Y
|
||||
// spans (height-1) rows for amp [0,1] with amp 1 at the TOP (y increases downward).
|
||||
@@ -203,7 +198,7 @@ VelocityPoint VelocityCurve::movePoint(std::size_t index, double velocity, doubl
|
||||
// Interior point: clamp X strictly within its immediate neighbours so it can't cross them.
|
||||
const double lo = points_[index - 1].velocity;
|
||||
const double hi = points_[index + 1].velocity;
|
||||
newVel = clamp(clampVelocity(velocity), lo, hi);
|
||||
newVel = std::clamp(clampVelocity(velocity), lo, hi);
|
||||
}
|
||||
points_[index] = VelocityPoint{newVel, newAmp};
|
||||
return points_[index];
|
||||
@@ -274,4 +269,4 @@ bool VelocityCurve::equals(const VelocityCurve& other, double eps) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::engine
|
||||
@@ -38,7 +38,7 @@
|
||||
// Rect — the future editor shell (S-VIEW-10) passes its box coords directly. Mirror of envelope_edit's
|
||||
// role, but one layer lower, so the coupling stays out of the engine core.
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::engine {
|
||||
|
||||
// The MIDI velocity domain [0,127] and the amp range [0,1] — the box every point clamps into.
|
||||
inline constexpr double kVelMin = 0.0;
|
||||
@@ -168,4 +168,4 @@ private:
|
||||
std::vector<VelocityPoint> points_;
|
||||
};
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::engine
|
||||
@@ -1,13 +1,13 @@
|
||||
// bank_sync.cpp — see bank_sync.h. Pure; standard library only.
|
||||
|
||||
#include "bank_sync.h"
|
||||
#include "core/instrument/map/bank_sync.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "core/wire/wire.h"
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::map {
|
||||
|
||||
std::int64_t parseBankGeneration(const std::string& raw) {
|
||||
// Whole-string, non-negative decimal parse WITHOUT exceptions or locale
|
||||
@@ -60,4 +60,4 @@ AssignConsumeDecision consumeDecision(const std::optional<AssignmentRequest>& re
|
||||
return d;
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::map
|
||||
@@ -21,9 +21,11 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "assignment_request.h" // AssignmentRequest (the decoded request this consumes)
|
||||
#include "core/wire/assignment_request.h" // AssignmentRequest (the decoded request this consumes)
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::map {
|
||||
|
||||
using wire::AssignmentRequest;
|
||||
|
||||
// The S9 bank-generation "generation 0 = never stamped" default. A project saved before
|
||||
// S9 shipped carries no bank_generation key; the bridge read yields an absent/empty value
|
||||
@@ -102,4 +104,4 @@ AssignConsumeDecision consumeDecision(const std::optional<AssignmentRequest>& re
|
||||
std::int64_t lastConsumed, bool resolves,
|
||||
bool isFocusedTarget);
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::map
|
||||
@@ -1,8 +1,8 @@
|
||||
// bridge_marshal.cpp — see bridge_marshal.h. Pure; no host types.
|
||||
|
||||
#include "bridge_marshal.h"
|
||||
#include "core/instrument/map/bridge_marshal.h"
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::map {
|
||||
|
||||
std::optional<std::string> decodeGetProjExtState(int apiReturn,
|
||||
const std::string& buffer) {
|
||||
@@ -13,4 +13,4 @@ std::optional<std::string> decodeGetProjExtState(int apiReturn,
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::map
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::map {
|
||||
|
||||
// Interpret a GetProjExtState result: the int return value (bytes the API reports for
|
||||
// the key) and the buffer it filled. Returns the value only when the API reported a
|
||||
@@ -34,4 +34,4 @@ namespace reasampler::vst {
|
||||
std::optional<std::string> decodeGetProjExtState(int apiReturn,
|
||||
const std::string& buffer);
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::map
|
||||
@@ -1,11 +1,11 @@
|
||||
// note_entry.cpp — see note_entry.h. PURE text->MIDI-note parse for the S12 numeric entry.
|
||||
|
||||
#include "note_entry.h"
|
||||
#include "core/instrument/map/note_entry.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::map {
|
||||
|
||||
namespace {
|
||||
char asciiUpper(char c) {
|
||||
@@ -110,4 +110,4 @@ std::optional<int> parseNoteEntry(const std::string& text) {
|
||||
return parseNoteName(s);
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::map
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::map {
|
||||
|
||||
// Parse a typed low/high/root field into a clamped MIDI note [0,127]. Accepts a decimal
|
||||
// integer OR a note name (see the header notes). Leading/trailing ASCII whitespace is
|
||||
@@ -30,4 +30,4 @@ namespace reasampler::vst {
|
||||
// into [0,127]; empty or unparseable input returns nullopt (no change). Pure — no host types.
|
||||
std::optional<int> parseNoteEntry(const std::string& text);
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::map
|
||||
@@ -1,7 +1,7 @@
|
||||
// sample_map — pure implementation. See sample_map.h. NO VST3 / REAPER / SWELL /
|
||||
// vendor includes; standard library + the pure bank_book / wav_trim / sampler_core.
|
||||
|
||||
#include "sample_map.h"
|
||||
#include "core/instrument/map/sample_map.h"
|
||||
|
||||
#include <algorithm> // std::min
|
||||
#include <cassert> // assert
|
||||
@@ -9,10 +9,12 @@
|
||||
#include <cstring> // std::memcpy
|
||||
#include <utility> // std::move
|
||||
|
||||
#include "master_gain.h" // masterGainMaxLinear — the v8 master-gain wire cap
|
||||
#include "core/instrument/engine/master_gain.h" // masterGainMaxLinear — the v8 master-gain wire cap
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
using instrument::engine::masterGainMaxLinear;
|
||||
|
||||
namespace {
|
||||
|
||||
// Translate a bank_model Sample's S2 intrinsics into the core's SampleLoop. The bank
|
||||
@@ -544,9 +546,9 @@ void putZonesPayload(std::vector<std::uint8_t>& out, const PerformanceMap& map)
|
||||
putU64le(out, doubleToBits(z.keyTrack));
|
||||
// PAYLOAD v7 (S-VIEW-9): the per-zone velocity->amp transfer curve, appended last. 4-byte LE
|
||||
// control-point count, then per point velocity + amp as IEEE-754 doubles (endpoints included).
|
||||
const std::vector<reasampler::vst::VelocityPoint>& pts = z.velocityCurve.points();
|
||||
const std::vector<VelocityPoint>& pts = z.velocityCurve.points();
|
||||
putU32le(out, static_cast<std::uint32_t>(pts.size()));
|
||||
for (const reasampler::vst::VelocityPoint& p : pts) {
|
||||
for (const VelocityPoint& p : pts) {
|
||||
putU64le(out, doubleToBits(p.velocity));
|
||||
putU64le(out, doubleToBits(p.amp));
|
||||
}
|
||||
@@ -642,7 +644,7 @@ void readZonesPayload(ByteReader& r, PerformanceMap& map, double projectRate) {
|
||||
// false mid-curve) leaves the flat default and the mid-zone break below drops the rest.
|
||||
if (curveTail) {
|
||||
const std::uint32_t ptCount = r.u32();
|
||||
std::vector<reasampler::vst::VelocityPoint> pts;
|
||||
std::vector<VelocityPoint> pts;
|
||||
// Bound the reserve to what the blob can actually hold (16 bytes/point) so a corrupt huge
|
||||
// count can't trigger a giant allocation before the bounded reads fail — the loop still
|
||||
// stops on r.ok, this only caps the speculative reserve.
|
||||
@@ -651,9 +653,9 @@ void readZonesPayload(ByteReader& r, PerformanceMap& map, double projectRate) {
|
||||
for (std::uint32_t p = 0; p < ptCount && r.ok; ++p) {
|
||||
const double vel = bitsToDouble(r.u64());
|
||||
const double amp = bitsToDouble(r.u64());
|
||||
pts.push_back(reasampler::vst::VelocityPoint{vel, amp});
|
||||
pts.push_back(VelocityPoint{vel, amp});
|
||||
}
|
||||
if (r.ok) z.velocityCurve = reasampler::vst::VelocityCurve::fromPoints(std::move(pts));
|
||||
if (r.ok) z.velocityCurve = reasampler::VelocityCurve::fromPoints(std::move(pts));
|
||||
}
|
||||
// Payload versions 4 (branch-only frames tail, never shipped) and any unknown pv leave the
|
||||
// seconds product defaults on z.play — a v4 blob cannot exist outside this branch.
|
||||
@@ -729,7 +731,7 @@ std::vector<std::uint8_t> serializeComponentState(const ComponentState& state) {
|
||||
// negative falls back to unity; above the +24 dB cap clamps to the cap.
|
||||
{
|
||||
double g = state.masterGainLinear;
|
||||
const double maxLin = vst::masterGainMaxLinear();
|
||||
const double maxLin = masterGainMaxLinear();
|
||||
if (!std::isfinite(g) || g < 0.0) g = 1.0;
|
||||
if (g > maxLin) g = maxLin;
|
||||
putU64le(out, doubleToBits(g));
|
||||
@@ -887,7 +889,7 @@ ComponentState deserializeComponentState(const std::vector<std::uint8_t>& bytes,
|
||||
if (!r.ok) return out; // truncated inside the gain double — out already carries
|
||||
// mode/marker/velocity/voice fields from above; unity holds
|
||||
out.masterGainLinear =
|
||||
(std::isfinite(g) && g >= 0.0 && g <= vst::masterGainMaxLinear() * (1.0 + 1e-9))
|
||||
(std::isfinite(g) && g >= 0.0 && g <= masterGainMaxLinear() * (1.0 + 1e-9))
|
||||
? g
|
||||
: 1.0;
|
||||
}
|
||||
@@ -23,12 +23,18 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "bank_book.h" // BankBook::deserialize (shared bank JSON parse)
|
||||
#include "sampler_core.h" // Keymap, SampleData, SampleLoop
|
||||
#include "wav_trim.h" // parseWavLayout, extractFloatFrames (shared WAV parse)
|
||||
#include "core/model/bank_book.h" // BankBook::deserialize (shared bank JSON parse)
|
||||
#include "core/instrument/engine/sampler_core.h" // Keymap, SampleData, SampleLoop
|
||||
#include "core/capture/wav_trim.h" // parseWavLayout, extractFloatFrames (shared WAV parse)
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
// Q-W1 interim: clean deps live in their sub-namespace homes now; sample_map
|
||||
// re-namespaces in its own split wave (Q-W2v).
|
||||
using audio::AudioSample;
|
||||
using instrument::engine::VelocityCurve;
|
||||
using instrument::engine::VelocityPoint;
|
||||
|
||||
// The bank sample this instance is bound to, distilled from the live "banks" blob:
|
||||
// the project-relative WAV path the file seam must resolve+decode, plus the S2 bank
|
||||
// intrinsics the core repitches / loops by. A pure value — no host, no PCM yet.
|
||||
@@ -287,7 +293,7 @@ struct PerformanceZone {
|
||||
// already-saved zone's soft hits play LOUDER than under the old linear map. Intended; do NOT
|
||||
// preserve the linear response. Carried to KeyZone by resolvePerformance, eval'd in Voice::start.
|
||||
// Sequenced on the zones-payload axis AFTER keyTrack (payload v6 -> v7).
|
||||
vst::VelocityCurve velocityCurve = vst::VelocityCurve::flat();
|
||||
VelocityCurve velocityCurve = VelocityCurve::flat();
|
||||
|
||||
// S15/S16 per-zone play parameters (play mode + AHDSR + Trigger %-length/fades; pitch
|
||||
// engine + AD pitch envelope). Instrument-owned (D-B), never a bank fact — mirror of the
|
||||
@@ -341,7 +347,7 @@ struct ResolvedZone {
|
||||
int highNote = 127;
|
||||
int rootNote = 60; // effective: override, else bank intrinsic, else 60
|
||||
double keyTrack = 1.0; // S-VIEW-6 key-tracking scalar, carried from PerformanceZone (1.0 = 100% ET)
|
||||
vst::VelocityCurve velocityCurve = vst::VelocityCurve::flat(); // S-VIEW-9 velocity->amp curve, carried from PerformanceZone
|
||||
VelocityCurve velocityCurve = VelocityCurve::flat(); // S-VIEW-9 velocity->amp curve, carried from PerformanceZone
|
||||
SampleLoop loop; // effective: loopOverride, else bank S2 intrinsic (S11)
|
||||
std::int64_t startFrame = 0; // effective initial read frame: startPoint, else 0 (S11)
|
||||
ZonePlaySeconds play; // S15/S16 per-zone play params (SECONDS; resolved to frames at build)
|
||||
@@ -1,10 +1,10 @@
|
||||
// trigger_seam.cpp — PURE Trigger-mode frames↔fraction converter (see trigger_seam.h).
|
||||
|
||||
#include "trigger_seam.h"
|
||||
#include "core/instrument/map/trigger_seam.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::map {
|
||||
|
||||
std::int64_t triggerPlayLength(double lengthFraction,
|
||||
std::int64_t frameCount,
|
||||
@@ -24,4 +24,4 @@ std::int64_t fadeFractionToFrames(double fadeFraction, std::int64_t playLength)
|
||||
return static_cast<std::int64_t>(fadeFraction * static_cast<double>(playLength) + 0.5);
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::map
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::map {
|
||||
|
||||
// The source-frame length of the Trigger played span:
|
||||
// postStart = max(0, frameCount - startFrame)
|
||||
@@ -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::vst
|
||||
} // namespace reasampler::instrument::map
|
||||
@@ -1,12 +1,12 @@
|
||||
// browser_scroll.cpp — see browser_scroll.h. PURE scroll + search geometry over the S10
|
||||
// capture_browser. No host types; only the shared Rect + BrowserLayout.
|
||||
|
||||
#include "browser_scroll.h"
|
||||
#include "core/instrument/ui/browser_scroll.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
// The minimum thumb height so a very long bank still yields a grabbable thumb.
|
||||
@@ -26,7 +26,7 @@ int scrollContentHeight(const BrowserLayout& layout, int cardCount) {
|
||||
|
||||
int scrollMaxOffset(const BrowserLayout& layout, int cardCount) {
|
||||
const int content = scrollContentHeight(layout, cardCount);
|
||||
const int gridH = (std::max)(0, layout.grid.height());
|
||||
const int gridH = (std::max)(0, layout.grid.height);
|
||||
return (std::max)(0, content - gridH);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ VisibleRange visibleCardRange(const BrowserLayout& layout, int cardCount, int of
|
||||
VisibleRange vr;
|
||||
if (cardCount <= 0) return vr;
|
||||
const int columns = (std::max)(1, layout.columns);
|
||||
const int gridH = (std::max)(0, layout.grid.height());
|
||||
const int gridH = (std::max)(0, layout.grid.height);
|
||||
if (gridH <= 0 || kBrowserCardHeight <= 0) {
|
||||
vr.first = 0;
|
||||
vr.last = 0;
|
||||
@@ -66,21 +66,21 @@ VisibleRange visibleCardRange(const BrowserLayout& layout, int cardCount, int of
|
||||
|
||||
Rect scrolledCardCellRect(const BrowserLayout& layout, int index, int offset) {
|
||||
Rect r = cardCellRect(layout, index);
|
||||
if (r.right <= r.left && r.bottom <= r.top) return r; // empty (negative index) stays empty
|
||||
return Rect{r.left, r.top - offset, r.right, r.bottom - offset};
|
||||
if (r.right() <= r.x && r.bottom() <= r.y) return r; // empty (negative index) stays empty
|
||||
return Rect::ltrb(r.x, r.y - offset, r.right(), r.bottom() - offset);
|
||||
}
|
||||
|
||||
Rect scrollThumbRect(const BrowserLayout& layout, int cardCount, int offset) {
|
||||
const int content = scrollContentHeight(layout, cardCount);
|
||||
const int gridH = (std::max)(0, layout.grid.height());
|
||||
const int gridH = (std::max)(0, layout.grid.height);
|
||||
if (content <= gridH || gridH <= 0) return Rect{}; // fits -> no scrollbar
|
||||
const int maxOff = content - gridH;
|
||||
if (offset < 0) offset = 0;
|
||||
if (offset > maxOff) offset = maxOff;
|
||||
|
||||
const int trackRight = layout.grid.right;
|
||||
const int trackRight = layout.grid.right();
|
||||
const int trackLeft = trackRight - kScrollbarWidth;
|
||||
const int trackTop = layout.grid.top;
|
||||
const int trackTop = layout.grid.y;
|
||||
|
||||
// Thumb height proportional to the visible fraction, floored at a grabbable minimum but
|
||||
// never taller than the track.
|
||||
@@ -95,13 +95,13 @@ Rect scrollThumbRect(const BrowserLayout& layout, int cardCount, int offset) {
|
||||
thumbTop = trackTop + static_cast<int>(
|
||||
static_cast<long long>(offset) * trackSpan / maxOff);
|
||||
}
|
||||
return Rect{trackLeft, thumbTop, trackRight, thumbTop + thumbH};
|
||||
return Rect::ltrb(trackLeft, thumbTop, trackRight, thumbTop + thumbH);
|
||||
}
|
||||
|
||||
int thumbDragToOffset(const BrowserLayout& layout, int cardCount, int startOffset,
|
||||
int dyPixels) {
|
||||
const int content = scrollContentHeight(layout, cardCount);
|
||||
const int gridH = (std::max)(0, layout.grid.height());
|
||||
const int gridH = (std::max)(0, layout.grid.height);
|
||||
if (content <= gridH || gridH <= 0) return clampScrollOffset(layout, cardCount, startOffset);
|
||||
|
||||
// Thumb height (same formula as scrollThumbRect) -> movable track span in thumb pixels.
|
||||
@@ -124,7 +124,7 @@ int thumbDragToOffset(const BrowserLayout& layout, int cardCount, int startOffse
|
||||
|
||||
Rect searchBoxRect(int w) {
|
||||
if (w <= 0) return Rect{};
|
||||
return Rect{0, 0, w, kSearchBoxHeight};
|
||||
return Rect::ltrb(0, 0, w, kSearchBoxHeight);
|
||||
}
|
||||
|
||||
bool nameMatchesQuery(const std::string& name, const std::string& query) {
|
||||
@@ -155,4 +155,4 @@ std::vector<int> filterNameIndices(const std::vector<std::string>& names,
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -24,9 +24,9 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "capture_browser.h" // BrowserLayout, cardCellRect, kBrowserCardHeight, Rect
|
||||
#include "core/instrument/ui/capture_browser.h" // BrowserLayout, cardCellRect, kBrowserCardHeight, Rect
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
// The width (px) of the vertical scrollbar gutter at the right edge of the grid. The shell
|
||||
// draws the track + thumb here and hit-tests thumb grabs against scrollThumbRect. Exposed so
|
||||
@@ -104,4 +104,4 @@ bool nameMatchesQuery(const std::string& name, const std::string& query);
|
||||
std::vector<int> filterNameIndices(const std::vector<std::string>& names,
|
||||
const std::string& query);
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -1,10 +1,10 @@
|
||||
// capture_browser.cpp — see capture_browser.h. Pure math; no host types.
|
||||
|
||||
#include "capture_browser.h"
|
||||
#include "core/instrument/ui/capture_browser.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -23,10 +23,10 @@ BrowserLayout layoutBrowser(int w, int h) {
|
||||
|
||||
BrowserLayout out;
|
||||
const int tabH = std::min(kBrowserTabHeight, ch);
|
||||
out.tabStrip = Rect{0, 0, cw, tabH};
|
||||
out.grid = Rect{0, tabH, cw, ch};
|
||||
out.tabStrip = Rect::ltrb(0, 0, cw, tabH);
|
||||
out.grid = Rect::ltrb(0, tabH, cw, ch);
|
||||
|
||||
const int gridW = std::max(0, out.grid.width());
|
||||
const int gridW = std::max(0, out.grid.width);
|
||||
out.columns = std::max(1, gridW / kBrowserCardWidth);
|
||||
return out;
|
||||
}
|
||||
@@ -36,38 +36,38 @@ Rect cardCellRect(const BrowserLayout& layout, int index) {
|
||||
const int cols = std::max(1, layout.columns);
|
||||
const int col = index % cols;
|
||||
const int row = index / cols;
|
||||
const int left = layout.grid.left + col * kBrowserCardWidth;
|
||||
const int top = layout.grid.top + row * kBrowserCardHeight;
|
||||
return Rect{left, top, left + kBrowserCardWidth, top + kBrowserCardHeight};
|
||||
const int left = layout.grid.x + col * kBrowserCardWidth;
|
||||
const int top = layout.grid.y + row * kBrowserCardHeight;
|
||||
return Rect::ltrb(left, top, left + kBrowserCardWidth, top + kBrowserCardHeight);
|
||||
}
|
||||
|
||||
Rect cardContentRect(const BrowserLayout& layout, int index) {
|
||||
if (index < 0) return Rect{};
|
||||
const Rect cell = cardCellRect(layout, index);
|
||||
return Rect{cell.left + kBrowserCardGutter, cell.top + kBrowserCardGutter,
|
||||
cell.right - kBrowserCardGutter, cell.bottom - kBrowserCardGutter};
|
||||
return Rect::ltrb(cell.x + kBrowserCardGutter, cell.y + kBrowserCardGutter,
|
||||
cell.right() - kBrowserCardGutter, cell.bottom() - kBrowserCardGutter);
|
||||
}
|
||||
|
||||
Rect cardThumbnailRect(const BrowserLayout& layout, int index) {
|
||||
if (index < 0) return Rect{};
|
||||
const Rect content = cardContentRect(layout, index);
|
||||
const int thumbH = std::min(kBrowserThumbHeight, std::max(0, content.height()));
|
||||
return Rect{content.left, content.top, content.right, content.top + thumbH};
|
||||
const int thumbH = std::min(kBrowserThumbHeight, std::max(0, content.height));
|
||||
return Rect::ltrb(content.x, content.y, content.right(), content.y + thumbH);
|
||||
}
|
||||
|
||||
Rect cardLabelRect(const BrowserLayout& layout, int index) {
|
||||
if (index < 0) return Rect{};
|
||||
const Rect content = cardContentRect(layout, index);
|
||||
const Rect thumb = cardThumbnailRect(layout, index);
|
||||
return Rect{content.left, thumb.bottom, content.right, content.bottom};
|
||||
return Rect::ltrb(content.x, thumb.bottom(), content.right(), content.bottom());
|
||||
}
|
||||
|
||||
int cardHitTest(const BrowserLayout& layout, int cardCount, int x, int y) {
|
||||
if (cardCount <= 0) return -1;
|
||||
if (!contains(layout.grid, x, y)) return -1;
|
||||
const int cols = std::max(1, layout.columns);
|
||||
const int col = (x - layout.grid.left) / kBrowserCardWidth;
|
||||
const int row = (y - layout.grid.top) / kBrowserCardHeight;
|
||||
const int col = (x - layout.grid.x) / kBrowserCardWidth;
|
||||
const int row = (y - layout.grid.y) / kBrowserCardHeight;
|
||||
if (col < 0 || col >= cols) return -1; // past the last column (right dead-zone)
|
||||
const int index = row * cols + col;
|
||||
if (index < 0 || index >= cardCount) return -1;
|
||||
@@ -79,9 +79,9 @@ int cardHitTest(const BrowserLayout& layout, int cardCount, int x, int y) {
|
||||
Rect filterTabRect(const BrowserLayout& layout, int tabCount, int index) {
|
||||
if (tabCount <= 0 || index < 0 || index >= tabCount) return Rect{};
|
||||
const Rect& strip = layout.tabStrip;
|
||||
const int left = tabEdge(strip.left, std::max(0, strip.width()), index, tabCount);
|
||||
const int right = tabEdge(strip.left, std::max(0, strip.width()), index + 1, tabCount);
|
||||
return Rect{left, strip.top, right, strip.bottom};
|
||||
const int left = tabEdge(strip.x, std::max(0, strip.width), index, tabCount);
|
||||
const int right = tabEdge(strip.x, std::max(0, strip.width), index + 1, tabCount);
|
||||
return Rect::ltrb(left, strip.y, right, strip.bottom());
|
||||
}
|
||||
|
||||
int filterTabHitTest(const BrowserLayout& layout, int tabCount, int x, int y) {
|
||||
@@ -93,4 +93,4 @@ int filterTabHitTest(const BrowserLayout& layout, int tabCount, int x, int y) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor_geometry.h" // Rect, contains — one shared geometry idiom
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect, contains — one shared geometry idiom
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
// Fixed browser metrics, exposed so the shell and tests agree. The card is sized to show a
|
||||
// peak thumbnail with a name + badge line under it — scannable by eye, not a dense list.
|
||||
@@ -37,12 +37,12 @@ inline constexpr int kBrowserThumbHeight = 44; // the peak-thumbnail band inside
|
||||
struct BrowserLayout {
|
||||
Rect tabStrip; // top: the bank-filter tabs
|
||||
Rect grid; // below the tabs: where the capture cards tile
|
||||
int columns = 1; // cards per row in `grid` (>= 1); derived from grid.width()
|
||||
int columns = 1; // cards per row in `grid` (>= 1); derived from grid.width
|
||||
};
|
||||
|
||||
// Divide a (w x h) browser area into its regions and compute the column count. Pure: same
|
||||
// inputs -> same layout. The tab strip takes a fixed height at the top (clamped so it never
|
||||
// exceeds the area); the grid takes the rest. columns = max(1, grid.width()/cardWidth) so a
|
||||
// exceeds the area); the grid takes the rest. columns = max(1, grid.width/cardWidth) so a
|
||||
// browser narrower than one card still lays out a single column. A zero/negative size
|
||||
// yields empty rects + columns==1.
|
||||
BrowserLayout layoutBrowser(int w, int 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::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -0,0 +1,41 @@
|
||||
// curve_popup.cpp — see curve_popup.h. Pure arithmetic; no LICE/VST3/REAPER includes.
|
||||
|
||||
#include "core/instrument/ui/curve_popup.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
int clampDim(int want, int lo, int hi, int windowDim) {
|
||||
const int clamped = (std::max)(lo, (std::min)(hi, want));
|
||||
return (std::min)(clamped, (std::max)(0, windowDim));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CurvePopupLayout computeCurvePopup(int w, int h) {
|
||||
CurvePopupLayout out;
|
||||
const int sheetW = clampDim((w * 60) / 100, kCurvePopupMinW, kCurvePopupMaxW, w);
|
||||
const int sheetH = clampDim((h * 55) / 100, kCurvePopupMinH, kCurvePopupMaxH, h);
|
||||
const int left = (w - sheetW) / 2;
|
||||
const int top = (h - sheetH) / 2;
|
||||
out.sheet = Rect::ltrb(left, top, left + sheetW, top + sheetH);
|
||||
|
||||
const int titleBottom = out.sheet.y + kCurvePopupTitleH;
|
||||
const int closeTop = out.sheet.y + (kCurvePopupTitleH - kCurvePopupCloseSize) / 2;
|
||||
out.close = Rect::ltrb(out.sheet.right() - kCurvePopupPad - kCurvePopupCloseSize, closeTop,
|
||||
out.sheet.right() - kCurvePopupPad, closeTop + kCurvePopupCloseSize);
|
||||
out.title = Rect::ltrb(out.sheet.x + kCurvePopupPad, out.sheet.y,
|
||||
out.close.x - kCurvePopupPad, titleBottom);
|
||||
|
||||
out.curveBox = Rect::ltrb(out.sheet.x + kCurvePopupPad, titleBottom + 2,
|
||||
out.sheet.right() - kCurvePopupPad,
|
||||
out.sheet.bottom() - kCurvePopupPad);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool popupOutsideSheet(const CurvePopupLayout& layout, int x, int y) {
|
||||
return !contains(layout.sheet, x, y);
|
||||
}
|
||||
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor_geometry.h" // Rect, contains
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect, contains
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
// Fixed popup metrics (spec r11), exposed so the shell and tests agree.
|
||||
inline constexpr int kCurvePopupMinW = 360;
|
||||
@@ -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::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -1,10 +1,10 @@
|
||||
// editor_geometry.cpp — see editor_geometry.h. Pure math; no host types.
|
||||
|
||||
#include "editor_geometry.h"
|
||||
#include "core/instrument/ui/editor_geometry.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -17,10 +17,8 @@ constexpr int kButtonHeight = 24;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool contains(const Rect& r, int x, int y) {
|
||||
if (r.width() <= 0 || r.height() <= 0) return false;
|
||||
return x >= r.left && x < r.right && y >= r.top && y < r.bottom;
|
||||
}
|
||||
// contains() now lives with the shared ui::Rect (core/ui/rect.h) — same half-open
|
||||
// semantics, re-exported through the header's using-declaration.
|
||||
|
||||
EditorLayout layoutEditor(int w, int h) {
|
||||
// Clamp the surface to non-negative extents so a degenerate view can't produce
|
||||
@@ -32,18 +30,18 @@ EditorLayout layoutEditor(int w, int h) {
|
||||
|
||||
// Title bar spans the top, clamped so it never exceeds the client height.
|
||||
const int titleH = std::min(kTitleBarHeight, ch);
|
||||
out.titleBar = Rect{0, 0, cw, titleH};
|
||||
out.titleBar = Rect::ltrb(0, 0, cw, titleH);
|
||||
|
||||
// Canvas is everything below the title bar.
|
||||
out.canvas = Rect{0, titleH, cw, ch};
|
||||
out.canvas = Rect::ltrb(0, titleH, cw, ch);
|
||||
|
||||
// Button sits at the top-left of the canvas, inset by a margin, and is clamped to
|
||||
// fit inside the canvas so it never overhangs on a small view.
|
||||
const int bx = out.canvas.left + kButtonMargin;
|
||||
const int by = out.canvas.top + kButtonMargin;
|
||||
const int bRight = std::min(bx + kButtonWidth, out.canvas.right);
|
||||
const int bBottom = std::min(by + kButtonHeight, out.canvas.bottom);
|
||||
out.button = Rect{bx, by, std::max(bx, bRight), std::max(by, bBottom)};
|
||||
const int bx = out.canvas.x + kButtonMargin;
|
||||
const int by = out.canvas.y + kButtonMargin;
|
||||
const int bRight = std::min(bx + kButtonWidth, out.canvas.right());
|
||||
const int bBottom = std::min(by + kButtonHeight, out.canvas.bottom());
|
||||
out.button = Rect::ltrb(bx, by, std::max(bx, bRight), std::max(by, bBottom));
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -55,23 +53,23 @@ HitTarget hitTest(const EditorLayout& layout, int x, int y) {
|
||||
|
||||
Rect sampleRowRect(const EditorLayout& layout, int index) {
|
||||
if (index < 0) return Rect{};
|
||||
const int top = layout.canvas.top + index * kSampleRowHeight;
|
||||
return Rect{layout.canvas.left, top, layout.canvas.right, top + kSampleRowHeight};
|
||||
const int top = layout.canvas.y + index * kSampleRowHeight;
|
||||
return Rect::ltrb(layout.canvas.x, top, layout.canvas.right(), top + kSampleRowHeight);
|
||||
}
|
||||
|
||||
int sampleRowHitTest(const EditorLayout& layout, int rowCount, int x, int y) {
|
||||
if (rowCount <= 0) return -1;
|
||||
// Must be within the canvas horizontally and at/below its top.
|
||||
if (x < layout.canvas.left || x >= layout.canvas.right) return -1;
|
||||
if (y < layout.canvas.top) return -1;
|
||||
if (x < layout.canvas.x || x >= layout.canvas.right()) return -1;
|
||||
if (y < layout.canvas.y) return -1;
|
||||
// Clip at the canvas bottom: clicks in the canvas's dead-zone below the last
|
||||
// visible row agree with sampleRowRect, which does not clamp rows to canvas.bottom.
|
||||
if (y >= layout.canvas.bottom) return -1;
|
||||
const int index = (y - layout.canvas.top) / kSampleRowHeight;
|
||||
// visible row agree with sampleRowRect, which does not clamp rows to canvas.bottom().
|
||||
if (y >= layout.canvas.bottom()) return -1;
|
||||
const int index = (y - layout.canvas.y) / kSampleRowHeight;
|
||||
if (index < 0 || index >= rowCount) return -1;
|
||||
// Guard the bottom edge: a click below the last row's bottom is outside.
|
||||
const Rect r = sampleRowRect(layout, index);
|
||||
if (y >= r.bottom) return -1;
|
||||
if (y >= r.bottom()) return -1;
|
||||
return index;
|
||||
}
|
||||
|
||||
@@ -85,60 +83,60 @@ KeymapEditorLayout layoutKeymapEditor(int w, int h) {
|
||||
// Split the canvas vertically: the left column is the bank-sample list, the right
|
||||
// column (1/kZonePanelFraction of the width) is the zone panel. Guard tiny widths so
|
||||
// the split point never crosses the canvas edges.
|
||||
const int canvasW = std::max(0, canvas.width());
|
||||
const int canvasW = std::max(0, canvas.width);
|
||||
const int splitW = canvasW / kZonePanelFraction; // width of the zone panel
|
||||
const int splitX = std::max(canvas.left, canvas.right - splitW);
|
||||
const int splitX = std::max(canvas.x, canvas.right() - splitW);
|
||||
|
||||
out.sampleList = Rect{canvas.left, canvas.top, splitX, canvas.bottom};
|
||||
out.zonePanel = Rect{splitX, canvas.top, canvas.right, canvas.bottom};
|
||||
out.sampleList = Rect::ltrb(canvas.x, canvas.y, splitX, canvas.bottom());
|
||||
out.zonePanel = Rect::ltrb(splitX, canvas.y, canvas.right(), canvas.bottom());
|
||||
|
||||
// "Add Zone" button spans the top of the zone panel, clamped to its height.
|
||||
const int addH = std::min(kAddZoneHeight, std::max(0, out.zonePanel.height()));
|
||||
const int addH = std::min(kAddZoneHeight, std::max(0, out.zonePanel.height));
|
||||
out.addZoneButton =
|
||||
Rect{out.zonePanel.left, out.zonePanel.top, out.zonePanel.right,
|
||||
out.zonePanel.top + addH};
|
||||
Rect::ltrb(out.zonePanel.x, out.zonePanel.y, out.zonePanel.right(),
|
||||
out.zonePanel.y + addH);
|
||||
|
||||
// Zone rows stack below the button.
|
||||
out.zoneRowArea = Rect{out.zonePanel.left, out.addZoneButton.bottom,
|
||||
out.zonePanel.right, out.zonePanel.bottom};
|
||||
out.zoneRowArea = Rect::ltrb(out.zonePanel.x, out.addZoneButton.bottom(),
|
||||
out.zonePanel.right(), out.zonePanel.bottom());
|
||||
return out;
|
||||
}
|
||||
|
||||
Rect keymapSampleRowRect(const KeymapEditorLayout& layout, int index) {
|
||||
if (index < 0) return Rect{};
|
||||
const int top = layout.sampleList.top + index * kSampleRowHeight;
|
||||
return Rect{layout.sampleList.left, top, layout.sampleList.right,
|
||||
top + kSampleRowHeight};
|
||||
const int top = layout.sampleList.y + index * kSampleRowHeight;
|
||||
return Rect::ltrb(layout.sampleList.x, top, layout.sampleList.right(),
|
||||
top + kSampleRowHeight);
|
||||
}
|
||||
|
||||
int keymapSampleRowHitTest(const KeymapEditorLayout& layout, int rowCount, int x, int y) {
|
||||
if (rowCount <= 0) return -1;
|
||||
const Rect& list = layout.sampleList;
|
||||
if (x < list.left || x >= list.right) return -1;
|
||||
if (y < list.top || y >= list.bottom) return -1;
|
||||
const int index = (y - list.top) / kSampleRowHeight;
|
||||
if (x < list.x || x >= list.right()) return -1;
|
||||
if (y < list.y || y >= list.bottom()) return -1;
|
||||
const int index = (y - list.y) / kSampleRowHeight;
|
||||
if (index < 0 || index >= rowCount) return -1;
|
||||
const Rect r = keymapSampleRowRect(layout, index);
|
||||
if (y >= r.bottom) return -1;
|
||||
if (y >= r.bottom()) return -1;
|
||||
return index;
|
||||
}
|
||||
|
||||
Rect zoneRowRect(const KeymapEditorLayout& layout, int index) {
|
||||
if (index < 0) return Rect{};
|
||||
const int top = layout.zoneRowArea.top + index * kZoneRowHeight;
|
||||
return Rect{layout.zoneRowArea.left, top, layout.zoneRowArea.right,
|
||||
top + kZoneRowHeight};
|
||||
const int top = layout.zoneRowArea.y + index * kZoneRowHeight;
|
||||
return Rect::ltrb(layout.zoneRowArea.x, top, layout.zoneRowArea.right(),
|
||||
top + kZoneRowHeight);
|
||||
}
|
||||
|
||||
ZoneHit zoneHitTest(const KeymapEditorLayout& layout, int zoneCount, int x, int y) {
|
||||
if (zoneCount <= 0) return ZoneHit{};
|
||||
const Rect& area = layout.zoneRowArea;
|
||||
if (x < area.left || x >= area.right) return ZoneHit{};
|
||||
if (y < area.top || y >= area.bottom) return ZoneHit{};
|
||||
const int index = (y - area.top) / kZoneRowHeight;
|
||||
if (x < area.x || x >= area.right()) return ZoneHit{};
|
||||
if (y < area.y || y >= area.bottom()) return ZoneHit{};
|
||||
const int index = (y - area.y) / kZoneRowHeight;
|
||||
if (index < 0 || index >= zoneCount) return ZoneHit{};
|
||||
const Rect row = zoneRowRect(layout, index);
|
||||
if (y >= row.bottom) return ZoneHit{};
|
||||
if (y >= row.bottom()) return ZoneHit{};
|
||||
|
||||
// Seven mini-buttons pinned to the right edge, right-to-left:
|
||||
// delete, root+, root-, high+, high-, low+, low-
|
||||
@@ -150,7 +148,7 @@ ZoneHit zoneHitTest(const KeymapEditorLayout& layout, int zoneCount, int x, int
|
||||
ZoneField::kDelete,
|
||||
};
|
||||
const int slots = 7;
|
||||
const int ctrlBlockLeft = row.right - slots * kZoneCtrlWidth;
|
||||
const int ctrlBlockLeft = row.right() - slots * kZoneCtrlWidth;
|
||||
if (x < ctrlBlockLeft) return ZoneHit{index, ZoneField::kZoneNone}; // label -> select
|
||||
const int slot = (x - ctrlBlockLeft) / kZoneCtrlWidth;
|
||||
if (slot < 0 || slot >= slots) return ZoneHit{index, ZoneField::kZoneNone};
|
||||
@@ -161,4 +159,4 @@ bool addZoneHitTest(const KeymapEditorLayout& layout, int x, int y) {
|
||||
return contains(layout.addZoneButton, x, y);
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -12,24 +12,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace reasampler::vst {
|
||||
#include "core/ui/rect.h"
|
||||
|
||||
// A plain integer rectangle. left/top inclusive, right/bottom exclusive — the same
|
||||
// half-open convention LICE/SWELL RECTs use, kept REAPER-free here.
|
||||
struct Rect {
|
||||
int left = 0;
|
||||
int top = 0;
|
||||
int right = 0;
|
||||
int bottom = 0;
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
int width() const { return right - left; }
|
||||
int height() const { return bottom - top; }
|
||||
};
|
||||
|
||||
// Returns true if (x, y) falls inside r under the half-open convention
|
||||
// (left <= x < right, top <= y < bottom). A zero-or-negative-area rect contains
|
||||
// nothing.
|
||||
bool contains(const Rect& r, int x, int y);
|
||||
// The shared pixel rectangle + containment test (Q-W1, T2-05 ≡ T4-21): the former
|
||||
// LTRB Rect defined here is folded into the ONE concrete ui::Rect (XYWH storage,
|
||||
// right()/bottom() accessors, Rect::ltrb() for edge-wise construction, same
|
||||
// half-open convention). Aliased here so every instrument-ui call site keeps its
|
||||
// established `Rect` / `contains` spelling.
|
||||
using Rect = ::reasampler::ui::Rect;
|
||||
using ::reasampler::ui::contains;
|
||||
|
||||
// The regions the spike editor draws, derived from the current view size. All are
|
||||
// clamped to the client area so a degenerate (too-small) view never yields a region
|
||||
@@ -93,7 +86,7 @@ inline constexpr int kAddZoneHeight = 22; // the "Add Zone" button band heig
|
||||
// The keymap editor's regions, derived from the (w x h) client area. All clamp to the
|
||||
// canvas so a degenerate view yields in-bounds rects.
|
||||
struct KeymapEditorLayout {
|
||||
EditorLayout base; // title bar + canvas (the sample list uses base.canvas.left half)
|
||||
EditorLayout base; // title bar + canvas (the sample list uses base.canvas.x half)
|
||||
Rect sampleList; // LEFT column: the bank-sample rows (sampleRowRect is relative here)
|
||||
Rect zonePanel; // RIGHT column: the "Add Zone" button + the zone rows
|
||||
Rect addZoneButton; // top of the zone panel
|
||||
@@ -146,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::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -1,10 +1,10 @@
|
||||
// embed_strip.cpp — see embed_strip.h. Pure math; no host types.
|
||||
|
||||
#include "embed_strip.h"
|
||||
#include "core/instrument/ui/embed_strip.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -42,22 +42,22 @@ EmbedLayout layoutEmbed(int w, int h) {
|
||||
}
|
||||
const int keymapBottom = ch - bandH;
|
||||
|
||||
out.keymap = Rect{0, 0, cw, keymapBottom};
|
||||
out.levelBand = Rect{0, keymapBottom, cw, ch};
|
||||
out.keymap = Rect::ltrb(0, 0, cw, keymapBottom);
|
||||
out.levelBand = Rect::ltrb(0, keymapBottom, cw, ch);
|
||||
return out;
|
||||
}
|
||||
|
||||
Rect zoneSegmentRect(const EmbedLayout& layout, int lowNote, int highNote) {
|
||||
const Rect& band = layout.keymap;
|
||||
const int bandWidth = std::max(0, band.width());
|
||||
const int bandWidth = std::max(0, band.width);
|
||||
|
||||
int lo = clampNote(lowNote);
|
||||
int hi = clampNote(highNote);
|
||||
if (lo > hi) lo = hi; // defensive: a malformed zone collapses rather than inverts
|
||||
|
||||
const int leftX = keyEdgeToX(band.left, bandWidth, lo);
|
||||
const int rightX = keyEdgeToX(band.left, bandWidth, hi + 1);
|
||||
return Rect{leftX, band.top, std::max(leftX, rightX), band.bottom};
|
||||
const int leftX = keyEdgeToX(band.x, bandWidth, lo);
|
||||
const int rightX = keyEdgeToX(band.x, bandWidth, hi + 1);
|
||||
return Rect::ltrb(leftX, band.y, std::max(leftX, rightX), band.bottom());
|
||||
}
|
||||
|
||||
int zoneAtPoint(const EmbedLayout& layout, const EmbedZone* zones, int zoneCount, int x,
|
||||
@@ -74,13 +74,13 @@ int zoneAtPoint(const EmbedLayout& layout, const EmbedZone* zones, int zoneCount
|
||||
|
||||
Rect levelFillRect(const EmbedLayout& layout, double level) {
|
||||
const Rect& band = layout.levelBand;
|
||||
if (band.width() <= 0 || band.height() <= 0) return Rect{};
|
||||
if (band.width <= 0 || band.height <= 0) return Rect{};
|
||||
double l = level;
|
||||
if (l < 0.0) l = 0.0;
|
||||
if (l > 1.0) l = 1.0;
|
||||
const int fillW = static_cast<int>(l * band.width());
|
||||
const int fillW = static_cast<int>(l * band.width);
|
||||
if (fillW <= 0) return Rect{};
|
||||
return Rect{band.left, band.top, band.left + fillW, band.bottom};
|
||||
return Rect::ltrb(band.x, band.y, band.x + fillW, band.bottom());
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -18,9 +18,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor_geometry.h" // Rect, contains — one shared geometry idiom
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect, contains — one shared geometry idiom
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
// The full MIDI key span the strip maps across its width. 128 keys (0..127); the strip's
|
||||
// horizontal axis is this range, so a zone [lowNote, highNote] becomes a sub-rectangle.
|
||||
@@ -54,7 +54,7 @@ struct EmbedLayout {
|
||||
EmbedLayout layoutEmbed(int w, int h);
|
||||
|
||||
// The horizontal sub-rectangle of the keymap band for a zone spanning [lowNote, highNote]
|
||||
// (inclusive). The 128-key span maps linearly across keymap.width(); the returned rect
|
||||
// (inclusive). The 128-key span maps linearly across keymap.width; the returned rect
|
||||
// spans the half-open pixel range [x(lowNote), x(highNote+1)) so adjacent zones (e.g.
|
||||
// 0..59 and 60..127) tile without a gap or overlap. Notes are clamped to [0,127] and low
|
||||
// is clamped to <= high, so a malformed zone yields an in-band (possibly zero-width) rect,
|
||||
@@ -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::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -1,24 +1,19 @@
|
||||
// envelope_edit.cpp — see envelope_edit.h. Pure inverse map + hit-test; no host types.
|
||||
|
||||
#include "envelope_edit.h"
|
||||
#include "core/instrument/ui/envelope_edit.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib> // std::abs
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
double clamp(double v, double lo, double hi) {
|
||||
if (v < lo) return lo;
|
||||
if (v > hi) return hi;
|
||||
return v;
|
||||
}
|
||||
|
||||
// Seconds represented by one horizontal pixel under the overlay's linear time base. Zero when the
|
||||
// area is degenerate (the caller then produces no motion). Matches envelope_overlay::timeToX.
|
||||
double secondsPerPixel(const Rect& area, double totalSeconds) {
|
||||
const int w = std::max(0, area.width());
|
||||
const int w = std::max(0, area.width);
|
||||
if (w <= 0 || totalSeconds <= 0.0) return 0.0;
|
||||
return totalSeconds / static_cast<double>(w);
|
||||
}
|
||||
@@ -36,7 +31,7 @@ double gateSecondsPerPixel(const Rect& area) {
|
||||
// Level (0..1) represented by one vertical pixel. levelToY spans (height-1) rows for [0,1], so one
|
||||
// pixel is 1/(height-1). Zero when degenerate. Matches envelope_overlay::levelToY.
|
||||
double levelPerPixel(const Rect& area) {
|
||||
const int h = std::max(0, area.height());
|
||||
const int h = std::max(0, area.height);
|
||||
if (h <= 1) return 0.0;
|
||||
return 1.0 / static_cast<double>(h - 1);
|
||||
}
|
||||
@@ -118,23 +113,23 @@ AmpEnvelope resolveNodeDrag(const AmpEnvelope& grabEnv, EnvNode node, const Rect
|
||||
// because every segment stays >= 0), so the [0, max] clamp is the whole constraint.
|
||||
case EnvNode::AttackEnd:
|
||||
out.attackSeconds =
|
||||
clamp(grabEnv.attackSeconds + gateDSec, 0.0, bounds.maxAttackSeconds);
|
||||
std::clamp(grabEnv.attackSeconds + gateDSec, 0.0, bounds.maxAttackSeconds);
|
||||
break;
|
||||
case EnvNode::HoldEnd:
|
||||
out.holdSeconds = clamp(grabEnv.holdSeconds + gateDSec, 0.0, bounds.maxHoldSeconds);
|
||||
out.holdSeconds = std::clamp(grabEnv.holdSeconds + gateDSec, 0.0, bounds.maxHoldSeconds);
|
||||
break;
|
||||
case EnvNode::DecayEnd: {
|
||||
// Sustain node: X sets decay time, Y sets sustain level (drag DOWN = higher y = lower
|
||||
// level, so subtract the level delta).
|
||||
out.decaySeconds = clamp(grabEnv.decaySeconds + gateDSec, 0.0, bounds.maxDecaySeconds);
|
||||
out.decaySeconds = std::clamp(grabEnv.decaySeconds + gateDSec, 0.0, bounds.maxDecaySeconds);
|
||||
const double lvlPerPx = levelPerPixel(area);
|
||||
const double dLevel = -static_cast<double>(dyPixels) * lvlPerPx;
|
||||
out.sustainLevel = clamp(grabEnv.sustainLevel + dLevel, 0.0, 1.0);
|
||||
out.sustainLevel = std::clamp(grabEnv.sustainLevel + dLevel, 0.0, 1.0);
|
||||
break;
|
||||
}
|
||||
case EnvNode::ReleaseEnd:
|
||||
out.releaseSeconds =
|
||||
clamp(grabEnv.releaseSeconds + gateDSec, 0.0, bounds.maxReleaseSeconds);
|
||||
std::clamp(grabEnv.releaseSeconds + gateDSec, 0.0, bounds.maxReleaseSeconds);
|
||||
break;
|
||||
|
||||
// --- Trigger: fades + length are FRACTIONS. X pixels convert to a fraction of the PLAYED
|
||||
@@ -154,7 +149,7 @@ AmpEnvelope resolveNodeDrag(const AmpEnvelope& grabEnv, EnvNode node, const Rect
|
||||
const double dFrac = playSeconds > 0.0 ? dSec / playSeconds : 0.0;
|
||||
const double hi = std::min(bounds.maxFadeInFraction,
|
||||
1.0 - std::max(0.0, grabEnv.fadeOutFraction));
|
||||
out.fadeInFraction = clamp(grabEnv.fadeInFraction + dFrac, 0.0, std::max(0.0, hi));
|
||||
out.fadeInFraction = std::clamp(grabEnv.fadeInFraction + dFrac, 0.0, std::max(0.0, hi));
|
||||
break;
|
||||
}
|
||||
case EnvNode::FadeOutStart: {
|
||||
@@ -165,13 +160,13 @@ AmpEnvelope resolveNodeDrag(const AmpEnvelope& grabEnv, EnvNode node, const Rect
|
||||
const double dFrac = playSeconds > 0.0 ? -dSec / playSeconds : 0.0;
|
||||
const double hi = std::min(bounds.maxFadeOutFraction,
|
||||
1.0 - std::max(0.0, grabEnv.fadeInFraction));
|
||||
out.fadeOutFraction = clamp(grabEnv.fadeOutFraction + dFrac, 0.0, std::max(0.0, hi));
|
||||
out.fadeOutFraction = std::clamp(grabEnv.fadeOutFraction + dFrac, 0.0, std::max(0.0, hi));
|
||||
break;
|
||||
}
|
||||
case EnvNode::LengthEnd: {
|
||||
// LengthEnd sits at lengthFraction of the WHOLE sample; X maps to a fraction of it.
|
||||
const double dFrac = totalSeconds > 0.0 ? dSec / totalSeconds : 0.0;
|
||||
out.lengthFraction = clamp(grabEnv.lengthFraction + dFrac, 0.0, bounds.maxLengthFraction);
|
||||
out.lengthFraction = std::clamp(grabEnv.lengthFraction + dFrac, 0.0, bounds.maxLengthFraction);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -182,4 +177,4 @@ AmpEnvelope resolveNodeDrag(const AmpEnvelope& grabEnv, EnvNode node, const Rect
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -42,10 +42,10 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "editor_geometry.h" // Rect
|
||||
#include "envelope_overlay.h" // EnvNode, EnvMode, AmpEnvelope, EnvVertex, timeToX/levelToY
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect
|
||||
#include "core/instrument/ui/envelope_overlay.h" // EnvNode, EnvMode, AmpEnvelope, EnvVertex, timeToX/levelToY
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
// The pick radius (px) around a node's drawn point: a grab within this many pixels (in BOTH x and
|
||||
// y) of a node handle grabs it. Mirrors waveform_view's kMarkerGrabWidth — wide enough to grab a
|
||||
@@ -105,4 +105,4 @@ AmpEnvelope resolveNodeDrag(const AmpEnvelope& grabEnv, EnvNode node, const Rect
|
||||
double totalSeconds, const EnvClampBounds& bounds,
|
||||
int dxPixels, int dyPixels);
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -1,25 +1,29 @@
|
||||
// envelope_overlay.cpp — see envelope_overlay.h. Pure geometry; no host types.
|
||||
|
||||
#include "envelope_overlay.h"
|
||||
#include "core/instrument/ui/envelope_overlay.h"
|
||||
|
||||
#include "core/util/clamp01.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
using util::clamp01; // the ONE unit-interval clamp (Q-W1, T4-24)
|
||||
|
||||
int timeToX(const Rect& area, double totalSeconds, double t) {
|
||||
const int w = std::max(0, area.width());
|
||||
if (w <= 0 || totalSeconds <= 0.0) return area.left;
|
||||
const int w = std::max(0, area.width);
|
||||
if (w <= 0 || totalSeconds <= 0.0) return area.x;
|
||||
if (t < 0.0) t = 0.0;
|
||||
// Linear map, clamped on BOTH sides (FA2 bounds invariant): t past totalSeconds pins to the
|
||||
// last in-bounds column area.right-1. Clamp in DOUBLE space BEFORE the integer cast — a huge
|
||||
// last in-bounds column area.right()-1. Clamp in DOUBLE space BEFORE the integer cast — a huge
|
||||
// t would overflow a 32-bit long (Windows) and wrap to the WRONG edge — then round.
|
||||
double px = (t / totalSeconds) * static_cast<double>(w);
|
||||
if (px > static_cast<double>(w - 1)) px = static_cast<double>(w - 1);
|
||||
return area.left + static_cast<int>(px + 0.5);
|
||||
return area.x + static_cast<int>(px + 0.5);
|
||||
}
|
||||
|
||||
int gateTimedWidth(const Rect& area) {
|
||||
const int w = std::max(0, area.width());
|
||||
const int w = std::max(0, area.width);
|
||||
if (w <= 0) return 0;
|
||||
const int sustainPx =
|
||||
static_cast<int>(kGateSustainDisplayFraction * static_cast<double>(w) + 0.5);
|
||||
@@ -38,24 +42,19 @@ double gatePxPerSecond(const Rect& area) {
|
||||
}
|
||||
|
||||
int levelToY(const Rect& area, double level) {
|
||||
const int h = std::max(0, area.height());
|
||||
if (h <= 0) return area.top;
|
||||
const int h = std::max(0, area.height);
|
||||
if (h <= 0) return area.y;
|
||||
if (level < 0.0) level = 0.0;
|
||||
if (level > 1.0) level = 1.0;
|
||||
// Level 1 -> top row, level 0 -> bottom row (bottom-1 under the half-open convention). The
|
||||
// range spans (h-1) pixels so both endpoints land ON a drawable row.
|
||||
const int span = h - 1;
|
||||
const long dy = static_cast<long>((1.0 - level) * static_cast<double>(span) + 0.5);
|
||||
return area.top + static_cast<int>(dy);
|
||||
return area.y + static_cast<int>(dy);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
double clamp01(double v) {
|
||||
if (v < 0.0) return 0.0;
|
||||
if (v > 1.0) return 1.0;
|
||||
return v;
|
||||
}
|
||||
|
||||
EnvVertex vtx(EnvNode node, const Rect& area, double totalSeconds, double t, double level) {
|
||||
EnvVertex v;
|
||||
@@ -71,12 +70,12 @@ EnvVertex vtx(EnvNode node, const Rect& area, double totalSeconds, double t, dou
|
||||
// DOUBLE space to the last in-bounds column BEFORE the integer cast (FA2 bounds invariant; a
|
||||
// huge px would overflow a 32-bit long on Windows and wrap to the WRONG edge).
|
||||
EnvVertex gateVtx(EnvNode node, const Rect& area, double px, double level) {
|
||||
const int w = std::max(1, area.width());
|
||||
const int w = std::max(1, area.width);
|
||||
if (px < 0.0) px = 0.0;
|
||||
if (px > static_cast<double>(w - 1)) px = static_cast<double>(w - 1);
|
||||
EnvVertex v;
|
||||
v.node = node;
|
||||
v.x = area.left + static_cast<int>(px + 0.5);
|
||||
v.x = area.x + static_cast<int>(px + 0.5);
|
||||
v.y = levelToY(area, level);
|
||||
v.level = level;
|
||||
return v;
|
||||
@@ -95,7 +94,7 @@ std::vector<EnvVertex> gatePolyline(const AmpEnvelope& env, const Rect& area) {
|
||||
// gets a kGateNodeSepPx base so consecutive nodes never coincide (every node individually
|
||||
// grabbable at any params, incl. the tier-0 zero-hold/zero-decay defaults). The sustain
|
||||
// plateau is the fixed reserve between DecayEnd and ReleaseStart.
|
||||
const int W = std::max(1, area.width());
|
||||
const int W = std::max(1, area.width);
|
||||
const double sustainPx = static_cast<double>(W - gateTimedWidth(area));
|
||||
const double sep = static_cast<double>(kGateNodeSepPx);
|
||||
const double pps = gatePxPerSecond(area);
|
||||
@@ -162,7 +161,7 @@ std::vector<EnvVertex> triggerPolyline(const AmpEnvelope& env, const Rect& area,
|
||||
|
||||
std::vector<EnvVertex> buildEnvelopePolyline(const AmpEnvelope& env, const Rect& area,
|
||||
double totalSeconds) {
|
||||
if (area.width() <= 0 || area.height() <= 0 || totalSeconds <= 0.0) {
|
||||
if (area.width <= 0 || area.height <= 0 || totalSeconds <= 0.0) {
|
||||
// Degenerate surface: a two-point flat baseline at level 0 so the shell always has a line.
|
||||
return {vtx(EnvNode::Origin, area, 1.0, 0.0, 0.0),
|
||||
vtx(EnvNode::ReleaseEnd, area, 1.0, 1.0, 0.0)};
|
||||
@@ -173,4 +172,4 @@ std::vector<EnvVertex> buildEnvelopePolyline(const AmpEnvelope& env, const Rect&
|
||||
: triggerPolyline(env, area, totalSeconds);
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -25,7 +25,7 @@
|
||||
// the vertical axis is LEVEL (0 at rect bottom, 1 at rect top).
|
||||
//
|
||||
// BOUNDS INVARIANT (FA2). EVERY vertex of EVERY polyline is clamped inside the canvas:
|
||||
// x in [area.left, area.right-1], y in [area.top, area.bottom-1] (half-open rect convention).
|
||||
// x in [area.x, area.right()-1], y in [area.y, area.bottom()-1] (half-open rect convention).
|
||||
// No node and no drawn segment ever exceeds the canvas — paint-time clipping of handles is no
|
||||
// longer needed (and never fires) in the shell.
|
||||
//
|
||||
@@ -33,8 +33,8 @@
|
||||
// * The EnvNode enum is UNCHANGED (same node set, same draggable set — Origin + ReleaseStart
|
||||
// remain the only non-draggable anchors).
|
||||
// * ALL vertices are now in-bounds (see above). The shell's previous "skip handle when
|
||||
// v.x >= waveArea.right" clip is dead code: ReleaseEnd (Gate) and FadeOutStart/LengthEnd
|
||||
// (Trigger, at full length / zero fade-out) now land at area.right-1 and MUST get handles.
|
||||
// v.x >= waveArea.right()" clip is dead code: ReleaseEnd (Gate) and FadeOutStart/LengthEnd
|
||||
// (Trigger, at full length / zero fade-out) now land at area.right()-1 and MUST get handles.
|
||||
// * Gate's x-axis is SCHEMATIC, not PCM-aligned: the timed region is scaled to the param
|
||||
// domain (4 x kGateStageMaxSeconds), the sustain reserve is a fixed width, and every
|
||||
// segment carries a kGateNodeSepPx pixel base. The Gate curve does NOT line up with the
|
||||
@@ -60,9 +60,9 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "editor_geometry.h" // Rect — the shared geometry idiom
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect — the shared geometry idiom
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
// The play mode the overlay draws — a LOCAL mirror of sampler_core's PlayMode kept here so the
|
||||
// geometry module stays engine-free (the shell maps the zone's PlayMode to this). Same two cases.
|
||||
@@ -173,7 +173,7 @@ inline constexpr int kGateNodeSepPx = 8;
|
||||
// (param clamps are caller-supplied in envelope_edit); only layout does.
|
||||
inline constexpr double kGateStageMaxSeconds = 2.0;
|
||||
|
||||
// The pixel width of the Gate timed region: area.width() minus the sustain-plateau reserve,
|
||||
// The pixel width of the Gate timed region: area.width minus the sustain-plateau reserve,
|
||||
// floored at 1 px so the px<->seconds scale never degenerates for a non-empty area. Returns 0
|
||||
// for a zero/negative-width area. Shared by gatePolyline and envelope_edit's gate drag scale.
|
||||
int gateTimedWidth(const Rect& area);
|
||||
@@ -188,7 +188,7 @@ double gatePxPerSecond(const Rect& area);
|
||||
|
||||
// Map an amp envelope to its polyline vertices inside `area`, over a sample of `totalSeconds`
|
||||
// wall-clock duration. `area` is the waveform rect (left/top inclusive, right/bottom exclusive);
|
||||
// y maps level 0..1 across [area.bottom-1 .. area.top] (level 1 at the TOP). The polyline reads
|
||||
// y maps level 0..1 across [area.bottom()-1 .. area.y] (level 1 at the TOP). The polyline reads
|
||||
// left-to-right in draw order, Origin first.
|
||||
//
|
||||
// TIME BASE (FA2).
|
||||
@@ -205,25 +205,25 @@ double gatePxPerSecond(const Rect& area);
|
||||
// lengthFraction * totalSeconds; fade-in/out are fractions OF that played span. Nodes past
|
||||
// the played span never appear (FadeOutStart/LengthEnd sit at the played span's right edge).
|
||||
//
|
||||
// BOUNDS: every vertex is inside the canvas — x in [area.left, area.right-1], y in
|
||||
// [area.top, area.bottom-1]. Nothing maps past area.right (the pre-FA2 release tail is gone). A
|
||||
// BOUNDS: every vertex is inside the canvas — x in [area.x, area.right()-1], y in
|
||||
// [area.y, area.bottom()-1]. Nothing maps past area.right() (the pre-FA2 release tail is gone). A
|
||||
// degenerate area (zero width/height) or totalSeconds <= 0 yields the two-point flat baseline
|
||||
// [Origin, end at level 0] so the shell always has a drawable line. Pure — same inputs, same
|
||||
// polyline.
|
||||
std::vector<EnvVertex> buildEnvelopePolyline(const AmpEnvelope& env, const Rect& area,
|
||||
double totalSeconds);
|
||||
|
||||
// Map a time (seconds) to a pixel x inside `area`: t=0 -> area.left, t=totalSeconds ->
|
||||
// area.right-1, linear, CLAMPED on both sides (t < 0 pins to area.left; t past totalSeconds pins
|
||||
// to area.right-1 — the in-bounds invariant, FA2). A zero-width area or totalSeconds <= 0 yields
|
||||
// area.left. Pure — the shared time->x map the Trigger polyline and the node hit-test
|
||||
// Map a time (seconds) to a pixel x inside `area`: t=0 -> area.x, t=totalSeconds ->
|
||||
// area.right()-1, linear, CLAMPED on both sides (t < 0 pins to area.x; t past totalSeconds pins
|
||||
// to area.right()-1 — the in-bounds invariant, FA2). A zero-width area or totalSeconds <= 0 yields
|
||||
// area.x. Pure — the shared time->x map the Trigger polyline and the node hit-test
|
||||
// (envelope_edit) use, so the drawn handle and its grab region agree.
|
||||
int timeToX(const Rect& area, double totalSeconds, double t);
|
||||
|
||||
// Map a level (0..1) to a pixel y inside `area`: level 1 -> area.top, level 0 -> area.bottom-1
|
||||
// Map a level (0..1) to a pixel y inside `area`: level 1 -> area.y, level 0 -> area.bottom()-1
|
||||
// (so the full-amplitude line sits at the top edge and silence at the bottom pixel row). level is
|
||||
// clamped to [0,1]. A zero-height area yields area.top. Pure — the shared level->y map the polyline
|
||||
// clamped to [0,1]. A zero-height area yields area.y. Pure — the shared level->y map the polyline
|
||||
// and the node hit-test share.
|
||||
int levelToY(const Rect& area, double level);
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -1,10 +1,10 @@
|
||||
// keyboard_strip.cpp — see keyboard_strip.h. Pure math; no host types.
|
||||
|
||||
#include "keyboard_strip.h"
|
||||
#include "core/instrument/ui/keyboard_strip.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -30,24 +30,24 @@ StripLayout layoutStrip(int w, int h) {
|
||||
const int cw = std::max(0, w);
|
||||
const int ch = std::max(0, h);
|
||||
StripLayout out;
|
||||
out.keys = Rect{0, 0, cw, ch};
|
||||
out.keys = Rect::ltrb(0, 0, cw, ch);
|
||||
return out;
|
||||
}
|
||||
|
||||
int keyLeftX(const StripLayout& layout, int note) {
|
||||
const Rect& band = layout.keys;
|
||||
const int bandWidth = std::max(0, band.width());
|
||||
const int bandWidth = std::max(0, band.width);
|
||||
// note is a KEY here (0..127); its left edge is boundary `note`. Callers pass note+1 to
|
||||
// get a key's right edge, and 128 maps to the band right.
|
||||
const int edge = note < 0 ? 0 : (note > kStripKeyCount ? kStripKeyCount : note);
|
||||
return keyEdgeToX(band.left, bandWidth, edge);
|
||||
return keyEdgeToX(band.x, bandWidth, edge);
|
||||
}
|
||||
|
||||
Rect keyRect(const StripLayout& layout, int note) {
|
||||
const int n = clampNote(note);
|
||||
const int leftX = keyLeftX(layout, n);
|
||||
const int rightX = keyLeftX(layout, n + 1);
|
||||
return Rect{leftX, layout.keys.top, std::max(leftX, rightX), layout.keys.bottom};
|
||||
return Rect::ltrb(leftX, layout.keys.y, std::max(leftX, rightX), layout.keys.bottom());
|
||||
}
|
||||
|
||||
Rect rootMarkerRect(const StripLayout& layout, int rootNote) {
|
||||
@@ -57,11 +57,11 @@ Rect rootMarkerRect(const StripLayout& layout, int rootNote) {
|
||||
int keyAtPoint(const StripLayout& layout, int x, int y) {
|
||||
const Rect& band = layout.keys;
|
||||
if (!contains(band, x, y)) return -1;
|
||||
const int bandWidth = std::max(0, band.width());
|
||||
const int bandWidth = std::max(0, band.width);
|
||||
if (bandWidth <= 0) return -1;
|
||||
// Invert keyEdgeToX: the key whose half-open [leftX, rightX) contains x. Floor-divide
|
||||
// the pixel offset back to a key; clamp defensively (a point on band.right-1 maps to 127).
|
||||
const int offset = x - band.left;
|
||||
// the pixel offset back to a key; clamp defensively (a point on band.right()-1 maps to 127).
|
||||
const int offset = x - band.x;
|
||||
int note = (offset * kStripKeyCount) / bandWidth;
|
||||
return clampNote(note);
|
||||
}
|
||||
@@ -72,22 +72,22 @@ Rect zoneBarRect(const StripLayout& layout, int lowNote, int highNote) {
|
||||
if (lo > hi) lo = hi; // defensive: a malformed zone collapses rather than inverts
|
||||
const int leftX = keyLeftX(layout, lo);
|
||||
const int rightX = keyLeftX(layout, hi + 1);
|
||||
return Rect{leftX, layout.keys.top, std::max(leftX, rightX), layout.keys.bottom};
|
||||
return Rect::ltrb(leftX, layout.keys.y, std::max(leftX, rightX), layout.keys.bottom());
|
||||
}
|
||||
|
||||
ZoneGrab zoneGrabAt(const StripLayout& layout, int lowNote, int highNote, int x, int y) {
|
||||
const Rect bar = zoneBarRect(layout, lowNote, highNote);
|
||||
if (!contains(bar, x, y)) return ZoneGrab::kNone;
|
||||
|
||||
const int barW = bar.width();
|
||||
const int barW = bar.width;
|
||||
// A narrow bar (< 2*edge) has no body: split at the midpoint, LOW edge wins the tie so
|
||||
// a click exactly on the midpoint resizes low (deterministic).
|
||||
if (barW < 2 * kStripEdgeGrabWidth) {
|
||||
const int mid = bar.left + barW / 2;
|
||||
const int mid = bar.x + barW / 2;
|
||||
return x <= mid ? ZoneGrab::kLowEdge : ZoneGrab::kHighEdge;
|
||||
}
|
||||
if (x < bar.left + kStripEdgeGrabWidth) return ZoneGrab::kLowEdge;
|
||||
if (x >= bar.right - kStripEdgeGrabWidth) return ZoneGrab::kHighEdge;
|
||||
if (x < bar.x + kStripEdgeGrabWidth) return ZoneGrab::kLowEdge;
|
||||
if (x >= bar.right() - kStripEdgeGrabWidth) return ZoneGrab::kHighEdge;
|
||||
return ZoneGrab::kBody;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ bool isNaturalKey(int note) {
|
||||
|
||||
int resolveDragNote(const StripLayout& layout, int startNote, int dxPixels) {
|
||||
if (dxPixels == 0) return clampNote(startNote);
|
||||
const int bandWidth = std::max(0, layout.keys.width());
|
||||
const int bandWidth = std::max(0, layout.keys.width);
|
||||
if (bandWidth <= 0) return clampNote(startNote); // zero-width -> no motion
|
||||
// Proportional shift: same linear mapping as keyAtPoint/keyEdgeToX so click and drag
|
||||
// agree across the full strip, even on non-divisible-by-128 widths. The proportional
|
||||
@@ -146,4 +146,4 @@ int resolveDragNote(const StripLayout& layout, int startNote, int dxPixels) {
|
||||
return clampNote(startNote + shift);
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "editor_geometry.h" // Rect, contains — one shared geometry idiom
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect, contains — one shared geometry idiom
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
// The full MIDI key span the strip maps across its width: 128 keys (0..127). Named
|
||||
// distinctly from embed_strip's kEmbedKeyCount (same value) so the two strips stay
|
||||
@@ -42,7 +42,7 @@ inline constexpr int kStripEdgeGrabWidth = 6;
|
||||
// takes the whole area today (a future octave-label lane can carve a sub-band here without
|
||||
// changing callers). Clamped so a degenerate (tiny/zero) size never yields an inverted rect.
|
||||
struct StripLayout {
|
||||
Rect keys; // the key band: the 128-key span maps linearly across keys.width()
|
||||
Rect keys; // the key band: the 128-key span maps linearly across keys.width
|
||||
};
|
||||
|
||||
// Divide a (w x h) strip area into its regions. Pure: same inputs -> same layout. A zero or
|
||||
@@ -50,7 +50,7 @@ struct StripLayout {
|
||||
StripLayout layoutStrip(int w, int h);
|
||||
|
||||
// The x pixel (inside the keys band) of the LEFT edge of key `note` (0..127). The 128-key
|
||||
// span maps linearly across keys.width(); key N occupies the half-open pixel range
|
||||
// span maps linearly across keys.width; key N occupies the half-open pixel range
|
||||
// [keyLeftX(N), keyLeftX(N+1)). Notes are clamped to [0,127]; note==128 maps to the band's
|
||||
// right edge (so a key's right edge is keyLeftX(note+1)). Pure.
|
||||
int keyLeftX(const StripLayout& layout, int note);
|
||||
@@ -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::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -1,10 +1,10 @@
|
||||
// knob_deck.cpp — see knob_deck.h. Pure arithmetic; no LICE/VST3/REAPER includes.
|
||||
|
||||
#include "knob_deck.h"
|
||||
#include "core/instrument/ui/knob_deck.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -33,19 +33,20 @@ DeckGroupLayout layoutGroup(const DeckGroupDesc& g, const Rect& box) {
|
||||
out.id = g.id;
|
||||
out.box = box;
|
||||
|
||||
const int captionTop = box.top + kDeckGroupPadY;
|
||||
const int innerLeft = box.left + kDeckGroupPadX;
|
||||
const int innerRight = box.right - kDeckGroupPadX;
|
||||
const int captionTop = box.y + kDeckGroupPadY;
|
||||
const int innerLeft = box.x + kDeckGroupPadX;
|
||||
const int innerRight = box.right() - kDeckGroupPadX;
|
||||
|
||||
// Caption row: text left, compact toggle right-anchored (r11 — the not-full-width home).
|
||||
out.caption = Rect{innerLeft, captionTop, innerRight, captionTop + kDeckCaptionH};
|
||||
out.caption = Rect::ltrb(innerLeft, captionTop, innerRight, captionTop + kDeckCaptionH);
|
||||
if (g.captionToggle.id >= 0) {
|
||||
const int segW = g.captionToggle.segWidth;
|
||||
const int togTop = captionTop + (kDeckCaptionH - kDeckToggleH) / 2;
|
||||
const Rect seg1{innerRight - segW, togTop, innerRight, togTop + kDeckToggleH};
|
||||
const Rect seg0{seg1.left - segW, togTop, seg1.left, togTop + kDeckToggleH};
|
||||
const Rect seg1 = Rect::ltrb(innerRight - segW, togTop, innerRight, togTop + kDeckToggleH);
|
||||
const Rect seg0 = Rect::ltrb(seg1.x - segW, togTop, seg1.x, togTop + kDeckToggleH);
|
||||
out.captionToggle = DeckToggleLayout{g.captionToggle.id, seg0, seg1};
|
||||
out.caption.right = seg0.left - kDeckToggleGap; // caption text stops at the toggle
|
||||
// Caption text stops at the toggle: pull the right edge in (XYWH: shrink width).
|
||||
out.caption.width = (seg0.x - kDeckToggleGap) - out.caption.x;
|
||||
}
|
||||
|
||||
// Knob row: fixed cells left-to-right, then the optional row toggle.
|
||||
@@ -54,12 +55,12 @@ DeckGroupLayout layoutGroup(const DeckGroupDesc& g, const Rect& box) {
|
||||
for (int id : g.cellIds) {
|
||||
DeckCellLayout c;
|
||||
c.id = id;
|
||||
c.cell = Rect{x, cellTop, x + kDeckCellW, cellTop + kDeckCellH};
|
||||
c.cell = Rect::ltrb(x, cellTop, x + kDeckCellW, cellTop + kDeckCellH);
|
||||
const int knobLeft = x + (kDeckCellW - kDeckKnobSize) / 2;
|
||||
const int knobTop = cellTop + 4;
|
||||
c.knob = Rect{knobLeft, knobTop, knobLeft + kDeckKnobSize, knobTop + kDeckKnobSize};
|
||||
c.knob = Rect::ltrb(knobLeft, knobTop, knobLeft + kDeckKnobSize, knobTop + kDeckKnobSize);
|
||||
const int labelTop = knobTop + kDeckKnobSize + 4;
|
||||
c.label = Rect{c.cell.left, labelTop, c.cell.right, labelTop + kDeckCellLabelH};
|
||||
c.label = Rect::ltrb(c.cell.x, labelTop, c.cell.right(), labelTop + kDeckCellLabelH);
|
||||
out.cells.push_back(c);
|
||||
x += kDeckCellW;
|
||||
}
|
||||
@@ -67,8 +68,8 @@ DeckGroupLayout layoutGroup(const DeckGroupDesc& g, const Rect& box) {
|
||||
if (!g.cellIds.empty()) x += kDeckToggleGap;
|
||||
const int segW = g.rowToggle.segWidth;
|
||||
const int togTop = cellTop + (kDeckCellH - kDeckToggleH) / 2;
|
||||
const Rect seg0{x, togTop, x + segW, togTop + kDeckToggleH};
|
||||
const Rect seg1{seg0.right, togTop, seg0.right + segW, togTop + kDeckToggleH};
|
||||
const Rect seg0 = Rect::ltrb(x, togTop, x + segW, togTop + kDeckToggleH);
|
||||
const Rect seg1 = Rect::ltrb(seg0.right(), togTop, seg0.right() + segW, togTop + kDeckToggleH);
|
||||
out.rowToggle = DeckToggleLayout{g.rowToggle.id, seg0, seg1};
|
||||
}
|
||||
return out;
|
||||
@@ -120,9 +121,9 @@ DeckLayout layoutDeck(const std::vector<DeckGroupDesc>& groups, int left, int to
|
||||
rowHasGroup = false;
|
||||
}
|
||||
if (rowHasGroup) x += kDeckGroupGap;
|
||||
const Rect box{x, y, x + w, y + kDeckGroupH};
|
||||
const Rect box = Rect::ltrb(x, y, x + w, y + kDeckGroupH);
|
||||
out.groups.push_back(layoutGroup(g, box));
|
||||
x = box.right;
|
||||
x = box.right();
|
||||
rowHasGroup = true;
|
||||
}
|
||||
out.height = out.rowCount * kDeckGroupH + (out.rowCount - 1) * kDeckRowGap;
|
||||
@@ -152,4 +153,4 @@ DeckHit hitTestDeck(const DeckLayout& layout, int x, int y) {
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -27,9 +27,9 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "editor_geometry.h" // Rect, contains — the shared geometry idiom
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect, contains — the shared geometry idiom
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
// Fixed deck metrics (spec r11), exposed so the shell and tests agree.
|
||||
inline constexpr int kDeckCellW = 48; // one knob cell
|
||||
@@ -130,4 +130,4 @@ struct DeckHit {
|
||||
// Pure — the shell's routing entry point.
|
||||
DeckHit hitTestDeck(const DeckLayout& layout, int x, int y);
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -1,30 +1,34 @@
|
||||
// param_slider.cpp — see param_slider.h. PURE control-surface geometry for the S12/S15/S16
|
||||
// editor parameter panel. No host types; only the shared Rect + contains().
|
||||
|
||||
#include "param_slider.h"
|
||||
#include "core/instrument/ui/param_slider.h"
|
||||
|
||||
#include "core/util/clamp01.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
using util::clamp01; // the ONE unit-interval clamp (Q-W1, T4-24)
|
||||
|
||||
std::vector<ControlRow> layoutControls(const Rect& panel,
|
||||
const std::vector<ControlDesc>& controls) {
|
||||
std::vector<ControlRow> out;
|
||||
if (controls.empty() || panel.width() <= 0 || panel.height() <= 0) return out;
|
||||
if (controls.empty() || panel.width <= 0 || panel.height <= 0) return out;
|
||||
out.reserve(controls.size());
|
||||
|
||||
// The label column is clamped so a narrow panel still leaves a control column.
|
||||
const int labelW = (std::min)(kControlLabelWidth, (std::max)(0, panel.width() / 2));
|
||||
int rowTop = panel.top;
|
||||
const int labelW = (std::min)(kControlLabelWidth, (std::max)(0, panel.width / 2));
|
||||
int rowTop = panel.y;
|
||||
for (const ControlDesc& d : controls) {
|
||||
ControlRow r;
|
||||
r.id = d.id;
|
||||
r.kind = d.kind;
|
||||
const int rowBottom = rowTop + kControlRowHeight;
|
||||
r.row = Rect{panel.left, rowTop, panel.right, rowBottom};
|
||||
r.label = Rect{panel.left, rowTop, panel.left + labelW, rowBottom};
|
||||
r.control = Rect{panel.left + labelW, rowTop, panel.right, rowBottom};
|
||||
r.row = Rect::ltrb(panel.x, rowTop, panel.right(), rowBottom);
|
||||
r.label = Rect::ltrb(panel.x, rowTop, panel.x + labelW, rowBottom);
|
||||
r.control = Rect::ltrb(panel.x + labelW, rowTop, panel.right(), rowBottom);
|
||||
out.push_back(r);
|
||||
rowTop = rowBottom + kControlRowGap;
|
||||
}
|
||||
@@ -33,13 +37,13 @@ std::vector<ControlRow> layoutControls(const Rect& panel,
|
||||
|
||||
Rect toggleSegmentRect(const Rect& control, int seg) {
|
||||
if (seg < 0 || seg >= kToggleSegments) return Rect{};
|
||||
const int w = control.width();
|
||||
if (w <= 0 || control.height() <= 0) return Rect{};
|
||||
const int w = control.width;
|
||||
if (w <= 0 || control.height <= 0) return Rect{};
|
||||
const int segW = w / kToggleSegments;
|
||||
const int left = control.left + seg * segW;
|
||||
const int left = control.x + seg * segW;
|
||||
// The last segment absorbs the width remainder so the segments tile the whole control.
|
||||
const int right = (seg == kToggleSegments - 1) ? control.right : left + segW;
|
||||
return Rect{left, control.top, right, control.bottom};
|
||||
const int right = (seg == kToggleSegments - 1) ? control.right() : left + segW;
|
||||
return Rect::ltrb(left, control.y, right, control.bottom());
|
||||
}
|
||||
|
||||
int toggleSegmentHitTest(const Rect& control, int x, int y) {
|
||||
@@ -52,31 +56,31 @@ int toggleSegmentHitTest(const Rect& control, int x, int y) {
|
||||
|
||||
Rect sliderTrackRect(const Rect& control) {
|
||||
// Inset a half-handle at each end so the handle stays fully inside the control at value
|
||||
// 0 and 1. The handle CENTER ranges across [track.left, track.right].
|
||||
// 0 and 1. The handle CENTER ranges across [track.x, track.right()].
|
||||
const int half = kSliderHandleWidth / 2;
|
||||
if (control.width() <= kSliderHandleWidth || control.height() <= 0) return Rect{};
|
||||
return Rect{control.left + half, control.top, control.right - half, control.bottom};
|
||||
if (control.width <= kSliderHandleWidth || control.height <= 0) return Rect{};
|
||||
return Rect::ltrb(control.x + half, control.y, control.right() - half, control.bottom());
|
||||
}
|
||||
|
||||
Rect sliderHandleRect(const Rect& control, double value) {
|
||||
const Rect track = sliderTrackRect(control);
|
||||
if (track.width() <= 0) return Rect{};
|
||||
if (track.width <= 0) return Rect{};
|
||||
if (value < 0.0) value = 0.0;
|
||||
if (value > 1.0) value = 1.0;
|
||||
const int span = track.width(); // handle-center movable span
|
||||
const int centerX = track.left + static_cast<int>(value * span + 0.5);
|
||||
const int span = track.width; // handle-center movable span
|
||||
const int centerX = track.x + static_cast<int>(value * span + 0.5);
|
||||
const int half = kSliderHandleWidth / 2;
|
||||
return Rect{centerX - half, control.top, centerX - half + kSliderHandleWidth,
|
||||
control.bottom};
|
||||
return Rect::ltrb(centerX - half, control.y, centerX - half + kSliderHandleWidth,
|
||||
control.bottom());
|
||||
}
|
||||
|
||||
double valueAtPoint(const Rect& control, int x) {
|
||||
const Rect track = sliderTrackRect(control);
|
||||
const int span = track.width();
|
||||
const int span = track.width;
|
||||
if (span <= 0) return 0.0;
|
||||
if (x <= track.left) return 0.0;
|
||||
if (x >= track.right) return 1.0;
|
||||
return static_cast<double>(x - track.left) / static_cast<double>(span);
|
||||
if (x <= track.x) return 0.0;
|
||||
if (x >= track.right()) return 1.0;
|
||||
return static_cast<double>(x - track.x) / static_cast<double>(span);
|
||||
}
|
||||
|
||||
// --- Radial knob (Wave A FA4) ---------------------------------------------------------
|
||||
@@ -95,16 +99,15 @@ double normDeg(double deg) {
|
||||
return deg;
|
||||
}
|
||||
|
||||
double clamp01(double v) { return (std::min)(1.0, (std::max)(0.0, v)); }
|
||||
|
||||
} // namespace
|
||||
|
||||
KnobGeometry computeKnob(const Rect& cell) {
|
||||
if (cell.width() <= 0 || cell.height() <= 0) return KnobGeometry{};
|
||||
if (cell.width <= 0 || cell.height <= 0) return KnobGeometry{};
|
||||
KnobGeometry g;
|
||||
g.centerX = (cell.left + cell.right) / 2.0;
|
||||
g.centerY = (cell.top + cell.bottom) / 2.0;
|
||||
g.radius = (std::min)(cell.width(), cell.height()) / 2.0;
|
||||
g.centerX = (cell.x + cell.right()) / 2.0;
|
||||
g.centerY = (cell.y + cell.bottom()) / 2.0;
|
||||
g.radius = (std::min)(cell.width, cell.height) / 2.0;
|
||||
return g;
|
||||
}
|
||||
|
||||
@@ -154,4 +157,4 @@ int controlAtPoint(const std::vector<ControlRow>& rows, int x, int y) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -24,9 +24,9 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "editor_geometry.h" // Rect, contains — one shared geometry idiom
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect, contains — one shared geometry idiom
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
// Fixed control-panel metrics, exposed so the shell and tests agree.
|
||||
inline constexpr int kControlRowHeight = 22; // one control row (incl. its inter-row gap)
|
||||
@@ -81,7 +81,7 @@ int toggleSegmentHitTest(const Rect& control, int x, int y);
|
||||
|
||||
// The slider track sub-rect inside a slider control's `control` rect: the control inset so the
|
||||
// handle (kSliderHandleWidth) stays fully within the control at value 0 and 1 (a half-handle
|
||||
// margin at each end). The handle CENTER ranges across [track.left, track.right] as the value
|
||||
// margin at each end). The handle CENTER ranges across [track.x, track.right()] as the value
|
||||
// ranges [0,1]. The shell draws the track fill + handle here. A degenerate control yields an
|
||||
// empty rect. Pure.
|
||||
Rect sliderTrackRect(const Rect& control);
|
||||
@@ -177,4 +177,4 @@ double knobDragValue(double startValue, int dyPixels,
|
||||
// toggleSegmentHitTest / knobDragValue over the ensuing drag) and commits.
|
||||
int controlAtPoint(const std::vector<ControlRow>& rows, int x, int y);
|
||||
|
||||
} // namespace reasampler::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -1,11 +1,11 @@
|
||||
// waveform_view.cpp — see waveform_view.h. Pure math; no host types.
|
||||
|
||||
#include "waveform_view.h"
|
||||
#include "core/instrument/ui/waveform_view.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib> // std::abs (int overload)
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -18,21 +18,21 @@ std::int64_t clampFrame(std::int64_t f, std::int64_t frameCount) {
|
||||
} // namespace
|
||||
|
||||
int frameToX(const Rect& area, std::int64_t frameCount, std::int64_t frame) {
|
||||
const int w = std::max(0, area.width());
|
||||
if (frameCount <= 0 || w <= 0) return area.left;
|
||||
const int w = std::max(0, area.width);
|
||||
if (frameCount <= 0 || w <= 0) return area.x;
|
||||
const std::int64_t f = clampFrame(frame, frameCount);
|
||||
// Linear map: x = left + round(f * w / frameCount). Rounding keeps the marker line
|
||||
// visually centered on its frame; the divide is exact rational (multiply first).
|
||||
const std::int64_t num = f * static_cast<std::int64_t>(w) + frameCount / 2;
|
||||
return area.left + static_cast<int>(num / frameCount);
|
||||
return area.x + static_cast<int>(num / frameCount);
|
||||
}
|
||||
|
||||
std::int64_t xToFrame(const Rect& area, std::int64_t frameCount, int x) {
|
||||
const int w = std::max(0, area.width());
|
||||
const int w = std::max(0, area.width);
|
||||
if (frameCount <= 0 || w <= 0) return 0;
|
||||
if (x <= area.left) return 0;
|
||||
if (x >= area.right) return frameCount;
|
||||
const std::int64_t dx = static_cast<std::int64_t>(x - area.left);
|
||||
if (x <= area.x) return 0;
|
||||
if (x >= area.right()) return frameCount;
|
||||
const std::int64_t dx = static_cast<std::int64_t>(x - area.x);
|
||||
// Inverse of frameToX: frame = round(dx * frameCount / w). Round so click and marker draw
|
||||
// agree at bin granularity.
|
||||
const std::int64_t num = dx * frameCount + static_cast<std::int64_t>(w) / 2;
|
||||
@@ -54,7 +54,7 @@ std::int64_t resolveDragFrame(const Rect& area, std::int64_t frameCount, std::in
|
||||
int dxPixels) {
|
||||
const std::int64_t start = clampFrame(startFrame, frameCount);
|
||||
if (dxPixels == 0) return start;
|
||||
const int w = std::max(0, area.width());
|
||||
const int w = std::max(0, area.width);
|
||||
if (frameCount <= 0 || w <= 0) return start; // no room to move
|
||||
// Proportional shift, rounded to the nearest frame (same linear map as frameToX/xToFrame).
|
||||
const std::int64_t magnitude =
|
||||
@@ -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::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -24,10 +24,12 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "editor_geometry.h" // Rect, contains — one shared geometry idiom
|
||||
#include "peaks.h" // AudioSample (float), the mono PCM the snap scans
|
||||
#include "core/instrument/ui/editor_geometry.h" // Rect, contains — one shared geometry idiom
|
||||
#include "core/audio/peaks.h" // AudioSample (float), the mono PCM the snap scans
|
||||
|
||||
namespace reasampler::vst {
|
||||
namespace reasampler::instrument::ui {
|
||||
|
||||
using audio::AudioSample;
|
||||
|
||||
// The width (px) of a marker's grab region either side of its x line: a grab within this many
|
||||
// pixels of a marker's drawn x is a grab OF that marker. Mirrors keyboard_strip's edge-grab
|
||||
@@ -35,14 +37,14 @@ namespace reasampler::vst {
|
||||
// distinguishable.
|
||||
inline constexpr int kMarkerGrabWidth = 5;
|
||||
|
||||
// The x pixel (inside `area`) of frame `frame` under the linear map: frame 0 -> area.left,
|
||||
// frame frameCount -> area.right. A frame is clamped to [0, frameCount] before mapping, so an
|
||||
// The x pixel (inside `area`) of frame `frame` under the linear map: frame 0 -> area.x,
|
||||
// frame frameCount -> area.right(). A frame is clamped to [0, frameCount] before mapping, so an
|
||||
// out-of-range frame pins to an edge rather than escaping the rect. frameCount <= 0 or a
|
||||
// zero-width area pins every frame to area.left (a degenerate, non-inverting result). Pure.
|
||||
// zero-width area pins every frame to area.x (a degenerate, non-inverting result). Pure.
|
||||
int frameToX(const Rect& area, std::int64_t frameCount, std::int64_t frame);
|
||||
|
||||
// The frame a point x (inside `area`) maps to under the inverse linear map, clamped to
|
||||
// [0, frameCount]. A point left of area.left yields 0; right of area.right yields frameCount.
|
||||
// [0, frameCount]. A point left of area.x yields 0; right of area.right() yields frameCount.
|
||||
// frameCount <= 0 or a zero-width area yields 0. Pure — the inverse of frameToX (round-trips
|
||||
// to the same frame at bin granularity).
|
||||
std::int64_t xToFrame(const Rect& area, std::int64_t frameCount, int x);
|
||||
@@ -80,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::vst
|
||||
} // namespace reasampler::instrument::ui
|
||||
@@ -138,7 +138,7 @@ public:
|
||||
|
||||
// Captures the raw source text of one value verbatim (string-aware brace
|
||||
// matching), so a nested blob can be handed to its own parser — the
|
||||
// bank_book -> BankIndex::deserialize seam.
|
||||
// bank_book -> BankModel::deserialize seam.
|
||||
bool captureValue(std::string& raw);
|
||||
|
||||
private:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "bank_book.h"
|
||||
#include "core/model/bank_book.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
@@ -9,136 +9,16 @@
|
||||
//
|
||||
// JSON rides on the shared core/json lexical layer (Q-W1), matching bank_model
|
||||
// and view_mode_model. The book blob nests one bank object per bank, each carrying that
|
||||
// bank's BankIndex serialized by bank_model's OWN writer (BankIndex::serialize),
|
||||
// bank's BankModel serialized by bank_model's OWN writer (BankModel::serialize),
|
||||
// so per-bank sample serialization stays owned by bank_model and is not duplicated
|
||||
// here. The book writer emits the bank envelope (id / displayName / ordinal) plus a
|
||||
// raw "index" member whose value is the BankIndex blob verbatim; the parser splits
|
||||
// raw "index" member whose value is the BankModel blob verbatim; the parser splits
|
||||
// the book envelope, then hands each nested index blob straight to
|
||||
// BankIndex::deserialize. Ints use %d; strings are escaped by writeEscaped.
|
||||
// BankModel::deserialize. Ints use %d; strings are escaped by writeEscaped.
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
// ===========================================================================
|
||||
// SlotMap — the L7 gap-preserving display-position carrier (pure). See bank_book.h.
|
||||
// The invariant: entries_ is kept sorted ascending by slot, one id per slot, one
|
||||
// slot per id. Every mutator restores it; queries assume it.
|
||||
// ===========================================================================
|
||||
|
||||
void SlotMap::sortBySlot() {
|
||||
std::stable_sort(entries_.begin(), entries_.end(),
|
||||
[](const Entry& a, const Entry& b) { return a.slot < b.slot; });
|
||||
}
|
||||
|
||||
int SlotMap::slotOf(const std::string& id) const {
|
||||
for (const auto& e : entries_)
|
||||
if (e.id == id) return e.slot;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string SlotMap::idAt(int slot) const {
|
||||
for (const auto& e : entries_)
|
||||
if (e.slot == slot) return e.id;
|
||||
return {};
|
||||
}
|
||||
|
||||
int SlotMap::maxSlot() const {
|
||||
int m = -1;
|
||||
for (const auto& e : entries_)
|
||||
if (e.slot > m) m = e.slot;
|
||||
return m;
|
||||
}
|
||||
|
||||
std::vector<std::string> SlotMap::orderedIds() const {
|
||||
// entries_ is sorted ascending by slot, so a straight walk is display order.
|
||||
std::vector<std::string> out;
|
||||
out.reserve(entries_.size());
|
||||
for (const auto& e : entries_) out.push_back(e.id);
|
||||
return out;
|
||||
}
|
||||
|
||||
void SlotMap::append(const std::string& id) {
|
||||
if (id.empty()) return;
|
||||
remove(id); // an existing id is re-appended, not left in place
|
||||
entries_.push_back(Entry{id, maxSlot() + 1}); // next free slot after the last occupied
|
||||
sortBySlot();
|
||||
}
|
||||
|
||||
bool SlotMap::remove(const std::string& id) {
|
||||
for (auto it = entries_.begin(); it != entries_.end(); ++it) {
|
||||
if (it->id == id) {
|
||||
entries_.erase(it); // leaves the slot empty — no re-pack
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SlotMap::reorder(const std::string& id, int targetSlot) {
|
||||
if (slotOf(id) < 0) return false; // not mapped -> no mutation
|
||||
if (targetSlot < 0) targetSlot = 0;
|
||||
if (slotOf(id) == targetSlot) return false; // already there — true no-op
|
||||
|
||||
// Detach the moving id first so the occupancy test below sees the post-move world.
|
||||
remove(id);
|
||||
|
||||
const bool occupied = !idAt(targetSlot).empty();
|
||||
if (occupied) {
|
||||
// Insert-before-and-shift: every occupant at slot >= targetSlot shifts up by one,
|
||||
// preserving relative order and interior gaps above the target. The moving id then
|
||||
// takes targetSlot cleanly.
|
||||
for (auto& e : entries_)
|
||||
if (e.slot >= targetSlot) ++e.slot;
|
||||
}
|
||||
entries_.push_back(Entry{id, targetSlot});
|
||||
sortBySlot();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SlotMap::resetDense(const std::vector<std::string>& ids) {
|
||||
entries_.clear();
|
||||
int slot = 0;
|
||||
for (const auto& id : ids) {
|
||||
if (id.empty()) continue;
|
||||
if (slotOf(id) >= 0) continue; // skip a duplicate id (one slot per id)
|
||||
entries_.push_back(Entry{id, slot++});
|
||||
}
|
||||
// Already ascending by construction; no sort needed.
|
||||
}
|
||||
|
||||
void SlotMap::reconcile(const std::vector<std::string>& liveIds) {
|
||||
// Drop markers whose sample left the index.
|
||||
entries_.erase(
|
||||
std::remove_if(entries_.begin(), entries_.end(),
|
||||
[&](const Entry& e) {
|
||||
return std::find(liveIds.begin(), liveIds.end(), e.id) ==
|
||||
liveIds.end();
|
||||
}),
|
||||
entries_.end());
|
||||
// Append live ids that have no mapping yet (out-of-band index growth), in liveIds
|
||||
// order, each to the next free slot after the current frontier.
|
||||
for (const auto& id : liveIds)
|
||||
if (slotOf(id) < 0) append(id);
|
||||
sortBySlot();
|
||||
}
|
||||
|
||||
bool SlotMap::operator==(const SlotMap& o) const {
|
||||
return entries_ == o.entries_;
|
||||
}
|
||||
|
||||
SlotMap SlotMap::fromEntries(const std::vector<std::pair<std::string, int>>& pairs) {
|
||||
SlotMap m;
|
||||
for (const auto& [id, slot] : pairs) {
|
||||
if (id.empty() || slot < 0) continue; // drop malformed pair
|
||||
if (m.slotOf(id) >= 0) continue; // duplicate id: first wins
|
||||
if (!m.idAt(slot).empty()) continue; // slot taken: never double-occupy
|
||||
m.entries_.push_back(Entry{id, slot});
|
||||
}
|
||||
m.sortBySlot();
|
||||
return m;
|
||||
}
|
||||
|
||||
// SlotMap::serialize is defined in the JSON writer section below (it reuses the
|
||||
// shared core/json emit helpers).
|
||||
// SlotMap lives in core/model/slot_map.cpp (extracted Q-W1, T4-05).
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BankBook — construction + bank lookup
|
||||
@@ -165,12 +45,12 @@ const Bank* BankBook::bank(const std::string& id) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BankIndex* BankBook::index(const std::string& id) {
|
||||
BankModel* BankBook::index(const std::string& id) {
|
||||
Bank* b = bank(id);
|
||||
return b ? &b->index : nullptr;
|
||||
}
|
||||
|
||||
const BankIndex* BankBook::index(const std::string& id) const {
|
||||
const BankModel* BankBook::index(const std::string& id) const {
|
||||
const Bank* b = bank(id);
|
||||
return b ? &b->index : nullptr;
|
||||
}
|
||||
@@ -326,13 +206,13 @@ bool BankBook::evacuate(const std::string& id) {
|
||||
if (src == nullptr) return false;
|
||||
|
||||
// Move every member into the pool, index-only, observing destination collapse.
|
||||
// Snapshot the members first, then clear the source — BankIndex has no bulk move,
|
||||
// Snapshot the members first, then clear the source — BankModel has no bulk move,
|
||||
// and adding into the pool must not alias the vector we are draining.
|
||||
BankIndex& poolIndex = pool().index;
|
||||
BankModel& poolIndex = pool().index;
|
||||
const std::vector<Sample> members = src->index.all(); // copy
|
||||
for (const auto& s : members)
|
||||
poolIndex.add(s); // Added or Collapsed; either way the pool now holds the hash
|
||||
src->index = BankIndex{}; // leave the evacuated bank empty
|
||||
src->index = BankModel{}; // leave the evacuated bank empty
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -346,12 +226,12 @@ bool BankBook::setActiveBank(const std::string& id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BankIndex& BankBook::activeIndex() {
|
||||
BankModel& BankBook::activeIndex() {
|
||||
// activeBankId_ always names a live bank; it falls back to the pool on delete.
|
||||
return bank(activeBankId_)->index;
|
||||
}
|
||||
|
||||
const BankIndex& BankBook::activeIndex() const {
|
||||
const BankModel& BankBook::activeIndex() const {
|
||||
return bank(activeBankId_)->index;
|
||||
}
|
||||
|
||||
@@ -361,11 +241,11 @@ const BankIndex& BankBook::activeIndex() const {
|
||||
|
||||
namespace {
|
||||
|
||||
// Adds `s` to `dest` and maps the BankIndex outcome onto the transfer outcome for
|
||||
// Adds `s` to `dest` and maps the BankModel outcome onto the transfer outcome for
|
||||
// the "gained a NEW entry" case (`gained`) vs the collapse case. Rejected outcomes
|
||||
// (absolute path / empty id) cannot occur here: the sample already passed add() on
|
||||
// the source side, so its path and id are already valid.
|
||||
TransferResult applyDestAdd(BankIndex& dest, const Sample& s, TransferResult gained) {
|
||||
TransferResult applyDestAdd(BankModel& dest, const Sample& s, TransferResult gained) {
|
||||
return dest.add(s) == AddResult::Collapsed ? TransferResult::Collapsed : gained;
|
||||
}
|
||||
|
||||
@@ -445,7 +325,7 @@ bool BankBook::updateSampleInPlace(const std::string& sampleId, const Sample& up
|
||||
namespace {
|
||||
|
||||
// The bank's live sample ids in INDEX (insertion) order — the reconcile/migration seed.
|
||||
std::vector<std::string> indexIds(const BankIndex& idx) {
|
||||
std::vector<std::string> indexIds(const BankModel& idx) {
|
||||
std::vector<std::string> ids;
|
||||
for (const auto& s : idx.all()) ids.push_back(s.id);
|
||||
return ids;
|
||||
@@ -557,20 +437,6 @@ using ObjWriter = json::Writer;
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string SlotMap::serialize() const {
|
||||
// Array of {id, slot} objects in ascending slot order (entries_ is kept sorted).
|
||||
std::string out;
|
||||
out += '[';
|
||||
for (std::size_t i = 0; i < entries_.size(); ++i) {
|
||||
if (i) out += ',';
|
||||
ObjWriter e(out);
|
||||
e.keyStr("id", entries_[i].id);
|
||||
e.keyRaw("slot", intToStr(entries_[i].slot));
|
||||
}
|
||||
out += ']';
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string BankBook::serialize() const {
|
||||
std::string out;
|
||||
{
|
||||
@@ -578,7 +444,7 @@ std::string BankBook::serialize() const {
|
||||
root.keyRaw("version", intToStr(1));
|
||||
root.keyStr("activeBank", activeBankId_);
|
||||
|
||||
// banks: array of { id, displayName, ordinal, index: <BankIndex blob> }.
|
||||
// banks: array of { id, displayName, ordinal, index: <BankModel blob> }.
|
||||
// The pool rides in as bank-zero, persisted identically to any named bank.
|
||||
root.keyBegin("banks");
|
||||
out += '[';
|
||||
@@ -589,7 +455,7 @@ std::string BankBook::serialize() const {
|
||||
b.keyStr("displayName", banks_[i].displayName);
|
||||
b.keyRaw("ordinal", intToStr(banks_[i].ordinal));
|
||||
// The nested index is bank_model's own JSON, emitted verbatim so the
|
||||
// per-sample shape stays owned by BankIndex::serialize (not duplicated).
|
||||
// per-sample shape stays owned by BankModel::serialize (not duplicated).
|
||||
b.keyRaw("index", banks_[i].index.serialize());
|
||||
// L7 display positions (gap-preserving). Absent on a pre-L7 blob; the
|
||||
// parser defaults such a bank's slots from insertion order on load.
|
||||
@@ -638,7 +504,7 @@ bool parseBank(json::Reader& r, Bank& b) {
|
||||
} else if (key == "index") {
|
||||
std::string raw;
|
||||
if (!r.captureValue(raw)) return false;
|
||||
auto idx = BankIndex::deserialize(raw);
|
||||
auto idx = BankModel::deserialize(raw);
|
||||
if (!idx) return false; // a malformed nested index fails the whole parse
|
||||
b.index = std::move(*idx);
|
||||
haveIndex = true;
|
||||
@@ -718,7 +584,7 @@ bool parseBook(json::Reader& r, const std::string& raw, std::vector<Bank>& banks
|
||||
if (!r.parseString(activeBank)) return false;
|
||||
} else if (key == "samples") {
|
||||
// Legacy marker. The legacy index is re-parsed from the whole input below
|
||||
// (BankIndex::deserialize owns that shape); here we only skip the value to
|
||||
// (BankModel::deserialize owns that shape); here we only skip the value to
|
||||
// keep the scan well-formed and note that we saw it.
|
||||
sawSamples = true;
|
||||
if (!r.skipValue()) return false;
|
||||
@@ -734,7 +600,7 @@ bool parseBook(json::Reader& r, const std::string& raw, std::vector<Bank>& banks
|
||||
// --- Legacy migration: a bare bank_index (samples, no banks) → pool. ---
|
||||
if (!sawBanks) {
|
||||
if (!sawSamples) return false; // neither shape's marker → malformed
|
||||
auto legacy = BankIndex::deserialize(raw);
|
||||
auto legacy = BankModel::deserialize(raw);
|
||||
if (!legacy) return false;
|
||||
Bank pool;
|
||||
pool.id = kPoolBankId;
|
||||
@@ -10,9 +10,9 @@
|
||||
// -- What it is --------------------------------------------------------------
|
||||
//
|
||||
// An ordered registry of banks. Each bank = { stable id, display name, ordinal,
|
||||
// BankIndex }. The book WRAPS N BankIndex instances — bank_model / BankIndex are
|
||||
// BankModel }. The book WRAPS N BankModel instances — bank_model / BankModel are
|
||||
// UNTOUCHED (additive: no bankId on Sample). Movement of samples between banks is
|
||||
// index-only (remove from source's BankIndex, add to destination's); files never
|
||||
// index-only (remove from source's BankModel, add to destination's); files never
|
||||
// relocate — banks are logical groupings over one shared file pool.
|
||||
//
|
||||
// -- The pool (privileged, not special-cased) --------------------------------
|
||||
@@ -42,113 +42,30 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "bank_model.h"
|
||||
#include "core/model/bank_model.h"
|
||||
#include "core/model/slot_map.h"
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
// Q-W1 interim: this god module re-namespaces in its own split wave; until then the
|
||||
// clean model types it wraps live in reasampler::model.
|
||||
using namespace model;
|
||||
|
||||
// The pool's fixed identity. The id is reserved: createBank rejects it, and the
|
||||
// pool is always bank-zero. The name is fixed: renameBank rejects the pool.
|
||||
inline constexpr const char* kPoolBankId = "pool";
|
||||
inline constexpr const char* kPoolBankName = "Pool";
|
||||
|
||||
// SlotMap — the L7 gap-preserving display-position carrier for ONE bank (F2 settled:
|
||||
// plain interchangeable slots, NOT M9 fixed/addressable slots). A slot is just a
|
||||
// display position a sample id occupies; the map is sample id -> slot (>= 0). Gaps
|
||||
// are first-class: a bank may have a sample at slot 1 with slot 0 empty (an empty
|
||||
// first row above an occupied second row). At most one id per slot (a slot is never
|
||||
// double-occupied) and at most one slot per id (an id sits in exactly one place).
|
||||
//
|
||||
// Position lives HERE, not on Sample (CLAUDE.md wrapping discipline): a copy of one
|
||||
// sample into two banks may sit at different slots, so position is a per-bank display
|
||||
// concern owned by the bank's membership. bank_model / Sample stay untouched.
|
||||
//
|
||||
// PURE: standard library only. Hard-tested to the bar of BankIndex's round-trip.
|
||||
class SlotMap {
|
||||
public:
|
||||
// The slot an id occupies, or -1 if the id is not mapped. O(N).
|
||||
int slotOf(const std::string& id) const;
|
||||
|
||||
// The id occupying `slot`, or "" if the slot is empty. O(N).
|
||||
std::string idAt(int slot) const;
|
||||
|
||||
// The highest occupied slot, or -1 when the map is empty. Defines the append
|
||||
// frontier and (with trailing-empty trim) the content extent.
|
||||
int maxSlot() const;
|
||||
|
||||
// Ids in ASCENDING slot order (the deterministic display order). Empty slots
|
||||
// produce no entry — the caller iterates occupants; sparse layout is a draw
|
||||
// concern that reads slotOf/idAt, not this list.
|
||||
std::vector<std::string> orderedIds() const;
|
||||
|
||||
// Places `id` at the next free slot after the last occupied one (append). If the
|
||||
// id is already mapped it is first removed (leaving its old slot empty), then
|
||||
// appended — an append never fills an earlier gap. No-op guard: empty id ignored.
|
||||
void append(const std::string& id);
|
||||
|
||||
// Drops `id`'s mapping, LEAVING ITS SLOT EMPTY (no re-pack) so every other id
|
||||
// keeps its position. Returns true if the id was mapped.
|
||||
bool remove(const std::string& id);
|
||||
|
||||
// Moves `id` to `targetSlot`, gap-preserving (F3 reorder semantics):
|
||||
// * target slot EMPTY -> `id` moves there; its old slot is left empty.
|
||||
// * target slot OCCUPIED -> insert-before-and-shift: `id` takes targetSlot and
|
||||
// every occupant at slot >= targetSlot (except `id` itself) shifts up by one,
|
||||
// preserving their relative order and never colliding. Matches file-manager
|
||||
// reorder. Interior gaps between shifted occupants are preserved as-is
|
||||
// (shift is +1 on each occupant, so the gap structure above the target is kept).
|
||||
// * negative targetSlot is clamped to 0.
|
||||
// Returns false (no mutation) if `id` is not mapped. Deterministic.
|
||||
bool reorder(const std::string& id, int targetSlot);
|
||||
|
||||
// Rebuilds the map densely from `ids` in the given order (slot i = ids[i]),
|
||||
// dropping any prior state. The migration path: a pre-L7 bank with no persisted
|
||||
// slot data is seeded from its BankIndex insertion order, densely packed (no gaps),
|
||||
// so it is visually identical on first post-L7 load. Empty/duplicate ids skipped.
|
||||
void resetDense(const std::vector<std::string>& ids);
|
||||
|
||||
// Drops any mapping whose id is NOT in `liveIds` (a stale marker whose sample left
|
||||
// the index) and appends any live id that has NO mapping yet (a sample the index
|
||||
// gained out-of-band). Slots of surviving ids are untouched (gaps preserved). Keeps
|
||||
// the map consistent with the bank's membership without a re-pack. Deterministic:
|
||||
// orphan appends follow `liveIds` order.
|
||||
void reconcile(const std::vector<std::string>& liveIds);
|
||||
|
||||
bool empty() const { return entries_.empty(); }
|
||||
std::size_t size() const { return entries_.size(); }
|
||||
|
||||
bool operator==(const SlotMap& o) const;
|
||||
|
||||
// JSON fragment (an array of {id, slot} objects, ascending slot). Emitted as the
|
||||
// bank envelope's "slots" member by BankBook::serialize; parsed back by its parser.
|
||||
// Round-trips losslessly with the rest of the bank.
|
||||
std::string serialize() const;
|
||||
|
||||
// Builds a map from explicit (id, slot) pairs parsed from persisted JSON. Enforces
|
||||
// the map invariants defensively against a hand-edited blob: a duplicate id keeps
|
||||
// its FIRST occurrence; a slot already taken by a kept id drops the later pair
|
||||
// (never double-occupies); an empty id or negative slot is dropped. The result is
|
||||
// sorted ascending by slot. reconcile() against live membership runs afterward, so
|
||||
// a lossy repair here degrades gracefully rather than corrupting lookup.
|
||||
static SlotMap fromEntries(const std::vector<std::pair<std::string, int>>& pairs);
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
std::string id;
|
||||
int slot = 0;
|
||||
bool operator==(const Entry& o) const { return id == o.id && slot == o.slot; }
|
||||
};
|
||||
std::vector<Entry> entries_; // kept sorted ascending by slot (invariant)
|
||||
|
||||
void sortBySlot();
|
||||
};
|
||||
// SlotMap — extracted to its own TU/header pair (Q-W1, T4-05): core/model/slot_map.h.
|
||||
// Included above because Bank carries one per bank.
|
||||
|
||||
// One bank: a stable id, a display name, an ordinal (tab/display order), and its
|
||||
// own BankIndex. The pool is the bank whose id == kPoolBankId.
|
||||
// own BankModel. The pool is the bank whose id == kPoolBankId.
|
||||
struct Bank {
|
||||
std::string id; // stable, persisted; the pool's is kPoolBankId
|
||||
std::string displayName; // mutable for named banks; fixed "Pool" for the pool
|
||||
int ordinal = 0; // display order; pool is 0, named banks 1..N
|
||||
BankIndex index; // this bank's samples
|
||||
BankModel index; // this bank's samples
|
||||
SlotMap slots; // L7 display positions of this bank's samples (gap-preserving)
|
||||
|
||||
bool isPool() const { return id == kPoolBankId; }
|
||||
@@ -251,10 +168,10 @@ public:
|
||||
// bank — an invalid set never corrupts state.
|
||||
bool setActiveBank(const std::string& id);
|
||||
|
||||
// The active bank's BankIndex — the index the capture layer adds to. Always
|
||||
// The active bank's BankModel — the index the capture layer adds to. Always
|
||||
// valid (the active id always names a live bank; it falls back to the pool).
|
||||
BankIndex& activeIndex();
|
||||
const BankIndex& activeIndex() const;
|
||||
BankModel& activeIndex();
|
||||
const BankModel& activeIndex() const;
|
||||
|
||||
// -- Sample movement (index-only; files never relocate) ------------------
|
||||
|
||||
@@ -334,7 +251,7 @@ public:
|
||||
|
||||
// Refreshes a sample IN PLACE wherever it lives in the book (M10 re-capture):
|
||||
// finds the bank holding `sampleId` and replaces its entry with `updated`
|
||||
// (order-preserving, no dedup — see BankIndex::updateInPlace). Scans banks in
|
||||
// (order-preserving, no dedup — see BankModel::updateInPlace). Scans banks in
|
||||
// ordinal order and updates the FIRST holder (a sample id is unique within a
|
||||
// bank; the same id living in two banks via copy would update the earliest, which
|
||||
// is acceptable — re-capture operates on the panel's focused single selection).
|
||||
@@ -372,9 +289,9 @@ public:
|
||||
Bank* bank(const std::string& id);
|
||||
const Bank* bank(const std::string& id) const;
|
||||
|
||||
// The bank's BankIndex by id, or nullptr. Convenience over bank()->index.
|
||||
BankIndex* index(const std::string& id);
|
||||
const BankIndex* index(const std::string& id) const;
|
||||
// The bank's BankModel by id, or nullptr. Convenience over bank()->index.
|
||||
BankModel* index(const std::string& id);
|
||||
const BankModel* index(const std::string& id) const;
|
||||
|
||||
// The pool (always present). Never null.
|
||||
Bank& pool();
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "bank_model.h"
|
||||
#include "core/model/bank_model.h"
|
||||
|
||||
#include <cctype>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// shortest form that round-trips every IEEE-754 double exactly, so the
|
||||
// deserialize(serialize(x)) == x invariant holds bit-for-bit.
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::model {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// equality
|
||||
@@ -74,10 +74,10 @@ static bool isAbsolutePath(const std::string& p) {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// BankIndex
|
||||
// BankModel
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
AddResult BankIndex::add(const Sample& sample) {
|
||||
AddResult BankModel::add(const Sample& sample) {
|
||||
if (sample.id.empty()) return AddResult::RejectedEmptyId;
|
||||
if (isAbsolutePath(sample.relativePath)) return AddResult::RejectedAbsolutePath;
|
||||
|
||||
@@ -88,7 +88,7 @@ AddResult BankIndex::add(const Sample& sample) {
|
||||
return AddResult::Added;
|
||||
}
|
||||
|
||||
bool BankIndex::remove(const std::string& id) {
|
||||
bool BankModel::remove(const std::string& id) {
|
||||
for (auto it = samples_.begin(); it != samples_.end(); ++it) {
|
||||
if (it->id == id) {
|
||||
samples_.erase(it);
|
||||
@@ -98,7 +98,7 @@ bool BankIndex::remove(const std::string& id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BankIndex::updateInPlace(const std::string& id, const Sample& updated) {
|
||||
bool BankModel::updateInPlace(const std::string& id, const Sample& updated) {
|
||||
if (isAbsolutePath(updated.relativePath)) return false; // invariant still holds
|
||||
for (auto& s : samples_) {
|
||||
if (s.id == id) {
|
||||
@@ -109,20 +109,20 @@ bool BankIndex::updateInPlace(const std::string& id, const Sample& updated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Sample* BankIndex::query(const std::string& id) const {
|
||||
const Sample* BankModel::query(const std::string& id) const {
|
||||
for (const auto& s : samples_)
|
||||
if (s.id == id) return &s;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Sample* BankIndex::findByHash(const std::string& contentHash) const {
|
||||
const Sample* BankModel::findByHash(const std::string& contentHash) const {
|
||||
if (contentHash.empty()) return nullptr; // empty hashes never dedup
|
||||
for (const auto& s : samples_)
|
||||
if (s.contentHash == contentHash) return &s;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool BankIndex::moveTier(const std::string& id, Tier tier) {
|
||||
bool BankModel::moveTier(const std::string& id, Tier tier) {
|
||||
for (auto& s : samples_) {
|
||||
if (s.id == id) {
|
||||
s.tier = tier;
|
||||
@@ -132,7 +132,7 @@ bool BankIndex::moveTier(const std::string& id, Tier tier) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<Sample> BankIndex::byTier(Tier tier) const {
|
||||
std::vector<Sample> BankModel::byTier(Tier tier) const {
|
||||
std::vector<Sample> out;
|
||||
for (const auto& s : samples_)
|
||||
if (s.tier == tier) out.push_back(s);
|
||||
@@ -223,7 +223,7 @@ void writeSample(std::string& out, const Sample& s) {
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string BankIndex::serialize() const {
|
||||
std::string BankModel::serialize() const {
|
||||
std::string out;
|
||||
{
|
||||
ObjWriter root(out);
|
||||
@@ -410,7 +410,7 @@ bool parseSample(json::Reader& r, Sample& s) {
|
||||
return r.consume('}');
|
||||
}
|
||||
|
||||
bool parseIndex(json::Reader& r, BankIndex& out) {
|
||||
bool parseIndex(json::Reader& r, BankModel& out) {
|
||||
if (!r.consume('{')) return false;
|
||||
r.skipWs();
|
||||
if (r.consume('}')) return true; // empty object — vacuously an empty index
|
||||
@@ -451,11 +451,11 @@ bool parseIndex(json::Reader& r, BankIndex& out) {
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<BankIndex> BankIndex::deserialize(const std::string& blob) {
|
||||
BankIndex idx;
|
||||
std::optional<BankModel> BankModel::deserialize(const std::string& blob) {
|
||||
BankModel idx;
|
||||
json::Reader r(blob);
|
||||
if (!parseIndex(r, idx)) return std::nullopt;
|
||||
return idx;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::model
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
// bank_model — the HEART of ReaSampler, deliberately free of any REAPER type so
|
||||
// it compiles and unit-tests OUTSIDE the DAW. It owns the per-project sample
|
||||
// bank: the `Sample` metadata struct and the `BankIndex` (add / remove / query /
|
||||
// bank: the `Sample` metadata struct and the `BankModel` (add / remove / query /
|
||||
// tier moves / dedup-by-hash + JSON round-trip to/from std::string).
|
||||
//
|
||||
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::model {
|
||||
|
||||
// How the source audio was obtained. Kept in the pure core (no REAPER coupling);
|
||||
// the capture backends (M3/M8) map their own notion onto these.
|
||||
@@ -81,7 +81,7 @@ struct LoopPoints {
|
||||
|
||||
// The metadata record for one captured sample. The audio itself lives in a
|
||||
// project-relative file; `relativePath` is ALWAYS relative (enforced at the
|
||||
// BankIndex::add boundary — see AddResult).
|
||||
// BankModel::add boundary — see AddResult).
|
||||
struct Sample {
|
||||
std::string id; // stable unique id (assigned by the caller)
|
||||
std::string displayName;
|
||||
@@ -128,7 +128,7 @@ struct Sample {
|
||||
|
||||
Tier tier = Tier::Scratch;
|
||||
|
||||
std::string contentHash; // dedup key (see BankIndex)
|
||||
std::string contentHash; // dedup key (see BankModel)
|
||||
|
||||
std::optional<Provenance> provenance; // set only when resampled
|
||||
|
||||
@@ -141,7 +141,7 @@ struct Sample {
|
||||
bool isAutoPrunable() const { return tier == Tier::Scratch; }
|
||||
};
|
||||
|
||||
// Outcome of BankIndex::add. `add` rejects rather than silently mutating:
|
||||
// Outcome of BankModel::add. `add` rejects rather than silently mutating:
|
||||
// - RejectedAbsolutePath: relativePath was absolute (precision invariant).
|
||||
// - RejectedEmptyId: id was empty (the collection is keyed by id).
|
||||
// - Collapsed: content hash matched an existing entry; the existing
|
||||
@@ -157,7 +157,7 @@ enum class AddResult {
|
||||
// An ordered, id-keyed collection of Samples with content-hash dedup, tier
|
||||
// moves/filtering, and lossless JSON round-trip. Insertion order is preserved
|
||||
// so a future panel (M5) can iterate in stable order.
|
||||
class BankIndex {
|
||||
class BankModel {
|
||||
public:
|
||||
// Adds a sample. Enforces the relative-paths-only invariant and dedups by
|
||||
// content hash (an equal-hash add collapses onto the existing entry rather
|
||||
@@ -200,7 +200,7 @@ public:
|
||||
std::size_t size() const { return samples_.size(); }
|
||||
bool empty() const { return samples_.empty(); }
|
||||
|
||||
bool operator==(const BankIndex& o) const { return samples_ == o.samples_; }
|
||||
bool operator==(const BankModel& o) const { return samples_ == o.samples_; }
|
||||
|
||||
// Serializes the whole index to a JSON string (lossless round-trip).
|
||||
std::string serialize() const;
|
||||
@@ -208,10 +208,10 @@ public:
|
||||
// Parses a JSON string produced by serialize(). Returns std::nullopt on
|
||||
// malformed / truncated input (error signaled, never UB). On success the
|
||||
// returned index satisfies deserialize(serialize(x)) == x.
|
||||
static std::optional<BankIndex> deserialize(const std::string& json);
|
||||
static std::optional<BankModel> deserialize(const std::string& json);
|
||||
|
||||
private:
|
||||
std::vector<Sample> samples_; // insertion order preserved
|
||||
};
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::model
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "owned_manifest.h"
|
||||
#include "core/model/owned_manifest.h"
|
||||
|
||||
#include <cctype>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
//
|
||||
// so a compact writer + a focused string-array domain parse is all it needs.
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::model {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// path invariant (mirror of bank_model's isAbsolutePath)
|
||||
@@ -110,4 +110,4 @@ std::optional<OwnedFileManifest> OwnedFileManifest::deserialize(const std::strin
|
||||
return m;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::model
|
||||
@@ -23,17 +23,17 @@
|
||||
// -- The relative-paths-only invariant ---------------------------------------
|
||||
//
|
||||
// A manifest path is ALWAYS project-relative (same invariant as Sample.relativePath
|
||||
// and the persisted BankIndex). add() rejects an absolute path rather than guess a
|
||||
// and the persisted BankModel). add() rejects an absolute path rather than guess a
|
||||
// relativization — the pure model has no project root, so a "normalization" would be
|
||||
// a guess that could point at the wrong file (mirror of BankIndex::add's rejection).
|
||||
// a guess that could point at the wrong file (mirror of BankModel::add's rejection).
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::model {
|
||||
|
||||
// Outcome of an add(). Mirrors BankIndex::AddResult's honesty — the op reports what
|
||||
// Outcome of an add(). Mirrors BankModel::AddResult's honesty — the op reports what
|
||||
// happened rather than silently mutating on a bad request.
|
||||
// - Added: the path was new and recorded.
|
||||
// - AlreadyPresent: the path was already in the manifest (dedup no-op).
|
||||
@@ -88,4 +88,4 @@ private:
|
||||
std::vector<std::string> paths_; // insertion order; deduplicated
|
||||
};
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::model
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "provenance.h"
|
||||
#include "core/model/provenance.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
// injection-proof on its own and can be embedded whole as one more length-prefixed
|
||||
// field of the fingerprint.
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::model {
|
||||
|
||||
bool CaptureRecipe::operator==(const CaptureRecipe& o) const {
|
||||
return scope == o.scope && sourceMode == o.sourceMode &&
|
||||
@@ -159,4 +159,4 @@ std::optional<std::string> detectParent(
|
||||
return parent;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::model
|
||||
@@ -35,7 +35,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::model {
|
||||
|
||||
// Capture scope, mirrored from render_settings' CaptureScope but kept independent
|
||||
// here so the pure provenance module does not pull the whole render_settings graph
|
||||
@@ -146,4 +146,4 @@ std::optional<std::string> detectParent(
|
||||
const std::vector<std::string>& sourceItemFiles,
|
||||
const std::vector<BankFileRef>& bankFiles);
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::model
|
||||
@@ -0,0 +1,145 @@
|
||||
#include "core/model/slot_map.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "core/json/json.h"
|
||||
|
||||
// slot_map implementation (extracted from bank_book, Q-W1 T4-05).
|
||||
//
|
||||
// The invariant: entries_ is kept sorted ascending by slot, one id per slot, one
|
||||
// slot per id. Every mutator restores it; queries assume it. serialize rides the
|
||||
// shared core/json emit helpers — the emitted fragment is byte-identical to the
|
||||
// pre-extraction bank_book writer.
|
||||
|
||||
namespace reasampler::model {
|
||||
|
||||
void SlotMap::sortBySlot() {
|
||||
std::stable_sort(entries_.begin(), entries_.end(),
|
||||
[](const Entry& a, const Entry& b) { return a.slot < b.slot; });
|
||||
}
|
||||
|
||||
int SlotMap::slotOf(const std::string& id) const {
|
||||
for (const auto& e : entries_)
|
||||
if (e.id == id) return e.slot;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string SlotMap::idAt(int slot) const {
|
||||
for (const auto& e : entries_)
|
||||
if (e.slot == slot) return e.id;
|
||||
return {};
|
||||
}
|
||||
|
||||
int SlotMap::maxSlot() const {
|
||||
int m = -1;
|
||||
for (const auto& e : entries_)
|
||||
if (e.slot > m) m = e.slot;
|
||||
return m;
|
||||
}
|
||||
|
||||
std::vector<std::string> SlotMap::orderedIds() const {
|
||||
// entries_ is sorted ascending by slot, so a straight walk is display order.
|
||||
std::vector<std::string> out;
|
||||
out.reserve(entries_.size());
|
||||
for (const auto& e : entries_) out.push_back(e.id);
|
||||
return out;
|
||||
}
|
||||
|
||||
void SlotMap::append(const std::string& id) {
|
||||
if (id.empty()) return;
|
||||
remove(id); // an existing id is re-appended, not left in place
|
||||
entries_.push_back(Entry{id, maxSlot() + 1}); // next free slot after the last occupied
|
||||
sortBySlot();
|
||||
}
|
||||
|
||||
bool SlotMap::remove(const std::string& id) {
|
||||
for (auto it = entries_.begin(); it != entries_.end(); ++it) {
|
||||
if (it->id == id) {
|
||||
entries_.erase(it); // leaves the slot empty — no re-pack
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SlotMap::reorder(const std::string& id, int targetSlot) {
|
||||
if (slotOf(id) < 0) return false; // not mapped -> no mutation
|
||||
if (targetSlot < 0) targetSlot = 0;
|
||||
if (slotOf(id) == targetSlot) return false; // already there — true no-op
|
||||
|
||||
// Detach the moving id first so the occupancy test below sees the post-move world.
|
||||
remove(id);
|
||||
|
||||
const bool occupied = !idAt(targetSlot).empty();
|
||||
if (occupied) {
|
||||
// Insert-before-and-shift: every occupant at slot >= targetSlot shifts up by one,
|
||||
// preserving relative order and interior gaps above the target. The moving id then
|
||||
// takes targetSlot cleanly.
|
||||
for (auto& e : entries_)
|
||||
if (e.slot >= targetSlot) ++e.slot;
|
||||
}
|
||||
entries_.push_back(Entry{id, targetSlot});
|
||||
sortBySlot();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SlotMap::resetDense(const std::vector<std::string>& ids) {
|
||||
entries_.clear();
|
||||
int slot = 0;
|
||||
for (const auto& id : ids) {
|
||||
if (id.empty()) continue;
|
||||
if (slotOf(id) >= 0) continue; // skip a duplicate id (one slot per id)
|
||||
entries_.push_back(Entry{id, slot++});
|
||||
}
|
||||
// Already ascending by construction; no sort needed.
|
||||
}
|
||||
|
||||
void SlotMap::reconcile(const std::vector<std::string>& liveIds) {
|
||||
// Drop markers whose sample left the index.
|
||||
entries_.erase(
|
||||
std::remove_if(entries_.begin(), entries_.end(),
|
||||
[&](const Entry& e) {
|
||||
return std::find(liveIds.begin(), liveIds.end(), e.id) ==
|
||||
liveIds.end();
|
||||
}),
|
||||
entries_.end());
|
||||
// Append live ids that have no mapping yet (out-of-band index growth), in liveIds
|
||||
// order, each to the next free slot after the current frontier.
|
||||
for (const auto& id : liveIds)
|
||||
if (slotOf(id) < 0) append(id);
|
||||
sortBySlot();
|
||||
}
|
||||
|
||||
bool SlotMap::operator==(const SlotMap& o) const {
|
||||
return entries_ == o.entries_;
|
||||
}
|
||||
|
||||
SlotMap SlotMap::fromEntries(const std::vector<std::pair<std::string, int>>& pairs) {
|
||||
SlotMap m;
|
||||
for (const auto& [id, slot] : pairs) {
|
||||
if (id.empty() || slot < 0) continue; // drop malformed pair
|
||||
if (m.slotOf(id) >= 0) continue; // duplicate id: first wins
|
||||
if (!m.idAt(slot).empty()) continue; // slot taken: never double-occupy
|
||||
m.entries_.push_back(Entry{id, slot});
|
||||
}
|
||||
m.sortBySlot();
|
||||
return m;
|
||||
}
|
||||
|
||||
std::string SlotMap::serialize() const {
|
||||
// Array of {id, slot} objects in ascending slot order (entries_ is kept sorted).
|
||||
// json::Writer + numToStr are the same emit path the pre-extraction writer used,
|
||||
// so the fragment is byte-identical.
|
||||
std::string out;
|
||||
out += '[';
|
||||
for (std::size_t i = 0; i < entries_.size(); ++i) {
|
||||
if (i) out += ',';
|
||||
json::Writer e(out);
|
||||
e.keyStr("id", entries_[i].id);
|
||||
e.keyRaw("slot", json::numToStr(entries_[i].slot));
|
||||
}
|
||||
out += ']';
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace reasampler::model
|
||||
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
// slot_map — the L7 gap-preserving display-position carrier for ONE bank (F2 settled:
|
||||
// plain interchangeable slots, NOT M9 fixed/addressable slots). A slot is just a
|
||||
// display position a sample id occupies; the map is sample id -> slot (>= 0). Gaps
|
||||
// are first-class: a bank may have a sample at slot 1 with slot 0 empty (an empty
|
||||
// first row above an occupied second row). At most one id per slot (a slot is never
|
||||
// double-occupied) and at most one slot per id (an id sits in exactly one place).
|
||||
//
|
||||
// Position lives HERE, not on Sample (CLAUDE.md wrapping discipline): a copy of one
|
||||
// sample into two banks may sit at different slots, so position is a per-bank display
|
||||
// concern owned by the bank's membership. bank_model / Sample stay untouched.
|
||||
//
|
||||
// Extracted from bank_book (Q-W1, T4-05): a self-contained ordered-slot container
|
||||
// with its own serialize, distinct from the multi-bank registry that carries it.
|
||||
// Behavior covered by bank_book_tests (the round-trip + reorder/reconcile suites);
|
||||
// a dedicated slot_map_tests target is a welcome follow-up, not a Q-W1 requirement.
|
||||
//
|
||||
// PURE: standard library + core/json (serialize) only.
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler::model {
|
||||
|
||||
class SlotMap {
|
||||
public:
|
||||
// The slot an id occupies, or -1 if the id is not mapped. O(N).
|
||||
int slotOf(const std::string& id) const;
|
||||
|
||||
// The id occupying `slot`, or "" if the slot is empty. O(N).
|
||||
std::string idAt(int slot) const;
|
||||
|
||||
// The highest occupied slot, or -1 when the map is empty. Defines the append
|
||||
// frontier and (with trailing-empty trim) the content extent.
|
||||
int maxSlot() const;
|
||||
|
||||
// Ids in ASCENDING slot order (the deterministic display order). Empty slots
|
||||
// produce no entry — the caller iterates occupants; sparse layout is a draw
|
||||
// concern that reads slotOf/idAt, not this list.
|
||||
std::vector<std::string> orderedIds() const;
|
||||
|
||||
// Places `id` at the next free slot after the last occupied one (append). If the
|
||||
// id is already mapped it is first removed (leaving its old slot empty), then
|
||||
// appended — an append never fills an earlier gap. No-op guard: empty id ignored.
|
||||
void append(const std::string& id);
|
||||
|
||||
// Drops `id`'s mapping, LEAVING ITS SLOT EMPTY (no re-pack) so every other id
|
||||
// keeps its position. Returns true if the id was mapped.
|
||||
bool remove(const std::string& id);
|
||||
|
||||
// Moves `id` to `targetSlot`, gap-preserving (F3 reorder semantics):
|
||||
// * target slot EMPTY -> `id` moves there; its old slot is left empty.
|
||||
// * target slot OCCUPIED -> insert-before-and-shift: `id` takes targetSlot and
|
||||
// every occupant at slot >= targetSlot (except `id` itself) shifts up by one,
|
||||
// preserving their relative order and never colliding. Matches file-manager
|
||||
// reorder. Interior gaps between shifted occupants are preserved as-is
|
||||
// (shift is +1 on each occupant, so the gap structure above the target is kept).
|
||||
// * negative targetSlot is clamped to 0.
|
||||
// Returns false (no mutation) if `id` is not mapped. Deterministic.
|
||||
bool reorder(const std::string& id, int targetSlot);
|
||||
|
||||
// Rebuilds the map densely from `ids` in the given order (slot i = ids[i]),
|
||||
// dropping any prior state. The migration path: a pre-L7 bank with no persisted
|
||||
// slot data is seeded from its BankModel insertion order, densely packed (no gaps),
|
||||
// so it is visually identical on first post-L7 load. Empty/duplicate ids skipped.
|
||||
void resetDense(const std::vector<std::string>& ids);
|
||||
|
||||
// Drops any mapping whose id is NOT in `liveIds` (a stale marker whose sample left
|
||||
// the index) and appends any live id that has NO mapping yet (a sample the index
|
||||
// gained out-of-band). Slots of surviving ids are untouched (gaps preserved). Keeps
|
||||
// the map consistent with the bank's membership without a re-pack. Deterministic:
|
||||
// orphan appends follow `liveIds` order.
|
||||
void reconcile(const std::vector<std::string>& liveIds);
|
||||
|
||||
bool empty() const { return entries_.empty(); }
|
||||
std::size_t size() const { return entries_.size(); }
|
||||
|
||||
bool operator==(const SlotMap& o) const;
|
||||
|
||||
// JSON fragment (an array of {id, slot} objects, ascending slot). Emitted as the
|
||||
// bank envelope's "slots" member by BankBook::serialize; parsed back by its parser.
|
||||
// Round-trips losslessly with the rest of the bank.
|
||||
std::string serialize() const;
|
||||
|
||||
// Builds a map from explicit (id, slot) pairs parsed from persisted JSON. Enforces
|
||||
// the map invariants defensively against a hand-edited blob: a duplicate id keeps
|
||||
// its FIRST occurrence; a slot already taken by a kept id drops the later pair
|
||||
// (never double-occupies); an empty id or negative slot is dropped. The result is
|
||||
// sorted ascending by slot. reconcile() against live membership runs afterward, so
|
||||
// a lossy repair here degrades gracefully rather than corrupting lookup.
|
||||
static SlotMap fromEntries(const std::vector<std::pair<std::string, int>>& pairs);
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
std::string id;
|
||||
int slot = 0;
|
||||
bool operator==(const Entry& o) const { return id == o.id && slot == o.slot; }
|
||||
};
|
||||
std::vector<Entry> entries_; // kept sorted ascending by slot (invariant)
|
||||
|
||||
void sortBySlot();
|
||||
};
|
||||
|
||||
} // namespace reasampler::model
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
// core/namespaces.h — Q-W1 INTERIM flat-namespace shim for the not-yet-split
|
||||
// god/shell TUs (bank_panel / actions / persist / ingest / main / view / capture
|
||||
// shells / the VST editor+processor). The Q-W1 sub-namespaces move every clean pure
|
||||
// module's symbols out of the flat `reasampler` namespace; the god modules keep their
|
||||
// pre-split internals, which reference those symbols unqualified (or qualified as
|
||||
// `reasampler::X`). Nominating every sub-namespace inside `reasampler` restores both
|
||||
// forms ([namespace.qual]p2 routes qualified lookup through using-directives), so the
|
||||
// god internals stay untouched until their own split waves.
|
||||
//
|
||||
// SCOPE CONTRACT: included ONLY by god/shell TUs pending their split wave
|
||||
// (Q-W2/Q-W2v/Q-W3/Q-W4/Q-W5). Clean core modules must NOT include this — they
|
||||
// reference cross-subsystem symbols by their real namespace homes. Each split wave
|
||||
// drops this include from the TUs it rewrites; when the last split lands, delete
|
||||
// this header.
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
namespace model {}
|
||||
namespace view {}
|
||||
namespace capture {}
|
||||
namespace audio {}
|
||||
namespace ui {}
|
||||
namespace reclaim {}
|
||||
namespace version {}
|
||||
namespace json {}
|
||||
namespace util {}
|
||||
namespace wire {}
|
||||
namespace instrument {
|
||||
namespace engine {}
|
||||
namespace map {}
|
||||
namespace ui {}
|
||||
} // namespace instrument
|
||||
|
||||
using namespace model;
|
||||
using namespace view;
|
||||
using namespace capture;
|
||||
using namespace audio;
|
||||
using namespace ui;
|
||||
using namespace reclaim;
|
||||
using namespace version;
|
||||
using namespace util;
|
||||
using namespace wire;
|
||||
using namespace instrument::engine;
|
||||
using namespace instrument::map;
|
||||
using namespace instrument::ui;
|
||||
|
||||
} // namespace reasampler
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "prune_reconcile.h"
|
||||
#include "core/reclaim/prune_reconcile.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// keeping a path iff it is owned AND not referenced. Walking `present` (not owned)
|
||||
// gives the ∩-present clause for free and yields output in folder-enumeration order.
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::reclaim {
|
||||
|
||||
std::vector<std::string> pruneOrphans(const std::vector<std::string>& present,
|
||||
const std::vector<std::string>& referenced,
|
||||
@@ -83,4 +83,4 @@ std::vector<std::string> pruneDeletePlan(const std::vector<std::string>& confirm
|
||||
return plan;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::reclaim
|
||||
@@ -32,7 +32,7 @@
|
||||
// -- Path representation: EXACT-STRING match (safety-critical) -----------------
|
||||
//
|
||||
// Every path in the model is a project-relative string compared VERBATIM: Sample.
|
||||
// relativePath, OwnedFileManifest::contains (p == relativePath), and BankIndex all
|
||||
// relativePath, OwnedFileManifest::contains (p == relativePath), and BankModel all
|
||||
// use raw std::string equality — no separator normalization, no case-folding, no
|
||||
// trailing-slash trimming. This core MATCHES that convention exactly: it compares
|
||||
// the raw strings the shell supplies. Feeding a consistent spelling across the three
|
||||
@@ -46,7 +46,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::reclaim {
|
||||
|
||||
// The dry-run prune result (Phase R, Wave 2 — report only, no deletion). The thin
|
||||
// prune shell (persist) fills this from pruneOrphans() + a per-file size stat and hands
|
||||
@@ -175,4 +175,4 @@ PruneReport buildPruneReport(const std::vector<std::string>& orphans,
|
||||
std::vector<std::string> pruneDeletePlan(const std::vector<std::string>& confirmed,
|
||||
const std::vector<std::string>& freshOrphans);
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::reclaim
|
||||
@@ -1,10 +1,10 @@
|
||||
// action_bar — pure implementation. See action_bar.h. NO REAPER / SWELL / LICE / vendor.
|
||||
|
||||
#include "action_bar.h"
|
||||
#include "core/ui/action_bar.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -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
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "core/ui/rect.h"
|
||||
// action_bar — the REAPER-free, LICE-free layout + hit-test math behind the bank_panel's
|
||||
// TASK-GROUPED toolbars (Phase L, L2 + L4 + L6). L2's dock-panel layout redesign (DS-3: a
|
||||
// thorough layout, not a re-skin) groups the action-trigger button inventory BY TASK — a compact
|
||||
@@ -35,7 +36,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
// The task cluster a button belongs to (the L2 "group by task" mandate). The order here is
|
||||
// NOT itself the bar order — the caller passes ClusterSpecs in the order it wants; this enum
|
||||
@@ -58,16 +59,7 @@ enum class ActionCluster {
|
||||
// The bar the clusters are drawn into, top-left origin (SWELL/LICE convention). (x, y) is the
|
||||
// top-left corner; width/height are the bar extents. The panel reserves this as a fixed-height
|
||||
// band (its own judgment where — above the tail footer, below the split body).
|
||||
struct ActionBarRect {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool operator==(const ActionBarRect& o) const {
|
||||
return x == o.x && y == o.y && width == o.width && height == o.height;
|
||||
}
|
||||
};
|
||||
using ActionBarRect = Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h), role-aliased
|
||||
|
||||
// One visible button's placement within the bar, top-left origin. `index` is the button's
|
||||
// position in the caller's flat action list (the caller supplies actions in cluster order, so
|
||||
@@ -156,4 +148,4 @@ std::vector<ActionBarSlot> computeBarSlots(const ActionBarRect& bar,
|
||||
int hitTestActionBar(int px, int py, const ActionBarRect& bar,
|
||||
const std::vector<ClusterSpec>& clusters, const ActionBarSpec& spec);
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,11 +1,11 @@
|
||||
// bank_grid — pure implementation. See bank_grid.h. NO REAPER / SWELL / vendor.
|
||||
|
||||
#include "bank_grid.h"
|
||||
#include "core/ui/bank_grid.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -224,4 +224,4 @@ float compressAmplitudeForDisplay(float linear) {
|
||||
return linear < 0.0f ? -clamped : clamped;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "core/ui/rect.h"
|
||||
// bank_grid — the REAPER-free layout math and cache-key logic behind the docked
|
||||
// bank_panel (M5, Wave A). The panel shell (bank_panel.cpp) owns the SWELL window,
|
||||
// LICE drawing, and PCM reads; ALL of that is REAPER-bound and DAW-verified. What
|
||||
@@ -14,22 +15,13 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
// A single cell's pixel rectangle within the panel, top-left origin (SWELL/LICE
|
||||
// convention). (x, y) is the top-left corner; width/height are the cell extents.
|
||||
// These are the draw bounds for one sample's thumbnail; the panel draws its
|
||||
// waveform envelope inside this rect (minus any internal padding it applies).
|
||||
struct CellRect {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool operator==(const CellRect& o) const {
|
||||
return x == o.x && y == o.y && width == o.width && height == o.height;
|
||||
}
|
||||
};
|
||||
using CellRect = Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h), role-aliased
|
||||
|
||||
// Fixed inputs that shape the grid. All in pixels. cellWidth/cellHeight are the
|
||||
// TARGET cell size; the layout fits as many whole columns as the panel width
|
||||
@@ -183,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
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,8 +1,8 @@
|
||||
// card_drag — pure implementation. See card_drag.h. NO REAPER / SWELL / LICE / OS / vendor.
|
||||
|
||||
#include "card_drag.h"
|
||||
#include "core/ui/card_drag.h"
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -93,4 +93,4 @@ int hitTestSlot(int px, int py, const std::vector<SlotCellRect>& rects) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -30,10 +30,10 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "bank_grid.h" // CellRect
|
||||
#include "drag_out.h" // PanelClientRect, DragState
|
||||
#include "core/ui/bank_grid.h" // CellRect
|
||||
#include "core/ui/drag_out.h" // PanelClientRect, DragState
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
// Which drop region the pointer currently sits over WITHIN the client rect. The shell
|
||||
// classifies the live pointer against its own region geometry (tab strip / other bank
|
||||
@@ -144,4 +144,4 @@ std::vector<SlotCellRect> computeSlotRectsForDrop(int maxSlot, int panelWidth,
|
||||
// callers reason in model slots.
|
||||
int hitTestSlot(int px, int py, const std::vector<SlotCellRect>& rects);
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,11 +1,11 @@
|
||||
// card_meta — pure implementation. See card_meta.h. NO REAPER / SWELL / LICE / vendor.
|
||||
|
||||
#include "card_meta.h"
|
||||
#include "core/ui/card_meta.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
std::string formatBarsBeats(const MusicalLength& m) {
|
||||
// No derivable musical read-out without a positive tempo AND a stamped meter.
|
||||
@@ -61,4 +61,4 @@ std::string formatSecondsMs(double lengthSeconds) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
// The musical length inputs, taken straight off a Sample (L7 F1 capture-time stamp):
|
||||
// lengthSeconds — captured length in wall-clock seconds (>= 0).
|
||||
@@ -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
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,9 +1,9 @@
|
||||
// component_geometry — pure implementation. See component_geometry.h. NO REAPER / SWELL /
|
||||
// LICE / vendor. Standard library only.
|
||||
|
||||
#include "component_geometry.h"
|
||||
#include "core/ui/component_geometry.h"
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
bool hitTestBox(int px, int py, const KitBox& box) {
|
||||
if (box.empty()) return false;
|
||||
@@ -105,4 +105,4 @@ int waveformColumnCount(const KitBox& box) {
|
||||
return w > 0 ? w : 0;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "core/ui/rect.h"
|
||||
// component_geometry — the REAPER-free, LICE-free geometry + hit-test math for the shared
|
||||
// drawing kit's generic components (Phase L, L1): a button box, a slider's track/handle,
|
||||
// and a list row. These are the kit-level primitives that DON'T already have a pure owner:
|
||||
@@ -20,23 +21,12 @@
|
||||
// PURE MODULE: NO REAPER types, NO SWELL, NO LICE, NO vendor/ includes. Standard library
|
||||
// only. Builds and unit-tests without REAPER. Mirror of mode_switch / prune_button.
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
// A generic pixel box, top-left origin (SWELL/LICE convention). Shared shape for the kit
|
||||
// component rects below. A zero-area box (empty()) means "nothing to draw / hit" — the
|
||||
// same graceful-suppression convention prune_button uses.
|
||||
struct KitBox {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool empty() const { return width <= 0 || height <= 0; }
|
||||
|
||||
bool operator==(const KitBox& o) const {
|
||||
return x == o.x && y == o.y && width == o.width && height == o.height;
|
||||
}
|
||||
};
|
||||
using KitBox = Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h), role-aliased
|
||||
|
||||
// True iff (px, py) falls inside `box`, half-open bounds [x, x+width) x [y, y+height) —
|
||||
// the same discipline as every sibling hit-test so draw and hit-test never double-claim a
|
||||
@@ -136,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
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,10 +1,10 @@
|
||||
// drag_out — pure implementation. See drag_out.h. NO REAPER / SWELL / OS / vendor.
|
||||
|
||||
#include "drag_out.h"
|
||||
#include "core/ui/drag_out.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -51,4 +51,4 @@ PathList assemblePathList(const std::vector<ResolvedSample>& resolved) {
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "core/ui/rect.h"
|
||||
// drag_out — the REAPER-free / OS-free decision logic behind the bank_panel's native OS
|
||||
// drag-out (Milestone 11, the final polish point). Two pure concerns live here so they are
|
||||
// unit-tested outside the DAW (CLAUDE.md §load-bearing split); the OLE / SWELL initiation
|
||||
@@ -29,7 +30,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
// --- Gesture boundary ---------------------------------------------------------
|
||||
|
||||
@@ -37,16 +38,7 @@ namespace reasampler {
|
||||
// LICE convention). width/height are the extents; a point (px, py) is INSIDE when
|
||||
// x <= px < x + width and y <= py < y + height (half-open, matching the panel's other
|
||||
// hit-tests so the edge is claimed consistently).
|
||||
struct PanelClientRect {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool operator==(const PanelClientRect& o) const {
|
||||
return x == o.x && y == o.y && width == o.width && height == o.height;
|
||||
}
|
||||
};
|
||||
using PanelClientRect = Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h), role-aliased
|
||||
|
||||
// The live drag state the shell tracks, reduced to what the boundary decision needs:
|
||||
// whether a drag is currently active (threshold crossed) and whether the armed payload
|
||||
@@ -137,4 +129,4 @@ struct PathList {
|
||||
// case-insensitive dedup on Windows — the pure layer does not guess a platform rule).
|
||||
PathList assemblePathList(const std::vector<ResolvedSample>& resolved);
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,8 +1,8 @@
|
||||
// footer_bar — pure implementation. See footer_bar.h. NO REAPER / SWELL / LICE / vendor.
|
||||
|
||||
#include "footer_bar.h"
|
||||
#include "core/ui/footer_bar.h"
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -66,4 +66,4 @@ FooterHit hitTestFooterBar(int px, int py, const FooterBarLayout& layout) {
|
||||
return FooterHit::None;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "core/ui/rect.h"
|
||||
// footer_bar — the REAPER-free, LICE-free layout + hit-test math for the bank_panel's L4
|
||||
// footer LEFT group: the narrowed [Arrange|Design] mode toggle, its compact per-mode count
|
||||
// label, and the Tail button, laid out left-to-right at the footer's left. The panel shell
|
||||
@@ -24,32 +25,21 @@
|
||||
// tiling and hit-test, so mode_switch stays the ONE owner of segment geometry. footer_bar
|
||||
// decides the toggle's placement + overall width; mode_switch subdivides it.
|
||||
//
|
||||
// NAME NOTE (brief §name-collision): ButtonRect / FooterRect / SegmentRect / ActionBarRect /
|
||||
// KitBox / KitButtonBox are already owned in this namespace; grep-checked FooterBar* / FooterHit
|
||||
// FREE before minting. FooterRect (prune_button) is the input strip type and is REUSED here
|
||||
// (same concept — the footer strip); the new output/spec/hit types carry the FooterBar* prefix.
|
||||
// Naming: the rect-role family (ButtonRect / FooterRect / FooterBarRect / ...) is unified on
|
||||
// the ONE concrete ui::Rect (core/ui/rect.h, Q-W1 T2-05) — the per-role names are aliases, so
|
||||
// the former hand-collision bookkeeping is retired. FooterRect (prune_button) remains the
|
||||
// shared input-strip spelling; this module's output/spec/hit types carry the FooterBar* prefix.
|
||||
//
|
||||
// PURE MODULE: NO REAPER types, NO SWELL, NO LICE, NO vendor/ includes. Standard library only.
|
||||
|
||||
#include "prune_button.h" // FooterRect — the footer strip input type (shared, not re-minted)
|
||||
#include "core/ui/prune_button.h" // FooterRect — the footer strip input type (shared, not re-minted)
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
// One placed affordance's pixel rectangle within the footer, top-left origin. A zero-area rect
|
||||
// (empty()) means "not placed" (the footer was too narrow to host it after the ones before it),
|
||||
// so the shell draws/hit-tests nothing for it — graceful degradation, mirroring prune_button.
|
||||
struct FooterBarRect {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool empty() const { return width <= 0 || height <= 0; }
|
||||
|
||||
bool operator==(const FooterBarRect& o) const {
|
||||
return x == o.x && y == o.y && width == o.width && height == o.height;
|
||||
}
|
||||
};
|
||||
using FooterBarRect = Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h), role-aliased
|
||||
|
||||
// The laid-out footer LEFT group: the mode toggle box, the count label box, and the Tail
|
||||
// button box, in left-to-right order. Any box may be empty (suppressed) when the footer is
|
||||
@@ -113,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
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,10 +1,10 @@
|
||||
// mode_enable — pure implementation. See mode_enable.h. NO REAPER / SWELL / LICE / vendor.
|
||||
|
||||
#include "mode_enable.h"
|
||||
#include "core/ui/mode_enable.h"
|
||||
|
||||
#include "view_mode_model.h" // kArrangeModeId / kDesignModeId — the ONE home for the mode ids
|
||||
#include "core/view/view_mode_model.h" // kArrangeModeId / kDesignModeId — the ONE home for the mode ids
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
bool tagButtonEnabled(const std::string& activeModeId, TagTarget target) {
|
||||
// The target's own mode id, so the rule is a single "target != active" compare.
|
||||
@@ -18,4 +18,4 @@ bool tagButtonEnabled(const std::string& activeModeId, TagTarget target) {
|
||||
return activeModeId != targetId;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
// A tag button's TARGET mode — the mode it sends the selection to when fired. Arrange = the
|
||||
// untagged default (returning the selection to Arrange), Design = tagged into the Design mode.
|
||||
@@ -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
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,8 +1,8 @@
|
||||
// overflow_menu — pure implementation. See overflow_menu.h. NO REAPER / SWELL / LICE / vendor.
|
||||
|
||||
#include "overflow_menu.h"
|
||||
#include "core/ui/overflow_menu.h"
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
int menuButtonReserve(const MenuBarRect& bar, const MenuButtonSpec& spec) {
|
||||
if (bar.width <= 0 || bar.height <= 0 || spec.buttonWidth <= 0) return 0;
|
||||
@@ -39,4 +39,4 @@ bool hitTestMenuButton(int px, int py, const MenuButtonRect& button) {
|
||||
py >= button.y && py < button.y + button.height;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "core/ui/rect.h"
|
||||
// overflow_menu — the REAPER-free layout math behind the bank_panel TOP toolbar's "⋯ / More"
|
||||
// overflow-menu button (Phase L, L5, refinement 1). The rare capture variants (Batch Items /
|
||||
// Batch Razor / Capture RT) move OFF the always-visible top bar into a popup opened by a small
|
||||
@@ -15,39 +16,19 @@
|
||||
// Mirror of prune_button / mode_switch. The bar rect type it consumes mirrors action_bar's
|
||||
// ActionBarRect shape but is named distinctly to avoid coupling the two modules.
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
// The toolbar band the button is drawn into, top-left origin (SWELL/LICE convention). The
|
||||
// shell derives this from topToolbarRect(). A distinct type from action_bar::ActionBarRect so
|
||||
// this module stands alone (same shape; deliberate — the two modules are not coupled).
|
||||
struct MenuBarRect {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool operator==(const MenuBarRect& o) const {
|
||||
return x == o.x && y == o.y && width == o.width && height == o.height;
|
||||
}
|
||||
};
|
||||
using MenuBarRect = Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h), role-aliased
|
||||
|
||||
// The More button's pixel rectangle within the band, top-left origin. A zero-area rect
|
||||
// (width <= 0 or height <= 0) means "no button" — the band is degenerate or too narrow to
|
||||
// place the button clear of its left inset; the caller must not draw or hit-test it. The
|
||||
// three variants stay reachable via their bindable commands, so a suppressed button is
|
||||
// graceful, not a lost affordance.
|
||||
struct MenuButtonRect {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool empty() const { return width <= 0 || height <= 0; }
|
||||
|
||||
bool operator==(const MenuButtonRect& o) const {
|
||||
return x == o.x && y == o.y && width == o.width && height == o.height;
|
||||
}
|
||||
};
|
||||
using MenuButtonRect = Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h), role-aliased
|
||||
|
||||
// Layout inputs for the More button, in pixels. Defaults match the bank_panel top-toolbar
|
||||
// metrics; the shell passes its own so draw and hit-test share one source of truth.
|
||||
@@ -84,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
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "prune_button.h"
|
||||
#include "core/ui/prune_button.h"
|
||||
|
||||
// prune_button implementation — right-anchored button placement in the footer strip,
|
||||
// with a left-collision suppression rule. Trivially auditable arithmetic; the safety
|
||||
// property (a suppressed/empty button never claims a click) is a pure predicate tested
|
||||
// outside the DAW.
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
ButtonRect computePruneButton(const FooterRect& footer, const PruneButtonSpec& spec) {
|
||||
if (footer.width <= 0 || footer.height <= 0) return ButtonRect{}; // degenerate footer
|
||||
@@ -36,4 +36,4 @@ bool hitTestPruneButton(int px, int py, const ButtonRect& button) {
|
||||
py >= button.y && py < button.y + button.height;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "core/ui/rect.h"
|
||||
// prune_button — the REAPER-free layout math behind the bank_panel's Prune button
|
||||
// (Phase R, Wave 3 — R3, fork R-E). A single labelled button drawn in the panel's
|
||||
// tail-footer strip that fires the "Prune bank folder" command. The panel shell
|
||||
@@ -24,37 +25,17 @@
|
||||
// is always reachable via its bindable command, so a hidden button is a graceful
|
||||
// degradation, not a lost affordance.
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
// The footer strip the button is drawn into, top-left origin (SWELL/LICE
|
||||
// convention). (x, y) is the top-left corner; width/height are the strip extents.
|
||||
// bank_panel derives this from panelFooter() and passes it here.
|
||||
struct FooterRect {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool operator==(const FooterRect& o) const {
|
||||
return x == o.x && y == o.y && width == o.width && height == o.height;
|
||||
}
|
||||
};
|
||||
using FooterRect = Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h), role-aliased
|
||||
|
||||
// A button's pixel rectangle within the footer, top-left origin. A zero-area rect
|
||||
// (width <= 0 or height <= 0) means "no button" — the footer is too narrow to place
|
||||
// it, or the footer itself is degenerate; the caller must not draw or hit-test it.
|
||||
struct ButtonRect {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool empty() const { return width <= 0 || height <= 0; }
|
||||
|
||||
bool operator==(const ButtonRect& o) const {
|
||||
return x == o.x && y == o.y && width == o.width && height == o.height;
|
||||
}
|
||||
};
|
||||
using ButtonRect = Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h), role-aliased
|
||||
|
||||
// Layout inputs for the prune button, in pixels. Defaults match the bank_panel footer
|
||||
// metrics; the shell passes its own so draw and hit-test share one source of truth.
|
||||
@@ -98,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
|
||||
} // namespace reasampler::ui
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
// rect.h — the ONE concrete pixel rectangle (Q-W1, T2-05 ≡ T4-21).
|
||||
//
|
||||
// Before Q-W1 the codebase carried 12+ byte-identical {x, y, width, height} structs
|
||||
// (ButtonRect / FooterRect / CellRect / KitBox / ...) plus a second LTRB grammar on
|
||||
// the VST side (editor_geometry's left/top/right/bottom Rect). This is the single
|
||||
// owner: one CONCRETE type (deliberately NOT a template — the role types differed in
|
||||
// name only, so a template would model nothing), with per-role aliases at the old
|
||||
// definition sites so call sites keep their semantic names
|
||||
// (`using ButtonRect = ui::Rect;`).
|
||||
//
|
||||
// Grammar: XYWH storage (the majority grammar — every extension role struct), with
|
||||
// right()/bottom() accessors and an ltrb() factory so the former LTRB call sites
|
||||
// convert mechanically. Half-open on both axes: a rect covers
|
||||
// [x, x+width) × [y, y+height) — the same convention LICE/SWELL RECTs use, and the
|
||||
// one every hitTest* in the codebase already implements.
|
||||
//
|
||||
// PURE MODULE: standard library only. Header-only; behavior is covered by the role
|
||||
// modules' own test executables (prune_button / footer_bar / bank_grid / ... and the
|
||||
// instrument-ui suites), which exercise every alias against these semantics.
|
||||
|
||||
namespace reasampler::ui {
|
||||
|
||||
struct Rect {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
// Exclusive edges (half-open convention).
|
||||
int right() const { return x + width; }
|
||||
int bottom() const { return y + height; }
|
||||
|
||||
// A zero-or-negative-area rect means "not placed / suppressed": the caller must
|
||||
// not draw or hit-test it (the shared graceful-degradation contract).
|
||||
bool empty() const { return width <= 0 || height <= 0; }
|
||||
|
||||
// The former LTRB grammar's constructor (editor_geometry and friends): edges in,
|
||||
// extents stored. right/bottom exclusive, matching right()/bottom().
|
||||
static Rect ltrb(int left, int top, int right, int bottom) {
|
||||
return Rect{left, top, right - left, bottom - top};
|
||||
}
|
||||
|
||||
bool operator==(const Rect& o) const {
|
||||
return x == o.x && y == o.y && width == o.width && height == o.height;
|
||||
}
|
||||
bool operator!=(const Rect& o) const { return !(*this == o); }
|
||||
};
|
||||
|
||||
// True iff (px, py) falls inside r under the half-open convention. An empty rect
|
||||
// contains nothing, so a suppressed affordance can never claim a click.
|
||||
inline bool contains(const Rect& r, int px, int py) {
|
||||
return px >= r.x && px < r.x + r.width && py >= r.y && py < r.y + r.height;
|
||||
}
|
||||
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,10 +1,10 @@
|
||||
// tab_strip — pure implementation. See tab_strip.h. NO REAPER / SWELL / vendor.
|
||||
|
||||
#include "tab_strip.h"
|
||||
#include "core/ui/tab_strip.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
TabStripLayout computeTabStripLayout(const TabStripRect& strip, int tabCount,
|
||||
const TabStripSpec& spec, int scrollOffset) {
|
||||
@@ -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
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "core/ui/rect.h"
|
||||
// tab_strip — the REAPER-free layout + hit-test math behind the bank_panel's
|
||||
// named-banks tab strip (Phase B, Wave 4 — B4). The named-banks region of the
|
||||
// vertical-split bank window is a LICE-drawn tab strip (one tab per named bank,
|
||||
@@ -16,21 +17,12 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
// The strip the tabs are drawn into, top-left origin (SWELL/LICE convention).
|
||||
// (x, y) is the top-left corner; width/height are the strip extents. The panel
|
||||
// reserves this as a fixed-height band at the top of the named-banks region.
|
||||
struct TabStripRect {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool operator==(const TabStripRect& o) const {
|
||||
return x == o.x && y == o.y && width == o.width && height == o.height;
|
||||
}
|
||||
};
|
||||
using TabStripRect = Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h), role-aliased
|
||||
|
||||
// Fixed inputs that shape the strip. tabWidth is the pixel width of each tab (fixed
|
||||
// so the strip reads as a uniform segmented control and overflow math stays simple —
|
||||
@@ -132,4 +124,4 @@ struct TabHit {
|
||||
TabHit hitTestTabStrip(int px, int py, const TabStripRect& strip, int tabCount,
|
||||
const TabStripSpec& spec, int scrollOffset);
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
@@ -1,11 +1,11 @@
|
||||
// theme — pure implementation. See theme.h. NO REAPER / SWELL / LICE / vendor.
|
||||
|
||||
#include "theme.h"
|
||||
#include "core/ui/theme.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace reasampler {
|
||||
namespace reasampler::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -190,4 +190,4 @@ double textFloor(TextClass cls) {
|
||||
return cls == TextClass::Body ? 4.5 : 3.0;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
} // namespace reasampler::ui
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user