From 847936f813d2060cd7663ae31a1d9ede739f4131 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Tue, 28 Jul 2026 20:48:56 -0400 Subject: [PATCH] =?UTF-8?q?Q-W1=20pt2:=20core/shell/app=20relocation=20+?= =?UTF-8?q?=20sub-namespaces;=20one=20concrete=20ui::Rect=20(LTRB=20fork?= =?UTF-8?q?=20retired);=20slot=5Fmap=20split=20from=20bank=5Fbook;=20BankI?= =?UTF-8?q?ndex=E2=86=92BankModel;=2059/59=20green?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 237 +++++------ src/actions.cpp | 21 +- src/actions.h | 1 + src/{ => app}/main.cpp | 23 +- src/bank_panel.cpp | 93 ++--- src/bank_panel.h | 3 +- src/{ => core/audio}/peaks.cpp | 6 +- src/{ => core/audio}/peaks.h | 4 +- src/{ => core/capture}/batch_capture.cpp | 6 +- src/{ => core/capture}/batch_capture.h | 4 +- src/{ => core/capture}/capture_paths.cpp | 6 +- src/{ => core/capture}/capture_paths.h | 8 +- src/{ => core/capture}/insert_plan.cpp | 6 +- src/{ => core/capture}/insert_plan.h | 4 +- src/{ => core/capture}/realtime_record.cpp | 6 +- src/{ => core/capture}/realtime_record.h | 10 +- src/{ => core/capture}/render_settings.cpp | 6 +- src/{ => core/capture}/render_settings.h | 8 +- src/{ => core/capture}/tail_control.cpp | 6 +- src/{ => core/capture}/tail_control.h | 6 +- src/{ => core/capture}/wav_trim.cpp | 6 +- src/{ => core/capture}/wav_trim.h | 8 +- .../instrument/engine}/master_gain.cpp | 12 +- .../instrument/engine}/master_gain.h | 4 +- .../instrument/engine}/pitch_shift.cpp | 6 +- .../instrument/engine}/pitch_shift.h | 8 +- .../instrument/engine}/sampler_core.cpp | 4 +- .../instrument/engine}/sampler_core.h | 17 +- .../instrument/engine}/velocity_curve.cpp | 17 +- .../instrument/engine}/velocity_curve.h | 4 +- .../instrument/map}/bank_sync.cpp | 6 +- src/{vst => core/instrument/map}/bank_sync.h | 8 +- .../instrument/map}/bridge_marshal.cpp | 6 +- .../instrument/map}/bridge_marshal.h | 4 +- .../instrument/map}/note_entry.cpp | 6 +- src/{vst => core/instrument/map}/note_entry.h | 4 +- .../instrument/map}/sample_map.cpp | 20 +- src/{vst => core/instrument/map}/sample_map.h | 16 +- .../instrument/map}/trigger_seam.cpp | 6 +- .../instrument/map}/trigger_seam.h | 4 +- .../instrument/ui}/browser_scroll.cpp | 26 +- .../instrument/ui}/browser_scroll.h | 6 +- .../instrument/ui}/capture_browser.cpp | 38 +- .../instrument/ui}/capture_browser.h | 10 +- src/core/instrument/ui/curve_popup.cpp | 41 ++ src/{vst => core/instrument/ui}/curve_popup.h | 6 +- .../instrument/ui}/editor_geometry.cpp | 90 ++-- .../instrument/ui}/editor_geometry.h | 29 +- .../instrument/ui}/embed_strip.cpp | 24 +- src/{vst => core/instrument/ui}/embed_strip.h | 8 +- .../instrument/ui}/envelope_edit.cpp | 31 +- .../instrument/ui}/envelope_edit.h | 8 +- .../instrument/ui}/envelope_overlay.cpp | 39 +- .../instrument/ui}/envelope_overlay.h | 32 +- .../instrument/ui}/keyboard_strip.cpp | 32 +- .../instrument/ui}/keyboard_strip.h | 10 +- src/{vst => core/instrument/ui}/knob_deck.cpp | 35 +- src/{vst => core/instrument/ui}/knob_deck.h | 6 +- .../instrument/ui}/param_slider.cpp | 65 +-- .../instrument/ui}/param_slider.h | 8 +- .../instrument/ui}/waveform_view.cpp | 22 +- .../instrument/ui}/waveform_view.h | 18 +- src/core/json/json.h | 2 +- src/{ => core/model}/bank_book.cpp | 174 +------- src/{ => core/model}/bank_book.h | 121 +----- src/{ => core/model}/bank_model.cpp | 30 +- src/{ => core/model}/bank_model.h | 18 +- src/{ => core/model}/owned_manifest.cpp | 6 +- src/{ => core/model}/owned_manifest.h | 10 +- src/{ => core/model}/provenance.cpp | 6 +- src/{ => core/model}/provenance.h | 4 +- src/core/model/slot_map.cpp | 145 +++++++ src/core/model/slot_map.h | 106 +++++ src/core/namespaces.h | 48 +++ src/{ => core/reclaim}/prune_reconcile.cpp | 6 +- src/{ => core/reclaim}/prune_reconcile.h | 6 +- src/{ => core/ui}/action_bar.cpp | 6 +- src/{ => core/ui}/action_bar.h | 16 +- src/{ => core/ui}/bank_grid.cpp | 6 +- src/{ => core/ui}/bank_grid.h | 16 +- src/{ => core/ui}/card_drag.cpp | 6 +- src/{ => core/ui}/card_drag.h | 8 +- src/{ => core/ui}/card_meta.cpp | 6 +- src/{ => core/ui}/card_meta.h | 4 +- src/{ => core/ui}/component_geometry.cpp | 6 +- src/{ => core/ui}/component_geometry.h | 18 +- src/{ => core/ui}/drag_out.cpp | 6 +- src/{ => core/ui}/drag_out.h | 16 +- src/{ => core/ui}/footer_bar.cpp | 6 +- src/{ => core/ui}/footer_bar.h | 28 +- src/{ => core/ui}/mode_enable.cpp | 8 +- src/{ => core/ui}/mode_enable.h | 4 +- src/{ => core/ui}/overflow_menu.cpp | 6 +- src/{ => core/ui}/overflow_menu.h | 29 +- src/{ => core/ui}/prune_button.cpp | 6 +- src/{ => core/ui}/prune_button.h | 29 +- src/core/ui/rect.h | 56 +++ src/{ => core/ui}/tab_strip.cpp | 6 +- src/{ => core/ui}/tab_strip.h | 16 +- src/{ => core/ui}/theme.cpp | 6 +- src/{ => core/ui}/theme.h | 4 +- src/{ => core/ui}/tooltip.cpp | 6 +- src/{ => core/ui}/tooltip.h | 4 +- src/core/util/clamp01.h | 14 + src/core/util/file_bytes.cpp | 4 +- src/core/util/file_bytes.h | 4 +- src/{ => core/version}/app_version.cpp | 6 +- src/{ => core/version}/app_version.h | 4 +- src/{ => core/version}/version_generated.h.in | 0 src/{ => core/view}/guid_diff.cpp | 6 +- src/{ => core/view}/guid_diff.h | 4 +- src/{ => core/view}/lane_keys.cpp | 6 +- src/{ => core/view}/lane_keys.h | 4 +- src/{ => core/view}/mode_switch.cpp | 6 +- src/{ => core/view}/mode_switch.h | 27 +- src/{ => core/view}/view_mode_model.cpp | 8 +- src/{ => core/view}/view_mode_model.h | 0 src/{ => core/view}/view_tree.cpp | 6 +- src/{ => core/view}/view_tree.h | 6 +- src/{ => core/wire}/assignment_request.cpp | 6 +- src/{ => core/wire}/assignment_request.h | 6 +- src/{ => core/wire}/instrument_drop.cpp | 10 +- src/{ => core/wire}/instrument_drop.h | 4 +- src/{ => core/wire}/sample_usage.cpp | 6 +- src/{ => core/wire}/sample_usage.h | 4 +- src/ext_keys.h | 3 +- src/ingest.cpp | 17 +- src/ingest.h | 1 + src/persist.cpp | 21 +- src/persist.h | 31 +- src/{ => shell/actions}/drag_out_win.cpp | 3 +- src/{ => shell/actions}/drag_out_win.h | 1 + .../actions}/instrument_drop_win.cpp | 7 +- src/{ => shell/actions}/instrument_drop_win.h | 1 + src/{ => shell/capture}/capture.cpp | 7 +- src/{ => shell/capture}/capture.h | 5 +- src/{ => shell/capture}/capture_realtime.cpp | 13 +- src/{ => shell/capture}/insert.cpp | 11 +- src/{ => shell/capture}/insert.h | 3 +- src/{ => shell/capture}/item_read.cpp | 3 +- src/{ => shell/capture}/item_read.h | 1 + src/{ => shell/capture}/provenance_shell.cpp | 9 +- src/{ => shell/capture}/provenance_shell.h | 3 +- src/{ => shell/capture}/track_guid.cpp | 3 +- src/{ => shell/capture}/track_guid.h | 1 + .../instrument}/reaper_bridge.cpp | 7 +- src/{vst => shell/instrument}/reaper_bridge.h | 1 + .../instrument}/reasampler_embed.cpp | 49 +-- .../instrument}/reasampler_embed.h | 3 +- .../instrument}/reasampler_uid.h | 0 .../instrument}/reasampler_vst.h | 3 +- src/{vst => shell/instrument}/vst_entry.cpp | 5 +- src/{ => shell/panel}/draw_kit.cpp | 5 +- src/{ => shell/panel}/draw_kit.h | 7 +- src/{ => shell/persist}/usage_scan.cpp | 11 +- src/{ => shell/persist}/usage_scan.h | 1 + src/{ => shell/view}/view.cpp | 11 +- src/{ => shell/view}/view.h | 3 +- src/vst/curve_popup.cpp | 41 -- src/vst/reasampler_editor.cpp | 387 +++++++++--------- src/vst/reasampler_editor.h | 15 +- src/vst/reasampler_processor.cpp | 17 +- src/vst/reasampler_processor.h | 7 +- tests/test_action_bar.cpp | 3 +- tests/test_app_version.cpp | 3 +- tests/test_app_version_padding.cpp | 3 +- tests/test_assignment_request.cpp | 3 +- tests/test_bank_book.cpp | 17 +- tests/test_bank_grid.cpp | 3 +- tests/test_bank_model.cpp | 61 +-- tests/test_bank_sync.cpp | 6 +- tests/test_batch_capture.cpp | 3 +- tests/test_bridge_marshal.cpp | 7 +- tests/test_browser_scroll.cpp | 35 +- tests/test_capture_browser.cpp | 97 ++--- tests/test_capture_paths.cpp | 7 +- tests/test_card_drag.cpp | 3 +- tests/test_card_meta.cpp | 3 +- tests/test_component_geometry.cpp | 3 +- tests/test_curve_popup.cpp | 65 +-- tests/test_drag_out.cpp | 3 +- tests/test_editor_geometry.cpp | 197 ++++----- tests/test_embed_strip.cpp | 87 ++-- tests/test_envelope_edit.cpp | 33 +- tests/test_envelope_overlay.cpp | 93 ++--- tests/test_file_bytes.cpp | 1 + tests/test_footer_bar.cpp | 3 +- tests/test_guid_diff.cpp | 3 +- tests/test_insert_plan.cpp | 3 +- tests/test_instrument_drop.cpp | 5 +- tests/test_json.cpp | 1 + tests/test_keyboard_strip.cpp | 73 ++-- tests/test_knob_deck.cpp | 65 +-- tests/test_lane_keys.cpp | 3 +- tests/test_master_gain.cpp | 7 +- tests/test_mode_enable.cpp | 5 +- tests/test_mode_switch.cpp | 3 +- tests/test_note_entry.cpp | 7 +- tests/test_overflow_menu.cpp | 3 +- tests/test_owned_manifest.cpp | 3 +- tests/test_param_slider.cpp | 127 +++--- tests/test_peaks.cpp | 3 +- tests/test_pitch_shift.cpp | 3 +- tests/test_provenance.cpp | 11 +- tests/test_prune_button.cpp | 3 +- tests/test_prune_reconcile.cpp | 6 +- tests/test_realtime_record.cpp | 3 +- tests/test_render_settings.cpp | 3 +- tests/test_sample_map.cpp | 33 +- tests/test_sample_usage.cpp | 6 +- tests/test_sampler_core.cpp | 15 +- tests/test_tab_strip.cpp | 3 +- tests/test_tail_control.cpp | 3 +- tests/test_theme.cpp | 3 +- tests/test_tooltip.cpp | 3 +- tests/test_trigger_seam.cpp | 7 +- tests/test_velocity_curve.cpp | 7 +- tests/test_view_mode_model.cpp | 5 +- tests/test_view_tree.cpp | 3 +- tests/test_wav_trim.cpp | 3 +- tests/test_waveform_view.cpp | 73 ++-- tests/test_wire.cpp | 1 + 222 files changed, 2247 insertions(+), 2079 deletions(-) rename src/{ => app}/main.cpp (99%) rename src/{ => core/audio}/peaks.cpp (98%) rename src/{ => core/audio}/peaks.h (99%) rename src/{ => core/capture}/batch_capture.cpp (96%) rename src/{ => core/capture}/batch_capture.h (98%) rename src/{ => core/capture}/capture_paths.cpp (99%) rename src/{ => core/capture}/capture_paths.h (98%) rename src/{ => core/capture}/insert_plan.cpp (93%) rename src/{ => core/capture}/insert_plan.h (98%) rename src/{ => core/capture}/realtime_record.cpp (98%) rename src/{ => core/capture}/realtime_record.h (98%) rename src/{ => core/capture}/render_settings.cpp (98%) rename src/{ => core/capture}/render_settings.h (98%) rename src/{ => core/capture}/tail_control.cpp (97%) rename src/{ => core/capture}/tail_control.h (95%) rename src/{ => core/capture}/wav_trim.cpp (98%) rename src/{ => core/capture}/wav_trim.h (97%) rename src/{vst => core/instrument/engine}/master_gain.cpp (85%) rename src/{vst => core/instrument/engine}/master_gain.h (97%) rename src/{vst => core/instrument/engine}/pitch_shift.cpp (99%) rename src/{vst => core/instrument/engine}/pitch_shift.h (98%) rename src/{vst => core/instrument/engine}/sampler_core.cpp (99%) rename src/{vst => core/instrument/engine}/sampler_core.h (98%) rename src/{vst => core/instrument/engine}/velocity_curve.cpp (97%) rename src/{vst => core/instrument/engine}/velocity_curve.h (99%) rename src/{vst => core/instrument/map}/bank_sync.cpp (95%) rename src/{vst => core/instrument/map}/bank_sync.h (96%) rename src/{vst => core/instrument/map}/bridge_marshal.cpp (80%) rename src/{vst => core/instrument/map}/bridge_marshal.h (95%) rename src/{vst => core/instrument/map}/note_entry.cpp (96%) rename src/{vst => core/instrument/map}/note_entry.h (95%) rename src/{vst => core/instrument/map}/sample_map.cpp (98%) rename src/{vst => core/instrument/map}/sample_map.h (98%) rename src/{vst => core/instrument/map}/trigger_seam.cpp (87%) rename src/{vst => core/instrument/map}/trigger_seam.h (97%) rename src/{vst => core/instrument/ui}/browser_scroll.cpp (88%) rename src/{vst => core/instrument/ui}/browser_scroll.h (97%) rename src/{vst => core/instrument/ui}/capture_browser.cpp (68%) rename src/{vst => core/instrument/ui}/capture_browser.h (96%) create mode 100644 src/core/instrument/ui/curve_popup.cpp rename src/{vst => core/instrument/ui}/curve_popup.h (94%) rename src/{vst => core/instrument/ui}/editor_geometry.cpp (64%) rename src/{vst => core/instrument/ui}/editor_geometry.h (91%) rename src/{vst => core/instrument/ui}/embed_strip.cpp (78%) rename src/{vst => core/instrument/ui}/embed_strip.h (95%) rename src/{vst => core/instrument/ui}/envelope_edit.cpp (88%) rename src/{vst => core/instrument/ui}/envelope_edit.h (96%) rename src/{vst => core/instrument/ui}/envelope_overlay.cpp (90%) rename src/{vst => core/instrument/ui}/envelope_overlay.h (92%) rename src/{vst => core/instrument/ui}/keyboard_strip.cpp (86%) rename src/{vst => core/instrument/ui}/keyboard_strip.h (96%) rename src/{vst => core/instrument/ui}/knob_deck.cpp (79%) rename src/{vst => core/instrument/ui}/knob_deck.h (97%) rename src/{vst => core/instrument/ui}/param_slider.cpp (71%) rename src/{vst => core/instrument/ui}/param_slider.h (97%) rename src/{vst => core/instrument/ui}/waveform_view.cpp (88%) rename src/{vst => core/instrument/ui}/waveform_view.h (89%) rename src/{ => core/model}/bank_book.cpp (84%) rename src/{ => core/model}/bank_book.h (78%) rename src/{ => core/model}/bank_model.cpp (96%) rename src/{ => core/model}/bank_model.h (95%) rename src/{ => core/model}/owned_manifest.cpp (97%) rename src/{ => core/model}/owned_manifest.h (93%) rename src/{ => core/model}/provenance.cpp (98%) rename src/{ => core/model}/provenance.h (99%) create mode 100644 src/core/model/slot_map.cpp create mode 100644 src/core/model/slot_map.h create mode 100644 src/core/namespaces.h rename src/{ => core/reclaim}/prune_reconcile.cpp (97%) rename src/{ => core/reclaim}/prune_reconcile.h (99%) rename src/{ => core/ui}/action_bar.cpp (98%) rename src/{ => core/ui}/action_bar.h (96%) rename src/{ => core/ui}/bank_grid.cpp (98%) rename src/{ => core/ui}/bank_grid.h (97%) rename src/{ => core/ui}/card_drag.cpp (97%) rename src/{ => core/ui}/card_drag.h (98%) rename src/{ => core/ui}/card_meta.cpp (96%) rename src/{ => core/ui}/card_meta.h (98%) rename src/{ => core/ui}/component_geometry.cpp (97%) rename src/{ => core/ui}/component_geometry.h (95%) rename src/{ => core/ui}/drag_out.cpp (95%) rename src/{ => core/ui}/drag_out.h (96%) rename src/{ => core/ui}/footer_bar.cpp (96%) rename src/{ => core/ui}/footer_bar.h (88%) rename src/{ => core/ui}/mode_enable.cpp (77%) rename src/{ => core/ui}/mode_enable.h (97%) rename src/{ => core/ui}/overflow_menu.cpp (94%) rename src/{ => core/ui}/overflow_menu.h (87%) rename src/{ => core/ui}/prune_button.cpp (94%) rename src/{ => core/ui}/prune_button.h (90%) create mode 100644 src/core/ui/rect.h rename src/{ => core/ui}/tab_strip.cpp (98%) rename src/{ => core/ui}/tab_strip.h (95%) rename src/{ => core/ui}/theme.cpp (99%) rename src/{ => core/ui}/theme.h (99%) rename src/{ => core/ui}/tooltip.cpp (95%) rename src/{ => core/ui}/tooltip.h (98%) create mode 100644 src/core/util/clamp01.h rename src/{ => core/version}/app_version.cpp (98%) rename src/{ => core/version}/app_version.h (99%) rename src/{ => core/version}/version_generated.h.in (100%) rename src/{ => core/view}/guid_diff.cpp (94%) rename src/{ => core/view}/guid_diff.h (98%) rename src/{ => core/view}/lane_keys.cpp (95%) rename src/{ => core/view}/lane_keys.h (98%) rename src/{ => core/view}/mode_switch.cpp (96%) rename src/{ => core/view}/mode_switch.h (84%) rename src/{ => core/view}/view_mode_model.cpp (99%) rename src/{ => core/view}/view_mode_model.h (100%) rename src/{ => core/view}/view_tree.cpp (93%) rename src/{ => core/view}/view_tree.h (93%) rename src/{ => core/wire}/assignment_request.cpp (92%) rename src/{ => core/wire}/assignment_request.h (97%) rename src/{ => core/wire}/instrument_drop.cpp (93%) rename src/{ => core/wire}/instrument_drop.h (99%) rename src/{ => core/wire}/sample_usage.cpp (99%) rename src/{ => core/wire}/sample_usage.h (99%) rename src/{ => shell/actions}/drag_out_win.cpp (99%) rename src/{ => shell/actions}/drag_out_win.h (98%) rename src/{ => shell/actions}/instrument_drop_win.cpp (96%) rename src/{ => shell/actions}/instrument_drop_win.h (99%) rename src/{ => shell/capture}/capture.cpp (99%) rename src/{ => shell/capture}/capture.h (98%) rename src/{ => shell/capture}/capture_realtime.cpp (99%) rename src/{ => shell/capture}/insert.cpp (97%) rename src/{ => shell/capture}/insert.h (97%) rename src/{ => shell/capture}/item_read.cpp (94%) rename src/{ => shell/capture}/item_read.h (98%) rename src/{ => shell/capture}/provenance_shell.cpp (95%) rename src/{ => shell/capture}/provenance_shell.h (98%) rename src/{ => shell/capture}/track_guid.cpp (91%) rename src/{ => shell/capture}/track_guid.h (97%) rename src/{vst => shell/instrument}/reaper_bridge.cpp (97%) rename src/{vst => shell/instrument}/reaper_bridge.h (99%) rename src/{vst => shell/instrument}/reasampler_embed.cpp (87%) rename src/{vst => shell/instrument}/reasampler_embed.h (97%) rename src/{vst => shell/instrument}/reasampler_uid.h (100%) rename src/{vst => shell/instrument}/reasampler_vst.h (95%) rename src/{vst => shell/instrument}/vst_entry.cpp (95%) rename src/{ => shell/panel}/draw_kit.cpp (98%) rename src/{ => shell/panel}/draw_kit.h (96%) rename src/{ => shell/persist}/usage_scan.cpp (96%) rename src/{ => shell/persist}/usage_scan.h (99%) rename src/{ => shell/view}/view.cpp (99%) rename src/{ => shell/view}/view.h (98%) delete mode 100644 src/vst/curve_popup.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c17901..8c9989d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 (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 . 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 diff --git a/src/actions.cpp b/src/actions.cpp index 185afea..6f025b3 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -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 #include -#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; diff --git a/src/actions.h b/src/actions.h index e9d9bfe..492f0e5 100644 --- a/src/actions.h +++ b/src/actions.h @@ -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 diff --git a/src/main.cpp b/src/app/main.cpp similarity index 99% rename from src/main.cpp rename to src/app/main.cpp index 668eba4..3c5aafb 100644 --- a/src/main.cpp +++ b/src/app/main.cpp @@ -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 #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 // 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 diff --git a/src/bank_panel.cpp b/src/bank_panel.cpp index 5a0b618..94d6fd2 100644 --- a/src/bank_panel.cpp +++ b/src/bank_panel.cpp @@ -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 #include -#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(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(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& 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& sampleIds, const std::string& srcBankId) { @@ -2190,7 +2191,7 @@ void removeSamples(const std::vector& 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 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 ids; const RegionDisplay disp = focusedDisplay(); @@ -2212,7 +2213,7 @@ std::vector resolveDragPathsForOs() { std::vector 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(idx->size()) : 0; const int focus = g_panel.selection.focus; if (focus >= 0) diff --git a/src/bank_panel.h b/src/bank_panel.h index 8a3b5c8..0139852 100644 --- a/src/bank_panel.h +++ b/src/bank_panel.h @@ -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 #include -#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 { diff --git a/src/peaks.cpp b/src/core/audio/peaks.cpp similarity index 98% rename from src/peaks.cpp rename to src/core/audio/peaks.cpp index b2a695a..3c4f03a 100644 --- a/src/peaks.cpp +++ b/src/core/audio/peaks.cpp @@ -1,4 +1,4 @@ -#include "peaks.h" +#include "core/audio/peaks.h" #include #include @@ -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& interleaved, std::size_t channelCount, @@ -123,4 +123,4 @@ std::size_t lastFrameAboveThreshold(const std::vector& interleaved, return kNoFrameAboveThreshold; } -} // namespace reasampler +} // namespace reasampler::audio \ No newline at end of file diff --git a/src/peaks.h b/src/core/audio/peaks.h similarity index 99% rename from src/peaks.h rename to src/core/audio/peaks.h index dc1365a..f1e1ee9 100644 --- a/src/peaks.h +++ b/src/core/audio/peaks.h @@ -11,7 +11,7 @@ #include #include -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& interleaved, std::size_t frameCount, AudioSample linearThreshold); -} // namespace reasampler +} // namespace reasampler::audio \ No newline at end of file diff --git a/src/batch_capture.cpp b/src/core/capture/batch_capture.cpp similarity index 96% rename from src/batch_capture.cpp rename to src/core/capture/batch_capture.cpp index 3e46851..1d85add 100644 --- a/src/batch_capture.cpp +++ b/src/core/capture/batch_capture.cpp @@ -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 -namespace reasampler { +namespace reasampler::capture { std::vector planCaptureUnits(const std::vector& ranges) { std::vector units; @@ -73,4 +73,4 @@ std::string BatchOutcome::summaryLine(const std::string& noun) const { return line; } -} // namespace reasampler +} // namespace reasampler::capture \ No newline at end of file diff --git a/src/batch_capture.h b/src/core/capture/batch_capture.h similarity index 98% rename from src/batch_capture.h rename to src/core/capture/batch_capture.h index 213eb9e..9ad5084 100644 --- a/src/batch_capture.h +++ b/src/core/capture/batch_capture.h @@ -29,7 +29,7 @@ #include #include -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 results_; }; -} // namespace reasampler +} // namespace reasampler::capture \ No newline at end of file diff --git a/src/capture_paths.cpp b/src/core/capture/capture_paths.cpp similarity index 99% rename from src/capture_paths.cpp rename to src/core/capture/capture_paths.cpp index 33bfbce..f7dc629 100644 --- a/src/capture_paths.cpp +++ b/src/core/capture/capture_paths.cpp @@ -1,4 +1,4 @@ -#include "capture_paths.h" +#include "core/capture/capture_paths.h" #include #include @@ -8,7 +8,7 @@ #include #include -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 \ No newline at end of file diff --git a/src/capture_paths.h b/src/core/capture/capture_paths.h similarity index 98% rename from src/capture_paths.h rename to src/core/capture/capture_paths.h index 4ab8026..8c92268 100644 --- a/src/capture_paths.h +++ b/src/core/capture/capture_paths.h @@ -17,7 +17,7 @@ #include #include -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; // /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 "[_].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 \ No newline at end of file diff --git a/src/insert_plan.cpp b/src/core/capture/insert_plan.cpp similarity index 93% rename from src/insert_plan.cpp rename to src/core/capture/insert_plan.cpp index 9c3983e..d9590de 100644 --- a/src/insert_plan.cpp +++ b/src/core/capture/insert_plan.cpp @@ -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 \ No newline at end of file diff --git a/src/insert_plan.h b/src/core/capture/insert_plan.h similarity index 98% rename from src/insert_plan.h rename to src/core/capture/insert_plan.h index 7a6e2cb..a3b9770 100644 --- a/src/insert_plan.h +++ b/src/core/capture/insert_plan.h @@ -22,7 +22,7 @@ #include -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 \ No newline at end of file diff --git a/src/realtime_record.cpp b/src/core/capture/realtime_record.cpp similarity index 98% rename from src/realtime_record.cpp rename to src/core/capture/realtime_record.cpp index 9b287d1..8a42296 100644 --- a/src/realtime_record.cpp +++ b/src/core/capture/realtime_record.cpp @@ -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 \ No newline at end of file diff --git a/src/realtime_record.h b/src/core/capture/realtime_record.h similarity index 98% rename from src/realtime_record.h rename to src/core/capture/realtime_record.h index 82fec4c..3ab269b 100644 --- a/src/realtime_record.h +++ b/src/core/capture/realtime_record.h @@ -24,9 +24,13 @@ #include #include -#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 \ No newline at end of file diff --git a/src/render_settings.cpp b/src/core/capture/render_settings.cpp similarity index 98% rename from src/render_settings.cpp rename to src/core/capture/render_settings.cpp index 041cfa9..d73ff32 100644 --- a/src/render_settings.cpp +++ b/src/core/capture/render_settings.cpp @@ -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 #include #include -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& captureActionTable() { return table; } -} // namespace reasampler +} // namespace reasampler::capture \ No newline at end of file diff --git a/src/render_settings.h b/src/core/capture/render_settings.h similarity index 98% rename from src/render_settings.h rename to src/core/capture/render_settings.h index 8d337eb..75f8e30 100644 --- a/src/render_settings.h +++ b/src/core/capture/render_settings.h @@ -25,9 +25,11 @@ #include #include -#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& captureActionTable(); -} // namespace reasampler +} // namespace reasampler::capture \ No newline at end of file diff --git a/src/tail_control.cpp b/src/core/capture/tail_control.cpp similarity index 97% rename from src/tail_control.cpp rename to src/core/capture/tail_control.cpp index 2683e2e..1ee105d 100644 --- a/src/tail_control.cpp +++ b/src/core/capture/tail_control.cpp @@ -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 #include #include "core/json/json.h" -namespace reasampler { +namespace reasampler::capture { TailMode cycleTailMode(TailMode current) { switch (current) { @@ -128,4 +128,4 @@ std::optional deserializeTailSetting(const std::string& blob) { return out; } -} // namespace reasampler +} // namespace reasampler::capture \ No newline at end of file diff --git a/src/tail_control.h b/src/core/capture/tail_control.h similarity index 95% rename from src/tail_control.h rename to src/core/capture/tail_control.h index 1e7ab93..2ea3855 100644 --- a/src/tail_control.h +++ b/src/core/capture/tail_control.h @@ -12,9 +12,9 @@ #include #include -#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 deserializeTailSetting(const std::string& json); -} // namespace reasampler +} // namespace reasampler::capture \ No newline at end of file diff --git a/src/wav_trim.cpp b/src/core/capture/wav_trim.cpp similarity index 98% rename from src/wav_trim.cpp rename to src/core/capture/wav_trim.cpp index e8265ae..5c4d89b 100644 --- a/src/wav_trim.cpp +++ b/src/core/capture/wav_trim.cpp @@ -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 // 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 \ No newline at end of file diff --git a/src/wav_trim.h b/src/core/capture/wav_trim.h similarity index 97% rename from src/wav_trim.h rename to src/core/capture/wav_trim.h index 7acf5ee..f47eea2 100644 --- a/src/wav_trim.h +++ b/src/core/capture/wav_trim.h @@ -29,9 +29,11 @@ #include #include -#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 \ No newline at end of file diff --git a/src/vst/master_gain.cpp b/src/core/instrument/engine/master_gain.cpp similarity index 85% rename from src/vst/master_gain.cpp rename to src/core/instrument/engine/master_gain.cpp index 00413e4..4fac41a 100644 --- a/src/vst/master_gain.cpp +++ b/src/core/instrument/engine/master_gain.cpp @@ -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 #include #include #include -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 \ No newline at end of file diff --git a/src/vst/master_gain.h b/src/core/instrument/engine/master_gain.h similarity index 97% rename from src/vst/master_gain.h rename to src/core/instrument/engine/master_gain.h index 9ba20aa..dce0d67 100644 --- a/src/vst/master_gain.h +++ b/src/core/instrument/engine/master_gain.h @@ -19,7 +19,7 @@ #include -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 \ No newline at end of file diff --git a/src/vst/pitch_shift.cpp b/src/core/instrument/engine/pitch_shift.cpp similarity index 99% rename from src/vst/pitch_shift.cpp rename to src/core/instrument/engine/pitch_shift.cpp index dd73d16..96df91f 100644 --- a/src/vst/pitch_shift.cpp +++ b/src/core/instrument/engine/pitch_shift.cpp @@ -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 #include #include -namespace reasampler { +namespace reasampler::instrument::engine { namespace { @@ -431,4 +431,4 @@ AudioSample PitchShifter::processImpl(AudioSample in, const SpliceEvent* linked) return static_cast(out); } -} // namespace reasampler +} // namespace reasampler::instrument::engine \ No newline at end of file diff --git a/src/vst/pitch_shift.h b/src/core/instrument/engine/pitch_shift.h similarity index 98% rename from src/vst/pitch_shift.h rename to src/core/instrument/engine/pitch_shift.h index aed90d6..d866826 100644 --- a/src/vst/pitch_shift.h +++ b/src/core/instrument/engine/pitch_shift.h @@ -66,9 +66,11 @@ #include #include -#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 \ No newline at end of file diff --git a/src/vst/sampler_core.cpp b/src/core/instrument/engine/sampler_core.cpp similarity index 99% rename from src/vst/sampler_core.cpp rename to src/core/instrument/engine/sampler_core.cpp index c0920cd..0edf418 100644 --- a/src/vst/sampler_core.cpp +++ b/src/core/instrument/engine/sampler_core.cpp @@ -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 @@ -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 diff --git a/src/vst/sampler_core.h b/src/core/instrument/engine/sampler_core.h similarity index 98% rename from src/vst/sampler_core.h rename to src/core/instrument/engine/sampler_core.h index c2c68bc..354845c 100644 --- a/src/vst/sampler_core.h +++ b/src/core/instrument/engine/sampler_core.h @@ -22,12 +22,19 @@ #include #include -#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 diff --git a/src/vst/velocity_curve.cpp b/src/core/instrument/engine/velocity_curve.cpp similarity index 97% rename from src/vst/velocity_curve.cpp rename to src/core/instrument/engine/velocity_curve.cpp index 248184d..868073a 100644 --- a/src/vst/velocity_curve.cpp +++ b/src/core/instrument/engine/velocity_curve.cpp @@ -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 // std::max, std::min, std::abs, std::stable_sort #include // std::fabs #include // 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 \ No newline at end of file diff --git a/src/vst/velocity_curve.h b/src/core/instrument/engine/velocity_curve.h similarity index 99% rename from src/vst/velocity_curve.h rename to src/core/instrument/engine/velocity_curve.h index 6c761e9..5d1bb27 100644 --- a/src/vst/velocity_curve.h +++ b/src/core/instrument/engine/velocity_curve.h @@ -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 points_; }; -} // namespace reasampler::vst +} // namespace reasampler::instrument::engine \ No newline at end of file diff --git a/src/vst/bank_sync.cpp b/src/core/instrument/map/bank_sync.cpp similarity index 95% rename from src/vst/bank_sync.cpp rename to src/core/instrument/map/bank_sync.cpp index aff6c6b..b88eea7 100644 --- a/src/vst/bank_sync.cpp +++ b/src/core/instrument/map/bank_sync.cpp @@ -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 #include #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& re return d; } -} // namespace reasampler::vst +} // namespace reasampler::instrument::map \ No newline at end of file diff --git a/src/vst/bank_sync.h b/src/core/instrument/map/bank_sync.h similarity index 96% rename from src/vst/bank_sync.h rename to src/core/instrument/map/bank_sync.h index cb1002d..acaf1ad 100644 --- a/src/vst/bank_sync.h +++ b/src/core/instrument/map/bank_sync.h @@ -21,9 +21,11 @@ #include #include -#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& re std::int64_t lastConsumed, bool resolves, bool isFocusedTarget); -} // namespace reasampler::vst +} // namespace reasampler::instrument::map \ No newline at end of file diff --git a/src/vst/bridge_marshal.cpp b/src/core/instrument/map/bridge_marshal.cpp similarity index 80% rename from src/vst/bridge_marshal.cpp rename to src/core/instrument/map/bridge_marshal.cpp index 23d908c..3414c34 100644 --- a/src/vst/bridge_marshal.cpp +++ b/src/core/instrument/map/bridge_marshal.cpp @@ -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 decodeGetProjExtState(int apiReturn, const std::string& buffer) { @@ -13,4 +13,4 @@ std::optional decodeGetProjExtState(int apiReturn, return buffer; } -} // namespace reasampler::vst +} // namespace reasampler::instrument::map \ No newline at end of file diff --git a/src/vst/bridge_marshal.h b/src/core/instrument/map/bridge_marshal.h similarity index 95% rename from src/vst/bridge_marshal.h rename to src/core/instrument/map/bridge_marshal.h index 2c75068..3e432dd 100644 --- a/src/vst/bridge_marshal.h +++ b/src/core/instrument/map/bridge_marshal.h @@ -22,7 +22,7 @@ #include #include -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 decodeGetProjExtState(int apiReturn, const std::string& buffer); -} // namespace reasampler::vst +} // namespace reasampler::instrument::map \ No newline at end of file diff --git a/src/vst/note_entry.cpp b/src/core/instrument/map/note_entry.cpp similarity index 96% rename from src/vst/note_entry.cpp rename to src/core/instrument/map/note_entry.cpp index 99cb354..3e936e3 100644 --- a/src/vst/note_entry.cpp +++ b/src/core/instrument/map/note_entry.cpp @@ -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 #include -namespace reasampler::vst { +namespace reasampler::instrument::map { namespace { char asciiUpper(char c) { @@ -110,4 +110,4 @@ std::optional parseNoteEntry(const std::string& text) { return parseNoteName(s); } -} // namespace reasampler::vst +} // namespace reasampler::instrument::map \ No newline at end of file diff --git a/src/vst/note_entry.h b/src/core/instrument/map/note_entry.h similarity index 95% rename from src/vst/note_entry.h rename to src/core/instrument/map/note_entry.h index de3ae0a..07dbaf0 100644 --- a/src/vst/note_entry.h +++ b/src/core/instrument/map/note_entry.h @@ -22,7 +22,7 @@ #include #include -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 parseNoteEntry(const std::string& text); -} // namespace reasampler::vst +} // namespace reasampler::instrument::map \ No newline at end of file diff --git a/src/vst/sample_map.cpp b/src/core/instrument/map/sample_map.cpp similarity index 98% rename from src/vst/sample_map.cpp rename to src/core/instrument/map/sample_map.cpp index 959582d..7ae5813 100644 --- a/src/vst/sample_map.cpp +++ b/src/core/instrument/map/sample_map.cpp @@ -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 // std::min #include // assert @@ -9,10 +9,12 @@ #include // std::memcpy #include // 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& 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& pts = z.velocityCurve.points(); + const std::vector& pts = z.velocityCurve.points(); putU32le(out, static_cast(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 pts; + std::vector 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 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& 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; } diff --git a/src/vst/sample_map.h b/src/core/instrument/map/sample_map.h similarity index 98% rename from src/vst/sample_map.h rename to src/core/instrument/map/sample_map.h index d8703b9..76764b6 100644 --- a/src/vst/sample_map.h +++ b/src/core/instrument/map/sample_map.h @@ -23,12 +23,18 @@ #include #include -#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) diff --git a/src/vst/trigger_seam.cpp b/src/core/instrument/map/trigger_seam.cpp similarity index 87% rename from src/vst/trigger_seam.cpp rename to src/core/instrument/map/trigger_seam.cpp index 0ee61a4..3bcc81b 100644 --- a/src/vst/trigger_seam.cpp +++ b/src/core/instrument/map/trigger_seam.cpp @@ -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 -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(fadeFraction * static_cast(playLength) + 0.5); } -} // namespace reasampler::vst +} // namespace reasampler::instrument::map \ No newline at end of file diff --git a/src/vst/trigger_seam.h b/src/core/instrument/map/trigger_seam.h similarity index 97% rename from src/vst/trigger_seam.h rename to src/core/instrument/map/trigger_seam.h index d7ce3c3..483bb38 100644 --- a/src/vst/trigger_seam.h +++ b/src/core/instrument/map/trigger_seam.h @@ -25,7 +25,7 @@ #include -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 \ No newline at end of file diff --git a/src/vst/browser_scroll.cpp b/src/core/instrument/ui/browser_scroll.cpp similarity index 88% rename from src/vst/browser_scroll.cpp rename to src/core/instrument/ui/browser_scroll.cpp index 85be0f8..d643f1a 100644 --- a/src/vst/browser_scroll.cpp +++ b/src/core/instrument/ui/browser_scroll.cpp @@ -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 #include -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( static_cast(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 filterNameIndices(const std::vector& names, return out; } -} // namespace reasampler::vst +} // namespace reasampler::instrument::ui \ No newline at end of file diff --git a/src/vst/browser_scroll.h b/src/core/instrument/ui/browser_scroll.h similarity index 97% rename from src/vst/browser_scroll.h rename to src/core/instrument/ui/browser_scroll.h index 81aa831..f04e88c 100644 --- a/src/vst/browser_scroll.h +++ b/src/core/instrument/ui/browser_scroll.h @@ -24,9 +24,9 @@ #include #include -#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 filterNameIndices(const std::vector& names, const std::string& query); -} // namespace reasampler::vst +} // namespace reasampler::instrument::ui \ No newline at end of file diff --git a/src/vst/capture_browser.cpp b/src/core/instrument/ui/capture_browser.cpp similarity index 68% rename from src/vst/capture_browser.cpp rename to src/core/instrument/ui/capture_browser.cpp index a36f63e..969b540 100644 --- a/src/vst/capture_browser.cpp +++ b/src/core/instrument/ui/capture_browser.cpp @@ -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 -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 \ No newline at end of file diff --git a/src/vst/capture_browser.h b/src/core/instrument/ui/capture_browser.h similarity index 96% rename from src/vst/capture_browser.h rename to src/core/instrument/ui/capture_browser.h index 864f378..a6ea38e 100644 --- a/src/vst/capture_browser.h +++ b/src/core/instrument/ui/capture_browser.h @@ -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 \ No newline at end of file diff --git a/src/core/instrument/ui/curve_popup.cpp b/src/core/instrument/ui/curve_popup.cpp new file mode 100644 index 0000000..c01b1b0 --- /dev/null +++ b/src/core/instrument/ui/curve_popup.cpp @@ -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 + +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 \ No newline at end of file diff --git a/src/vst/curve_popup.h b/src/core/instrument/ui/curve_popup.h similarity index 94% rename from src/vst/curve_popup.h rename to src/core/instrument/ui/curve_popup.h index 3909b85..4abbf63 100644 --- a/src/vst/curve_popup.h +++ b/src/core/instrument/ui/curve_popup.h @@ -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 \ No newline at end of file diff --git a/src/vst/editor_geometry.cpp b/src/core/instrument/ui/editor_geometry.cpp similarity index 64% rename from src/vst/editor_geometry.cpp rename to src/core/instrument/ui/editor_geometry.cpp index f838420..cce57f7 100644 --- a/src/vst/editor_geometry.cpp +++ b/src/core/instrument/ui/editor_geometry.cpp @@ -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 -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 \ No newline at end of file diff --git a/src/vst/editor_geometry.h b/src/core/instrument/ui/editor_geometry.h similarity index 91% rename from src/vst/editor_geometry.h rename to src/core/instrument/ui/editor_geometry.h index 8ca0615..50fdaee 100644 --- a/src/vst/editor_geometry.h +++ b/src/core/instrument/ui/editor_geometry.h @@ -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 \ No newline at end of file diff --git a/src/vst/embed_strip.cpp b/src/core/instrument/ui/embed_strip.cpp similarity index 78% rename from src/vst/embed_strip.cpp rename to src/core/instrument/ui/embed_strip.cpp index e1bf0d9..3f1a02e 100644 --- a/src/vst/embed_strip.cpp +++ b/src/core/instrument/ui/embed_strip.cpp @@ -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 -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(l * band.width()); + const int fillW = static_cast(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 \ No newline at end of file diff --git a/src/vst/embed_strip.h b/src/core/instrument/ui/embed_strip.h similarity index 95% rename from src/vst/embed_strip.h rename to src/core/instrument/ui/embed_strip.h index b16a88d..df32b24 100644 --- a/src/vst/embed_strip.h +++ b/src/core/instrument/ui/embed_strip.h @@ -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 \ No newline at end of file diff --git a/src/vst/envelope_edit.cpp b/src/core/instrument/ui/envelope_edit.cpp similarity index 88% rename from src/vst/envelope_edit.cpp rename to src/core/instrument/ui/envelope_edit.cpp index 56381fa..68ab989 100644 --- a/src/vst/envelope_edit.cpp +++ b/src/core/instrument/ui/envelope_edit.cpp @@ -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 #include // 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(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(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(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 \ No newline at end of file diff --git a/src/vst/envelope_edit.h b/src/core/instrument/ui/envelope_edit.h similarity index 96% rename from src/vst/envelope_edit.h rename to src/core/instrument/ui/envelope_edit.h index abd7c62..808667d 100644 --- a/src/vst/envelope_edit.h +++ b/src/core/instrument/ui/envelope_edit.h @@ -42,10 +42,10 @@ #include #include -#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 \ No newline at end of file diff --git a/src/vst/envelope_overlay.cpp b/src/core/instrument/ui/envelope_overlay.cpp similarity index 90% rename from src/vst/envelope_overlay.cpp rename to src/core/instrument/ui/envelope_overlay.cpp index f413e41..2876f51 100644 --- a/src/vst/envelope_overlay.cpp +++ b/src/core/instrument/ui/envelope_overlay.cpp @@ -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 -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(w); if (px > static_cast(w - 1)) px = static_cast(w - 1); - return area.left + static_cast(px + 0.5); + return area.x + static_cast(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(kGateSustainDisplayFraction * static_cast(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((1.0 - level) * static_cast(span) + 0.5); - return area.top + static_cast(dy); + return area.y + static_cast(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(w - 1)) px = static_cast(w - 1); EnvVertex v; v.node = node; - v.x = area.left + static_cast(px + 0.5); + v.x = area.x + static_cast(px + 0.5); v.y = levelToY(area, level); v.level = level; return v; @@ -95,7 +94,7 @@ std::vector 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(W - gateTimedWidth(area)); const double sep = static_cast(kGateNodeSepPx); const double pps = gatePxPerSecond(area); @@ -162,7 +161,7 @@ std::vector triggerPolyline(const AmpEnvelope& env, const Rect& area, std::vector 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 buildEnvelopePolyline(const AmpEnvelope& env, const Rect& : triggerPolyline(env, area, totalSeconds); } -} // namespace reasampler::vst +} // namespace reasampler::instrument::ui \ No newline at end of file diff --git a/src/vst/envelope_overlay.h b/src/core/instrument/ui/envelope_overlay.h similarity index 92% rename from src/vst/envelope_overlay.h rename to src/core/instrument/ui/envelope_overlay.h index fc3780e..ee839cf 100644 --- a/src/vst/envelope_overlay.h +++ b/src/core/instrument/ui/envelope_overlay.h @@ -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 #include -#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 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 \ No newline at end of file diff --git a/src/vst/keyboard_strip.cpp b/src/core/instrument/ui/keyboard_strip.cpp similarity index 86% rename from src/vst/keyboard_strip.cpp rename to src/core/instrument/ui/keyboard_strip.cpp index 98638b1..f6795bb 100644 --- a/src/vst/keyboard_strip.cpp +++ b/src/core/instrument/ui/keyboard_strip.cpp @@ -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 -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 \ No newline at end of file diff --git a/src/vst/keyboard_strip.h b/src/core/instrument/ui/keyboard_strip.h similarity index 96% rename from src/vst/keyboard_strip.h rename to src/core/instrument/ui/keyboard_strip.h index ac8b4c3..2a6902b 100644 --- a/src/vst/keyboard_strip.h +++ b/src/core/instrument/ui/keyboard_strip.h @@ -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 \ No newline at end of file diff --git a/src/vst/knob_deck.cpp b/src/core/instrument/ui/knob_deck.cpp similarity index 79% rename from src/vst/knob_deck.cpp rename to src/core/instrument/ui/knob_deck.cpp index 9df5b8c..06be9b4 100644 --- a/src/vst/knob_deck.cpp +++ b/src/core/instrument/ui/knob_deck.cpp @@ -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 -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& 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 \ No newline at end of file diff --git a/src/vst/knob_deck.h b/src/core/instrument/ui/knob_deck.h similarity index 97% rename from src/vst/knob_deck.h rename to src/core/instrument/ui/knob_deck.h index 5a1a890..5ffa3fc 100644 --- a/src/vst/knob_deck.h +++ b/src/core/instrument/ui/knob_deck.h @@ -27,9 +27,9 @@ #include -#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 \ No newline at end of file diff --git a/src/vst/param_slider.cpp b/src/core/instrument/ui/param_slider.cpp similarity index 71% rename from src/vst/param_slider.cpp rename to src/core/instrument/ui/param_slider.cpp index a080e43..bc2b663 100644 --- a/src/vst/param_slider.cpp +++ b/src/core/instrument/ui/param_slider.cpp @@ -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 #include -namespace reasampler::vst { +namespace reasampler::instrument::ui { + +using util::clamp01; // the ONE unit-interval clamp (Q-W1, T4-24) std::vector layoutControls(const Rect& panel, const std::vector& controls) { std::vector 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 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(value * span + 0.5); + const int span = track.width; // handle-center movable span + const int centerX = track.x + static_cast(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(x - track.left) / static_cast(span); + if (x <= track.x) return 0.0; + if (x >= track.right()) return 1.0; + return static_cast(x - track.x) / static_cast(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& rows, int x, int y) { return -1; } -} // namespace reasampler::vst +} // namespace reasampler::instrument::ui \ No newline at end of file diff --git a/src/vst/param_slider.h b/src/core/instrument/ui/param_slider.h similarity index 97% rename from src/vst/param_slider.h rename to src/core/instrument/ui/param_slider.h index b853eaa..1cddbf7 100644 --- a/src/vst/param_slider.h +++ b/src/core/instrument/ui/param_slider.h @@ -24,9 +24,9 @@ #include -#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& rows, int x, int y); -} // namespace reasampler::vst +} // namespace reasampler::instrument::ui \ No newline at end of file diff --git a/src/vst/waveform_view.cpp b/src/core/instrument/ui/waveform_view.cpp similarity index 88% rename from src/vst/waveform_view.cpp rename to src/core/instrument/ui/waveform_view.cpp index 2b6d70c..0d566d6 100644 --- a/src/vst/waveform_view.cpp +++ b/src/core/instrument/ui/waveform_view.cpp @@ -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 #include // 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(w) + frameCount / 2; - return area.left + static_cast(num / frameCount); + return area.x + static_cast(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(x - area.left); + if (x <= area.x) return 0; + if (x >= area.right()) return frameCount; + const std::int64_t dx = static_cast(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(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 \ No newline at end of file diff --git a/src/vst/waveform_view.h b/src/core/instrument/ui/waveform_view.h similarity index 89% rename from src/vst/waveform_view.h rename to src/core/instrument/ui/waveform_view.h index 4831cbf..34e6948 100644 --- a/src/vst/waveform_view.h +++ b/src/core/instrument/ui/waveform_view.h @@ -24,10 +24,12 @@ #include -#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 \ No newline at end of file diff --git a/src/core/json/json.h b/src/core/json/json.h index ce1c281..2af28c3 100644 --- a/src/core/json/json.h +++ b/src/core/json/json.h @@ -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: diff --git a/src/bank_book.cpp b/src/core/model/bank_book.cpp similarity index 84% rename from src/bank_book.cpp rename to src/core/model/bank_book.cpp index df5b3a9..20372ac 100644 --- a/src/bank_book.cpp +++ b/src/core/model/bank_book.cpp @@ -1,4 +1,4 @@ -#include "bank_book.h" +#include "core/model/bank_book.h" #include #include @@ -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 SlotMap::orderedIds() const { - // entries_ is sorted ascending by slot, so a straight walk is display order. - std::vector 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& 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& 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>& 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 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 indexIds(const BankIndex& idx) { +std::vector indexIds(const BankModel& idx) { std::vector 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: }. + // banks: array of { id, displayName, ordinal, index: }. // 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& 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& 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; diff --git a/src/bank_book.h b/src/core/model/bank_book.h similarity index 78% rename from src/bank_book.h rename to src/core/model/bank_book.h index 9b4c6f8..6a66fe6 100644 --- a/src/bank_book.h +++ b/src/core/model/bank_book.h @@ -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 #include -#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 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& 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& 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>& 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 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(); diff --git a/src/bank_model.cpp b/src/core/model/bank_model.cpp similarity index 96% rename from src/bank_model.cpp rename to src/core/model/bank_model.cpp index 07f92ce..27c8ae8 100644 --- a/src/bank_model.cpp +++ b/src/core/model/bank_model.cpp @@ -1,4 +1,4 @@ -#include "bank_model.h" +#include "core/model/bank_model.h" #include @@ -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 BankIndex::byTier(Tier tier) const { +std::vector BankModel::byTier(Tier tier) const { std::vector 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::deserialize(const std::string& blob) { - BankIndex idx; +std::optional 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 \ No newline at end of file diff --git a/src/bank_model.h b/src/core/model/bank_model.h similarity index 95% rename from src/bank_model.h rename to src/core/model/bank_model.h index 5ad8472..01cf8af 100644 --- a/src/bank_model.h +++ b/src/core/model/bank_model.h @@ -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 #include -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; // 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 deserialize(const std::string& json); + static std::optional deserialize(const std::string& json); private: std::vector samples_; // insertion order preserved }; -} // namespace reasampler +} // namespace reasampler::model \ No newline at end of file diff --git a/src/owned_manifest.cpp b/src/core/model/owned_manifest.cpp similarity index 97% rename from src/owned_manifest.cpp rename to src/core/model/owned_manifest.cpp index ba21411..2dd68af 100644 --- a/src/owned_manifest.cpp +++ b/src/core/model/owned_manifest.cpp @@ -1,4 +1,4 @@ -#include "owned_manifest.h" +#include "core/model/owned_manifest.h" #include @@ -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::deserialize(const std::strin return m; } -} // namespace reasampler +} // namespace reasampler::model \ No newline at end of file diff --git a/src/owned_manifest.h b/src/core/model/owned_manifest.h similarity index 93% rename from src/owned_manifest.h rename to src/core/model/owned_manifest.h index fe3a3d7..629f67e 100644 --- a/src/owned_manifest.h +++ b/src/core/model/owned_manifest.h @@ -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 #include #include -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 paths_; // insertion order; deduplicated }; -} // namespace reasampler +} // namespace reasampler::model \ No newline at end of file diff --git a/src/provenance.cpp b/src/core/model/provenance.cpp similarity index 98% rename from src/provenance.cpp rename to src/core/model/provenance.cpp index 712394f..cd91e7b 100644 --- a/src/provenance.cpp +++ b/src/core/model/provenance.cpp @@ -1,4 +1,4 @@ -#include "provenance.h" +#include "core/model/provenance.h" #include @@ -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 detectParent( return parent; } -} // namespace reasampler +} // namespace reasampler::model \ No newline at end of file diff --git a/src/provenance.h b/src/core/model/provenance.h similarity index 99% rename from src/provenance.h rename to src/core/model/provenance.h index d8f5a66..1dd2ca0 100644 --- a/src/provenance.h +++ b/src/core/model/provenance.h @@ -35,7 +35,7 @@ #include #include -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 detectParent( const std::vector& sourceItemFiles, const std::vector& bankFiles); -} // namespace reasampler +} // namespace reasampler::model \ No newline at end of file diff --git a/src/core/model/slot_map.cpp b/src/core/model/slot_map.cpp new file mode 100644 index 0000000..2d60da7 --- /dev/null +++ b/src/core/model/slot_map.cpp @@ -0,0 +1,145 @@ +#include "core/model/slot_map.h" + +#include + +#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 SlotMap::orderedIds() const { + // entries_ is sorted ascending by slot, so a straight walk is display order. + std::vector 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& 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& 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>& 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 diff --git a/src/core/model/slot_map.h b/src/core/model/slot_map.h new file mode 100644 index 0000000..59f550a --- /dev/null +++ b/src/core/model/slot_map.h @@ -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 +#include +#include +#include + +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 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& 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& 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>& 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 entries_; // kept sorted ascending by slot (invariant) + + void sortBySlot(); +}; + +} // namespace reasampler::model diff --git a/src/core/namespaces.h b/src/core/namespaces.h new file mode 100644 index 0000000..fd5431d --- /dev/null +++ b/src/core/namespaces.h @@ -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 diff --git a/src/prune_reconcile.cpp b/src/core/reclaim/prune_reconcile.cpp similarity index 97% rename from src/prune_reconcile.cpp rename to src/core/reclaim/prune_reconcile.cpp index d605aad..a9b009b 100644 --- a/src/prune_reconcile.cpp +++ b/src/core/reclaim/prune_reconcile.cpp @@ -1,4 +1,4 @@ -#include "prune_reconcile.h" +#include "core/reclaim/prune_reconcile.h" #include @@ -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 pruneOrphans(const std::vector& present, const std::vector& referenced, @@ -83,4 +83,4 @@ std::vector pruneDeletePlan(const std::vector& confirm return plan; } -} // namespace reasampler +} // namespace reasampler::reclaim \ No newline at end of file diff --git a/src/prune_reconcile.h b/src/core/reclaim/prune_reconcile.h similarity index 99% rename from src/prune_reconcile.h rename to src/core/reclaim/prune_reconcile.h index a44660c..5d3809d 100644 --- a/src/prune_reconcile.h +++ b/src/core/reclaim/prune_reconcile.h @@ -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 #include -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& orphans, std::vector pruneDeletePlan(const std::vector& confirmed, const std::vector& freshOrphans); -} // namespace reasampler +} // namespace reasampler::reclaim \ No newline at end of file diff --git a/src/action_bar.cpp b/src/core/ui/action_bar.cpp similarity index 98% rename from src/action_bar.cpp rename to src/core/ui/action_bar.cpp index 3f7535c..65f197d 100644 --- a/src/action_bar.cpp +++ b/src/core/ui/action_bar.cpp @@ -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 -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 \ No newline at end of file diff --git a/src/action_bar.h b/src/core/ui/action_bar.h similarity index 96% rename from src/action_bar.h rename to src/core/ui/action_bar.h index 8e48a7e..286d928 100644 --- a/src/action_bar.h +++ b/src/core/ui/action_bar.h @@ -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 -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 computeBarSlots(const ActionBarRect& bar, int hitTestActionBar(int px, int py, const ActionBarRect& bar, const std::vector& clusters, const ActionBarSpec& spec); -} // namespace reasampler +} // namespace reasampler::ui \ No newline at end of file diff --git a/src/bank_grid.cpp b/src/core/ui/bank_grid.cpp similarity index 98% rename from src/bank_grid.cpp rename to src/core/ui/bank_grid.cpp index 1cc3a53..371296b 100644 --- a/src/bank_grid.cpp +++ b/src/core/ui/bank_grid.cpp @@ -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 #include -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 \ No newline at end of file diff --git a/src/bank_grid.h b/src/core/ui/bank_grid.h similarity index 97% rename from src/bank_grid.h rename to src/core/ui/bank_grid.h index 3616e96..c744208 100644 --- a/src/bank_grid.h +++ b/src/core/ui/bank_grid.h @@ -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 #include -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 \ No newline at end of file diff --git a/src/card_drag.cpp b/src/core/ui/card_drag.cpp similarity index 97% rename from src/card_drag.cpp rename to src/core/ui/card_drag.cpp index afe5971..6289d64 100644 --- a/src/card_drag.cpp +++ b/src/core/ui/card_drag.cpp @@ -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& rects) { return -1; } -} // namespace reasampler +} // namespace reasampler::ui \ No newline at end of file diff --git a/src/card_drag.h b/src/core/ui/card_drag.h similarity index 98% rename from src/card_drag.h rename to src/core/ui/card_drag.h index ef0681b..7ce7a9d 100644 --- a/src/card_drag.h +++ b/src/core/ui/card_drag.h @@ -30,10 +30,10 @@ #include -#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 computeSlotRectsForDrop(int maxSlot, int panelWidth, // callers reason in model slots. int hitTestSlot(int px, int py, const std::vector& rects); -} // namespace reasampler +} // namespace reasampler::ui \ No newline at end of file diff --git a/src/card_meta.cpp b/src/core/ui/card_meta.cpp similarity index 96% rename from src/card_meta.cpp rename to src/core/ui/card_meta.cpp index b2e44e9..a52c4e1 100644 --- a/src/card_meta.cpp +++ b/src/core/ui/card_meta.cpp @@ -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 #include -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 \ No newline at end of file diff --git a/src/card_meta.h b/src/core/ui/card_meta.h similarity index 98% rename from src/card_meta.h rename to src/core/ui/card_meta.h index 1c97fad..22cec45 100644 --- a/src/card_meta.h +++ b/src/core/ui/card_meta.h @@ -11,7 +11,7 @@ #include -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 \ No newline at end of file diff --git a/src/component_geometry.cpp b/src/core/ui/component_geometry.cpp similarity index 97% rename from src/component_geometry.cpp rename to src/core/ui/component_geometry.cpp index 65c1d0e..6d13d75 100644 --- a/src/component_geometry.cpp +++ b/src/core/ui/component_geometry.cpp @@ -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 \ No newline at end of file diff --git a/src/component_geometry.h b/src/core/ui/component_geometry.h similarity index 95% rename from src/component_geometry.h rename to src/core/ui/component_geometry.h index ff8e30f..af0419b 100644 --- a/src/component_geometry.h +++ b/src/core/ui/component_geometry.h @@ -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 \ No newline at end of file diff --git a/src/drag_out.cpp b/src/core/ui/drag_out.cpp similarity index 95% rename from src/drag_out.cpp rename to src/core/ui/drag_out.cpp index db58c65..54018a7 100644 --- a/src/drag_out.cpp +++ b/src/core/ui/drag_out.cpp @@ -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 -namespace reasampler { +namespace reasampler::ui { namespace { @@ -51,4 +51,4 @@ PathList assemblePathList(const std::vector& resolved) { return out; } -} // namespace reasampler +} // namespace reasampler::ui \ No newline at end of file diff --git a/src/drag_out.h b/src/core/ui/drag_out.h similarity index 96% rename from src/drag_out.h rename to src/core/ui/drag_out.h index eacf47f..919284b 100644 --- a/src/drag_out.h +++ b/src/core/ui/drag_out.h @@ -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 #include -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& resolved); -} // namespace reasampler +} // namespace reasampler::ui \ No newline at end of file diff --git a/src/footer_bar.cpp b/src/core/ui/footer_bar.cpp similarity index 96% rename from src/footer_bar.cpp rename to src/core/ui/footer_bar.cpp index b1e6824..ca8145a 100644 --- a/src/footer_bar.cpp +++ b/src/core/ui/footer_bar.cpp @@ -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 \ No newline at end of file diff --git a/src/footer_bar.h b/src/core/ui/footer_bar.h similarity index 88% rename from src/footer_bar.h rename to src/core/ui/footer_bar.h index bc24342..8f4e15e 100644 --- a/src/footer_bar.h +++ b/src/core/ui/footer_bar.h @@ -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 \ No newline at end of file diff --git a/src/mode_enable.cpp b/src/core/ui/mode_enable.cpp similarity index 77% rename from src/mode_enable.cpp rename to src/core/ui/mode_enable.cpp index 84df3ce..fc03d7b 100644 --- a/src/mode_enable.cpp +++ b/src/core/ui/mode_enable.cpp @@ -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 \ No newline at end of file diff --git a/src/mode_enable.h b/src/core/ui/mode_enable.h similarity index 97% rename from src/mode_enable.h rename to src/core/ui/mode_enable.h index 8391a46..be0be30 100644 --- a/src/mode_enable.h +++ b/src/core/ui/mode_enable.h @@ -17,7 +17,7 @@ #include -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 \ No newline at end of file diff --git a/src/overflow_menu.cpp b/src/core/ui/overflow_menu.cpp similarity index 94% rename from src/overflow_menu.cpp rename to src/core/ui/overflow_menu.cpp index 04b1d3b..71c0446 100644 --- a/src/overflow_menu.cpp +++ b/src/core/ui/overflow_menu.cpp @@ -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 \ No newline at end of file diff --git a/src/overflow_menu.h b/src/core/ui/overflow_menu.h similarity index 87% rename from src/overflow_menu.h rename to src/core/ui/overflow_menu.h index 88764fc..64bcfcb 100644 --- a/src/overflow_menu.h +++ b/src/core/ui/overflow_menu.h @@ -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 \ No newline at end of file diff --git a/src/prune_button.cpp b/src/core/ui/prune_button.cpp similarity index 94% rename from src/prune_button.cpp rename to src/core/ui/prune_button.cpp index 424c327..6ac2777 100644 --- a/src/prune_button.cpp +++ b/src/core/ui/prune_button.cpp @@ -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 \ No newline at end of file diff --git a/src/prune_button.h b/src/core/ui/prune_button.h similarity index 90% rename from src/prune_button.h rename to src/core/ui/prune_button.h index ff0e414..f482f01 100644 --- a/src/prune_button.h +++ b/src/core/ui/prune_button.h @@ -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 \ No newline at end of file diff --git a/src/core/ui/rect.h b/src/core/ui/rect.h new file mode 100644 index 0000000..92e64bd --- /dev/null +++ b/src/core/ui/rect.h @@ -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 diff --git a/src/tab_strip.cpp b/src/core/ui/tab_strip.cpp similarity index 98% rename from src/tab_strip.cpp rename to src/core/ui/tab_strip.cpp index 68c57a2..f3b48d0 100644 --- a/src/tab_strip.cpp +++ b/src/core/ui/tab_strip.cpp @@ -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 -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 \ No newline at end of file diff --git a/src/tab_strip.h b/src/core/ui/tab_strip.h similarity index 95% rename from src/tab_strip.h rename to src/core/ui/tab_strip.h index 7932070..c58809c 100644 --- a/src/tab_strip.h +++ b/src/core/ui/tab_strip.h @@ -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 -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 \ No newline at end of file diff --git a/src/theme.cpp b/src/core/ui/theme.cpp similarity index 99% rename from src/theme.cpp rename to src/core/ui/theme.cpp index 5e4bad3..d947d5c 100644 --- a/src/theme.cpp +++ b/src/core/ui/theme.cpp @@ -1,11 +1,11 @@ // theme — pure implementation. See theme.h. NO REAPER / SWELL / LICE / vendor. -#include "theme.h" +#include "core/ui/theme.h" #include #include -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 \ No newline at end of file diff --git a/src/theme.h b/src/core/ui/theme.h similarity index 99% rename from src/theme.h rename to src/core/ui/theme.h index 2137113..4e47916 100644 --- a/src/theme.h +++ b/src/core/ui/theme.h @@ -24,7 +24,7 @@ #include -namespace reasampler { +namespace reasampler::ui { // A straight 8-bit-per-channel RGBA color, LICE-free. The draw shell converts this to a // LICE_pixel via LICE_RGBA at the boundary (draw_kit); nothing here depends on LICE's @@ -115,4 +115,4 @@ double contrastRatio(const KitColor& a, const KitColor& b); // pair the kit actually draws. double textFloor(TextClass cls); -} // namespace reasampler +} // namespace reasampler::ui \ No newline at end of file diff --git a/src/tooltip.cpp b/src/core/ui/tooltip.cpp similarity index 95% rename from src/tooltip.cpp rename to src/core/ui/tooltip.cpp index b9d902a..d47848e 100644 --- a/src/tooltip.cpp +++ b/src/core/ui/tooltip.cpp @@ -1,8 +1,8 @@ // tooltip — pure implementation. See tooltip.h. NO REAPER / SWELL / LICE / vendor. -#include "tooltip.h" +#include "core/ui/tooltip.h" -namespace reasampler { +namespace reasampler::ui { std::string stripActionPrefix(const std::string& fullName, const std::string& prefix) { if (prefix.empty()) return fullName; @@ -50,4 +50,4 @@ TooltipBox computeTooltip(int anchorX, int anchorY, int anchorW, int anchorH, return box; } -} // namespace reasampler +} // namespace reasampler::ui \ No newline at end of file diff --git a/src/tooltip.h b/src/core/ui/tooltip.h similarity index 98% rename from src/tooltip.h rename to src/core/ui/tooltip.h index 38be963..74c5f5b 100644 --- a/src/tooltip.h +++ b/src/core/ui/tooltip.h @@ -13,7 +13,7 @@ #include -namespace reasampler { +namespace reasampler::ui { // The tooltip's box (top-left origin, SWELL/LICE convention). A zero-area rect means "do not // draw" (degenerate inputs); the caller checks empty() before drawing. @@ -59,4 +59,4 @@ TooltipBox computeTooltip(int anchorX, int anchorY, int anchorW, int anchorH, int textW, int textH, int clientW, int clientH, const TooltipSpec& spec); -} // namespace reasampler +} // namespace reasampler::ui \ No newline at end of file diff --git a/src/core/util/clamp01.h b/src/core/util/clamp01.h new file mode 100644 index 0000000..0a141a0 --- /dev/null +++ b/src/core/util/clamp01.h @@ -0,0 +1,14 @@ +#pragma once +// clamp01 — the ONE unit-interval clamp (Q-W1, T4-24). Replaces the per-module +// static copies (master_gain / param_slider / envelope_overlay / reasampler_editor). +// Deliberately the ternary form: comparisons with NaN are false, so a NaN input +// passes through unchanged rather than silently collapsing to a bound — the +// behavior of the majority of the retired copies. +// +// PURE: standard library only (not even that). + +namespace reasampler::util { + +constexpr double clamp01(double v) { return v < 0.0 ? 0.0 : (v > 1.0 ? 1.0 : v); } + +} // namespace reasampler::util diff --git a/src/core/util/file_bytes.cpp b/src/core/util/file_bytes.cpp index 09962e2..3a7b879 100644 --- a/src/core/util/file_bytes.cpp +++ b/src/core/util/file_bytes.cpp @@ -4,7 +4,7 @@ #include -namespace reasampler { +namespace reasampler::util { std::vector readFileBytes(const std::string& path) { std::ifstream f(path, std::ios::binary | std::ios::ate); @@ -18,4 +18,4 @@ std::vector readFileBytes(const std::string& path) { return bytes; } -} // namespace reasampler +} // namespace reasampler::util \ No newline at end of file diff --git a/src/core/util/file_bytes.h b/src/core/util/file_bytes.h index b1ee4bf..16fe9d4 100644 --- a/src/core/util/file_bytes.h +++ b/src/core/util/file_bytes.h @@ -9,11 +9,11 @@ #include #include -namespace reasampler { +namespace reasampler::util { // Reads the whole file at `path` into a byte buffer. Empty on ANY failure — // unopenable, empty file, or short read — so the caller has exactly one // "nothing to work with" branch. std::vector readFileBytes(const std::string& path); -} // namespace reasampler +} // namespace reasampler::util \ No newline at end of file diff --git a/src/app_version.cpp b/src/core/version/app_version.cpp similarity index 98% rename from src/app_version.cpp rename to src/core/version/app_version.cpp index 247c24a..c84d212 100644 --- a/src/app_version.cpp +++ b/src/core/version/app_version.cpp @@ -6,13 +6,13 @@ // parse/compare/classify logic (V1). No #ifdef forks leak beyond this file; the shells // consume the accessors below, so channel identity is one auditable definition. -#include "app_version.h" +#include "core/version/app_version.h" #include #include "version_generated.h" // REASAMPLER_VERSION_STRING + REASAMPLER_CHANNEL_IS_BETA -namespace reasampler { +namespace reasampler::version { namespace { // The one channel predicate every derivation below branches on — the single point the @@ -166,4 +166,4 @@ WritingVersion classifyWritingVersion(const std::string& rawStamp) { return wv; } -} // namespace reasampler +} // namespace reasampler::version \ No newline at end of file diff --git a/src/app_version.h b/src/core/version/app_version.h similarity index 99% rename from src/app_version.h rename to src/core/version/app_version.h index f940e6f..ea04abc 100644 --- a/src/app_version.h +++ b/src/core/version/app_version.h @@ -36,7 +36,7 @@ #include #include -namespace reasampler { +namespace reasampler::version { // --- Channel identity (V4, beta-in-isolation) --------------------------------------- // @@ -199,4 +199,4 @@ struct WritingVersion { // with the raw ext-state read and never has to reason about the cases itself. WritingVersion classifyWritingVersion(const std::string& rawStamp); -} // namespace reasampler +} // namespace reasampler::version \ No newline at end of file diff --git a/src/version_generated.h.in b/src/core/version/version_generated.h.in similarity index 100% rename from src/version_generated.h.in rename to src/core/version/version_generated.h.in diff --git a/src/guid_diff.cpp b/src/core/view/guid_diff.cpp similarity index 94% rename from src/guid_diff.cpp rename to src/core/view/guid_diff.cpp index 9a8253c..be4a2af 100644 --- a/src/guid_diff.cpp +++ b/src/core/view/guid_diff.cpp @@ -1,11 +1,11 @@ // guid_diff implementation — pure set arithmetic for new-content detection. See // guid_diff.h. No REAPER, no SWELL — std only. -#include "guid_diff.h" +#include "core/view/guid_diff.h" #include -namespace reasampler { +namespace reasampler::view { std::vector newGuids(const std::set& previous, const std::set& current) { @@ -41,4 +41,4 @@ void GuidBaseline::reset() { primed_ = false; // next observe() re-baselines (first-poll guard re-armed) } -} // namespace reasampler +} // namespace reasampler::view \ No newline at end of file diff --git a/src/guid_diff.h b/src/core/view/guid_diff.h similarity index 98% rename from src/guid_diff.h rename to src/core/view/guid_diff.h index fc4d4c8..2c7bfc3 100644 --- a/src/guid_diff.h +++ b/src/core/view/guid_diff.h @@ -17,7 +17,7 @@ #include #include -namespace reasampler { +namespace reasampler::view { // The GUIDs present in `current` but absent from `previous` — i.e. new since the // previous poll. Order is the set's ascending order (deterministic; the caller does @@ -59,4 +59,4 @@ private: bool primed_ = false; // false ⇒ next observe() sets the baseline }; -} // namespace reasampler +} // namespace reasampler::view \ No newline at end of file diff --git a/src/lane_keys.cpp b/src/core/view/lane_keys.cpp similarity index 95% rename from src/lane_keys.cpp rename to src/core/view/lane_keys.cpp index 41dcf41..b241ff7 100644 --- a/src/lane_keys.cpp +++ b/src/core/view/lane_keys.cpp @@ -1,10 +1,10 @@ // lane_keys implementation — pure string convention, no REAPER. See lane_keys.h. -#include "lane_keys.h" +#include "core/view/lane_keys.h" #include -namespace reasampler { +namespace reasampler::view { namespace { // Does `s` start with the managed-lane prefix? @@ -48,4 +48,4 @@ bool isOnManualLane(bool isFixedLaneTrack, const std::string& laneName) { return !hasManagedPrefix(laneName); } -} // namespace reasampler +} // namespace reasampler::view \ No newline at end of file diff --git a/src/lane_keys.h b/src/core/view/lane_keys.h similarity index 98% rename from src/lane_keys.h rename to src/core/view/lane_keys.h index a2a6446..6799704 100644 --- a/src/lane_keys.h +++ b/src/core/view/lane_keys.h @@ -33,7 +33,7 @@ #include #include -namespace reasampler { +namespace reasampler::view { // The prefix the tool stamps on every lane NAME it mints. A lane name carrying this // prefix is a managed lane the tool created; any other name (or an empty/unnamed lane) @@ -82,4 +82,4 @@ std::optional modeIdFromLaneName(const std::string& laneName); // inputs (I_FREEMODE result, P_LANENAME string) and never re-derives this logic. bool isOnManualLane(bool isFixedLaneTrack, const std::string& laneName); -} // namespace reasampler +} // namespace reasampler::view \ No newline at end of file diff --git a/src/mode_switch.cpp b/src/core/view/mode_switch.cpp similarity index 96% rename from src/mode_switch.cpp rename to src/core/view/mode_switch.cpp index 204c481..881af49 100644 --- a/src/mode_switch.cpp +++ b/src/core/view/mode_switch.cpp @@ -1,10 +1,10 @@ // mode_switch — pure implementation. See mode_switch.h. NO REAPER / SWELL / vendor. -#include "mode_switch.h" +#include "core/view/mode_switch.h" #include -namespace reasampler { +namespace reasampler::view { namespace { @@ -61,4 +61,4 @@ int hitTestSegment(int px, int py, const HeaderRect& header, int segmentCount) { return -1; } -} // namespace reasampler +} // namespace reasampler::view \ No newline at end of file diff --git a/src/mode_switch.h b/src/core/view/mode_switch.h similarity index 84% rename from src/mode_switch.h rename to src/core/view/mode_switch.h index 4813a8c..07c9ce7 100644 --- a/src/mode_switch.h +++ b/src/core/view/mode_switch.h @@ -1,4 +1,5 @@ #pragma once +#include "core/ui/rect.h" // mode_switch — the REAPER-free layout math behind the bank_panel's Design-View // mode switch (Phase D, Wave 4 — D5). A segmented control `[ Arrange | Design ]` // (N-mode general, one segment per registered mode) drawn in a fixed-height header @@ -13,35 +14,17 @@ #include -namespace reasampler { +namespace reasampler::view { // The header strip the switch is 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 at the top of its client area and offsets the grid below. -struct HeaderRect { - int x = 0; - int y = 0; - int width = 0; - int height = 0; - - bool operator==(const HeaderRect& o) const { - return x == o.x && y == o.y && width == o.width && height == o.height; - } -}; +using HeaderRect = ui::Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h), role-aliased // One segment's pixel rectangle within the header, top-left origin. These are the // draw bounds for one mode's button; the panel draws the mode's display name inside // it and lights it when it is the active mode. -struct SegmentRect { - int x = 0; - int y = 0; - int width = 0; - int height = 0; - - bool operator==(const SegmentRect& o) const { - return x == o.x && y == o.y && width == o.width && height == o.height; - } -}; +using SegmentRect = ui::Rect; // Q-W1: the shared concrete ui::Rect (core/ui/rect.h), role-aliased // Divides `header` into `segmentCount` equal segments left-to-right, in the caller's // order (the panel passes modes in ordinal order). Returns exactly segmentCount @@ -63,4 +46,4 @@ std::vector computeSegmentRects(const HeaderRect& header, // the panel drew there. int hitTestSegment(int px, int py, const HeaderRect& header, int segmentCount); -} // namespace reasampler +} // namespace reasampler::view \ No newline at end of file diff --git a/src/view_mode_model.cpp b/src/core/view/view_mode_model.cpp similarity index 99% rename from src/view_mode_model.cpp rename to src/core/view/view_mode_model.cpp index c215e56..f2cfbfc 100644 --- a/src/view_mode_model.cpp +++ b/src/core/view/view_mode_model.cpp @@ -1,4 +1,4 @@ -#include "view_mode_model.h" +#include "core/view/view_mode_model.h" #include #include @@ -6,7 +6,7 @@ #include #include "core/json/json.h" -#include "lane_keys.h" // laneNameForMode — the ONE durable managed-lane-key convention +#include "core/view/lane_keys.h" // laneNameForMode — the ONE durable managed-lane-key convention // view_mode_model implementation. // @@ -19,6 +19,10 @@ namespace reasampler { +// Q-W1 interim: laneNameForMode lives in reasampler::view now; this god module +// re-namespaces in its own split wave. +using view::laneNameForMode; + // --------------------------------------------------------------------------- // equality // --------------------------------------------------------------------------- diff --git a/src/view_mode_model.h b/src/core/view/view_mode_model.h similarity index 100% rename from src/view_mode_model.h rename to src/core/view/view_mode_model.h diff --git a/src/view_tree.cpp b/src/core/view/view_tree.cpp similarity index 93% rename from src/view_tree.cpp rename to src/core/view/view_tree.cpp index 3f5a966..65b239d 100644 --- a/src/view_tree.cpp +++ b/src/core/view/view_tree.cpp @@ -1,8 +1,8 @@ // view_tree — pure folder-depth walk. See view_tree.h. -#include "view_tree.h" +#include "core/view/view_tree.h" -namespace reasampler { +namespace reasampler::view { FolderTree buildFolderTree(const std::vector& entries) { FolderTree tree; @@ -38,4 +38,4 @@ FolderTree buildFolderTree(const std::vector& entries) { return tree; } -} // namespace reasampler +} // namespace reasampler::view \ No newline at end of file diff --git a/src/view_tree.h b/src/core/view/view_tree.h similarity index 93% rename from src/view_tree.h rename to src/core/view/view_tree.h index f349d84..ad63a91 100644 --- a/src/view_tree.h +++ b/src/core/view/view_tree.h @@ -11,9 +11,9 @@ #include #include -#include "view_mode_model.h" +#include "core/view/view_mode_model.h" -namespace reasampler { +namespace reasampler::view { // One track's contribution to the folder walk, read from REAPER in arrange order. // folderDepth is I_FOLDERDEPTH verbatim: 0 = normal, 1 = folder parent (opens a @@ -30,4 +30,4 @@ struct TrackFolderEntry { // is clamped to empty) so a corrupt/stale project can never fault the shell. FolderTree buildFolderTree(const std::vector& entries); -} // namespace reasampler +} // namespace reasampler::view \ No newline at end of file diff --git a/src/assignment_request.cpp b/src/core/wire/assignment_request.cpp similarity index 92% rename from src/assignment_request.cpp rename to src/core/wire/assignment_request.cpp index bbe5775..5ca645d 100644 --- a/src/assignment_request.cpp +++ b/src/core/wire/assignment_request.cpp @@ -1,10 +1,10 @@ // assignment_request.cpp — see assignment_request.h. Pure: standard library only. -#include "assignment_request.h" +#include "core/wire/assignment_request.h" #include "core/wire/wire.h" -namespace reasampler { +namespace reasampler::wire { namespace { @@ -40,4 +40,4 @@ std::optional decodeAssignmentRequest(const std::string& wire return req; } -} // namespace reasampler +} // namespace reasampler::wire \ No newline at end of file diff --git a/src/assignment_request.h b/src/core/wire/assignment_request.h similarity index 97% rename from src/assignment_request.h rename to src/core/wire/assignment_request.h index 46c1e03..5847405 100644 --- a/src/assignment_request.h +++ b/src/core/wire/assignment_request.h @@ -41,11 +41,11 @@ #include #include -namespace reasampler { +namespace reasampler::wire { // One assignment request: the ingested sample's identity + a monotonic disambiguator. // bankId — the bank the sample was ingested into (the active/target bank). -// sampleId — the ingested Sample's stable id (BankIndex key). +// sampleId — the ingested Sample's stable id (BankModel key). // generation — a monotonic value the reader compares to detect a NEW request. The // writer supplies a unix-epoch-seconds stamp; the reader treats it as an // opaque "did this change?" token, not a wall-clock it interprets. @@ -86,4 +86,4 @@ std::string encodeAssignmentRequest(const AssignmentRequest& req); // silently, never crashing or selecting a nonexistent entry. std::optional decodeAssignmentRequest(const std::string& wire); -} // namespace reasampler +} // namespace reasampler::wire \ No newline at end of file diff --git a/src/instrument_drop.cpp b/src/core/wire/instrument_drop.cpp similarity index 93% rename from src/instrument_drop.cpp rename to src/core/wire/instrument_drop.cpp index 9db7320..36dfd9d 100644 --- a/src/instrument_drop.cpp +++ b/src/core/wire/instrument_drop.cpp @@ -2,14 +2,14 @@ // NO REAPER / SWELL / VST3 SDK / vendor. Reuses sample_map's ComponentState serializer and // the SDK-free UID macros (vst/reasampler_uid.h). -#include "instrument_drop.h" +#include "core/wire/instrument_drop.h" #include -#include "vst/reasampler_uid.h" // REASAMPLER_ACTIVE_UID_* — the FROZEN, channel-selected class UID -#include "vst/sample_map.h" // ComponentState + serializeComponentState (the SHARED writer) +#include "shell/instrument/reasampler_uid.h" // REASAMPLER_ACTIVE_UID_* — the FROZEN, channel-selected class UID +#include "core/instrument/map/sample_map.h" // ComponentState + serializeComponentState (the SHARED writer) -namespace reasampler { +namespace reasampler::wire { namespace { @@ -106,4 +106,4 @@ bool infoNamesFxHotspot(const std::string& info) { return startsWith("fx_") || startsWith("tcp.fx") || startsWith("mcp.fx"); } -} // namespace reasampler +} // namespace reasampler::wire \ No newline at end of file diff --git a/src/instrument_drop.h b/src/core/wire/instrument_drop.h similarity index 99% rename from src/instrument_drop.h rename to src/core/wire/instrument_drop.h index 7576114..d44de6d 100644 --- a/src/instrument_drop.h +++ b/src/core/wire/instrument_drop.h @@ -36,7 +36,7 @@ #include #include -namespace reasampler { +namespace reasampler::wire { // The 32-char uppercase-hex class-ID string of THIS build's channel-active ReaSampler 9000 // VST3 class UID — exactly what Steinberg::FUID::toString renders and what a .vstpreset @@ -111,4 +111,4 @@ bool infoNamesFxHotspot(const std::string& info); // its setState expects. Not called by the shell (which uses the .vstpreset image). std::vector instrumentDropStateBytes(const std::string& sampleId); -} // namespace reasampler +} // namespace reasampler::wire \ No newline at end of file diff --git a/src/sample_usage.cpp b/src/core/wire/sample_usage.cpp similarity index 99% rename from src/sample_usage.cpp rename to src/core/wire/sample_usage.cpp index 88d64f0..3f45708 100644 --- a/src/sample_usage.cpp +++ b/src/core/wire/sample_usage.cpp @@ -1,12 +1,12 @@ // sample_usage.cpp — see sample_usage.h. Pure: standard library only. -#include "sample_usage.h" +#include "core/wire/sample_usage.h" #include #include "core/wire/wire.h" -namespace reasampler { +namespace reasampler::wire { namespace { @@ -230,4 +230,4 @@ bool identityMatches(const std::string& identity, const std::string& uidHexUpper return !nameUpper.empty() && up.find(nameUpper) != std::string::npos; } -} // namespace reasampler +} // namespace reasampler::wire \ No newline at end of file diff --git a/src/sample_usage.h b/src/core/wire/sample_usage.h similarity index 99% rename from src/sample_usage.h rename to src/core/wire/sample_usage.h index 3e90fee..1bef5eb 100644 --- a/src/sample_usage.h +++ b/src/core/wire/sample_usage.h @@ -105,7 +105,7 @@ #include #include -namespace reasampler { +namespace reasampler::wire { // One held capture: the bank sample id (attribution/debugging) + the project-relative // WAV path (the prune-protection payload — compared by EXACT string against the prune @@ -250,4 +250,4 @@ bool identityMatches(const std::string& identity, const std::string& uidHexUpper // ASCII-only uppercase (shared by the matcher and the shell's needle preparation). std::string toUpperAscii(const std::string& s); -} // namespace reasampler +} // namespace reasampler::wire \ No newline at end of file diff --git a/src/ext_keys.h b/src/ext_keys.h index 674e1f9..7fcb56d 100644 --- a/src/ext_keys.h +++ b/src/ext_keys.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" #pragma once // ext_keys — the SINGLE SOURCE OF TRUTH for the "reasampler" project ext-state // namespace + key names, shared by the extension (writer, via persist.h) and the @@ -15,7 +16,7 @@ // stored state. Changing any of them orphans that state. See persist.h for the // per-key retirement / migration semantics — this header only owns the spellings. -#include "app_version.h" +#include "core/version/app_version.h" namespace reasampler { diff --git a/src/ingest.cpp b/src/ingest.cpp index 376a8f5..250b201 100644 --- a/src/ingest.cpp +++ b/src/ingest.cpp @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // ingest.cpp — the S8 "ingest through the bank" shell (extension side). See ingest.h. // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h WITHOUT @@ -16,18 +17,18 @@ #include #include "actions.h" // persistBankOp — shared undo-block wrapper (R-B path) -#include "app_version.h" // channelCommandId / channelActionName -#include "assignment_request.h" // pure (bankId, sampleId, generation) encode -#include "bank_book.h" // BankBook, Bank, activeBankId / activeIndex -#include "bank_model.h" // Sample, AddResult, findByHash +#include "core/version/app_version.h" // channelCommandId / channelActionName +#include "core/wire/assignment_request.h" // pure (bankId, sampleId, generation) encode +#include "core/model/bank_book.h" // BankBook, Bank, activeBankId / activeIndex +#include "core/model/bank_model.h" // Sample, AddResult, findByHash #include "bank_panel.h" // bankPanelRefresh -#include "capture_paths.h" // deriveBankPaths / projectDirOfRpp / hashWavContent +#include "core/capture/capture_paths.h" // deriveBankPaths / projectDirOfRpp / hashWavContent #include "core/util/file_bytes.h" // shared whole-file loader (Q-W1, T2-03) -#include "instrument_drop.h" // pure buildInstrumentDropPreset (.vstpreset image for a sampleId) -#include "instrument_drop_win.h" // shell loadInstrumentOntoTrack (FX add+apply, no own undo block) +#include "core/wire/instrument_drop.h" // pure buildInstrumentDropPreset (.vstpreset image for a sampleId) +#include "shell/actions/instrument_drop_win.h" // shell loadInstrumentOntoTrack (FX add+apply, no own undo block) #include "persist.h" // ReaSamplerSession -#include "wav_trim.h" // parseWavLayout — 32f-float WAV validator for the fast path +#include "core/capture/wav_trim.h" // parseWavLayout — 32f-float WAV validator for the fast path #include "reaper_plugin.h" // reaper_plugin_info_t, gaccel_register_t (full defs) diff --git a/src/ingest.h b/src/ingest.h index a6b2203..3fcd40d 100644 --- a/src/ingest.h +++ b/src/ingest.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" #pragma once // ingest — the S8 "ingest through the bank" shell (EXTENSION side). // diff --git a/src/persist.cpp b/src/persist.cpp index d553925..dfb3e8f 100644 --- a/src/persist.cpp +++ b/src/persist.cpp @@ -1,4 +1,5 @@ -// persist.cpp — REAPER-facing implementation of the BankIndex <-> project +#include "core/namespaces.h" +// persist.cpp — REAPER-facing implementation of the BankModel <-> project // ext-state bridge (M4). See persist.h for the contract. // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h @@ -93,11 +94,11 @@ #include #endif -#include "app_version.h" -#include "capture_paths.h" -#include "prune_reconcile.h" -#include "usage_scan.h" // liveInstanceHeldPaths (pS-usage: instance holds join `referenced`) -#include "vst/bank_sync.h" // parseBankGeneration / formatBankGeneration (SHARED with the instrument reader) +#include "core/version/app_version.h" +#include "core/capture/capture_paths.h" +#include "core/reclaim/prune_reconcile.h" +#include "shell/persist/usage_scan.h" // liveInstanceHeldPaths (pS-usage: instance holds join `referenced`) +#include "core/instrument/map/bank_sync.h" // parseBankGeneration / formatBankGeneration (SHARED with the instrument reader) #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_EnumProjects @@ -258,11 +259,11 @@ bool ReaSamplerSession::saveToActiveProject() { // seam so the counter and MarkProjectDirty stay paired. The value is whatever // bumpBankGeneration() advanced it to since the last save (0 if never bumped / pre-S9), so // every content mutation's own save carries the fresh generation the instrument reads. The - // format is the SHARED pure encoder (vst::formatBankGeneration) so writer and reader agree + // format is the SHARED pure encoder (instrument::map::formatBankGeneration) so writer and reader agree // byte-for-byte — a decimal integer. Additive: does not disturb the blobs above. SetProjExtState(static_cast(proj), projExtNamespace(), kProjExtBankGenKey, - vst::formatBankGeneration(bankGeneration_).c_str()); + instrument::map::formatBankGeneration(bankGeneration_).c_str()); MarkProjectDirty(static_cast(proj)); return true; @@ -636,7 +637,7 @@ void ReaSamplerSession::loadFromProject(void* proj, const std::string& projectDi // rather than resetting to 0 on reopen — a next bump then reads > the stored value. A // project switch reads THAT project's counter, not the previous one's; an absent/malformed // stamp (pre-S9 or corrupt) parses to 0 via the SHARED decoder. proj == nullptr -> 0. - bankGeneration_ = vst::parseBankGeneration( + bankGeneration_ = instrument::map::parseBankGeneration( proj ? getProjExtStateString(static_cast(proj), projExtNamespace(), kProjExtBankGenKey) : std::string{}); @@ -683,7 +684,7 @@ void ReaSamplerSession::loadFromProject(void* proj, const std::string& projectDi // Idempotent, so a fresh empty book is a cheap no-op. book_.reconcileSlots(); - // Project-relative resolution is a READ-time concern: every BankIndex in the book + // Project-relative resolution is a READ-time concern: every BankModel in the book // stores only relative paths (invariant, enforced per-bank at add()), and consumers // (M5 panel, M6 insert) resolve each entry against the CURRENT project dir via // resolveBankFile(projectDir, relativePath). We do NOT rewrite stored paths to diff --git a/src/persist.h b/src/persist.h index 52b5cfc..1c9cbb2 100644 --- a/src/persist.h +++ b/src/persist.h @@ -1,12 +1,13 @@ +#include "core/namespaces.h" #pragma once -// persist — the REAPER-facing bridge between the in-memory BankIndex and project +// persist — the REAPER-facing bridge between the in-memory BankModel and project // ext state (CLAUDE.md §load-bearing split; CONTEXT.md §Persistence & paths). // -// Save: serialize the BankIndex JSON -> SetProjExtState under namespace +// Save: serialize the BankModel JSON -> SetProjExtState under namespace // "reasampler" (ext state lives inside the .rpp, so the index travels with the // project for free). // Load: on project load, GetProjExtState -> bank_model::deserialize -> in-memory -// BankIndex, then resolve each entry's bank file against the CURRENT project +// BankModel, then resolve each entry's bank file against the CURRENT project // dir (project-relative resolution — a project opened from a new location still // finds its bank). // Save-As: when the project path changes, relocate the physical bank folder so @@ -20,14 +21,14 @@ #include #include -#include "app_version.h" -#include "bank_book.h" -#include "bank_model.h" +#include "core/version/app_version.h" +#include "core/model/bank_book.h" +#include "core/model/bank_model.h" #include "ext_keys.h" -#include "owned_manifest.h" -#include "prune_reconcile.h" -#include "tail_control.h" -#include "view_mode_model.h" +#include "core/model/owned_manifest.h" +#include "core/reclaim/prune_reconcile.h" +#include "core/capture/tail_control.h" +#include "core/view/view_mode_model.h" namespace reasampler { @@ -100,21 +101,21 @@ public: ReaSamplerSession() = default; // The multi-bank book (Phase B): the pool + named banks, each wrapping a - // BankIndex, plus the active-bank id. The action layer (B3) creates / renames / + // BankModel, plus the active-bank id. The action layer (B3) creates / renames / // reorders / deletes banks and moves samples here; the panel (B4) reads it; // persist serializes it under the `banks` key on save and replaces it on load. BankBook& book() { return book_; } const BankBook& book() const { return book_; } - // The capture add-target: the ACTIVE bank's BankIndex (defaults to the pool). + // The capture add-target: the ACTIVE bank's BankModel (defaults to the pool). // The capture path adds a captured Sample through this seam, so a capture lands // in whichever bank is active — the single behavioural change B2 wires in over // M7/M8 (the capture backends are untouched; only the target index moved). The // panel/insert readers that displayed the single index continue to read it here // unchanged; today it resolves to the pool (default active), matching prior // single-bank behaviour, until B3/B4 let the user switch the active bank. - BankIndex& bank() { return book_.activeIndex(); } - const BankIndex& bank() const { return book_.activeIndex(); } + BankModel& bank() { return book_.activeIndex(); } + const BankModel& bank() const { return book_.activeIndex(); } // The in-memory Design-View model. The view/action layer mutates it (tag, // toggle, snapshot); persist serializes it on save and replaces it on project @@ -222,7 +223,7 @@ public: // routing). Non-throwing: every filesystem call uses error_code forms; a per-file // failure (locked, already gone) is recorded and skipped, never thrown across the C ABI. // - // Does NOT modify the BankIndex/book (orphans are unreferenced by definition) and does + // Does NOT modify the BankModel/book (orphans are unreferenced by definition) and does // NOT modify the OwnedFileManifest (a reclaimed file drops out of the (owned ∩ present) // algebra naturally once it is off disk — no persist write, so no undo-point question // and no risk to the referenced/owned safety). Writes NO ext-state at all. diff --git a/src/drag_out_win.cpp b/src/shell/actions/drag_out_win.cpp similarity index 99% rename from src/drag_out_win.cpp rename to src/shell/actions/drag_out_win.cpp index e555f24..3aa6e83 100644 --- a/src/drag_out_win.cpp +++ b/src/shell/actions/drag_out_win.cpp @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // drag_out_win — OS/COM initiation of native OS drag-out (M11). See drag_out_win.h. // // Windows path (primary): a hand-rolled minimal IDataObject exposing exactly one format, @@ -9,7 +10,7 @@ // Compiled into the reaper_reasampler MODULE. No REAPER API is used here (pure OS/COM); it // is a leaf the bank_panel calls. -#include "drag_out_win.h" +#include "shell/actions/drag_out_win.h" #ifdef _WIN32 diff --git a/src/drag_out_win.h b/src/shell/actions/drag_out_win.h similarity index 98% rename from src/drag_out_win.h rename to src/shell/actions/drag_out_win.h index 04ccb6b..4382c82 100644 --- a/src/drag_out_win.h +++ b/src/shell/actions/drag_out_win.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" #pragma once // drag_out_win — the OS/COM initiation half of native OS drag-out (Milestone 11). The pure // gesture-boundary decision and path-list assembly live in drag_out.*; THIS is the platform diff --git a/src/instrument_drop_win.cpp b/src/shell/actions/instrument_drop_win.cpp similarity index 96% rename from src/instrument_drop_win.cpp rename to src/shell/actions/instrument_drop_win.cpp index 363c9c8..763d3ba 100644 --- a/src/instrument_drop_win.cpp +++ b/src/shell/actions/instrument_drop_win.cpp @@ -1,9 +1,10 @@ +#include "core/namespaces.h" // instrument_drop_win — the REAPER shell for S17 drop-and-load. See instrument_drop_win.h. // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h WITHOUT // REAPERAPI_IMPLEMENT (main.cpp owns the pointers; here they are extern via the WANT list). -#include "instrument_drop_win.h" +#include "shell/actions/instrument_drop_win.h" #include #include @@ -13,8 +14,8 @@ #include #include -#include "app_version.h" // vstPluginName() — the CHANNEL-correct FX name (stable/beta pairing) -#include "instrument_drop.h" // infoNamesFxHotspot — the PURE, unit-tested hotspot classifier +#include "core/version/app_version.h" // vstPluginName() — the CHANNEL-correct FX name (stable/beta pairing) +#include "core/wire/instrument_drop.h" // infoNamesFxHotspot — the PURE, unit-tested hotspot classifier #include "reaper_plugin.h" diff --git a/src/instrument_drop_win.h b/src/shell/actions/instrument_drop_win.h similarity index 99% rename from src/instrument_drop_win.h rename to src/shell/actions/instrument_drop_win.h index 20a65cb..161b900 100644 --- a/src/instrument_drop_win.h +++ b/src/shell/actions/instrument_drop_win.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" #pragma once // instrument_drop_win — the REAPER-facing shell half of S17 drop-and-load. The pure gesture // decision lives in drag_out (DragGesture::InstrumentDrop) and the pure payload construction diff --git a/src/capture.cpp b/src/shell/capture/capture.cpp similarity index 99% rename from src/capture.cpp rename to src/shell/capture/capture.cpp index b53cb46..0269c07 100644 --- a/src/capture.cpp +++ b/src/shell/capture/capture.cpp @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // capture.cpp — REAPER-facing offline-render backend (OfflineRenderBackend). // // Compiled into the reaper_reasampler MODULE. Includes @@ -33,7 +34,7 @@ // is the realtime-record backend (M8), which captures the master bus output to a // temp track during playback and never invokes the offline render pipeline. -#include "capture.h" +#include "shell/capture/capture.h" #include #include @@ -42,9 +43,9 @@ #include #include -#include "capture_paths.h" +#include "core/capture/capture_paths.h" #include "core/util/file_bytes.h" -#include "render_settings.h" +#include "core/capture/render_settings.h" #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_EnumProjects diff --git a/src/capture.h b/src/shell/capture/capture.h similarity index 98% rename from src/capture.h rename to src/shell/capture/capture.h index 03b61ca..48190f8 100644 --- a/src/capture.h +++ b/src/shell/capture/capture.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" #pragma once // capture — the REAPER-facing capture shell (CLAUDE.md §load-bearing split). // @@ -19,8 +20,8 @@ #include #include -#include "bank_model.h" -#include "render_settings.h" // TailMode (pure) — the three-state tail contract +#include "core/model/bank_model.h" +#include "core/capture/render_settings.h" // TailMode (pure) — the three-state tail contract // MediaTrack is forward-declared (like track_guid.h) so this header stays // REAPER-free while RealtimeRecordBackend::begin can take the resolved source diff --git a/src/capture_realtime.cpp b/src/shell/capture/capture_realtime.cpp similarity index 99% rename from src/capture_realtime.cpp rename to src/shell/capture/capture_realtime.cpp index de084e8..21c0612 100644 --- a/src/capture_realtime.cpp +++ b/src/shell/capture/capture_realtime.cpp @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // capture_realtime.cpp — REAPER-facing realtime-record backend (RealtimeRecordBackend). // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h @@ -67,7 +68,7 @@ // Item realtime is deferred (UnsupportedMode): item scope would need per-item take // isolation on top of the tap, which is a separate increment. -#include "capture.h" +#include "shell/capture/capture.h" #include #include @@ -78,12 +79,12 @@ #include #include -#include "capture_paths.h" // hashBytes, deriveBankPaths +#include "core/capture/capture_paths.h" // hashBytes, deriveBankPaths #include "core/util/file_bytes.h" // shared whole-file loader (Q-W1, T2-03) -#include "peaks.h" // lastFrameAboveThreshold, AudioSample -#include "realtime_record.h" -#include "render_settings.h" // autoTrimEndRatio, realtimeRecordWindowEnd -#include "wav_trim.h" // parseWavLayout, extractFloatFrames, planWavTruncate +#include "core/audio/peaks.h" // lastFrameAboveThreshold, AudioSample +#include "core/capture/realtime_record.h" +#include "core/capture/render_settings.h" // autoTrimEndRatio, realtimeRecordWindowEnd +#include "core/capture/wav_trim.h" // parseWavLayout, extractFloatFrames, planWavTruncate #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_EnumProjects diff --git a/src/insert.cpp b/src/shell/capture/insert.cpp similarity index 97% rename from src/insert.cpp rename to src/shell/capture/insert.cpp index 509200b..d6344dc 100644 --- a/src/insert.cpp +++ b/src/shell/capture/insert.cpp @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // insert.cpp — REAPER-facing placement shell (M6). See insert.h. // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h @@ -30,15 +31,15 @@ // doc-comment says "Set exactly one track selected, deselect all others" — // this is the strongest confirmation we have; flagged for DAW-verification. -#include "insert.h" +#include "shell/capture/insert.h" #include #include #include -#include "bank_model.h" +#include "core/model/bank_model.h" #include "bank_panel.h" -#include "capture_paths.h" +#include "core/capture/capture_paths.h" #include "persist.h" #define REAPERAPI_MINIMAL @@ -130,8 +131,8 @@ InsertResult runInsert(ReaSamplerSession* session, const InsertRequest& request) // necessarily the active/capture-target bank. Fall back to the active bank when // the source id names no bank (defensive). const std::string srcBankId = bankPanelSelectedSourceBankId(); - const BankIndex* srcIndex = session->book().index(srcBankId); - const BankIndex& bank = srcIndex ? *srcIndex : session->bank(); + const BankModel* srcIndex = session->book().index(srcBankId); + const BankModel& bank = srcIndex ? *srcIndex : session->bank(); const Sample* sample = bank.query(id); if (!sample) { result.status = InsertStatus::NothingResolved; return result; } diff --git a/src/insert.h b/src/shell/capture/insert.h similarity index 97% rename from src/insert.h rename to src/shell/capture/insert.h index b61aee9..5c6ea59 100644 --- a/src/insert.h +++ b/src/shell/capture/insert.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" #pragma once // insert — placement of bank samples into the arrange (M6). REAPER-facing shell: // it reads the bank_panel's current selection, resolves each selected sample's @@ -17,7 +18,7 @@ // The header is SDK-free: all REAPER API use lives in insert.cpp. The pure // mode-bit arithmetic lives in insert_plan (unit-tested outside the DAW). -#include "insert_plan.h" +#include "core/capture/insert_plan.h" namespace reasampler { diff --git a/src/item_read.cpp b/src/shell/capture/item_read.cpp similarity index 94% rename from src/item_read.cpp rename to src/shell/capture/item_read.cpp index 40edc65..fb84fa6 100644 --- a/src/item_read.cpp +++ b/src/shell/capture/item_read.cpp @@ -1,9 +1,10 @@ +#include "core/namespaces.h" // item_read.cpp — the single MediaItem* read seam (GUID + fixed-lane name). See // item_read.h. Compiled into the reaper_reasampler MODULE; includes // reaper_plugin_functions.h WITHOUT REAPERAPI_IMPLEMENT (main.cpp is the one TU that // defines the API pointers — CLAUDE.md §contract). -#include "item_read.h" +#include "shell/capture/item_read.h" #include diff --git a/src/item_read.h b/src/shell/capture/item_read.h similarity index 98% rename from src/item_read.h rename to src/shell/capture/item_read.h index 7bb74e9..1aec24a 100644 --- a/src/item_read.h +++ b/src/shell/capture/item_read.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" #pragma once // item_read — the ONE place a MediaItem* is read for its canonical GUID string and for // the durable P_LANENAME of the fixed lane it sits on. Before this seam, view.cpp and diff --git a/src/provenance_shell.cpp b/src/shell/capture/provenance_shell.cpp similarity index 95% rename from src/provenance_shell.cpp rename to src/shell/capture/provenance_shell.cpp index 111ae6c..f0a1319 100644 --- a/src/provenance_shell.cpp +++ b/src/shell/capture/provenance_shell.cpp @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // provenance_shell.cpp — the REAPER reads behind Milestone 10. See provenance_shell.h. // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h @@ -19,13 +20,13 @@ // * CountTracks / GetTrack (track scan) // * guidToString (via track_guid) -#include "provenance_shell.h" +#include "shell/capture/provenance_shell.h" #include -#include "bank_book.h" // BankBook, Bank, BankIndex::all -#include "capture_paths.h" // resolveBankFile, normalizeSlashes -#include "track_guid.h" // guidString — the ONE canonical GUID key formatter +#include "core/model/bank_book.h" // BankBook, Bank, BankModel::all +#include "core/capture/capture_paths.h" // resolveBankFile, normalizeSlashes +#include "shell/capture/track_guid.h" // guidString — the ONE canonical GUID key formatter #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_TrackFX_GetCount diff --git a/src/provenance_shell.h b/src/shell/capture/provenance_shell.h similarity index 98% rename from src/provenance_shell.h rename to src/shell/capture/provenance_shell.h index e53888b..594002e 100644 --- a/src/provenance_shell.h +++ b/src/shell/capture/provenance_shell.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" #pragma once // provenance_shell — the REAPER-facing reads Milestone 10 needs, in one place. // @@ -19,7 +20,7 @@ #include #include -#include "provenance.h" +#include "core/model/provenance.h" class MediaTrack; class MediaItem; diff --git a/src/track_guid.cpp b/src/shell/capture/track_guid.cpp similarity index 91% rename from src/track_guid.cpp rename to src/shell/capture/track_guid.cpp index 8a0de5b..4a47d0b 100644 --- a/src/track_guid.cpp +++ b/src/shell/capture/track_guid.cpp @@ -1,9 +1,10 @@ +#include "core/namespaces.h" // track_guid.cpp — the single MediaTrack* -> canonical GUID key formatter. See // track_guid.h. Compiled into the reaper_reasampler MODULE; includes // reaper_plugin_functions.h WITHOUT REAPERAPI_IMPLEMENT (main.cpp is the one TU // that defines the API pointers — CLAUDE.md §contract). -#include "track_guid.h" +#include "shell/capture/track_guid.h" #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_GetTrackGUID diff --git a/src/track_guid.h b/src/shell/capture/track_guid.h similarity index 97% rename from src/track_guid.h rename to src/shell/capture/track_guid.h index f13bac3..0bbadd5 100644 --- a/src/track_guid.h +++ b/src/shell/capture/track_guid.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" #pragma once // track_guid — the ONE place a MediaTrack* is formatted into the canonical GUID // string used as a membership-index key. Both the Design View shell (view.cpp) and diff --git a/src/vst/reaper_bridge.cpp b/src/shell/instrument/reaper_bridge.cpp similarity index 97% rename from src/vst/reaper_bridge.cpp rename to src/shell/instrument/reaper_bridge.cpp index 0c5dc49..91fa451 100644 --- a/src/vst/reaper_bridge.cpp +++ b/src/shell/instrument/reaper_bridge.cpp @@ -1,11 +1,12 @@ +#include "core/namespaces.h" // reaper_bridge.cpp — see reaper_bridge.h. The DAW-facing edge; keep it thin. -#include "reaper_bridge.h" +#include "shell/instrument/reaper_bridge.h" #include -#include "bridge_marshal.h" -#include "capture_paths.h" // projectDirOfRpp (shared M4 project-dir derivation) +#include "core/instrument/map/bridge_marshal.h" +#include "core/capture/capture_paths.h" // projectDirOfRpp (shared M4 project-dir derivation) #include "ext_keys.h" // kProjExtNamespace (shared wire contract) // The VST3 base types must be included before REAPER's VST3 interface header, which diff --git a/src/vst/reaper_bridge.h b/src/shell/instrument/reaper_bridge.h similarity index 99% rename from src/vst/reaper_bridge.h rename to src/shell/instrument/reaper_bridge.h index 78582de..004c54a 100644 --- a/src/vst/reaper_bridge.h +++ b/src/shell/instrument/reaper_bridge.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // reaper_bridge.h — the REAPER VST-host bridge (Phase S1 read spike). THIN shell: // resolves REAPER API functions by name over the host context and reads the live // "reasampler" project ext-state. The fiddly decode lives in bridge_marshal (pure). diff --git a/src/vst/reasampler_embed.cpp b/src/shell/instrument/reasampler_embed.cpp similarity index 87% rename from src/vst/reasampler_embed.cpp rename to src/shell/instrument/reasampler_embed.cpp index 6e739fd..e3c46bf 100644 --- a/src/vst/reasampler_embed.cpp +++ b/src/shell/instrument/reasampler_embed.cpp @@ -1,22 +1,23 @@ +#include "core/namespaces.h" // reasampler_embed.cpp — see reasampler_embed.h. The IReaperUIEmbedInterface shell. // Windows-only (D5); guarded so a non-Windows build degrades to a stub that reports // "not supported" and draws nothing. -#include "reasampler_embed.h" +#include "shell/instrument/reasampler_embed.h" #include #include -#include "app_version.h" // vstPluginName (channel-derived embed label, S18) -#include "bank_sync.h" // parseBankGeneration (S9 dirty-guard over the per-paint refresh) -#include "component_geometry.h" // KitBox — the kit text/fill draw box (Phase L, L3) -#include "draw_kit.h" // the L1 draw kit: fillSurface/text (L3) -#include "editor_geometry.h" // Rect (shared with embed_strip) -#include "embed_strip.h" // the pure strip layout + hit-test +#include "core/version/app_version.h" // vstPluginName (channel-derived embed label, S18) +#include "core/instrument/map/bank_sync.h" // parseBankGeneration (S9 dirty-guard over the per-paint refresh) +#include "core/ui/component_geometry.h" // KitBox — the kit text/fill draw box (Phase L, L3) +#include "shell/panel/draw_kit.h" // the L1 draw kit: fillSurface/text (L3) +#include "core/instrument/ui/editor_geometry.h" // Rect (shared with embed_strip) +#include "core/instrument/ui/embed_strip.h" // the pure strip layout + hit-test #include "ext_keys.h" // kProjExtBanksKey / kProjExtBankGenKey -#include "reaper_bridge.h" +#include "shell/instrument/reaper_bridge.h" #include "reasampler_processor.h" -#include "theme.h" // Role / InteractionState / spectralColor (L3) +#include "core/ui/theme.h" // Role / InteractionState / spectralColor (L3) // wdltypes.h first: it defines INT_PTR portably (and pulls on Windows), which // reaper_plugin_fx_embed.h's REAPER_FXEMBED_IBitmap::Extended needs as its return type. @@ -48,7 +49,7 @@ namespace { // (component_geometry). Every embed surface now draws by palette ROLE via the L1 kit, retiring // the local pre-L1 forest-green palette + raw GDI DrawTextA. KitBox toKitBox(const Rect& r) { - return KitBox{r.left, r.top, r.width(), r.height()}; + return KitBox{r.x, r.y, r.width, r.height}; } // A short display name for a bank sample id, from the snapshotted list (the editor's helper, @@ -198,12 +199,12 @@ bool ReaSamplerEmbed::paint(TPtrInt bitmap, TPtrInt drawInfo) { if (map_.zones.empty()) { // No opt-in zones authored: a faint bg/cell band spanning the keymap area so the strip // reads as "present, no zones" — the default single-capture face lives in the editor. - LICE_FillRect(bmp, layout.keymap.left, layout.keymap.top, layout.keymap.width(), - layout.keymap.height(), toLice(roleColor(Role::BgCell)), 0.5f, 0); + LICE_FillRect(bmp, layout.keymap.x, layout.keymap.y, layout.keymap.width, + layout.keymap.height, toLice(roleColor(Role::BgCell)), 0.5f, 0); const std::string label = reasampler::vstPluginName() + // channel-derived (S18) (samples_.empty() ? " (bank empty)" : " (no zones)"); - const Rect labelR{layout.keymap.left + 4, layout.keymap.top, layout.keymap.right, - layout.keymap.bottom}; + const Rect labelR = Rect::ltrb(layout.keymap.x + 4, layout.keymap.y, layout.keymap.right(), + layout.keymap.bottom()); text(bmp, toKitBox(labelR), label.c_str(), Font::Label, Role::TextPrimary, Align::Left); } else { // Draw each zone as a segment across the keymap span, first-match order (so the painted @@ -214,26 +215,26 @@ bool ReaSamplerEmbed::paint(TPtrInt bitmap, TPtrInt drawInfo) { for (int i = 0; i < static_cast(map_.zones.size()); ++i) { const PerformanceZone& z = map_.zones[i]; const Rect r = zoneSegmentRect(layout, z.lowNote, z.highNote); - if (r.width() <= 0) continue; + if (r.width <= 0) continue; const bool sel = (i == selectedZone_); if (sel) { // Static glow halo, then the crisp accent-primary fill. - LICE_FillRect(bmp, r.left - 2, r.top, r.width() + 4, r.height(), + LICE_FillRect(bmp, r.x - 2, r.y, r.width + 4, r.height, toLice(roleColor(Role::AccentHot)), 0.30f, 0); - LICE_FillRect(bmp, r.left, r.top, r.width(), r.height(), + LICE_FillRect(bmp, r.x, r.y, r.width, r.height, toLice(roleColor(Role::AccentPrimary)), 1.0f, 0); } else { const double t = ((z.lowNote + z.highNote) * 0.5) / 127.0; - LICE_FillRect(bmp, r.left, r.top, r.width(), r.height(), + LICE_FillRect(bmp, r.x, r.y, r.width, r.height, toLice(spectralColor(t)), 0.65f, 0); } - LICE_DrawRect(bmp, r.left, r.top, r.width() - 1, r.height() - 1, + LICE_DrawRect(bmp, r.x, r.y, r.width - 1, r.height - 1, toLice(roleColor(Role::LineHairline)), 1.0f, 0); // Label the segment with the sample name when it is wide enough to read. The // selected (accent-fill) segment draws its label in bg/base for contrast (the // tight text-on-pastel pair, §4); the rest in text/primary. - if (r.width() >= 24) { - const Rect lr{r.left + 3, r.top, r.right - 2, r.bottom}; + if (r.width >= 24) { + const Rect lr = Rect::ltrb(r.x + 3, r.y, r.right() - 2, r.bottom()); text(bmp, toKitBox(lr), sampleLabel(samples_, z.sampleId).c_str(), Font::Label, sel ? Role::BgBase : Role::TextPrimary, Align::Left); } @@ -242,12 +243,12 @@ bool ReaSamplerEmbed::paint(TPtrInt bitmap, TPtrInt drawInfo) { // The level band: a recessed bg/cell channel with an accent-primary fill following the // live activity level (a direct level follow — the one permitted "motion", §3.5). - if (layout.levelBand.height() > 0) { + if (layout.levelBand.height > 0) { fillSurface(bmp, toKitBox(layout.levelBand), Role::BgCell, InteractionState::Pressed); const double level = processor_ ? processor_->embedActivityLevel() : 0.0; const Rect fill = levelFillRect(layout, level); - if (fill.width() > 0) { - LICE_FillRect(bmp, fill.left, fill.top, fill.width(), fill.height(), + if (fill.width > 0) { + LICE_FillRect(bmp, fill.x, fill.y, fill.width, fill.height, toLice(roleColor(Role::AccentPrimary)), 1.0f, 0); } } diff --git a/src/vst/reasampler_embed.h b/src/shell/instrument/reasampler_embed.h similarity index 97% rename from src/vst/reasampler_embed.h rename to src/shell/instrument/reasampler_embed.h index 33a017c..bf4f52f 100644 --- a/src/vst/reasampler_embed.h +++ b/src/shell/instrument/reasampler_embed.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // reasampler_embed.h — the S6 embedded TCP/MCP UI shell. Implements REAPER's // IReaperUIEmbedInterface (vendor/reaper-sdk/sdk/reaper_plugin_fx_embed.h + // reaper_vst3_interfaces.h) so the instrument draws a compact keymap/level strip INLINE in @@ -38,7 +39,7 @@ #include "pluginterfaces/base/funknown.h" -#include "sample_map.h" // SampleChoice, PerformanceMap (the state the strip reflects) +#include "core/instrument/map/sample_map.h" // SampleChoice, PerformanceMap (the state the strip reflects) // REAPER's VST3-side embed interface (vendored). Uses UNQUALIFIED Steinberg types, so it is // pulled into the Steinberg namespace the same way reaper_bridge.cpp includes the host diff --git a/src/vst/reasampler_uid.h b/src/shell/instrument/reasampler_uid.h similarity index 100% rename from src/vst/reasampler_uid.h rename to src/shell/instrument/reasampler_uid.h diff --git a/src/vst/reasampler_vst.h b/src/shell/instrument/reasampler_vst.h similarity index 95% rename from src/vst/reasampler_vst.h rename to src/shell/instrument/reasampler_vst.h index abd5fc8..7e252ea 100644 --- a/src/vst/reasampler_vst.h +++ b/src/shell/instrument/reasampler_vst.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // reasampler_vst.h — shared identity constants for the ReaSampler VST3 instrument // (Phase S). One place for the plugin's class UID, name, vendor, and version so the // processor, factory, and editor agree. @@ -21,7 +22,7 @@ #include "pluginterfaces/base/funknown.h" -#include "reasampler_uid.h" // the FROZEN UID macros + channel selection (SDK-free values) +#include "shell/instrument/reasampler_uid.h" // the FROZEN UID macros + channel selection (SDK-free values) namespace reasampler::vst { diff --git a/src/vst/vst_entry.cpp b/src/shell/instrument/vst_entry.cpp similarity index 95% rename from src/vst/vst_entry.cpp rename to src/shell/instrument/vst_entry.cpp index ea49a15..4b32533 100644 --- a/src/vst/vst_entry.cpp +++ b/src/shell/instrument/vst_entry.cpp @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // vst_entry.cpp — the VST3 module class factory (Phase S1). Enumerates the one class // this module offers (the ReaSampler instrument) via the SDK's factory macros. The // Windows module exports — GetPluginFactory (here, via BEGIN_FACTORY) and @@ -21,10 +22,10 @@ #include "pluginterfaces/vst/ivstaudioprocessor.h" // kVstAudioEffectClass, PlugType -#include "app_version.h" // vstPluginName / appVersion — the channel-derived identity +#include "core/version/app_version.h" // vstPluginName / appVersion — the channel-derived identity #include "ext_keys.h" // kProjExtNamespace — the pairing-surface assertion target #include "reasampler_processor.h" -#include "reasampler_vst.h" // channel-selected class UID (REASAMPLER_ACTIVE_UID_*) +#include "shell/instrument/reasampler_vst.h" // channel-selected class UID (REASAMPLER_ACTIVE_UID_*) // CHANNEL PAIRING INVARIANT (S18). The instrument's PLUGIN identity forks by the ONE channel // bit (REASAMPLER_CHANNEL_IS_BETA — the class UID selected in reasampler_vst.h, the filename diff --git a/src/draw_kit.cpp b/src/shell/panel/draw_kit.cpp similarity index 98% rename from src/draw_kit.cpp rename to src/shell/panel/draw_kit.cpp index 4c935fa..bc7587e 100644 --- a/src/draw_kit.cpp +++ b/src/shell/panel/draw_kit.cpp @@ -1,14 +1,15 @@ +#include "core/namespaces.h" // draw_kit — the LICE/SWELL shell half of the drawing kit. See draw_kit.h. // // Compiled into the reaper_reasampler MODULE. SHELL layer: it is the only kit file that // touches LICE + SWELL. All colors come from the pure `theme` module; all geometry from // the pure `component_geometry` module. DAW-verified, not unit-tested. -#include "draw_kit.h" +#include "shell/panel/draw_kit.h" #include -#include "bank_grid.h" // compressAmplitudeForDisplay — the shared dB display curve (pure) +#include "core/ui/bank_grid.h" // compressAmplitudeForDisplay — the shared dB display curve (pure) // SWELL / LICE. On Windows use native Win32 (windows.h first); on mac/linux SWELL is // provided by the host. Mirrors bank_panel.cpp's include discipline. diff --git a/src/draw_kit.h b/src/shell/panel/draw_kit.h similarity index 96% rename from src/draw_kit.h rename to src/shell/panel/draw_kit.h index ba69dae..594f3e0 100644 --- a/src/draw_kit.h +++ b/src/shell/panel/draw_kit.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" #pragma once // draw_kit — the LICE-facing SHELL half of the shared drawing kit (Phase L, L1). This is // the ONE source of drawing for the whole system: every surface (bank_panel now; the VST @@ -23,9 +24,9 @@ // DOUBLE-BUFFER DISCIPLINE (§3.5 "zero-jank"): every function here draws into the caller's // offscreen LICE_IBitmap; the caller BitBlt's once. Nothing here draws direct-to-DC. -#include "component_geometry.h" // KitBox / SliderGeometry — the pure geometry the shell draws -#include "peaks.h" // Envelope — the waveform primitive's input -#include "theme.h" // Role / InteractionState / KitColor / TextClass +#include "core/ui/component_geometry.h" // KitBox / SliderGeometry — the pure geometry the shell draws +#include "core/audio/peaks.h" // Envelope — the waveform primitive's input +#include "core/ui/theme.h" // Role / InteractionState / KitColor / TextClass // LICE types at the boundary (this is the shell half). LICE_IBitmap is forward-declared // to keep the header light. LICE_pixel is a typedef (unsigned int) — not forward-declarable diff --git a/src/usage_scan.cpp b/src/shell/persist/usage_scan.cpp similarity index 96% rename from src/usage_scan.cpp rename to src/shell/persist/usage_scan.cpp index 49b91d8..416ab52 100644 --- a/src/usage_scan.cpp +++ b/src/shell/persist/usage_scan.cpp @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // usage_scan.cpp — see usage_scan.h. The REAPER reads behind the pS-usage prune // protection; every decision is in the pure sample_usage module, this TU only reads. // @@ -16,7 +17,7 @@ // * TakeFX_GetCount / TakeFX_GetNamedConfigParm (~6710/6774) // * guidToString (via track_guid::guidString) -#include "usage_scan.h" +#include "shell/persist/usage_scan.h" #include #include @@ -25,11 +26,11 @@ #include #include -#include "app_version.h" // vstPluginName / vstOutputName (channel name needles) +#include "core/version/app_version.h" // vstPluginName / vstOutputName (channel name needles) #include "ext_keys.h" // kProjExtNamespace / kProjExtUsageKeyPrefix -#include "instrument_drop.h" // vstClassIdHex — the frozen channel class-UID hex -#include "sample_usage.h" // identityMatches, foldUsageRecords (the pure decisions) -#include "track_guid.h" // guidString — the ONE canonical GUID key formatter +#include "core/wire/instrument_drop.h" // vstClassIdHex — the frozen channel class-UID hex +#include "core/wire/sample_usage.h" // identityMatches, foldUsageRecords (the pure decisions) +#include "shell/capture/track_guid.h" // guidString — the ONE canonical GUID key formatter #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_EnumProjExtState diff --git a/src/usage_scan.h b/src/shell/persist/usage_scan.h similarity index 99% rename from src/usage_scan.h rename to src/shell/persist/usage_scan.h index e9fd5d8..21db288 100644 --- a/src/usage_scan.h +++ b/src/shell/persist/usage_scan.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" #pragma once // usage_scan — the EXTENSION-side shell of the pS-usage seam (see sample_usage.h for // the pure core, the fail-safe folds, and the full design note). At prune-scan time it diff --git a/src/view.cpp b/src/shell/view/view.cpp similarity index 99% rename from src/view.cpp rename to src/shell/view/view.cpp index a6c4321..3c6f5fd 100644 --- a/src/view.cpp +++ b/src/shell/view/view.cpp @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // view.cpp — REAPER-facing Design View shell (Phase D2). See view.h. // // Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h @@ -8,7 +9,7 @@ // module so it is unit-tested outside the DAW; this file owns only the REAPER // reads/writes and the snapshot-before-park ordering. -#include "view.h" +#include "shell/view/view.h" #include #include @@ -18,10 +19,10 @@ #include #include -#include "item_read.h" -#include "lane_keys.h" -#include "track_guid.h" -#include "view_tree.h" +#include "shell/capture/item_read.h" +#include "core/view/lane_keys.h" +#include "shell/capture/track_guid.h" +#include "core/view/view_tree.h" #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_CountTracks diff --git a/src/view.h b/src/shell/view/view.h similarity index 98% rename from src/view.h rename to src/shell/view/view.h index ae5feb0..1bf8fa6 100644 --- a/src/view.h +++ b/src/shell/view/view.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" #pragma once // view — the REAPER-facing shell of the Design View feature (Phase D2). It is the // mirror of the capture shell: the ViewModeModel (pure, D1) holds the mode/ @@ -24,7 +25,7 @@ #include -#include "view_mode_model.h" +#include "core/view/view_mode_model.h" // REAPER's opaque project handle. Forward-declared to keep this header SDK-free; // the .cpp includes reaper_plugin_functions.h and sees the real class. diff --git a/src/vst/curve_popup.cpp b/src/vst/curve_popup.cpp deleted file mode 100644 index 07ffe53..0000000 --- a/src/vst/curve_popup.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// curve_popup.cpp — see curve_popup.h. Pure arithmetic; no LICE/VST3/REAPER includes. - -#include "curve_popup.h" - -#include - -namespace reasampler::vst { - -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{left, top, left + sheetW, top + sheetH}; - - const int titleBottom = out.sheet.top + kCurvePopupTitleH; - const int closeTop = out.sheet.top + (kCurvePopupTitleH - kCurvePopupCloseSize) / 2; - out.close = Rect{out.sheet.right - kCurvePopupPad - kCurvePopupCloseSize, closeTop, - out.sheet.right - kCurvePopupPad, closeTop + kCurvePopupCloseSize}; - out.title = Rect{out.sheet.left + kCurvePopupPad, out.sheet.top, - out.close.left - kCurvePopupPad, titleBottom}; - - out.curveBox = Rect{out.sheet.left + 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::vst diff --git a/src/vst/reasampler_editor.cpp b/src/vst/reasampler_editor.cpp index 1ec3362..0bf74fb 100644 --- a/src/vst/reasampler_editor.cpp +++ b/src/vst/reasampler_editor.cpp @@ -1,3 +1,5 @@ +#include "core/namespaces.h" +#include "core/util/clamp01.h" // reasampler_editor.cpp — see reasampler_editor.h. The IPlugView<->LICE bridge for the // ReaSampler 9000 capture-first editor (Phase S10). Windows-only (D5); the whole file is // guarded so a non-Windows build (not a target) degrades to the CPluginView defaults. @@ -11,28 +13,28 @@ #include #include -#include "browser_scroll.h" // S12 scroll-window + thumb + type-to-filter search geometry -#include "capture_browser.h" -#include "capture_paths.h" // resolveBankFile (shared M4 path resolution) -#include "component_geometry.h" // KitBox — the kit text/fill draw box (Phase L, L3) +#include "core/instrument/ui/browser_scroll.h" // S12 scroll-window + thumb + type-to-filter search geometry +#include "core/instrument/ui/capture_browser.h" +#include "core/capture/capture_paths.h" // resolveBankFile (shared M4 path resolution) +#include "core/ui/component_geometry.h" // KitBox — the kit text/fill draw box (Phase L, L3) #include "core/util/file_bytes.h" // shared whole-file loader (Q-W1, T2-03) -#include "curve_popup.h" // r11 centered curve-popup sheet geometry (FB1) -#include "draw_kit.h" // the L1 draw kit: fillSurface/drawButton/text/drawWaveform (L3) -#include "editor_geometry.h" // Rect, contains +#include "core/instrument/ui/curve_popup.h" // r11 centered curve-popup sheet geometry (FB1) +#include "shell/panel/draw_kit.h" // the L1 draw kit: fillSurface/drawButton/text/drawWaveform (L3) +#include "core/instrument/ui/editor_geometry.h" // Rect, contains #include "ext_keys.h" -#include "keyboard_strip.h" -#include "master_gain.h" // r11 master-gain dB<->linear<->knob taper (FB1) -#include "theme.h" // Role / InteractionState / KitColor / spectralColor (L3) -#include "note_entry.h" // S12 direct numeric note-entry parse -#include "param_slider.h" // the FA4 radial-knob primitive (value<->needle map, drag delta) -#include "peaks.h" // computeEnvelope -#include "reaper_bridge.h" +#include "core/instrument/ui/keyboard_strip.h" +#include "core/instrument/engine/master_gain.h" // r11 master-gain dB<->linear<->knob taper (FB1) +#include "core/ui/theme.h" // Role / InteractionState / KitColor / spectralColor (L3) +#include "core/instrument/map/note_entry.h" // S12 direct numeric note-entry parse +#include "core/instrument/ui/param_slider.h" // the FA4 radial-knob primitive (value<->needle map, drag delta) +#include "core/audio/peaks.h" // computeEnvelope +#include "shell/instrument/reaper_bridge.h" #include "reasampler_processor.h" -#include "app_version.h" // vstPluginName (channel-derived editor title band, S18) -#include "sample_map.h" -#include "wav_trim.h" // parseWavLayout, extractFloatFrames -#include "trigger_seam.h" // triggerPlayLength / framesToFadeFraction / fadeFractionToFrames (S-VIEW-3) -#include "waveform_view.h" // frame<->pixel markers + zero-crossing snap (S11) +#include "core/version/app_version.h" // vstPluginName (channel-derived editor title band, S18) +#include "core/instrument/map/sample_map.h" +#include "core/capture/wav_trim.h" // parseWavLayout, extractFloatFrames +#include "core/instrument/map/trigger_seam.h" // triggerPlayLength / framesToFadeFraction / fadeFractionToFrames (S-VIEW-3) +#include "core/instrument/ui/waveform_view.h" // frame<->pixel markers + zero-crossing snap (S11) #ifdef _WIN32 #include // GET_X_LPARAM / GET_Y_LPARAM @@ -86,7 +88,7 @@ constexpr Role kRoleLoopMarker = Role::AccentTertiary; // through the L1 kit (theme roles + draw_kit), retiring the shell's raw LICE_RGBA palette + // GDI DrawTextA path. KitBox toKitBox(const Rect& r) { - return KitBox{r.left, r.top, r.width(), r.height()}; + return KitBox{r.x, r.y, r.width, r.height}; } // Kit text in a palette ROLE (the common case). Left/Right/Center via Align. @@ -400,7 +402,6 @@ constexpr double kFadeMaxSeconds = 2.0; // Trigger fade throw ceili constexpr double kPitchDepthMaxSemis = 24.0; // AD pitch depth throw: +/-24 st, centered constexpr double kKeyTrackMax = 2.0; // S-VIEW-6 key-track slider ceiling (0..200%) -double clamp01(double v) { return v < 0.0 ? 0.0 : (v > 1.0 ? 1.0 : v); } } // namespace double ReaSamplerEditor::controlValue(int id, const ZonePlaySeconds& play) const { @@ -952,12 +953,12 @@ struct SampleBands { SampleBands computeSampleBands(int w, int h, int deckH) { SampleBands b; const int titleH = (std::min)(kTitleHeight, h); - b.title = Rect{0, 0, w, titleH}; + b.title = Rect::ltrb(0, 0, w, titleH); // Two nav buttons right-anchored in the title band (Browse then Zone). const int navTop = 2; const int navBot = (std::max)(navTop, titleH - 2); - const Rect zone{w - kPad - kNavButtonWidth, navTop, w - kPad, navBot}; - const Rect browse{zone.left - 4 - kNavButtonWidth, navTop, zone.left - 4, navBot}; + const Rect zone = Rect::ltrb(w - kPad - kNavButtonWidth, navTop, w - kPad, navBot); + const Rect browse = Rect::ltrb(zone.x - 4 - kNavButtonWidth, navTop, zone.x - 4, navBot); b.navBrowse = browse; b.navZone = zone; @@ -969,9 +970,9 @@ SampleBands computeSampleBands(int w, int h, int deckH) { clusterTop = heroBottom + 4; deckTop = clusterTop + kClusterHeight + 4; } - b.hero = Rect{kPad, titleH, w - kPad, heroBottom}; - b.cluster = Rect{0, clusterTop, w, clusterTop + kClusterHeight}; - b.deck = Rect{kPad, deckTop, w - kPad, deckTop + deckH}; + b.hero = Rect::ltrb(kPad, titleH, w - kPad, heroBottom); + b.cluster = Rect::ltrb(0, clusterTop, w, clusterTop + kClusterHeight); + b.deck = Rect::ltrb(kPad, deckTop, w - kPad, deckTop + deckH); return b; } @@ -988,49 +989,49 @@ struct ClusterRects { }; ClusterRects clusterRects(const Rect& cluster, const Rect& chanMono) { ClusterRects r; - const int stripTop = cluster.top + (cluster.height() - kStripBandHeight) / 2; + const int stripTop = cluster.y + (cluster.height - kStripBandHeight) / 2; const int stripBot = stripTop + kStripBandHeight; - const int curveTop = cluster.top + (cluster.height() - kCurveBtnSize) / 2; - r.curveBtn = Rect{chanMono.left - kPad - kCurveBtnSize, curveTop, - chanMono.left - kPad, curveTop + kCurveBtnSize}; - r.velCell = Rect{r.curveBtn.left - kPad - kVelCellW, stripTop, - r.curveBtn.left - kPad, stripBot}; - const int knobLeft = r.velCell.left + (kVelCellW - kDeckKnobSize) / 2; - r.velKnob = Rect{knobLeft, r.velCell.top, knobLeft + kDeckKnobSize, - r.velCell.top + kDeckKnobSize}; - r.velLabel = Rect{r.velCell.left, r.velKnob.bottom, r.velCell.right, r.velCell.bottom}; - r.preview = Rect{r.velCell.left - kPad - kPreviewBtnW, stripTop, - r.velCell.left - kPad, stripBot}; - r.rootStrip = Rect{cluster.left + kPad, stripTop, r.preview.left - kPad, stripBot}; + const int curveTop = cluster.y + (cluster.height - kCurveBtnSize) / 2; + r.curveBtn = Rect::ltrb(chanMono.x - kPad - kCurveBtnSize, curveTop, + chanMono.x - kPad, curveTop + kCurveBtnSize); + r.velCell = Rect::ltrb(r.curveBtn.x - kPad - kVelCellW, stripTop, + r.curveBtn.x - kPad, stripBot); + const int knobLeft = r.velCell.x + (kVelCellW - kDeckKnobSize) / 2; + r.velKnob = Rect::ltrb(knobLeft, r.velCell.y, knobLeft + kDeckKnobSize, + r.velCell.y + kDeckKnobSize); + r.velLabel = Rect::ltrb(r.velCell.x, r.velKnob.bottom(), r.velCell.right(), r.velCell.bottom()); + r.preview = Rect::ltrb(r.velCell.x - kPad - kPreviewBtnW, stripTop, + r.velCell.x - kPad, stripBot); + r.rootStrip = Rect::ltrb(cluster.x + kPad, stripTop, r.preview.x - kPad, stripBot); return r; } // The Zone-view keyboard strip rect. Zone content sits below the "+ Add Zone" affordance // (top+4, height 20) with a 12px gap, padded 8px horizontally. All call sites use this formula. Rect zonesStripArea(const Rect& content) { - const int stripTop = content.top + 4 + 20 + 12; // addR.bottom + 12 - return Rect{content.left + kPad, stripTop, content.right - kPad, - stripTop + kStripBandHeight}; + const int stripTop = content.y + 4 + 20 + 12; // addR.bottom() + 12 + return Rect::ltrb(content.x + kPad, stripTop, content.right() - kPad, + stripTop + kStripBandHeight); } // The S12 numeric-entry field ROW area inside the Zones legend: a band to the right of the // sample label on the legend row. Three equal fields (low/high/root) tile it. Both draw + -// hit-test use this single formula so they never drift. Anchored off zonesStripArea.bottom so +// hit-test use this single formula so they never drift. Anchored off zonesStripArea.bottom() so // the legend top tracks the strip bottom without re-inlining the strip arithmetic here. Rect noteEntryFieldsArea(const Rect& content) { - const int stripBottom = zonesStripArea(content).bottom; - const int top = stripBottom + 8; // legendTop (== zonesStripArea.bottom + 8) - return Rect{content.left + 8 + 128, top, content.right - 8, top + 18}; + const int stripBottom = zonesStripArea(content).bottom(); + const int top = stripBottom + 8; // legendTop (== zonesStripArea.bottom() + 8) + return Rect::ltrb(content.x + 8 + 128, top, content.right() - 8, top + 18); } // The rect of note-entry field `f` (0=low, 1=high, 2=root) within the fields area: three equal // segments left-to-right. An out-of-range index yields an empty rect. Rect noteEntryFieldRect(const Rect& fields, int f) { - if (f < 0 || f > 2 || fields.width() <= 0) return Rect{}; - const int segW = fields.width() / 3; - const int left = fields.left + f * segW + (f > 0 ? 4 : 0); // small inter-field gap - const int right = (f == 2) ? fields.right : fields.left + (f + 1) * segW; - return Rect{left, fields.top, right, fields.bottom}; + if (f < 0 || f > 2 || fields.width <= 0) return Rect{}; + const int segW = fields.width / 3; + const int left = fields.x + f * segW + (f > 0 ? 4 : 0); // small inter-field gap + const int right = (f == 2) ? fields.right() : fields.x + (f + 1) * segW; + return Rect::ltrb(left, fields.y, right, fields.bottom()); } // The S12/S15/S16 parameter-control panel rect inside the Zones content: below the strip + @@ -1038,9 +1039,9 @@ Rect noteEntryFieldRect(const Rect& fields, int f) { // Zones mode-content area. Both draw + hit-test use this single formula so they never drift. Rect zonesControlPanel(const Rect& content) { const Rect strip = zonesStripArea(content); - const int panelTop = strip.bottom + 8 + 18 + 8; // strip + the 18px legend row + gap - return Rect{content.left + kPad, panelTop, content.right - kPad, - content.bottom - 4}; + const int panelTop = strip.bottom() + 8 + 18 + 8; // strip + the 18px legend row + gap + return Rect::ltrb(content.x + kPad, panelTop, content.right() - kPad, + content.bottom() - 4); } // FB2 (R11-F2 parity): the Zone panel's per-zone controls render as the SAME knob deck the @@ -1052,22 +1053,22 @@ Rect zonesControlPanel(const Rect& content) { // drift. Rect zonesDeckArea(const Rect& content) { const Rect panel = zonesControlPanel(content); - return Rect{panel.left, panel.top, panel.right - kCurveBtnSize - kPad, panel.bottom}; + return Rect::ltrb(panel.x, panel.y, panel.right() - kCurveBtnSize - kPad, panel.bottom()); } // The Zone panel's mini curve-preview button (opens the SAME popup editor as the Sample // cluster's button): the cluster's 28px square, right-anchored at the panel top. Rect zonesCurveButton(const Rect& content) { const Rect panel = zonesControlPanel(content); - return Rect{panel.right - kCurveBtnSize, panel.top, panel.right, panel.top + kCurveBtnSize}; + return Rect::ltrb(panel.right() - kCurveBtnSize, panel.y, panel.right(), panel.y + kCurveBtnSize); } // The pure-module mapping Box for a drawn curve rect: inset from the border so node handles and // the pick radius stay inside the box. Every consumer (paint, hit-test, add, drag) derives the // Box through this ONE formula, so drawn nodes and grabs can never drift apart. VelocityCurve::Box curveBoxFromRect(const Rect& r) { - return VelocityCurve::Box{r.left + kVelCurveInset, r.top + kVelCurveInset, - (std::max)(0, r.width() - 2 * kVelCurveInset), - (std::max)(0, r.height() - 2 * kVelCurveInset)}; + return VelocityCurve::Box{r.x + kVelCurveInset, r.y + kVelCurveInset, + (std::max)(0, r.width - 2 * kVelCurveInset), + (std::max)(0, r.height - 2 * kVelCurveInset)}; } // The S7 mono/stereo toggle (S-VIEW-2: moved here from Browse to the Sample cluster band — it is @@ -1078,10 +1079,10 @@ constexpr int kChanSegW = 52; constexpr int kChanSegH = 18; struct ChannelToggleRects { Rect mono; Rect stereo; }; ChannelToggleRects channelToggleRects(const Rect& area) { - const int top = area.top + (area.height() - kChanSegH) / 2; - const int right = area.right - kPad; - const Rect stereo{right - kChanSegW, top, right, top + kChanSegH}; - const Rect mono{stereo.left - kChanSegW, top, stereo.left, top + kChanSegH}; + const int top = area.y + (area.height - kChanSegH) / 2; + const int right = area.right() - kPad; + const Rect stereo = Rect::ltrb(right - kChanSegW, top, right, top + kChanSegH); + const Rect mono = Rect::ltrb(stereo.x - kChanSegW, top, stereo.x, top + kChanSegH); return {mono, stereo}; } @@ -1135,11 +1136,11 @@ void drawKnobFace(LICE_IBitmap* bmp, const Rect& knobRect, double value01, // faint per-octave hairline ticks for orientation. Shared by the setup face + the Zones strip // so both read as the same spectrum. `stripArea` is the absolute strip rect. void drawSpectralStrip(LICE_IBitmap* bmp, const Rect& stripArea) { - if (stripArea.width() <= 0 || stripArea.height() <= 0) return; - const StripLayout sl = layoutStrip(stripArea.width(), stripArea.height()); - const int sx = stripArea.left; - const int sy = stripArea.top; - const int h = stripArea.height(); + if (stripArea.width <= 0 || stripArea.height <= 0) return; + const StripLayout sl = layoutStrip(stripArea.width, stripArea.height); + const int sx = stripArea.x; + const int sy = stripArea.y; + const int h = stripArea.height; // A pastel spectral column per key. Each key's local x from keyRect; fill from this key's // left to the next key's left so the sweep tiles with no gaps. Low alpha keeps it a quiet // backdrop the root/zone marks sit over. S-VIEW-7: OVERLAY the two-tone piano-key pattern — @@ -1149,8 +1150,8 @@ void drawSpectralStrip(LICE_IBitmap* bmp, const Rect& stripArea) { const LICE_pixel darkKey = toLice(roleColor(Role::BgBase)); for (int n = 0; n <= 127; ++n) { const Rect k = keyRect(sl, n); - const int x0 = k.left + sx; - const int x1 = (n < 127) ? keyRect(sl, n + 1).left + sx : stripArea.right; + const int x0 = k.x + sx; + const int x1 = (n < 127) ? keyRect(sl, n + 1).x + sx : stripArea.right(); const int cw = (std::max)(1, x1 - x0); const KitColor hue = spectralColor(static_cast(n) / 127.0); LICE_FillRect(bmp, x0, sy, cw, h, toLice(hue), 0.55f, 0); @@ -1164,23 +1165,23 @@ void drawSpectralStrip(LICE_IBitmap* bmp, const Rect& stripArea) { const LICE_pixel tick = toLice(roleColor(Role::LineHairline)); for (int n = 0; n <= 127; n += 12) { const Rect k = keyRect(sl, n); - LICE_Line(bmp, k.left + sx, sy, k.left + sx, sy + h, tick, 1.0f, 0, false); + LICE_Line(bmp, k.x + sx, sy, k.x + sx, sy + h, tick, 1.0f, 0, false); } } // Draw the single-capture root marker on the strip: an accent-primary bar with a soft STATIC // glow (a wider, lower-alpha accent bar behind it) — the "this is live" mark. Never animated. void drawRootMarker(LICE_IBitmap* bmp, const Rect& stripArea, const StripLayout& sl, int root) { - const int sx = stripArea.left; - const int sy = stripArea.top; - const int h = stripArea.height(); + const int sx = stripArea.x; + const int sy = stripArea.y; + const int h = stripArea.height; const Rect marker = rootMarkerRect(sl, root); - const int mw = (std::max)(2, marker.width()); + const int mw = (std::max)(2, marker.width); const LICE_pixel accent = toLice(roleColor(Role::AccentPrimary)); const LICE_pixel glow = toLice(roleColor(Role::AccentHot)); // Static glow: a wider low-alpha halo behind the crisp bar (a drawn state, not a pulse). - LICE_FillRect(bmp, marker.left + sx - 3, sy, mw + 6, h, glow, 0.30f, 0); - LICE_FillRect(bmp, marker.left + sx, sy, mw, h, accent, 1.0f, 0); + LICE_FillRect(bmp, marker.x + sx - 3, sy, mw + 6, h, glow, 0.30f, 0); + LICE_FillRect(bmp, marker.x + sx, sy, mw, h, accent, 1.0f, 0); } } // namespace @@ -1210,7 +1211,7 @@ void ReaSamplerEditor::paint(HDC hdc) { if (dropHintTicks_ > 0) { const int bannerTop = (std::min)(kTitleHeight, h); const int bannerH = (std::min)(kTitleHeight + 8, (std::max)(0, h - bannerTop)); - Rect banner{0, bannerTop, w, bannerTop + bannerH}; + Rect banner = Rect::ltrb(0, bannerTop, w, bannerTop + bannerH); // A transient notice, not the live layer — draw it on the accent-tertiary categorical // hue with a dark label so it reads as "attention, not action". fillSurface(&bmp, toKitBox(banner), Role::AccentTertiary, InteractionState::Rest); @@ -1227,7 +1228,7 @@ void ReaSamplerEditor::paint(HDC hdc) { namespace { void drawTitleBand(LICE_IBitmap* bmp, const Rect& title, const std::string& readout) { fillSurface(bmp, toKitBox(title), Role::BgPanel, InteractionState::Rest); - Rect titleText{title.left + 8, title.top, title.right - 8, title.bottom}; + Rect titleText = Rect::ltrb(title.x + 8, title.y, title.right() - 8, title.bottom()); kitText(bmp, titleText, readout.c_str(), Font::Title, Role::TextPrimary); } } // namespace @@ -1278,7 +1279,7 @@ void ReaSamplerEditor::paintSample(LICE_IBitmap* bmp, int w, int h) { // Nothing loaded yet: the Sample face is the empty state — a "pick a capture" prompt pointing // at Browse (which is lit above). No hero waveform / controls to draw. if (empty) { - Rect body{bands.hero.left, bands.hero.top, bands.hero.right, bands.deck.bottom}; + Rect body = Rect::ltrb(bands.hero.x, bands.hero.y, bands.hero.right(), bands.deck.bottom()); paintEmptyState(bmp, body); return; } @@ -1293,7 +1294,7 @@ void ReaSamplerEditor::paintSample(LICE_IBitmap* bmp, int w, int h) { const std::int64_t frames = static_cast(pcm.size()); const Rect waveArea = bands.hero; fillSurface(bmp, toKitBox(waveArea), Role::BgBase, InteractionState::Rest); - if (frames > 0 && waveArea.width() > 0) { + if (frames > 0 && waveArea.width > 0) { // FA3 gap-free: one bin per drawn pixel column (kWaveformOversample == 1, so this // multiplies by 1). The gap-free draw comes from peaks::columnMinMax's exact // partition — extra bins produce no visible change. Clamped to frame count below. @@ -1310,7 +1311,7 @@ void ReaSamplerEditor::paintSample(LICE_IBitmap* bmp, int w, int h) { const int lx = frameToX(waveArea, frames, m.loopStart); const int rx = frameToX(waveArea, frames, m.loopEnd); if (rx > lx) { - LICE_FillRect(bmp, lx, waveArea.top, rx - lx, waveArea.height(), + LICE_FillRect(bmp, lx, waveArea.y, rx - lx, waveArea.height, toLice(roleColor(kRoleLoopMarker)), 0.20f, 0); } } @@ -1320,7 +1321,7 @@ void ReaSamplerEditor::paintSample(LICE_IBitmap* bmp, int w, int h) { const int mx = frameToX(waveArea, frames, markerFrames[i]); const bool loopMarker = (i != 0); const float alpha = (loopMarker && !m.hasLoop) ? 0.4f : 1.0f; - LICE_FillRect(bmp, mx - 1, waveArea.top, 2, waveArea.height(), + LICE_FillRect(bmp, mx - 1, waveArea.y, 2, waveArea.height, toLice(roleColor(markerRoles[i])), alpha, 0); } @@ -1336,9 +1337,9 @@ void ReaSamplerEditor::paintSample(LICE_IBitmap* bmp, int w, int h) { const ChannelToggleRects chan = channelToggleRects(bands.cluster); const ClusterRects cr = clusterRects(bands.cluster, chan.mono); int root = effectiveRoot(); - if (cr.rootStrip.width() > 0) { + if (cr.rootStrip.width > 0) { drawSpectralStrip(bmp, cr.rootStrip); - const StripLayout sl = layoutStrip(cr.rootStrip.width(), cr.rootStrip.height()); + const StripLayout sl = layoutStrip(cr.rootStrip.width, cr.rootStrip.height); drawRootMarker(bmp, cr.rootStrip, sl, root); } @@ -1392,7 +1393,7 @@ void ReaSamplerEditor::paintSample(LICE_IBitmap* bmp, int w, int h) { void ReaSamplerEditor::paintEnvelopeOverlay(LICE_IBitmap* bmp, const Rect& waveArea, const PerformanceZone& zone, std::int64_t frames) { - if (frames <= 0 || waveArea.width() <= 0 || waveArea.height() <= 0) return; + if (frames <= 0 || waveArea.width <= 0 || waveArea.height <= 0) return; const double rate = liveSampleRate(); if (rate <= 0.0) return; const double totalSeconds = static_cast(frames) / rate; @@ -1404,14 +1405,14 @@ void ReaSamplerEditor::paintEnvelopeOverlay(LICE_IBitmap* bmp, const Rect& waveA // curve over the waveform. Clip x to the wave rect (a Gate release tail maps past the right). const LICE_pixel line = toLice(roleColor(Role::AccentSecondary)); for (std::size_t i = 1; i < poly.size(); ++i) { - const int x0 = (std::max)(waveArea.left, (std::min)(waveArea.right - 1, poly[i - 1].x)); - const int x1 = (std::max)(waveArea.left, (std::min)(waveArea.right - 1, poly[i].x)); + const int x0 = (std::max)(waveArea.x, (std::min)(waveArea.right() - 1, poly[i - 1].x)); + const int x1 = (std::max)(waveArea.x, (std::min)(waveArea.right() - 1, poly[i].x)); LICE_Line(bmp, x0, poly[i - 1].y, x1, poly[i].y, line, 1.0f, 0, true); } // Draggable node handles: a small square per DRAGGABLE node (Origin + ReleaseStart are draw- // only). Lit accent-hot when this node is the grabbed one. FA2 guarantees every vertex is // in-bounds (the pre-FA2 right-edge clip is dead and removed — edge nodes like ReleaseEnd - // at area.right-1 MUST get handles); the handle SQUARE is additionally clamped inside the + // at area.right()-1 MUST get handles); the handle SQUARE is additionally clamped inside the // hero rect so a 6px box on an edge node never overhangs into the neighbouring bands. const LICE_pixel handle = toLice(roleColor(Role::AccentPrimary)); const LICE_pixel handleHot = toLice(roleColor(Role::AccentHot)); @@ -1419,21 +1420,21 @@ void ReaSamplerEditor::paintEnvelopeOverlay(LICE_IBitmap* bmp, const Rect& waveA if (v.node == EnvNode::Origin || v.node == EnvNode::ReleaseStart) continue; const bool grabbed = (drag_ == DragKind::kEnvNode && envNode_ == v.node); const int r = 3; - const int hx = (std::max)(waveArea.left + r, (std::min)(waveArea.right - 1 - r, v.x)); - const int hy = (std::max)(waveArea.top + r, (std::min)(waveArea.bottom - 1 - r, v.y)); + const int hx = (std::max)(waveArea.x + r, (std::min)(waveArea.right() - 1 - r, v.x)); + const int hy = (std::max)(waveArea.y + r, (std::min)(waveArea.bottom() - 1 - r, v.y)); LICE_FillRect(bmp, hx - r, hy - r, 2 * r, 2 * r, grabbed ? handleHot : handle, 1.0f, 0); } } void ReaSamplerEditor::paintVelocityCurve(LICE_IBitmap* bmp, const Rect& r, const PerformanceZone& zone) { - if (r.width() <= 0 || r.height() <= 0) return; // defensive (degenerate rect) + if (r.width <= 0 || r.height <= 0) return; // defensive (degenerate rect) // The bordered box: a panel surface + hairline border, drawn by palette role. No corner // caption — the popup sheet's own "VELOCITY -> AMP" title labels this context (FB2: the // popup is the only host). fillSurface(bmp, toKitBox(r), Role::BgPanel, InteractionState::Rest); - LICE_DrawRect(bmp, r.left, r.top, r.width() - 1, r.height() - 1, + LICE_DrawRect(bmp, r.x, r.y, r.width - 1, r.height - 1, toLice(roleColor(Role::LineHairline)), 1.0f, 0); const VelocityCurve::Box box = curveBoxFromRect(r); @@ -1462,12 +1463,12 @@ void ReaSamplerEditor::paintVelocityCurve(LICE_IBitmap* bmp, const Rect& r, const LICE_pixel handleHot = toLice(roleColor(Role::AccentHot)); const LICE_pixel handleWarn = toLice(roleColor(Role::Warn)); // Drag-off check: during a kCurveNode drag on THIS box, is the live cursor beyond the margin? - const bool dragOffArmed = (drag_ == DragKind::kCurveNode && dragCurveRect_.left == r.left && - dragCurveRect_.top == r.top) && - (dragCurX_ < r.left - kCurveDragOffMargin || - dragCurX_ > r.right + kCurveDragOffMargin || - dragCurY_ < r.top - kCurveDragOffMargin || - dragCurY_ > r.bottom + kCurveDragOffMargin); + const bool dragOffArmed = (drag_ == DragKind::kCurveNode && dragCurveRect_.x == r.x && + dragCurveRect_.y == r.y) && + (dragCurX_ < r.x - kCurveDragOffMargin || + dragCurX_ > r.right() + kCurveDragOffMargin || + dragCurY_ < r.y - kCurveDragOffMargin || + dragCurY_ > r.bottom() + kCurveDragOffMargin); for (std::size_t i = 0; i < curve.points().size(); ++i) { const auto np = VelocityCurve::pixelFromPoint(box, curve.points()[i]); const bool grabbed = (drag_ == DragKind::kCurveNode && @@ -1484,8 +1485,8 @@ void ReaSamplerEditor::paintVelocityCurve(LICE_IBitmap* bmp, const Rect& r, void ReaSamplerEditor::paintKnobDeck(LICE_IBitmap* bmp, const Rect& deckArea, const PerformanceZone& zone, const std::vector& descs) { - if (deckArea.width() <= 0 || deckArea.height() <= 0) return; - const DeckLayout dl = layoutDeck(descs, deckArea.left, deckArea.top, deckArea.width()); + if (deckArea.width <= 0 || deckArea.height <= 0) return; + const DeckLayout dl = layoutDeck(descs, deckArea.x, deckArea.y, deckArea.width); const ZonePlaySeconds& play = zone.play; const bool isMono = (voiceMode_ == VoiceMode::Mono); const LICE_pixel hairline = toLice(roleColor(Role::LineHairline)); @@ -1538,7 +1539,7 @@ void ReaSamplerEditor::paintKnobDeck(LICE_IBitmap* bmp, const Rect& deckArea, for (const DeckGroupLayout& g : dl.groups) { // The fence: a bg/panel box with a hairline border, caption micro-caps left. fillSurface(bmp, toKitBox(g.box), Role::BgPanel, InteractionState::Rest); - LICE_DrawRect(bmp, g.box.left, g.box.top, g.box.width() - 1, g.box.height() - 1, + LICE_DrawRect(bmp, g.box.x, g.box.y, g.box.width - 1, g.box.height - 1, hairline, 1.0f, 0); const char* caption = ""; switch (g.id) { @@ -1599,7 +1600,7 @@ void ReaSamplerEditor::paintKnobDeck(LICE_IBitmap* bmp, const Rect& deckArea, void ReaSamplerEditor::paintCurveButton(LICE_IBitmap* bmp, const Rect& r, const PerformanceZone& zone) { - if (r.width() <= 0 || r.height() <= 0) return; + if (r.width <= 0 || r.height <= 0) return; // The mini curve-preview button (r11/FB2 — shared by the Sample cluster and the Zone // panel): a hairline-bordered bg/cell square with the zone's live velocity curve traced // in miniature (no node markers at this scale). Hover lifts it; it draws ACTIVE @@ -1610,11 +1611,11 @@ void ReaSamplerEditor::paintCurveButton(LICE_IBitmap* bmp, const Rect& r, hov ? InteractionState::Hover : InteractionState::Rest); const KitColor border = curvePopupOpen_ ? roleColor(Role::AccentPrimary) : roleColor(Role::LineHairline); - LICE_DrawRect(bmp, r.left, r.top, r.width() - 1, r.height() - 1, toLice(border), 1.0f, 0); + LICE_DrawRect(bmp, r.x, r.y, r.width - 1, r.height - 1, toLice(border), 1.0f, 0); const VelocityCurve& curve = zone.velocityCurve; const int inset = 3; - const VelocityCurve::Box mini{r.left + inset, r.top + inset, r.width() - 2 * inset, - r.height() - 2 * inset}; + const VelocityCurve::Box mini{r.x + inset, r.y + inset, r.width - 2 * inset, + r.height - 2 * inset}; if (mini.width > 1 && mini.height > 1) { const LICE_pixel trace = toLice(roleColor(Role::AccentSecondary)); int prevX = 0, prevY = 0; @@ -1635,8 +1636,8 @@ void ReaSamplerEditor::paintCurvePopup(LICE_IBitmap* bmp, int w, int h) { LICE_FillRect(bmp, 0, 0, w, h, toLice(roleColor(Role::BgBase)), 0.50f, 0); const CurvePopupLayout pl = computeCurvePopup(w, h); fillSurface(bmp, toKitBox(pl.sheet), Role::BgPanel, InteractionState::Rest); - LICE_DrawRect(bmp, pl.sheet.left, pl.sheet.top, pl.sheet.width() - 1, - pl.sheet.height() - 1, toLice(roleColor(Role::LineHairline)), 1.0f, 0); + LICE_DrawRect(bmp, pl.sheet.x, pl.sheet.y, pl.sheet.width - 1, + pl.sheet.height - 1, toLice(roleColor(Role::LineHairline)), 1.0f, 0); kitText(bmp, pl.title, "VELOCITY -> AMP", Font::Micro, Role::TextDim); { const KitButtonBox box{toKitBox(pl.close)}; @@ -1759,8 +1760,8 @@ void ReaSamplerEditor::paintEmptyState(LICE_IBitmap* bmp, const Rect& area) { // Split the area so the primary line sits centered and the S13 ingest affordance sits just // below it. The affordance is the SHIPPED ingest gesture (drop onto the docked panel) — kept // discoverable here regardless of whether a drop ever lands on THIS window. - Rect primary{area.left, area.top, area.right, area.top + area.height() / 2}; - Rect hint{area.left, primary.bottom, area.right, area.bottom}; + Rect primary = Rect::ltrb(area.x, area.y, area.right(), area.y + area.height / 2); + Rect hint = Rect::ltrb(area.x, primary.bottom(), area.right(), area.bottom()); kitTextCentered(bmp, primary, msg, Font::Label, Role::TextDim); kitTextCentered(bmp, hint, "To add a sample: drop a file onto the ReaSampler bank panel (the docked window).", @@ -1784,18 +1785,18 @@ constexpr int kBrowseFooterH = 30; BrowseModal computeBrowseModal(int w, int h) { BrowseModal m; const int titleH = (std::min)(kTitleHeight, h); - m.title = Rect{0, 0, w, titleH}; - m.back = Rect{w - kPad - kNavButtonWidth, 2, w - kPad, (std::max)(2, titleH - 2)}; + m.title = Rect::ltrb(0, 0, w, titleH); + m.back = Rect::ltrb(w - kPad - kNavButtonWidth, 2, w - kPad, (std::max)(2, titleH - 2)); // Search box below the title, spanning the width (searchBoxRect lays it out from 0). const Rect sb = searchBoxRect(w); - m.search = Rect{kPad, titleH, w - kPad, titleH + sb.height()}; - const int footerTop = (std::max)(m.search.bottom, h - kBrowseFooterH); - m.content = Rect{0, m.search.bottom, w, footerTop}; + m.search = Rect::ltrb(kPad, titleH, w - kPad, titleH + sb.height); + const int footerTop = (std::max)(m.search.bottom(), h - kBrowseFooterH); + m.content = Rect::ltrb(0, m.search.bottom(), w, footerTop); // Footer: Cancel (left) + Load (right). const int fTop = footerTop + 3; const int fBot = (std::max)(fTop, h - 3); - m.cancel = Rect{kPad, fTop, kPad + 90, fBot}; - m.confirm = Rect{w - kPad - 90, fTop, w - kPad, fBot}; + m.cancel = Rect::ltrb(kPad, fTop, kPad + 90, fBot); + m.confirm = Rect::ltrb(w - kPad - 90, fTop, w - kPad, fBot); return m; } } // namespace @@ -1823,29 +1824,29 @@ void ReaSamplerEditor::paintBrowse(LICE_IBitmap* bmp, int w, int h) { : InteractionState::Rest); fillSurface(bmp, toKitBox(searchAbs), Role::BgCell, searchState); if (searchFocused_) { - LICE_DrawRect(bmp, searchAbs.left, searchAbs.top, searchAbs.width() - 1, - searchAbs.height() - 1, toLice(roleColor(Role::TextPrimary)), 1.0f, 0); + LICE_DrawRect(bmp, searchAbs.x, searchAbs.y, searchAbs.width - 1, + searchAbs.height - 1, toLice(roleColor(Role::TextPrimary)), 1.0f, 0); } { std::string sb = searchQuery_.empty() ? std::string("Search captures...") : ("Search: " + searchQuery_ + (searchFocused_ ? "_" : "")); - Rect sbText{searchAbs.left + 6, searchAbs.top, searchAbs.right - 6, searchAbs.bottom}; + Rect sbText = Rect::ltrb(searchAbs.x + 6, searchAbs.y, searchAbs.right() - 6, searchAbs.bottom()); kitText(bmp, sbText, sb.c_str(), Font::Label, searchQuery_.empty() ? Role::TextDim : Role::TextPrimary); } // Tabs + card grid, laid out over the content sub-area by the pure module (origin-offset). const Rect browserArea = bm.content; - const BrowserLayout bl = layoutBrowser(browserArea.width(), browserArea.height()); - const int ox = browserArea.left; - const int oy = browserArea.top; + const BrowserLayout bl = layoutBrowser(browserArea.width, browserArea.height); + const int ox = browserArea.x; + const int oy = browserArea.y; scrollOffset_ = clampScrollOffset(bl, static_cast(visible_.size()), scrollOffset_); const int tabCount = static_cast(banks_.size()) + 1; for (int i = 0; i < tabCount; ++i) { Rect t = filterTabRect(bl, tabCount, i); - t = Rect{t.left + ox, t.top + oy, t.right + ox, t.bottom + oy}; + t = Rect::ltrb(t.x + ox, t.y + oy, t.right() + ox, t.bottom() + oy); const std::string label = (i == 0) ? "All" : banks_[static_cast(i - 1)].displayName; const bool active = (i == 0) ? activeFilterBankId_.empty() : (banks_[static_cast(i - 1)].id == activeFilterBankId_); @@ -1867,12 +1868,12 @@ void ReaSamplerEditor::paintBrowse(LICE_IBitmap* bmp, int w, int h) { Rect content = cardContentRect(bl, i); Rect thumb = cardThumbnailRect(bl, i); Rect labelR = cardLabelRect(bl, i); - content = Rect{content.left + ox, content.top + oy - scrollOffset_, - content.right + ox, content.bottom + oy - scrollOffset_}; - thumb = Rect{thumb.left + ox, thumb.top + oy - scrollOffset_, - thumb.right + ox, thumb.bottom + oy - scrollOffset_}; - labelR = Rect{labelR.left + ox, labelR.top + oy - scrollOffset_, - labelR.right + ox, labelR.bottom + oy - scrollOffset_}; + content = Rect::ltrb(content.x + ox, content.y + oy - scrollOffset_, + content.right() + ox, content.bottom() + oy - scrollOffset_); + thumb = Rect::ltrb(thumb.x + ox, thumb.y + oy - scrollOffset_, + thumb.right() + ox, thumb.bottom() + oy - scrollOffset_); + labelR = Rect::ltrb(labelR.x + ox, labelR.y + oy - scrollOffset_, + labelR.right() + ox, labelR.bottom() + oy - scrollOffset_); const SampleChoice& s = visible_[static_cast(i)]; const bool pending = (s.id == browsePendingId_); @@ -1883,13 +1884,13 @@ void ReaSamplerEditor::paintBrowse(LICE_IBitmap* bmp, int w, int h) { const KitColor cardBorder = pending ? roleColor(Role::AccentPrimary) : (loaded ? roleColor(Role::AccentTertiary) : roleColor(Role::LineHairline)); - LICE_DrawRect(bmp, content.left, content.top, content.width() - 1, content.height() - 1, + LICE_DrawRect(bmp, content.x, content.y, content.width - 1, content.height - 1, toLice(cardBorder), 1.0f, 0); drawEnvelope(bmp, thumb, thumbnailFor(s.id, bins)); std::string caption = s.displayName.empty() ? s.id : s.displayName; - Rect nameR{labelR.left + 3, labelR.top, labelR.right - 3, labelR.top + labelR.height() / 2}; - Rect badgeR{labelR.left + 3, nameR.bottom, labelR.right - 3, labelR.bottom}; + Rect nameR = Rect::ltrb(labelR.x + 3, labelR.y, labelR.right() - 3, labelR.y + labelR.height / 2); + Rect badgeR = Rect::ltrb(labelR.x + 3, nameR.bottom(), labelR.right() - 3, labelR.bottom()); kitText(bmp, nameR, caption.c_str(), Font::Label, Role::TextPrimary); std::string badge; if (s.rootNote) badge = "root " + noteLabel(*s.rootNote); @@ -1901,10 +1902,10 @@ void ReaSamplerEditor::paintBrowse(LICE_IBitmap* bmp, int w, int h) { // Scrollbar thumb. { const Rect thumb = scrollThumbRect(bl, cardCount, scrollOffset_); - if (thumb.height() > 0) { + if (thumb.height > 0) { const bool dragging = (drag_ == DragKind::kScrollThumb); const KitColor tc = roleColor(dragging ? Role::AccentHot : Role::AccentPrimary); - LICE_FillRect(bmp, thumb.left + ox, thumb.top + oy, thumb.width(), thumb.height(), + LICE_FillRect(bmp, thumb.x + ox, thumb.y + oy, thumb.width, thumb.height, toLice(tc), 0.8f, 0); } } @@ -1913,7 +1914,7 @@ void ReaSamplerEditor::paintBrowse(LICE_IBitmap* bmp, int w, int h) { // Footer: Cancel (discard, return to Sample) + Load (commit the pending pick). Load is inert // (no accent) until a card is picked. Draw a footer strip so the buttons read as a modal bar. - Rect footer{0, bm.content.bottom, w, h}; + Rect footer = Rect::ltrb(0, bm.content.bottom(), w, h); fillSurface(bmp, toKitBox(footer), Role::BgPanel, InteractionState::Rest); { const KitButtonBox box{toKitBox(bm.cancel)}; @@ -1935,17 +1936,17 @@ void ReaSamplerEditor::paintBrowse(LICE_IBitmap* bmp, int w, int h) { namespace { Rect zoneContentArea(int w, int h) { const int titleH = (std::min)(kTitleHeight, h); - return Rect{0, titleH, w, h}; + return Rect::ltrb(0, titleH, w, h); } } // namespace void ReaSamplerEditor::paintZone(LICE_IBitmap* bmp, int w, int h) { // Title band + Back button (returns to Sample). The Zone surface is button-summoned and returns // to the Sample home on close. - const Rect title{0, 0, w, (std::min)(kTitleHeight, h)}; + const Rect title = Rect::ltrb(0, 0, w, (std::min)(kTitleHeight, h)); drawTitleBand(bmp, title, "Zone - keyboard map"); { - const Rect back{w - kPad - kNavButtonWidth, 2, w - kPad, (std::max)(2, title.bottom - 2)}; + const Rect back = Rect::ltrb(w - kPad - kNavButtonWidth, 2, w - kPad, (std::max)(2, title.bottom() - 2)); const KitButtonBox box{toKitBox(back)}; const InteractionState st = isHovered(HoverKind::kBack, -1) ? InteractionState::Hover : InteractionState::Rest; @@ -1957,8 +1958,8 @@ void ReaSamplerEditor::paintZone(LICE_IBitmap* bmp, int w, int h) { // A single "+ Add Zone" affordance at the top of the content, then the keyboard strip // with one bar per zone. Delete is a small × on the selected zone (keystroke also). - Rect addR{content.left + pad, content.top + 4, content.left + pad + 96, - content.top + 4 + 20}; + Rect addR = Rect::ltrb(content.x + pad, content.y + 4, content.x + pad + 96, + content.y + 4 + 20); { const KitButtonBox box{toKitBox(addR)}; const InteractionState state = @@ -1966,7 +1967,7 @@ void ReaSamplerEditor::paintZone(LICE_IBitmap* bmp, int w, int h) { drawButton(bmp, box, "+ Add Zone", state, /*warn=*/false); } - Rect delR{addR.right + 8, addR.top, addR.right + 8 + 64, addR.bottom}; + Rect delR = Rect::ltrb(addR.right() + 8, addR.y, addR.right() + 8 + 64, addR.bottom()); if (selectedZone_ >= 0) { const KitButtonBox box{toKitBox(delR)}; const InteractionState state = @@ -1981,22 +1982,22 @@ void ReaSamplerEditor::paintZone(LICE_IBitmap* bmp, int w, int h) { // zone is live"); the rest take the categorical secondary hue at low alpha. const Rect stripArea = zonesStripArea(content); drawSpectralStrip(bmp, stripArea); - const StripLayout sl = layoutStrip(stripArea.width(), stripArea.height()); - const int sx = stripArea.left; - const int sy = stripArea.top; + const StripLayout sl = layoutStrip(stripArea.width, stripArea.height); + const int sx = stripArea.x; + const int sy = stripArea.y; for (int i = 0; i < static_cast(map_.zones.size()); ++i) { const PerformanceZone& z = map_.zones[static_cast(i)]; Rect bar = zoneBarRect(sl, z.lowNote, z.highNote); - const int bw = (std::max)(2, bar.width()); + const int bw = (std::max)(2, bar.width); const bool sel = (i == selectedZone_); if (sel) { // Static glow halo behind the live zone, then the crisp accent-primary bar. - LICE_FillRect(bmp, bar.left + sx - 2, sy, bw + 4, stripArea.height(), + LICE_FillRect(bmp, bar.x + sx - 2, sy, bw + 4, stripArea.height, toLice(roleColor(Role::AccentHot)), 0.30f, 0); - LICE_FillRect(bmp, bar.left + sx, sy, bw, stripArea.height(), + LICE_FillRect(bmp, bar.x + sx, sy, bw, stripArea.height, toLice(roleColor(Role::AccentPrimary)), 1.0f, 0); } else { - LICE_FillRect(bmp, bar.left + sx, sy, bw, stripArea.height(), + LICE_FillRect(bmp, bar.x + sx, sy, bw, stripArea.height, toLice(roleColor(Role::AccentSecondary)), 0.55f, 0); } } @@ -2004,11 +2005,11 @@ void ReaSamplerEditor::paintZone(LICE_IBitmap* bmp, int w, int h) { // A one-line legend of the selected zone below the strip, with three click-to-type numeric // entry fields (low / high / root) — S12 direct numeric entry. Clicking a field focuses it // (entryField_) and typed text commits via parseNoteEntry on Enter. - const int legendTop = stripArea.bottom + 8; - Rect infoR{stripArea.left, legendTop, stripArea.right, legendTop + 18}; + const int legendTop = stripArea.bottom() + 8; + Rect infoR = Rect::ltrb(stripArea.x, legendTop, stripArea.right(), legendTop + 18); if (selectedZone_ >= 0 && selectedZone_ < static_cast(map_.zones.size())) { const PerformanceZone& z = map_.zones[static_cast(selectedZone_)]; - kitText(bmp, Rect{infoR.left, infoR.top, infoR.left + 120, infoR.bottom}, + kitText(bmp, Rect::ltrb(infoR.x, infoR.y, infoR.x + 120, infoR.bottom()), sampleLabel(samples_, processor_ ? processor_->sampleRefs() : SampleRefs{}, z.sampleId) .c_str(), @@ -2027,11 +2028,11 @@ void ReaSamplerEditor::paintZone(LICE_IBitmap* bmp, int w, int h) { editing ? InteractionState::Focus : InteractionState::Rest); const KitColor border = editing ? roleColor(Role::TextPrimary) : roleColor(Role::LineHairline); - LICE_DrawRect(bmp, fr.left, fr.top, fr.width() - 1, fr.height() - 1, + LICE_DrawRect(bmp, fr.x, fr.y, fr.width - 1, fr.height - 1, toLice(border), 1.0f, 0); std::string cap = std::string(names[f]) + ": " + (editing ? (entryText_ + "_") : vals[f]); - kitText(bmp, Rect{fr.left + 4, fr.top, fr.right - 2, fr.bottom}, cap.c_str(), + kitText(bmp, Rect::ltrb(fr.x + 4, fr.y, fr.right() - 2, fr.bottom()), cap.c_str(), Font::ValueMono, Role::TextPrimary); } } else if (map_.zones.empty()) { @@ -2077,9 +2078,9 @@ void ReaSamplerEditor::resolveHover(int x, int y) { else if (contains(bm.confirm, x, y)) h = {HoverKind::kBrowseConfirm, -1}; else if (contains(bm.search, x, y)) h = {HoverKind::kSearchBox, -1}; else { - const BrowserLayout bl = layoutBrowser(bm.content.width(), bm.content.height()); - const int bx = x - bm.content.left; - const int by = y - bm.content.top; + const BrowserLayout bl = layoutBrowser(bm.content.width, bm.content.height); + const int bx = x - bm.content.x; + const int by = y - bm.content.y; const int tabCount = static_cast(banks_.size()) + 1; const int tab = filterTabHitTest(bl, tabCount, bx, by); const int card = (tab >= 0) @@ -2099,11 +2100,11 @@ void ReaSamplerEditor::resolveHover(int x, int y) { if (idx >= 0) h = {HoverKind::kCurveNode, idx}; } } else if (view_ == View::kZone) { - const Rect back{w - kPad - kNavButtonWidth, 2, - w - kPad, (std::max)(2, (std::min)(kTitleHeight, hgt) - 2)}; + const Rect back = Rect::ltrb(w - kPad - kNavButtonWidth, 2, + w - kPad, (std::max)(2, (std::min)(kTitleHeight, hgt) - 2)); const Rect content = zoneContentArea(w, hgt); - Rect addR{content.left + kPad, content.top + 4, content.left + kPad + 96, content.top + 4 + 20}; - Rect delR{addR.right + 8, addR.top, addR.right + 8 + 64, addR.bottom}; + Rect addR = Rect::ltrb(content.x + kPad, content.y + 4, content.x + kPad + 96, content.y + 4 + 20); + Rect delR = Rect::ltrb(addR.right() + 8, addR.y, addR.right() + 8 + 64, addR.bottom()); if (contains(back, x, y)) { h = {HoverKind::kBack, -1}; } else if (contains(addR, x, y)) { @@ -2119,8 +2120,8 @@ void ReaSamplerEditor::resolveHover(int x, int y) { const ZonePlaySeconds& play = map_.zones[static_cast(selectedZone_)].play; const Rect deckArea = zonesDeckArea(content); - const DeckLayout dl = layoutDeck(zoneDeckGroupDescs(play), deckArea.left, - deckArea.top, deckArea.width()); + const DeckLayout dl = layoutDeck(zoneDeckGroupDescs(play), deckArea.x, + deckArea.y, deckArea.width); const DeckHit dh = hitTestDeck(dl, x, y); if (dh.kind != DeckHitKind::None) h = {HoverKind::kControl, dh.id}; } @@ -2147,7 +2148,7 @@ void ReaSamplerEditor::resolveHover(int x, int y) { else if (contains(bands.deck, x, y)) { // A deck knob/toggle under the pointer: knobs light + swap label->value. const DeckLayout dl = - layoutDeck(descs, bands.deck.left, bands.deck.top, bands.deck.width()); + layoutDeck(descs, bands.deck.x, bands.deck.y, bands.deck.width); const DeckHit dh = hitTestDeck(dl, x, y); if (dh.kind != DeckHitKind::None) h = {HoverKind::kControl, dh.id}; } @@ -2194,9 +2195,9 @@ void ReaSamplerEditor::onMouseDown(int x, int y) { if (contains(bm.search, x, y)) { searchFocused_ = true; invalidate(); return; } searchFocused_ = false; - const BrowserLayout bl = layoutBrowser(bm.content.width(), bm.content.height()); - const int bx = x - bm.content.left; - const int by = y - bm.content.top; + const BrowserLayout bl = layoutBrowser(bm.content.width, bm.content.height); + const int bx = x - bm.content.x; + const int by = y - bm.content.y; const int tabCount = static_cast(banks_.size()) + 1; const int tab = filterTabHitTest(bl, tabCount, bx, by); if (tab >= 0) { @@ -2207,9 +2208,9 @@ void ReaSamplerEditor::onMouseDown(int x, int y) { return; } const Rect thumb = scrollThumbRect(bl, static_cast(visible_.size()), scrollOffset_); - if (thumb.height() > 0 && - contains(Rect{thumb.left + bm.content.left, thumb.top + bm.content.top, - thumb.right + bm.content.left, thumb.bottom + bm.content.top}, x, y)) { + if (thumb.height > 0 && + contains(Rect::ltrb(thumb.x + bm.content.x, thumb.y + bm.content.y, + thumb.right() + bm.content.x, thumb.bottom() + bm.content.y), x, y)) { drag_ = DragKind::kScrollThumb; dragStartY_ = y; dragStartScrollOffset_ = scrollOffset_; @@ -2308,8 +2309,8 @@ void ReaSamplerEditor::onMouseDown(int x, int y) { // precedent); knobs start a grab-anchored vertical drag. The deck band swallows its // clicks (no fall-through to the hero/markers). if (contains(bands.deck, x, y)) { - const DeckLayout dl = layoutDeck(deckDescs, bands.deck.left, bands.deck.top, - bands.deck.width()); + const DeckLayout dl = layoutDeck(deckDescs, bands.deck.x, bands.deck.y, + bands.deck.width); const DeckHit hit = hitTestDeck(dl, x, y); if (hit.kind == DeckHitKind::CaptionToggle || hit.kind == DeckHitKind::RowToggle) { switch (static_cast(hit.id)) { @@ -2422,9 +2423,9 @@ void ReaSamplerEditor::onMouseDown(int x, int y) { } // Fenced root strip: grab the root marker (remainder-width since r11). - if (cr.rootStrip.width() > 0) { - const StripLayout sl = layoutStrip(cr.rootStrip.width(), cr.rootStrip.height()); - const int note = keyAtPoint(sl, x - cr.rootStrip.left, y - cr.rootStrip.top); + if (cr.rootStrip.width > 0) { + const StripLayout sl = layoutStrip(cr.rootStrip.width, cr.rootStrip.height); + const int note = keyAtPoint(sl, x - cr.rootStrip.x, y - cr.rootStrip.y); if (note >= 0) { drag_ = DragKind::kRootMarker; dragStartX_ = x; @@ -2441,13 +2442,13 @@ void ReaSamplerEditor::onMouseDown(int x, int y) { // The curve popup is modal over the Zone surface too (FB2) — it owns every click while // open, checked before every Zone affordance (incl. Back). if (handlePopupMouseDown(w, h, x, y)) return; - const Rect back{w - kPad - kNavButtonWidth, 2, - w - kPad, (std::max)(2, (std::min)(kTitleHeight, h) - 2)}; + const Rect back = Rect::ltrb(w - kPad - kNavButtonWidth, 2, + w - kPad, (std::max)(2, (std::min)(kTitleHeight, h) - 2)); if (contains(back, x, y)) { view_ = View::kSample; invalidate(); return; } const Rect content = zoneContentArea(w, h); const int pad = 8; - Rect addR{content.left + pad, content.top + 4, content.left + pad + 96, - content.top + 4 + 20}; + Rect addR = Rect::ltrb(content.x + pad, content.y + 4, content.x + pad + 96, + content.y + 4 + 20); if (contains(addR, x, y)) { // Add a narrow default zone for the picked capture (or the first visible sample as a // sensible seed). No pick -> nothing to add. If a full-keyboard zone for the seed id @@ -2483,7 +2484,7 @@ void ReaSamplerEditor::onMouseDown(int x, int y) { commitAndReload(); return; } - Rect delR{addR.right + 8, addR.top, addR.right + 8 + 64, addR.bottom}; + Rect delR = Rect::ltrb(addR.right() + 8, addR.y, addR.right() + 8 + 64, addR.bottom()); if (selectedZone_ >= 0 && contains(delR, x, y)) { map_.zones.erase(map_.zones.begin() + selectedZone_); selectedZone_ = -1; @@ -2494,9 +2495,9 @@ void ReaSamplerEditor::onMouseDown(int x, int y) { // The zones strip: hit-test a bar edge/body to start a drag, or a bare key to set the // selected zone's root. const Rect stripArea = zonesStripArea(content); - const StripLayout sl = layoutStrip(stripArea.width(), stripArea.height()); - const int lx = x - stripArea.left; - const int ly = y - stripArea.top; + const StripLayout sl = layoutStrip(stripArea.width, stripArea.height); + const int lx = x - stripArea.x; + const int ly = y - stripArea.y; std::vector lows, highs; lows.reserve(map_.zones.size()); @@ -2558,8 +2559,8 @@ void ReaSamplerEditor::onMouseDown(int x, int y) { } const ZonePlaySeconds& play = map_.zones[static_cast(selectedZone_)].play; const Rect deckArea = zonesDeckArea(content); - const DeckLayout dl = layoutDeck(zoneDeckGroupDescs(play), deckArea.left, deckArea.top, - deckArea.width()); + const DeckLayout dl = layoutDeck(zoneDeckGroupDescs(play), deckArea.x, deckArea.y, + deckArea.width); const DeckHit hit = hitTestDeck(dl, x, y); if (hit.kind == DeckHitKind::CaptionToggle || hit.kind == DeckHitKind::RowToggle) { // Zone-param toggles (play mode / pitch engine / pitch-env enable): a discrete, @@ -2633,7 +2634,7 @@ void ReaSamplerEditor::onMouseMove(int x, int y) { // upsert by id so a repeated drag edits the same zone rather than stacking duplicates. const ChannelToggleRects chan = channelToggleRects(bands.cluster); const Rect stripArea = clusterRects(bands.cluster, chan.mono).rootStrip; - const StripLayout sl = layoutStrip(stripArea.width(), stripArea.height()); + const StripLayout sl = layoutStrip(stripArea.width, stripArea.height); const int note = resolveDragNote(sl, dragStartRoot_, dx); bool found = false; for (int i = 0; i < static_cast(map_.zones.size()); ++i) { @@ -2747,7 +2748,7 @@ void ReaSamplerEditor::onMouseMove(int x, int y) { // at paint from scrollOffset_. const int dyThumb = y - dragStartY_; const BrowseModal bm = computeBrowseModal(w, h); - const BrowserLayout bl = layoutBrowser(bm.content.width(), bm.content.height()); + const BrowserLayout bl = layoutBrowser(bm.content.width, bm.content.height); scrollOffset_ = thumbDragToOffset(bl, static_cast(visible_.size()), dragStartScrollOffset_, dyThumb); invalidate(); @@ -2758,7 +2759,7 @@ void ReaSamplerEditor::onMouseMove(int x, int y) { // in the Zone surface where selectedZone_ is set + the strip lives under its content area. if (selectedZone_ < 0 || selectedZone_ >= static_cast(map_.zones.size())) return; const Rect stripArea = zonesStripArea(zoneContentArea(w, h)); - const StripLayout sl = layoutStrip(stripArea.width(), stripArea.height()); + const StripLayout sl = layoutStrip(stripArea.width, stripArea.height); PerformanceZone& z = map_.zones[static_cast(selectedZone_)]; if (drag_ == DragKind::kZoneLow) { z.lowNote = (std::min)(resolveDragNote(sl, dragStartLow_, dx), z.highNote); @@ -2818,10 +2819,10 @@ void ReaSamplerEditor::onMouseUp(int x, int y) { // move — its amp keeps the last clamped drag value). if (kind == DragKind::kCurveNode && curveIdx >= 0 && curveZone >= 0 && curveZone < static_cast(map_.zones.size())) { - const bool off = x < curveRect.left - kCurveDragOffMargin || - x > curveRect.right + kCurveDragOffMargin || - y < curveRect.top - kCurveDragOffMargin || - y > curveRect.bottom + kCurveDragOffMargin; + const bool off = x < curveRect.x - kCurveDragOffMargin || + x > curveRect.right() + kCurveDragOffMargin || + y < curveRect.y - kCurveDragOffMargin || + y > curveRect.bottom() + kCurveDragOffMargin; if (off) { map_.zones[static_cast(curveZone)].velocityCurve.deletePoint( static_cast(curveIdx)); diff --git a/src/vst/reasampler_editor.h b/src/vst/reasampler_editor.h index 91e0082..03938d8 100644 --- a/src/vst/reasampler_editor.h +++ b/src/vst/reasampler_editor.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // reasampler_editor.h — the VST3 IPlugView LICE editor for the ReaSampler 9000 // capture-first UI (Phase S10). THIN shell: hosts a LICE-drawn child window inside the // host's IPlugView seat and routes host paint/mouse into the pure geometry modules @@ -30,13 +31,13 @@ #include "public.sdk/source/common/pluginview.h" -#include "editor_geometry.h" // Rect (the shell's sub-rect type, shared with the pure modules) -#include "envelope_edit.h" // EnvClampBounds / NodeHit (S-VIEW-3 envelope node hit-test/edit) -#include "envelope_overlay.h" // AmpEnvelope / EnvNode (S-VIEW-3 envelope overlay draw seam) -#include "knob_deck.h" // DeckGroupDesc / DeckLayout (r11 knob deck — Sample FB1, Zone FB2) -#include "peaks.h" // Envelope (the cached peak thumbnail) -#include "sample_map.h" // SampleChoice, BankChoice, PerformanceMap (the shell's snapshot) -#include "velocity_curve.h" // VelocityCurve (S-VIEW-10 transfer-curve editor state) +#include "core/instrument/ui/editor_geometry.h" // Rect (the shell's sub-rect type, shared with the pure modules) +#include "core/instrument/ui/envelope_edit.h" // EnvClampBounds / NodeHit (S-VIEW-3 envelope node hit-test/edit) +#include "core/instrument/ui/envelope_overlay.h" // AmpEnvelope / EnvNode (S-VIEW-3 envelope overlay draw seam) +#include "core/instrument/ui/knob_deck.h" // DeckGroupDesc / DeckLayout (r11 knob deck — Sample FB1, Zone FB2) +#include "core/audio/peaks.h" // Envelope (the cached peak thumbnail) +#include "core/instrument/map/sample_map.h" // SampleChoice, BankChoice, PerformanceMap (the shell's snapshot) +#include "core/instrument/engine/velocity_curve.h" // VelocityCurve (S-VIEW-10 transfer-curve editor state) #ifdef _WIN32 #include diff --git a/src/vst/reasampler_processor.cpp b/src/vst/reasampler_processor.cpp index 3cb03e2..4cce960 100644 --- a/src/vst/reasampler_processor.cpp +++ b/src/vst/reasampler_processor.cpp @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // reasampler_processor.cpp — see reasampler_processor.h. #include "reasampler_processor.h" @@ -17,17 +18,17 @@ #include "pluginterfaces/vst/ivstmidicontrollers.h" // kCtrlAllNotesOff / kCtrlAllSoundsOff (panic) #include "pluginterfaces/vst/vstspeaker.h" -#include "assignment_request.h" // decodeAssignmentRequest (S8 request wire parse) -#include "bank_sync.h" // S9/S8 pure decisions: parseBankGeneration, consumeDecision -#include "capture_paths.h" // resolveBankFile (shared M4 path resolution) +#include "core/wire/assignment_request.h" // decodeAssignmentRequest (S8 request wire parse) +#include "core/instrument/map/bank_sync.h" // S9/S8 pure decisions: parseBankGeneration, consumeDecision +#include "core/capture/capture_paths.h" // resolveBankFile (shared M4 path resolution) #include "core/util/file_bytes.h" // shared whole-file loader (Q-W1, T2-03) #include "ext_keys.h" // kProjExtBanksKey / kProjExtBankGenKey / kProjExtAssignKey (shared wire contract) -#include "master_gain.h" // masterGainMaxLinear (FB1 post-mixer gain clamp) +#include "core/instrument/engine/master_gain.h" // masterGainMaxLinear (FB1 post-mixer gain clamp) #include "reasampler_editor.h" -#include "reasampler_embed.h" // S6 embed shell + IReaperUIEmbedInterface (its iid DEF'd there) -#include "sample_map.h" // refs resolve, buildZonedKeymap, state (de)ser (pS self-contained) -#include "sample_usage.h" // pS-usage publish plan + wire (prune-protection seam) -#include "wav_trim.h" // parseWavLayout, extractFloatFrames (shared WAV parse) +#include "shell/instrument/reasampler_embed.h" // S6 embed shell + IReaperUIEmbedInterface (its iid DEF'd there) +#include "core/instrument/map/sample_map.h" // refs resolve, buildZonedKeymap, state (de)ser (pS self-contained) +#include "core/wire/sample_usage.h" // pS-usage publish plan + wire (prune-protection seam) +#include "core/capture/wav_trim.h" // parseWavLayout, extractFloatFrames (shared WAV parse) using namespace Steinberg; using namespace Steinberg::Vst; diff --git a/src/vst/reasampler_processor.h b/src/vst/reasampler_processor.h index 73f0e85..29b3b6c 100644 --- a/src/vst/reasampler_processor.h +++ b/src/vst/reasampler_processor.h @@ -1,3 +1,4 @@ +#include "core/namespaces.h" // reasampler_processor.h — the VST3 SingleComponentEffect (Phase S4, Tier 0). Wires the // pure S3 sampler core into a real VSTi: it declares an event-input bus + a stereo audio // output bus, marshals host MIDI note-on/off into the VoiceEngine, and renders the @@ -35,9 +36,9 @@ #include "public.sdk/source/vst/vstsinglecomponenteffect.h" -#include "reaper_bridge.h" -#include "sample_map.h" // PerformanceMap (the instrument's owned zoned keymap) -#include "sampler_core.h" +#include "shell/instrument/reaper_bridge.h" +#include "core/instrument/map/sample_map.h" // PerformanceMap (the instrument's owned zoned keymap) +#include "core/instrument/engine/sampler_core.h" namespace reasampler::vst { diff --git a/tests/test_action_bar.cpp b/tests/test_action_bar.cpp index cda6d88..7eba7b1 100644 --- a/tests/test_action_bar.cpp +++ b/tests/test_action_bar.cpp @@ -15,13 +15,14 @@ // degenerate/too-narrow bar handled without crash or overlap. // * Resize: no inventory item cut off or overlapping across a representative width range. -#include "../src/action_bar.h" +#include "../src/core/ui/action_bar.h" #include #include #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_app_version.cpp b/tests/test_app_version.cpp index 0fca97e..2f8c1e3 100644 --- a/tests/test_app_version.cpp +++ b/tests/test_app_version.cpp @@ -5,12 +5,13 @@ // back from ext state (PreVersioning / Unknown / Stamped). The ext-state I/O (persist) and // the show-version action (main) are DAW-verified shell. -#include "../src/app_version.h" +#include "../src/core/version/app_version.h" #include #include using namespace reasampler; +using namespace reasampler::version; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_app_version_padding.cpp b/tests/test_app_version_padding.cpp index 4e89dfd..295cc4d 100644 --- a/tests/test_app_version_padding.cpp +++ b/tests/test_app_version_padding.cpp @@ -25,12 +25,13 @@ // configure_file input in CMakeLists.txt. They are NOT the shipped version: never bump // them on release — their padded shape is the entire point. -#include "../src/app_version.h" +#include "../src/core/version/app_version.h" #include #include using namespace reasampler; +using namespace reasampler::version; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_assignment_request.cpp b/tests/test_assignment_request.cpp index 9dfe12c..45292e6 100644 --- a/tests/test_assignment_request.cpp +++ b/tests/test_assignment_request.cpp @@ -9,12 +9,13 @@ // trailing-garbage input -> nullopt (the reader's "no pending request" fallback hinges // on it). -#include "../src/assignment_request.h" +#include "../src/core/wire/assignment_request.h" #include #include using namespace reasampler; +using namespace reasampler::wire; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_bank_book.cpp b/tests/test_bank_book.cpp index 0e8674c..f72b69f 100644 --- a/tests/test_bank_book.cpp +++ b/tests/test_bank_book.cpp @@ -10,13 +10,14 @@ // pool, set named, invalid id); JSON round-trip lossless (full book); legacy // bank_index → pool migration. -#include "../src/bank_book.h" +#include "../src/core/model/bank_book.h" #include #include #include using namespace reasampler; +using namespace reasampler::model; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -339,9 +340,9 @@ static void testJsonEmptyBookRoundTrip() { } static void testLegacyMigration() { - // A bare legacy bank_index JSON (BankIndex::serialize output — has "samples", no + // A bare legacy bank_index JSON (BankModel::serialize output — has "samples", no // "banks") must promote into the pool: a book of { pool } with zero named banks. - BankIndex legacy; + BankModel legacy; CHECK(legacy.add(sampleWith("old1")) == AddResult::Added); CHECK(legacy.add(sampleWith("old2")) == AddResult::Added); std::string legacyJson = legacy.serialize(); @@ -361,7 +362,7 @@ static void testLegacyMigration() { } // An EMPTY legacy index ("{\"samples\":[]}" style via serialize) also migrates. - BankIndex emptyLegacy; + BankModel emptyLegacy; auto back2 = BankBook::deserialize(emptyLegacy.serialize()); CHECK(back2.has_value()); CHECK(back2 && back2->size() == 1 && back2->pool().index.empty()); @@ -408,7 +409,7 @@ static void testLoadPrefersBanksBlob() { CHECK(src.bank("drums")->index.add(sampleWith("new1")) == AddResult::Added); const std::string banksJson = src.serialize(); - BankIndex stale; + BankModel stale; CHECK(stale.add(sampleWith("stale-old")) == AddResult::Added); const std::string legacyJson = stale.serialize(); @@ -424,7 +425,7 @@ static void testLoadPrefersBanksBlob() { static void testLoadMigratesLegacyWhenNoBanks() { // No `banks` key, a legacy `bank_index` present: migrate into the pool, zero named. - BankIndex legacy; + BankModel legacy; CHECK(legacy.add(sampleWith("l1")) == AddResult::Added); CHECK(legacy.add(sampleWith("l2")) == AddResult::Added); const std::string legacyJson = legacy.serialize(); @@ -449,7 +450,7 @@ static void testLoadEmptyWhenNeither() { static void testLoadMalformedBanksDegradesWithoutLegacyFallback() { // A present-but-malformed `banks` blob must degrade to an empty book and must NOT // resurrect the stale legacy key (that would revive superseded single-bank state). - BankIndex stale; + BankModel stale; CHECK(stale.add(sampleWith("stale")) == AddResult::Added); const std::string legacyJson = stale.serialize(); @@ -936,7 +937,7 @@ static void testBankBookSlotsRoundTrip() { static void testMigrationDefaultsToInsertionOrderDense() { // A pre-L7 legacy bank_index blob carries no slot data. On load -> reconcileSlots // seeds dense insertion order (no gaps), so it is visually identical. - BankIndex legacy; + BankModel legacy; CHECK(legacy.add(sampleWith("o1")) == AddResult::Added); CHECK(legacy.add(sampleWith("o2")) == AddResult::Added); CHECK(legacy.add(sampleWith("o3")) == AddResult::Added); diff --git a/tests/test_bank_grid.cpp b/tests/test_bank_grid.cpp index 9d178b8..2c0b07e 100644 --- a/tests/test_bank_grid.cpp +++ b/tests/test_bank_grid.cpp @@ -13,7 +13,7 @@ // keyboard nav (arrow clamp, row moves, shift-extend, partial-last-row clamp, // fresh-panel focus). -#include "../src/bank_grid.h" +#include "../src/core/ui/bank_grid.h" #include #include @@ -21,6 +21,7 @@ #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_bank_model.cpp b/tests/test_bank_model.cpp index 5de98db..8111e91 100644 --- a/tests/test_bank_model.cpp +++ b/tests/test_bank_model.cpp @@ -6,12 +6,13 @@ // present AND absent), dedup-by-hash collapse, tier filter + tier move, // relative-path invariant, empty-index round-trip, malformed/truncated JSON. -#include "../src/bank_model.h" +#include "../src/core/model/bank_model.h" #include #include using namespace reasampler; +using namespace reasampler::model; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -65,12 +66,12 @@ static Sample minimalSample(const std::string& seed) { } static void testFullFieldRoundTrip() { - BankIndex idx; + BankModel idx; CHECK(idx.add(fullSample("a")) == AddResult::Added); CHECK(idx.add(minimalSample("b")) == AddResult::Added); std::string json = idx.serialize(); - auto back = BankIndex::deserialize(json); + auto back = BankModel::deserialize(json); CHECK(back.has_value()); CHECK(back && *back == idx); @@ -104,7 +105,7 @@ static void testFullFieldRoundTrip() { } static void testDedupByHash() { - BankIndex idx; + BankModel idx; Sample a = fullSample("x"); CHECK(idx.add(a) == AddResult::Added); @@ -128,7 +129,7 @@ static void testDedupByHash() { } static void testTierFilterAndMove() { - BankIndex idx; + BankModel idx; Sample scratch = minimalSample("s"); scratch.tier = Tier::Scratch; Sample archive = fullSample("a"); archive.tier = Tier::Archive; CHECK(idx.add(scratch) == AddResult::Added); @@ -153,7 +154,7 @@ static void testTierFilterAndMove() { } static void testRelativePathInvariant() { - BankIndex idx; + BankModel idx; // POSIX absolute, Windows drive, Windows backslash, UNC — all rejected. const char* absolutes[] = { @@ -184,7 +185,7 @@ static void testRelativePathInvariant() { // bypasses dedup (an in-place refresh is not a new insert). The relative-paths-only // invariant still guards the replacement. static void testUpdateInPlace() { - BankIndex idx; + BankModel idx; CHECK(idx.add(minimalSample("a")) == AddResult::Added); // id "min-a" CHECK(idx.add(minimalSample("b")) == AddResult::Added); // id "min-b" CHECK(idx.add(minimalSample("c")) == AddResult::Added); // id "min-c" @@ -221,10 +222,10 @@ static void testUpdateInPlace() { } static void testEmptyIndexRoundTrip() { - BankIndex idx; + BankModel idx; CHECK(idx.empty()); std::string json = idx.serialize(); - auto back = BankIndex::deserialize(json); + auto back = BankModel::deserialize(json); CHECK(back.has_value()); CHECK(back && back->empty()); CHECK(back && *back == idx); @@ -244,17 +245,17 @@ static void testMalformedJson() { "{\"samples\":[{\"createdTimestamp\":notanumber}]}", }; for (const char* j : bad) { - auto r = BankIndex::deserialize(j); + auto r = BankModel::deserialize(j); CHECK(!r.has_value()); // signaled as nullopt, no crash / UB } // A well-formed empty object deserializes to an empty index (lenient root). - auto ok = BankIndex::deserialize("{}"); + auto ok = BankModel::deserialize("{}"); CHECK(ok.has_value() && ok->empty()); } static void testRemoveAndQuery() { - BankIndex idx; + BankModel idx; CHECK(idx.add(fullSample("1")) == AddResult::Added); CHECK(idx.add(fullSample("2")) == AddResult::Added); CHECK(idx.query("id-1") != nullptr); @@ -267,7 +268,7 @@ static void testRemoveAndQuery() { // Fix 1: drive-relative and bare-drive forms must be rejected by add(). static void testAbsolutePathDriveRelative() { - BankIndex idx; + BankModel idx; // Drive-relative: resolves against the drive's CWD, not the project root. Sample dr = minimalSample("dr"); @@ -295,7 +296,7 @@ static void testAbsolutePathDriveRelative() { static void testUnicodeEscapeDecoding() { // é = U+00E9 → 2-byte UTF-8: 0xC3 0xA9 // JSON: "é" - auto r1 = BankIndex::deserialize( + auto r1 = BankModel::deserialize( "{\"samples\":[{\"id\":\"u1\",\"relativePath\":\"bank/u.wav\"," "\"displayName\":\"\\u00e9\"," "\"sourceMode\":0,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0," @@ -319,7 +320,7 @@ static void testUnicodeEscapeDecoding() { // 中 = U+4E2D → 3-byte UTF-8: 0xE4 0xB8 0xAD // JSON: "中" - auto r2 = BankIndex::deserialize( + auto r2 = BankModel::deserialize( "{\"samples\":[{\"id\":\"u2\",\"relativePath\":\"bank/u.wav\"," "\"displayName\":\"\\u4e2d\"," "\"sourceMode\":0,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0," @@ -343,7 +344,7 @@ static void testUnicodeEscapeDecoding() { } // 😀 = U+1F600 → surrogate pair 😀 → 4-byte UTF-8: 0xF0 0x9F 0x98 0x80 - auto r3 = BankIndex::deserialize( + auto r3 = BankModel::deserialize( "{\"samples\":[{\"id\":\"u3\",\"relativePath\":\"bank/u.wav\"," "\"displayName\":\"\\uD83D\\uDE00\"," "\"sourceMode\":0,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0," @@ -368,7 +369,7 @@ static void testUnicodeEscapeDecoding() { } // Unpaired high surrogate (no following \uDCxx) → nullopt. - auto r4 = BankIndex::deserialize( + auto r4 = BankModel::deserialize( "{\"samples\":[{\"id\":\"u4\",\"relativePath\":\"bank/u.wav\"," "\"displayName\":\"\\uD83D\"," "\"sourceMode\":0,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0," @@ -384,7 +385,7 @@ static void testUnicodeEscapeDecoding() { // Fix 3: strtoll overflow must reject the value, not clamp it silently. static void testIntegerOverflow() { // A timestamp value that overflows int64_t (> 9223372036854775807). - auto r = BankIndex::deserialize( + auto r = BankModel::deserialize( "{\"samples\":[{\"id\":\"ov1\",\"relativePath\":\"bank/ov.wav\"," "\"displayName\":\"\"," "\"sourceMode\":0,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0," @@ -400,7 +401,7 @@ static void testIntegerOverflow() { // Fix 4: out-of-range enum values must reject the sample, not produce invalid enum. static void testEnumRangeValidation() { // tier: 99 is not a valid Tier enumerator. - auto r1 = BankIndex::deserialize( + auto r1 = BankModel::deserialize( "{\"samples\":[{\"id\":\"en1\",\"relativePath\":\"bank/en.wav\"," "\"displayName\":\"\"," "\"sourceMode\":0,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0," @@ -413,7 +414,7 @@ static void testEnumRangeValidation() { CHECK(!r1.has_value()); // sourceMode: 99 is not a valid SourceMode enumerator. - auto r2 = BankIndex::deserialize( + auto r2 = BankModel::deserialize( "{\"samples\":[{\"id\":\"en2\",\"relativePath\":\"bank/en.wav\"," "\"displayName\":\"\"," "\"sourceMode\":99,\"sourceRange\":{\"startSeconds\":0.0,\"endSeconds\":0.0," @@ -442,7 +443,7 @@ static void testLegacyJsonDefaults() { "\"key\":null,\"levels\":{\"peakDb\":0.0,\"rmsDb\":0.0,\"lufs\":0.0}," "\"clipped\":false,\"tier\":0,\"contentHash\":\"h-leg1\"," "\"provenance\":null,\"createdTimestamp\":0}]}"; - auto r = BankIndex::deserialize(legacy); + auto r = BankModel::deserialize(legacy); CHECK(r.has_value()); if (r) { const Sample* s = r->query("leg1"); @@ -453,7 +454,7 @@ static void testLegacyJsonDefaults() { // Re-serialize is lossless: parsing it again yields an equal index. This // proves the absent fields did not silently gain values on the way out. std::string out = r->serialize(); - auto again = BankIndex::deserialize(out); + auto again = BankModel::deserialize(out); CHECK(again.has_value()); CHECK(again && *again == *r); if (again) { @@ -471,7 +472,7 @@ static void testLegacyJsonDefaults() { // storing a bogus value. static void testSeamFieldBoundaries() { // rootNote at both MIDI edges + equal-and-end-anchored loop points round-trip. - BankIndex idx; + BankModel idx; Sample lo = minimalSample("lo"); lo.contentHash = "h-lo"; lo.rootNote = 0; lo.loop = LoopPoints{0, 0}; // zero-length marker at frame 0 @@ -484,7 +485,7 @@ static void testSeamFieldBoundaries() { CHECK(idx.add(hi) == AddResult::Added); CHECK(idx.add(end) == AddResult::Added); - auto back = BankIndex::deserialize(idx.serialize()); + auto back = BankModel::deserialize(idx.serialize()); CHECK(back.has_value()); CHECK(back && *back == idx); if (back) { @@ -514,21 +515,21 @@ static void testSeamFieldBoundaries() { "\"clipped\":false,\"tier\":0,\"contentHash\":\"h-bad\"," "\"provenance\":null,\"createdTimestamp\":0}]}"; - CHECK(!BankIndex::deserialize(std::string(head) + "\"rootNote\":128," + tail).has_value()); - CHECK(!BankIndex::deserialize(std::string(head) + "\"rootNote\":-1," + tail).has_value()); - CHECK(!BankIndex::deserialize( + CHECK(!BankModel::deserialize(std::string(head) + "\"rootNote\":128," + tail).has_value()); + CHECK(!BankModel::deserialize(std::string(head) + "\"rootNote\":-1," + tail).has_value()); + CHECK(!BankModel::deserialize( std::string(head) + "\"loop\":{\"start\":10,\"end\":5}," + tail).has_value()); // start > end - CHECK(!BankIndex::deserialize( + CHECK(!BankModel::deserialize( std::string(head) + "\"loop\":{\"start\":-1,\"end\":5}," + tail).has_value()); // negative start } // S2 test case 3: the seam-field addition is purely additive — dedup-by-hash, tier -// moves/filtering, and BankIndex ordering are byte-for-byte unchanged by the +// moves/filtering, and BankModel ordering are byte-for-byte unchanged by the // presence (or absence) of rootNote/loop. Two samples differing ONLY in seam fields // but sharing a content hash still collapse; a seam-populated sample tiers exactly // like any other. static void testSeamFieldsAdditiveInvariant() { - BankIndex idx; + BankModel idx; Sample a = fullSample("z"); // has rootNote + loop populated CHECK(idx.add(a) == AddResult::Added); diff --git a/tests/test_bank_sync.cpp b/tests/test_bank_sync.cpp index cdcad2a..4195ecb 100644 --- a/tests/test_bank_sync.cpp +++ b/tests/test_bank_sync.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::bank_sync — no REAPER, no VST3, no framework. +// Standalone tests for reasampler::instrument::map::bank_sync — no REAPER, no VST3, no framework. // The S9 bank-generation change-detection + the S8 assignment-request consume DECISION // (the yes/no maths the instrument's off-audio-thread poll runs). The shell owns the // cadence + side effects; this proves the decision rules without a host. @@ -8,7 +8,7 @@ // (no request / not-newer / non-target / unresolvable-drop / apply), asserting both the // apply flag AND the advanced-marker value so a stale request is never re-evaluated. -#include "../src/vst/bank_sync.h" +#include "../src/core/instrument/map/bank_sync.h" #include #include @@ -16,7 +16,7 @@ #include using namespace reasampler; -using namespace reasampler::vst; +using namespace reasampler::instrument::map; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_batch_capture.cpp b/tests/test_batch_capture.cpp index 3cb8e93..f8dbd3b 100644 --- a/tests/test_batch_capture.cpp +++ b/tests/test_batch_capture.cpp @@ -9,13 +9,14 @@ // * batch result aggregation: all-success; partial-failure ORDERING (failed // ordinals reported in unit order); all-failed; single-unit noun singular. -#include "../src/batch_capture.h" +#include "../src/core/capture/batch_capture.h" #include #include #include using namespace reasampler; +using namespace reasampler::capture; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_bridge_marshal.cpp b/tests/test_bridge_marshal.cpp index 8c6abbb..926f1d2 100644 --- a/tests/test_bridge_marshal.cpp +++ b/tests/test_bridge_marshal.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::bridge_marshal — no VST3, no REAPER, no test +// Standalone tests for reasampler::instrument::map::bridge_marshal — no VST3, no REAPER, no test // framework. Same fast assert loop as the sibling pure tests: assert the REAPER // bridge-read marshalling (GetProjExtState result decode) directly, so the DAW-facing // shell only has to invoke the API. @@ -8,11 +8,12 @@ // (the instrument now parses the bank through the shared bank_book JSON path), so its // cases are gone with it. -#include "../src/vst/bridge_marshal.h" +#include "../src/core/instrument/map/bridge_marshal.h" #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::map; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_browser_scroll.cpp b/tests/test_browser_scroll.cpp index 9448f52..a1114cf 100644 --- a/tests/test_browser_scroll.cpp +++ b/tests/test_browser_scroll.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::browser_scroll — no VST3, no REAPER, no framework. +// Standalone tests for reasampler::instrument::ui::browser_scroll — no VST3, no REAPER, no framework. // Same fast assert loop as the sibling pure editor tests: assert the S12 scroll-window + // scrollbar-thumb + type-to-filter-search geometry LAYERED over the S10 capture_browser. // @@ -11,13 +11,14 @@ // (case-insensitive substring, empty-query identity, no-match); filterNameIndices preserving order // and returning every index for an empty query. -#include "../src/vst/browser_scroll.h" +#include "../src/core/instrument/ui/browser_scroll.h" #include #include #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -45,7 +46,7 @@ static void testMaxOffsetFitsAndOverflows() { CHECK(scrollMaxOffset(L, L.columns) == 0); // Many rows overflow -> max = content - gridHeight. const int many = L.columns * 20; - const int expect = scrollContentHeight(L, many) - L.grid.height(); + const int expect = scrollContentHeight(L, many) - L.grid.height; CHECK(scrollMaxOffset(L, many) == expect); CHECK(expect > 0); } @@ -67,7 +68,7 @@ static void testVisibleRangeTop() { const VisibleRange vr = visibleCardRange(L, many, 0); CHECK(vr.first == 0); // At offset 0, the last visible row is the one containing (gridH-1). - const int expectedLastRow = (L.grid.height() - 1) / kBrowserCardHeight + 1; + const int expectedLastRow = (L.grid.height - 1) / kBrowserCardHeight + 1; CHECK(vr.last == expectedLastRow * L.columns); } @@ -89,30 +90,30 @@ static void testScrolledCellShiftsUp() { const BrowserLayout L = wideLayout(); const Rect base = cardCellRect(L, 3); const Rect shifted = scrolledCardCellRect(L, 3, 40); - CHECK(shifted.top == base.top - 40); - CHECK(shifted.bottom == base.bottom - 40); - CHECK(shifted.left == base.left); + CHECK(shifted.y == base.y - 40); + CHECK(shifted.bottom() == base.bottom() - 40); + CHECK(shifted.x == base.x); } // --- scrollbar thumb ---------------------------------------------------------- static void testThumbEmptyWhenFits() { const BrowserLayout L = wideLayout(); - CHECK(scrollThumbRect(L, L.columns, 0).height() == 0); // one row fits -> no thumb + CHECK(scrollThumbRect(L, L.columns, 0).height == 0); // one row fits -> no thumb } static void testThumbProportionalAndClamped() { const BrowserLayout L = wideLayout(); const int many = L.columns * 20; const Rect atTop = scrollThumbRect(L, many, 0); - CHECK(atTop.height() > 0); - CHECK(atTop.top == L.grid.top); // at offset 0 the thumb starts at the track top - CHECK(atTop.width() == kScrollbarWidth); - CHECK(atTop.right == L.grid.right); + CHECK(atTop.height > 0); + CHECK(atTop.y == L.grid.y); // at offset 0 the thumb starts at the track top + CHECK(atTop.width == kScrollbarWidth); + CHECK(atTop.right() == L.grid.right()); // At max offset, the thumb bottom reaches the grid bottom (pinned to the end). const int maxOff = scrollMaxOffset(L, many); const Rect atMax = scrollThumbRect(L, many, maxOff); - CHECK(atMax.bottom == L.grid.top + L.grid.height()); + CHECK(atMax.bottom() == L.grid.y + L.grid.height); } static void testThumbDragIsInverse() { @@ -126,7 +127,7 @@ static void testThumbDragIsInverse() { CHECK(thumbDragToOffset(L, many, maxOff, -100000) == 0); // Dragging the thumb by the whole track span from top reaches (near) max. const Rect thumb = scrollThumbRect(L, many, 0); - const int trackSpan = L.grid.height() - thumb.height(); + const int trackSpan = L.grid.height - thumb.height; const int off = thumbDragToOffset(L, many, 0, trackSpan); CHECK(off >= maxOff - 2 && off <= maxOff); } @@ -135,8 +136,8 @@ static void testThumbDragIsInverse() { static void testSearchBoxRect() { const Rect r = searchBoxRect(200); - CHECK(r.left == 0 && r.top == 0 && r.right == 200 && r.height() == kSearchBoxHeight); - CHECK(searchBoxRect(0).width() == 0); + CHECK(r.x == 0 && r.y == 0 && r.right() == 200 && r.height == kSearchBoxHeight); + CHECK(searchBoxRect(0).width == 0); } static void testNameMatch() { diff --git a/tests/test_capture_browser.cpp b/tests/test_capture_browser.cpp index 9f8a7a0..9c0239e 100644 --- a/tests/test_capture_browser.cpp +++ b/tests/test_capture_browser.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::capture_browser — no VST3, no REAPER, no framework. +// Standalone tests for reasampler::instrument::ui::capture_browser — no VST3, no REAPER, no framework. // Same fast assert loop as the sibling pure tests (embed_strip / editor_geometry): assert // the capture-first browser's card-grid + bank-filter-tab layout and hit-testing directly. // @@ -10,11 +10,12 @@ // the strip into equal segments with the last tab absorbing the remainder; filterTabHitTest // hitting each tab and missing off-strip. -#include "../src/vst/capture_browser.h" +#include "../src/core/instrument/ui/capture_browser.h" #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -25,12 +26,12 @@ static int g_fail = 0; static void testLayoutNormalArea() { // Wide enough for several columns of the fixed-width card. const BrowserLayout L = layoutBrowser(560, 300); - CHECK(L.tabStrip.left == 0 && L.tabStrip.top == 0 && L.tabStrip.right == 560); - CHECK(L.tabStrip.height() == kBrowserTabHeight); + CHECK(L.tabStrip.x == 0 && L.tabStrip.y == 0 && L.tabStrip.right() == 560); + CHECK(L.tabStrip.height == kBrowserTabHeight); // The grid starts right below the tab strip and fills the rest, contiguous. - CHECK(L.grid.top == L.tabStrip.bottom); - CHECK(L.grid.bottom == 300 && L.grid.right == 560); - // columns = grid.width() / cardWidth (>= 1). + CHECK(L.grid.y == L.tabStrip.bottom()); + CHECK(L.grid.bottom() == 300 && L.grid.right() == 560); + // columns = grid.width / cardWidth (>= 1). CHECK(L.columns == 560 / kBrowserCardWidth); CHECK(L.columns >= 1); } @@ -39,22 +40,22 @@ static void testLayoutNarrowAreaSingleColumn() { // Narrower than one card: still a single column, no inversion. const BrowserLayout L = layoutBrowser(kBrowserCardWidth - 10, 200); CHECK(L.columns == 1); - CHECK(L.grid.width() >= 0); - CHECK(L.tabStrip.height() == kBrowserTabHeight); + CHECK(L.grid.width >= 0); + CHECK(L.tabStrip.height == kBrowserTabHeight); } static void testLayoutZeroArea() { const BrowserLayout L = layoutBrowser(0, 0); - CHECK(L.tabStrip.width() == 0 && L.tabStrip.height() == 0); - CHECK(L.grid.width() == 0); + CHECK(L.tabStrip.width == 0 && L.tabStrip.height == 0); + CHECK(L.grid.width == 0); CHECK(L.columns == 1); // never zero (avoids a divide-by-zero in card layout) } static void testLayoutTinyHeightClampsTabStrip() { // A height below the tab band: the tab strip clamps to the area, the grid is empty. const BrowserLayout L = layoutBrowser(560, kBrowserTabHeight - 6); - CHECK(L.tabStrip.height() == kBrowserTabHeight - 6); - CHECK(L.grid.height() <= 0); // no room left for cards + CHECK(L.tabStrip.height == kBrowserTabHeight - 6); + CHECK(L.grid.height <= 0); // no room left for cards } // --- card rects --------------------------------------------------------------- @@ -64,32 +65,32 @@ static void testCardCellsTileRowMajor() { const int cols = L.columns; // Card 0 is top-left of the grid. const Rect c0 = cardCellRect(L, 0); - CHECK(c0.left == L.grid.left && c0.top == L.grid.top); - CHECK(c0.width() == kBrowserCardWidth && c0.height() == kBrowserCardHeight); + CHECK(c0.x == L.grid.x && c0.y == L.grid.y); + CHECK(c0.width == kBrowserCardWidth && c0.height == kBrowserCardHeight); // Card 1 is one card-width to the right, same row. const Rect c1 = cardCellRect(L, 1); - CHECK(c1.left == L.grid.left + kBrowserCardWidth); - CHECK(c1.top == c0.top); + CHECK(c1.x == L.grid.x + kBrowserCardWidth); + CHECK(c1.y == c0.y); // The first card of the SECOND row wraps back to the left, one card-height down. const Rect wrap = cardCellRect(L, cols); - CHECK(wrap.left == L.grid.left); - CHECK(wrap.top == L.grid.top + kBrowserCardHeight); + CHECK(wrap.x == L.grid.x); + CHECK(wrap.y == L.grid.y + kBrowserCardHeight); } static void testCardCellNegativeIndex() { const BrowserLayout L = layoutBrowser(560, 300); const Rect r = cardCellRect(L, -1); - CHECK(r.left == 0 && r.top == 0 && r.right == 0 && r.bottom == 0); + CHECK(r.x == 0 && r.y == 0 && r.right() == 0 && r.bottom() == 0); } static void testCardContentInsetByGutter() { const BrowserLayout L = layoutBrowser(560, 300); const Rect cell = cardCellRect(L, 0); const Rect content = cardContentRect(L, 0); - CHECK(content.left == cell.left + kBrowserCardGutter); - CHECK(content.top == cell.top + kBrowserCardGutter); - CHECK(content.right == cell.right - kBrowserCardGutter); - CHECK(content.bottom == cell.bottom - kBrowserCardGutter); + CHECK(content.x == cell.x + kBrowserCardGutter); + CHECK(content.y == cell.y + kBrowserCardGutter); + CHECK(content.right() == cell.right() - kBrowserCardGutter); + CHECK(content.bottom() == cell.bottom() - kBrowserCardGutter); } static void testThumbnailAboveLabel() { @@ -98,12 +99,12 @@ static void testThumbnailAboveLabel() { const Rect thumb = cardThumbnailRect(L, 0); const Rect label = cardLabelRect(L, 0); // Thumbnail is the top band of the content; the label is the remainder below it, contiguous. - CHECK(thumb.left == content.left && thumb.right == content.right); - CHECK(thumb.top == content.top); - CHECK(thumb.height() == kBrowserThumbHeight); - CHECK(label.top == thumb.bottom); - CHECK(label.bottom == content.bottom); - CHECK(label.left == content.left && label.right == content.right); + CHECK(thumb.x == content.x && thumb.right() == content.right()); + CHECK(thumb.y == content.y); + CHECK(thumb.height == kBrowserThumbHeight); + CHECK(label.y == thumb.bottom()); + CHECK(label.bottom() == content.bottom()); + CHECK(label.x == content.x && label.right() == content.right()); } // --- cardHitTest -------------------------------------------------------------- @@ -111,8 +112,8 @@ static void testThumbnailAboveLabel() { static void testCardHitCenterOfCard() { const BrowserLayout L = layoutBrowser(560, 300); const Rect content = cardContentRect(L, 3); - const int cx = content.left + content.width() / 2; - const int cy = content.top + content.height() / 2; + const int cx = content.x + content.width / 2; + const int cy = content.y + content.height / 2; CHECK(cardHitTest(L, 12, cx, cy) == 3); } @@ -121,21 +122,21 @@ static void testCardHitMissesGutter() { // A point in the gutter between the content and the cell edge (top-left corner of cell 0) // is a miss — only the card CONTENT counts. const Rect cell = cardCellRect(L, 0); - CHECK(cardHitTest(L, 12, cell.left, cell.top) == -1); + CHECK(cardHitTest(L, 12, cell.x, cell.y) == -1); } static void testCardHitMissesPastLastCard() { const BrowserLayout L = layoutBrowser(560, 300); // Only 2 cards exist; a point on where card 5 WOULD be is a miss. const Rect content = cardContentRect(L, 5); - const int cx = content.left + content.width() / 2; - const int cy = content.top + content.height() / 2; + const int cx = content.x + content.width / 2; + const int cy = content.y + content.height / 2; CHECK(cardHitTest(L, 2, cx, cy) == -1); } static void testCardHitMissesTabStrip() { const BrowserLayout L = layoutBrowser(560, 300); - CHECK(cardHitTest(L, 12, 10, L.tabStrip.top + 2) == -1); + CHECK(cardHitTest(L, 12, 10, L.tabStrip.y + 2) == -1); } static void testCardHitZeroCards() { @@ -150,21 +151,21 @@ static void testFilterTabsTileStrip() { const int n = 4; // "All" + 3 banks const Rect t0 = filterTabRect(L, n, 0); const Rect tLast = filterTabRect(L, n, n - 1); - CHECK(t0.left == L.tabStrip.left); + CHECK(t0.x == L.tabStrip.x); // Adjacent tabs share an exact edge (no gap). - CHECK(filterTabRect(L, n, 0).right == filterTabRect(L, n, 1).left); - CHECK(filterTabRect(L, n, 1).right == filterTabRect(L, n, 2).left); + CHECK(filterTabRect(L, n, 0).right() == filterTabRect(L, n, 1).x); + CHECK(filterTabRect(L, n, 1).right() == filterTabRect(L, n, 2).x); // The last tab reaches the strip's right edge exactly (absorbs the remainder). - CHECK(tLast.right == L.tabStrip.right); + CHECK(tLast.right() == L.tabStrip.right()); // All tabs share the strip's height. - CHECK(t0.top == L.tabStrip.top && t0.bottom == L.tabStrip.bottom); + CHECK(t0.y == L.tabStrip.y && t0.bottom() == L.tabStrip.bottom()); } static void testFilterTabOutOfRange() { const BrowserLayout L = layoutBrowser(560, 300); - CHECK(filterTabRect(L, 3, -1).width() == 0); - CHECK(filterTabRect(L, 3, 3).width() == 0); - CHECK(filterTabRect(L, 0, 0).width() == 0); + CHECK(filterTabRect(L, 3, -1).width == 0); + CHECK(filterTabRect(L, 3, 3).width == 0); + CHECK(filterTabRect(L, 0, 0).width == 0); } static void testFilterTabHit() { @@ -172,12 +173,12 @@ static void testFilterTabHit() { const int n = 3; for (int i = 0; i < n; ++i) { const Rect t = filterTabRect(L, n, i); - const int cx = t.left + t.width() / 2; - const int cy = t.top + t.height() / 2; + const int cx = t.x + t.width / 2; + const int cy = t.y + t.height / 2; CHECK(filterTabHitTest(L, n, cx, cy) == i); } // Below the strip (in the grid) -> no tab. - CHECK(filterTabHitTest(L, n, 20, L.grid.top + 4) == -1); + CHECK(filterTabHitTest(L, n, 20, L.grid.y + 4) == -1); } int main() { diff --git a/tests/test_capture_paths.cpp b/tests/test_capture_paths.cpp index aff2b5e..3d253c9 100644 --- a/tests/test_capture_paths.cpp +++ b/tests/test_capture_paths.cpp @@ -1,9 +1,9 @@ // Standalone tests for reasampler::capture_paths — no REAPER, no framework. // The capture shell is DAW-bound and only verifiable in REAPER; this covers the // one genuinely pure piece: the bank-folder / unique-name / project-relative -// path arithmetic that feeds BankIndex::add's relative-only invariant. +// path arithmetic that feeds BankModel::add's relative-only invariant. -#include "../src/capture_paths.h" +#include "../src/core/capture/capture_paths.h" #include #include @@ -12,6 +12,7 @@ #include using namespace reasampler; +using namespace reasampler::capture; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -74,7 +75,7 @@ static void testDeriveRelativePathIsProjectRelative() { BankPaths p = deriveBankPaths("C:\\Users\\d\\proj", "master mix", "1753080000"); // Relative path is under the fixed bank subfolder, forward-slashed, .wav. CHECK(p.relativePath == "reasampler_bank/master_mix_1753080000.wav"); - // It must NOT be absolute by any of BankIndex::add's rejection rules: + // It must NOT be absolute by any of BankModel::add's rejection rules: // no leading '/', no drive letter, no backslash, no UNC prefix. CHECK(p.relativePath.find(':') == std::string::npos); CHECK(p.relativePath.find('\\') == std::string::npos); diff --git a/tests/test_card_drag.cpp b/tests/test_card_drag.cpp index a7018f4..473c6f1 100644 --- a/tests/test_card_drag.cpp +++ b/tests/test_card_drag.cpp @@ -7,11 +7,12 @@ // -> None); cursor-cue mapping (incl. Replace only for Replace); slot rects include empties // (gap layout), dense layout matches a plain grid, slot hit-test returns slot index + miss. -#include "../src/card_drag.h" +#include "../src/core/ui/card_drag.h" #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_card_meta.cpp b/tests/test_card_meta.cpp index 37fcdbf..b94974b 100644 --- a/tests/test_card_meta.cpp +++ b/tests/test_card_meta.cpp @@ -6,12 +6,13 @@ // long capture, non-4/4 meters (3/4 and 6/8), unstamped meter -> blank, unknown tempo -> // blank (s.ms still derivable); s.ms zero / sub-second / multi-second / ms carry / negative. -#include "../src/card_meta.h" +#include "../src/core/ui/card_meta.h" #include #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_component_geometry.cpp b/tests/test_component_geometry.cpp index 3d97095..a793ac4 100644 --- a/tests/test_component_geometry.cpp +++ b/tests/test_component_geometry.cpp @@ -7,11 +7,12 @@ // row, hover hit-test returns the right row and "no hit" outside/past the last row; and the // shared half-open box hit-test agrees with layout (no double-claimed pixel). -#include "../src/component_geometry.h" +#include "../src/core/ui/component_geometry.h" #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_curve_popup.cpp b/tests/test_curve_popup.cpp index 9e6c5c2..262e560 100644 --- a/tests/test_curve_popup.cpp +++ b/tests/test_curve_popup.cpp @@ -1,14 +1,15 @@ -// Standalone tests for reasampler::vst::curve_popup — no VST3, no REAPER, no framework. Same +// Standalone tests for reasampler::instrument::ui::curve_popup — no VST3, no REAPER, no framework. Same // fast assert loop as the sibling pure tests. Assert the r11 popup-sheet geometry at the size // clamps (the spec's width clamp(60%, 360..520) / height clamp(55%, 260..380)), the centering, // the title-row/close-button placement, the curve-box remainder, and the outside-sheet // dismissal test. -#include "../src/vst/curve_popup.h" +#include "../src/core/instrument/ui/curve_popup.h" #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -17,66 +18,66 @@ static int g_fail = 0; static void testDefaultWindowMidClamp() { // 840x620: 60% = 504 (inside 360..520), 55% = 341 (inside 260..380). const CurvePopupLayout pl = computeCurvePopup(840, 620); - CHECK(pl.sheet.width() == 504); - CHECK(pl.sheet.height() == 341); + CHECK(pl.sheet.width == 504); + CHECK(pl.sheet.height == 341); // Centered (within the integer-division pixel). - CHECK(pl.sheet.left == (840 - 504) / 2); - CHECK(pl.sheet.top == (620 - 341) / 2); + CHECK(pl.sheet.x == (840 - 504) / 2); + CHECK(pl.sheet.y == (620 - 341) / 2); } static void testMinClamp() { // The 560x460 constraint floor: 60% = 336 -> clamps UP to 360; 55% = 253 -> up to 260. const CurvePopupLayout pl = computeCurvePopup(560, 460); - CHECK(pl.sheet.width() == kCurvePopupMinW); - CHECK(pl.sheet.height() == kCurvePopupMinH); - CHECK(pl.sheet.left >= 0 && pl.sheet.right <= 560); - CHECK(pl.sheet.top >= 0 && pl.sheet.bottom <= 460); + CHECK(pl.sheet.width == kCurvePopupMinW); + CHECK(pl.sheet.height == kCurvePopupMinH); + CHECK(pl.sheet.x >= 0 && pl.sheet.right() <= 560); + CHECK(pl.sheet.y >= 0 && pl.sheet.bottom() <= 460); } static void testMaxClamp() { // A large window: 60% of 1600 = 960 -> clamps DOWN to 520; 55% of 900 = 495 -> down to 380. const CurvePopupLayout pl = computeCurvePopup(1600, 900); - CHECK(pl.sheet.width() == kCurvePopupMaxW); - CHECK(pl.sheet.height() == kCurvePopupMaxH); + CHECK(pl.sheet.width == kCurvePopupMaxW); + CHECK(pl.sheet.height == kCurvePopupMaxH); } static void testDegenerateWindowNeverOverhangs() { // A window smaller than the min clamp: the sheet caps at the window dimension (defensive — // below checkSizeConstraint, but geometry must stay sane). const CurvePopupLayout pl = computeCurvePopup(300, 200); - CHECK(pl.sheet.width() == 300); - CHECK(pl.sheet.height() == 200); - CHECK(pl.sheet.left == 0 && pl.sheet.top == 0); + CHECK(pl.sheet.width == 300); + CHECK(pl.sheet.height == 200); + CHECK(pl.sheet.x == 0 && pl.sheet.y == 0); } static void testTitleRowAndCurveBox() { const CurvePopupLayout pl = computeCurvePopup(840, 620); // Close: 18x18, right-anchored inside the title row. - CHECK(pl.close.width() == kCurvePopupCloseSize && pl.close.height() == kCurvePopupCloseSize); - CHECK(pl.close.right == pl.sheet.right - kCurvePopupPad); - CHECK(pl.close.top >= pl.sheet.top); - CHECK(pl.close.bottom <= pl.sheet.top + kCurvePopupTitleH); + CHECK(pl.close.width == kCurvePopupCloseSize && pl.close.height == kCurvePopupCloseSize); + CHECK(pl.close.right() == pl.sheet.right() - kCurvePopupPad); + CHECK(pl.close.y >= pl.sheet.y); + CHECK(pl.close.bottom() <= pl.sheet.y + kCurvePopupTitleH); // Title text: left of the close button, in the title row. - CHECK(pl.title.left == pl.sheet.left + kCurvePopupPad); - CHECK(pl.title.right <= pl.close.left); + CHECK(pl.title.x == pl.sheet.x + kCurvePopupPad); + CHECK(pl.title.right() <= pl.close.x); // Curve box: fills the remainder below the title row, inside the sheet margins. - CHECK(pl.curveBox.top >= pl.sheet.top + kCurvePopupTitleH); - CHECK(pl.curveBox.left == pl.sheet.left + kCurvePopupPad); - CHECK(pl.curveBox.right == pl.sheet.right - kCurvePopupPad); - CHECK(pl.curveBox.bottom == pl.sheet.bottom - kCurvePopupPad); - CHECK(pl.curveBox.width() > 0 && pl.curveBox.height() > 0); + CHECK(pl.curveBox.y >= pl.sheet.y + kCurvePopupTitleH); + CHECK(pl.curveBox.x == pl.sheet.x + kCurvePopupPad); + CHECK(pl.curveBox.right() == pl.sheet.right() - kCurvePopupPad); + CHECK(pl.curveBox.bottom() == pl.sheet.bottom() - kCurvePopupPad); + CHECK(pl.curveBox.width > 0 && pl.curveBox.height > 0); } static void testOutsideSheetDismissTest() { const CurvePopupLayout pl = computeCurvePopup(840, 620); // On the wash: outside. CHECK(popupOutsideSheet(pl, 0, 0)); - CHECK(popupOutsideSheet(pl, pl.sheet.left - 1, pl.sheet.top + 10)); - CHECK(popupOutsideSheet(pl, pl.sheet.right, pl.sheet.top + 10)); // half-open right edge + CHECK(popupOutsideSheet(pl, pl.sheet.x - 1, pl.sheet.y + 10)); + CHECK(popupOutsideSheet(pl, pl.sheet.right(), pl.sheet.y + 10)); // half-open right edge // On the sheet (title row, curve box, padding): inside. - CHECK(!popupOutsideSheet(pl, pl.sheet.left, pl.sheet.top)); - CHECK(!popupOutsideSheet(pl, pl.curveBox.left + 5, pl.curveBox.top + 5)); - CHECK(!popupOutsideSheet(pl, pl.sheet.right - 1, pl.sheet.bottom - 1)); + CHECK(!popupOutsideSheet(pl, pl.sheet.x, pl.sheet.y)); + CHECK(!popupOutsideSheet(pl, pl.curveBox.x + 5, pl.curveBox.y + 5)); + CHECK(!popupOutsideSheet(pl, pl.sheet.right() - 1, pl.sheet.bottom() - 1)); } int main() { diff --git a/tests/test_drag_out.cpp b/tests/test_drag_out.cpp index 8da3977..3511c44 100644 --- a/tests/test_drag_out.cpp +++ b/tests/test_drag_out.cpp @@ -9,13 +9,14 @@ // * Path-list assembly: single, multi, dedupe (cross-bank copy case), skip-missing, // skip-unresolved, empty selection, order preservation, mixed tallies. -#include "../src/drag_out.h" +#include "../src/core/ui/drag_out.h" #include #include #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_editor_geometry.cpp b/tests/test_editor_geometry.cpp index 2d363bf..858fcb7 100644 --- a/tests/test_editor_geometry.cpp +++ b/tests/test_editor_geometry.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::editor_geometry — no VST3, no REAPER, no test +// Standalone tests for reasampler::instrument::ui::editor_geometry — no VST3, no REAPER, no test // framework. Same fast assert loop as the sibling pure tests (mode_switch et al.): // assert the IPlugView LICE editor's layout math + hit-testing directly. // @@ -8,11 +8,12 @@ // the button, missing on the title/canvas, missing outside the surface, and boundary // pixels; layout<->hit-test agreement (a click on the drawn button rect hits it). -#include "../src/vst/editor_geometry.h" +#include "../src/core/instrument/ui/editor_geometry.h" #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -21,7 +22,7 @@ static int g_fail = 0; // --- contains() --------------------------------------------------------------- static void testContainsHalfOpen() { - Rect r{10, 20, 50, 40}; // [10,50) x [20,40) + Rect r = Rect::ltrb(10, 20, 50, 40); // [10,50) x [20,40) CHECK(contains(r, 10, 20)); // top-left inclusive CHECK(contains(r, 49, 39)); // bottom-right exclusive edge, inside CHECK(!contains(r, 50, 30)); // right edge excluded @@ -31,9 +32,9 @@ static void testContainsHalfOpen() { } static void testContainsDegenerate() { - CHECK(!contains(Rect{10, 10, 10, 20}, 10, 15)); // zero width - CHECK(!contains(Rect{10, 10, 20, 10}, 15, 10)); // zero height - CHECK(!contains(Rect{20, 10, 10, 20}, 15, 15)); // inverted (right < left) + CHECK(!contains(Rect::ltrb(10, 10, 10, 20), 10, 15)); // zero width + CHECK(!contains(Rect::ltrb(10, 10, 20, 10), 15, 10)); // zero height + CHECK(!contains(Rect::ltrb(20, 10, 10, 20), 15, 15)); // inverted (right < left) } // --- layoutEditor: normal view ------------------------------------------------ @@ -43,20 +44,20 @@ static void testLayoutNormalView() { // rest; button sits inside the canvas, inset by the margin. const EditorLayout L = layoutEditor(400, 260); - CHECK(L.titleBar.left == 0 && L.titleBar.top == 0); - CHECK(L.titleBar.right == 400); - CHECK(L.titleBar.height() > 0 && L.titleBar.height() <= 260); + CHECK(L.titleBar.x == 0 && L.titleBar.y == 0); + CHECK(L.titleBar.right() == 400); + CHECK(L.titleBar.height > 0 && L.titleBar.height <= 260); // Canvas begins right below the title bar and reaches the bottom-right. - CHECK(L.canvas.top == L.titleBar.bottom); - CHECK(L.canvas.right == 400 && L.canvas.bottom == 260); + CHECK(L.canvas.y == L.titleBar.bottom()); + CHECK(L.canvas.right() == 400 && L.canvas.bottom() == 260); // Button is inside the canvas (does not overhang any edge). - CHECK(L.button.left >= L.canvas.left); - CHECK(L.button.top >= L.canvas.top); - CHECK(L.button.right <= L.canvas.right); - CHECK(L.button.bottom <= L.canvas.bottom); - CHECK(L.button.width() > 0 && L.button.height() > 0); + CHECK(L.button.x >= L.canvas.x); + CHECK(L.button.y >= L.canvas.y); + CHECK(L.button.right() <= L.canvas.right()); + CHECK(L.button.bottom() <= L.canvas.bottom()); + CHECK(L.button.width > 0 && L.button.height > 0); } // --- layoutEditor: tiny view (clamping) --------------------------------------- @@ -65,25 +66,25 @@ static void testLayoutTinyViewClampsButton() { // A view narrower/shorter than the button's natural size: the button must clamp to // the canvas and never produce an inverted or overhanging rect. const EditorLayout L = layoutEditor(40, 40); - CHECK(L.button.right <= L.canvas.right); - CHECK(L.button.bottom <= L.canvas.bottom); - CHECK(L.button.right >= L.button.left); // never inverted - CHECK(L.button.bottom >= L.button.top); + CHECK(L.button.right() <= L.canvas.right()); + CHECK(L.button.bottom() <= L.canvas.bottom()); + CHECK(L.button.right() >= L.button.x); // never inverted + CHECK(L.button.bottom() >= L.button.y); // Title bar clamps to the client height when the view is shorter than its height. - CHECK(L.titleBar.bottom <= 40); + CHECK(L.titleBar.bottom() <= 40); } // --- layoutEditor: zero view (all empty, no inversion) ------------------------ static void testLayoutZeroView() { const EditorLayout L = layoutEditor(0, 0); - CHECK(L.titleBar.width() <= 0 || L.titleBar.height() <= 0); - CHECK(L.canvas.width() <= 0 || L.canvas.height() <= 0); + CHECK(L.titleBar.width <= 0 || L.titleBar.height <= 0); + CHECK(L.canvas.width <= 0 || L.canvas.height <= 0); // No rect is inverted. - CHECK(L.button.right >= L.button.left); - CHECK(L.button.bottom >= L.button.top); - CHECK(L.canvas.right >= L.canvas.left); - CHECK(L.canvas.bottom >= L.canvas.top); + CHECK(L.button.right() >= L.button.x); + CHECK(L.button.bottom() >= L.button.y); + CHECK(L.canvas.right() >= L.canvas.x); + CHECK(L.canvas.bottom() >= L.canvas.y); // A click anywhere on an empty layout hits nothing. CHECK(hitTest(L, 0, 0) == HitTarget::kNone); CHECK(hitTest(L, 5, 5) == HitTarget::kNone); @@ -94,15 +95,15 @@ static void testLayoutZeroView() { static void testHitTestButton() { const EditorLayout L = layoutEditor(400, 260); // Center of the button hits it. - const int cx = (L.button.left + L.button.right) / 2; - const int cy = (L.button.top + L.button.bottom) / 2; + const int cx = (L.button.x + L.button.right()) / 2; + const int cy = (L.button.y + L.button.bottom()) / 2; CHECK(hitTest(L, cx, cy) == HitTarget::kButton); } static void testHitTestMissesNonButton() { const EditorLayout L = layoutEditor(400, 260); // Title bar is inert in the spike. - CHECK(hitTest(L, 200, L.titleBar.top + 1) == HitTarget::kNone); + CHECK(hitTest(L, 200, L.titleBar.y + 1) == HitTarget::kNone); // Empty canvas away from the button. CHECK(hitTest(L, 380, 240) == HitTarget::kNone); // Outside the surface entirely. @@ -113,9 +114,9 @@ static void testHitTestMissesNonButton() { static void testHitTestButtonBoundary() { const EditorLayout L = layoutEditor(400, 260); // Top-left corner of the button is inclusive; the right/bottom edges are excluded. - CHECK(hitTest(L, L.button.left, L.button.top) == HitTarget::kButton); - CHECK(hitTest(L, L.button.right, L.button.top) == HitTarget::kNone); - CHECK(hitTest(L, L.button.left, L.button.bottom) == HitTarget::kNone); + CHECK(hitTest(L, L.button.x, L.button.y) == HitTarget::kButton); + CHECK(hitTest(L, L.button.right(), L.button.y) == HitTarget::kNone); + CHECK(hitTest(L, L.button.x, L.button.bottom()) == HitTarget::kNone); } // --- layout<->hit-test agreement ---------------------------------------------- @@ -124,8 +125,8 @@ static void testHitTestButtonBoundary() { // load-bearing consistency invariant between what the shell draws and what it routes. static void testHitTestMatchesDrawnButton() { const EditorLayout L = layoutEditor(320, 200); - for (int y = L.button.top; y < L.button.bottom; ++y) { - for (int x = L.button.left; x < L.button.right; ++x) { + for (int y = L.button.y; y < L.button.bottom(); ++y) { + for (int x = L.button.x; x < L.button.right(); ++x) { CHECK(hitTest(L, x, y) == HitTarget::kButton); } } @@ -138,14 +139,14 @@ static void testSampleRowRectStacks() { const Rect r0 = sampleRowRect(L, 0); const Rect r1 = sampleRowRect(L, 1); // Row 0 starts at the canvas top and spans its full width. - CHECK(r0.top == L.canvas.top); - CHECK(r0.left == L.canvas.left && r0.right == L.canvas.right); - CHECK(r0.height() == kSampleRowHeight); + CHECK(r0.y == L.canvas.y); + CHECK(r0.x == L.canvas.x && r0.right() == L.canvas.right()); + CHECK(r0.height == kSampleRowHeight); // Row 1 sits directly below row 0 (no gap, no overlap). - CHECK(r1.top == r0.bottom); - CHECK(r1.height() == kSampleRowHeight); + CHECK(r1.y == r0.bottom()); + CHECK(r1.height == kSampleRowHeight); // A negative index is an empty rect. - CHECK(sampleRowRect(L, -1).width() == 0 && sampleRowRect(L, -1).height() == 0); + CHECK(sampleRowRect(L, -1).width == 0 && sampleRowRect(L, -1).height == 0); } static void testSampleRowHitTestMapsClickToRow() { @@ -153,35 +154,35 @@ static void testSampleRowHitTestMapsClickToRow() { const int rows = 5; // A click in the vertical middle of row 2 resolves to index 2. const Rect r2 = sampleRowRect(L, 2); - const int midY = (r2.top + r2.bottom) / 2; + const int midY = (r2.y + r2.bottom()) / 2; CHECK(sampleRowHitTest(L, rows, 200, midY) == 2); // Row 0's top-left corner hits row 0. const Rect r0 = sampleRowRect(L, 0); - CHECK(sampleRowHitTest(L, rows, r0.left, r0.top) == 0); + CHECK(sampleRowHitTest(L, rows, r0.x, r0.y) == 0); } static void testSampleRowHitTestMisses() { const EditorLayout L = layoutEditor(400, 260); const int rows = 3; // Above the first row (in the title bar) -> no row. - CHECK(sampleRowHitTest(L, rows, 200, L.titleBar.top) == -1); + CHECK(sampleRowHitTest(L, rows, 200, L.titleBar.y) == -1); // Below the last row -> no row. const Rect last = sampleRowRect(L, rows - 1); - CHECK(sampleRowHitTest(L, rows, 200, last.bottom + 1) == -1); + CHECK(sampleRowHitTest(L, rows, 200, last.bottom() + 1) == -1); // Left of the canvas -> no row. - CHECK(sampleRowHitTest(L, rows, L.canvas.left - 1, last.top) == -1); + CHECK(sampleRowHitTest(L, rows, L.canvas.x - 1, last.y) == -1); // Zero rows -> always -1. - CHECK(sampleRowHitTest(L, 0, 200, L.canvas.top + 1) == -1); - // At or below canvas.bottom -> always -1, even if rowCount would cover that y. + CHECK(sampleRowHitTest(L, 0, 200, L.canvas.y + 1) == -1); + // At or below canvas.bottom() -> always -1, even if rowCount would cover that y. // This guards paint<->hit-test agreement: sampleRowRect does not clamp to canvas, - // so without this clip a row that extends past canvas.bottom would hit-test but + // so without this clip a row that extends past canvas.bottom() would hit-test but // never be drawn (or vice versa). - CHECK(sampleRowHitTest(L, rows, 200, L.canvas.bottom) == -1); + CHECK(sampleRowHitTest(L, rows, 200, L.canvas.bottom()) == -1); // Use a large rowCount so index arithmetic would return a valid row without the - // canvas.bottom guard — proving the guard fires independently of rowCount. + // canvas.bottom() guard — proving the guard fires independently of rowCount. const int bigRows = 1000; - CHECK(sampleRowHitTest(L, bigRows, 200, L.canvas.bottom) == -1); - CHECK(sampleRowHitTest(L, bigRows, 200, L.canvas.bottom + 5) == -1); + CHECK(sampleRowHitTest(L, bigRows, 200, L.canvas.bottom()) == -1); + CHECK(sampleRowHitTest(L, bigRows, 200, L.canvas.bottom() + 5) == -1); } // The drawn-row <-> hit-test agreement: every pixel inside a row rect must resolve to @@ -191,10 +192,10 @@ static void testSampleRowHitTestMatchesDrawnRows() { const int rows = 4; for (int i = 0; i < rows; ++i) { const Rect r = sampleRowRect(L, i); - if (r.top >= L.canvas.bottom) break; // clipped rows aren't clickable targets - const int y = (r.top + r.bottom) / 2; - if (y >= L.canvas.bottom) continue; - CHECK(sampleRowHitTest(L, rows, r.left + 1, y) == i); + if (r.y >= L.canvas.bottom()) break; // clipped rows aren't clickable targets + const int y = (r.y + r.bottom()) / 2; + if (y >= L.canvas.bottom()) continue; + CHECK(sampleRowHitTest(L, rows, r.x + 1, y) == i); } } @@ -204,30 +205,30 @@ static void testKeymapLayoutSplitsCanvas() { const KeymapEditorLayout L = layoutKeymapEditor(600, 300); // The left sample list and right zone panel partition the canvas with no overlap and // no gap: the list's right edge is the panel's left edge. - CHECK(L.sampleList.left == L.base.canvas.left); - CHECK(L.sampleList.right == L.zonePanel.left); - CHECK(L.zonePanel.right == L.base.canvas.right); - CHECK(L.sampleList.top == L.base.canvas.top); - CHECK(L.zonePanel.top == L.base.canvas.top); - CHECK(L.sampleList.bottom == L.base.canvas.bottom); - CHECK(L.zonePanel.bottom == L.base.canvas.bottom); - CHECK(L.sampleList.width() > 0 && L.zonePanel.width() > 0); + CHECK(L.sampleList.x == L.base.canvas.x); + CHECK(L.sampleList.right() == L.zonePanel.x); + CHECK(L.zonePanel.right() == L.base.canvas.right()); + CHECK(L.sampleList.y == L.base.canvas.y); + CHECK(L.zonePanel.y == L.base.canvas.y); + CHECK(L.sampleList.bottom() == L.base.canvas.bottom()); + CHECK(L.zonePanel.bottom() == L.base.canvas.bottom()); + CHECK(L.sampleList.width > 0 && L.zonePanel.width > 0); // Add-Zone button caps the panel; zone rows stack below it. - CHECK(L.addZoneButton.top == L.zonePanel.top); - CHECK(L.addZoneButton.left == L.zonePanel.left && L.addZoneButton.right == L.zonePanel.right); - CHECK(L.zoneRowArea.top == L.addZoneButton.bottom); - CHECK(L.zoneRowArea.bottom == L.zonePanel.bottom); + CHECK(L.addZoneButton.y == L.zonePanel.y); + CHECK(L.addZoneButton.x == L.zonePanel.x && L.addZoneButton.right() == L.zonePanel.right()); + CHECK(L.zoneRowArea.y == L.addZoneButton.bottom()); + CHECK(L.zoneRowArea.bottom() == L.zonePanel.bottom()); } static void checkNoInversion(const KeymapEditorLayout& L) { - CHECK(L.sampleList.right >= L.sampleList.left); - CHECK(L.zonePanel.right >= L.zonePanel.left); - CHECK(L.addZoneButton.right >= L.addZoneButton.left); - CHECK(L.addZoneButton.bottom >= L.addZoneButton.top); - CHECK(L.zoneRowArea.right >= L.zoneRowArea.left); - CHECK(L.zoneRowArea.bottom >= L.zoneRowArea.top); + CHECK(L.sampleList.right() >= L.sampleList.x); + CHECK(L.zonePanel.right() >= L.zonePanel.x); + CHECK(L.addZoneButton.right() >= L.addZoneButton.x); + CHECK(L.addZoneButton.bottom() >= L.addZoneButton.y); + CHECK(L.zoneRowArea.right() >= L.zoneRowArea.x); + CHECK(L.zoneRowArea.bottom() >= L.zoneRowArea.y); // Regions stay within the client area. - CHECK(L.zonePanel.right <= L.base.canvas.right); + CHECK(L.zonePanel.right() <= L.base.canvas.right()); } static void testKeymapLayoutTinyAndZeroNoInversion() { @@ -243,36 +244,36 @@ static void testKeymapSampleRowInLeftColumn() { const KeymapEditorLayout L = layoutKeymapEditor(600, 300); const Rect r0 = keymapSampleRowRect(L, 0); // Rows live in the LEFT column (not the full canvas width). - CHECK(r0.left == L.sampleList.left && r0.right == L.sampleList.right); - CHECK(r0.right < L.base.canvas.right); // strictly left of the zone panel - CHECK(r0.top == L.sampleList.top && r0.height() == kSampleRowHeight); + CHECK(r0.x == L.sampleList.x && r0.right() == L.sampleList.right()); + CHECK(r0.right() < L.base.canvas.right()); // strictly left of the zone panel + CHECK(r0.y == L.sampleList.y && r0.height == kSampleRowHeight); // Hit-test maps a left-column click to the row and rejects a click in the zone panel. - const int midY = (r0.top + r0.bottom) / 2; - CHECK(keymapSampleRowHitTest(L, 3, r0.left + 2, midY) == 0); - CHECK(keymapSampleRowHitTest(L, 3, L.zonePanel.left + 2, midY) == -1); + const int midY = (r0.y + r0.bottom()) / 2; + CHECK(keymapSampleRowHitTest(L, 3, r0.x + 2, midY) == 0); + CHECK(keymapSampleRowHitTest(L, 3, L.zonePanel.x + 2, midY) == -1); } static void testAddZoneHitTest() { const KeymapEditorLayout L = layoutKeymapEditor(600, 300); - const int cx = (L.addZoneButton.left + L.addZoneButton.right) / 2; - const int cy = (L.addZoneButton.top + L.addZoneButton.bottom) / 2; + const int cx = (L.addZoneButton.x + L.addZoneButton.right()) / 2; + const int cy = (L.addZoneButton.y + L.addZoneButton.bottom()) / 2; CHECK(addZoneHitTest(L, cx, cy)); // A click in the zone-row area below the button is NOT the Add button. - CHECK(!addZoneHitTest(L, cx, L.zoneRowArea.top + 2)); + CHECK(!addZoneHitTest(L, cx, L.zoneRowArea.y + 2)); // A click in the left list is NOT the Add button. - CHECK(!addZoneHitTest(L, L.sampleList.left + 2, L.sampleList.top + 2)); + CHECK(!addZoneHitTest(L, L.sampleList.x + 2, L.sampleList.y + 2)); } static void testZoneRowStacksAndSelects() { const KeymapEditorLayout L = layoutKeymapEditor(600, 300); const Rect z0 = zoneRowRect(L, 0); const Rect z1 = zoneRowRect(L, 1); - CHECK(z0.top == L.zoneRowArea.top && z0.height() == kZoneRowHeight); - CHECK(z1.top == z0.bottom); // stacked, no gap - CHECK(z0.left == L.zoneRowArea.left && z0.right == L.zoneRowArea.right); + CHECK(z0.y == L.zoneRowArea.y && z0.height == kZoneRowHeight); + CHECK(z1.y == z0.bottom()); // stacked, no gap + CHECK(z0.x == L.zoneRowArea.x && z0.right() == L.zoneRowArea.right()); // A click on the LABEL area (left part of a zone row) selects the zone with no field. - const int labelX = z0.left + 2; // far left = label, not a control - const int midY = (z0.top + z0.bottom) / 2; + const int labelX = z0.x + 2; // far left = label, not a control + const int midY = (z0.y + z0.bottom()) / 2; const ZoneHit h = zoneHitTest(L, 2, labelX, midY); CHECK(h.zoneIndex == 0 && h.field == ZoneField::kZoneNone); } @@ -280,10 +281,10 @@ static void testZoneRowStacksAndSelects() { static void testZoneRowControlsMapToFields() { const KeymapEditorLayout L = layoutKeymapEditor(600, 300); const Rect row = zoneRowRect(L, 0); - const int midY = (row.top + row.bottom) / 2; + const int midY = (row.y + row.bottom()) / 2; // The seven controls occupy the rightmost 7*kZoneCtrlWidth px, left-to-right: // low-, low+, high-, high+, root-, root+, delete. - const int block = row.right - 7 * kZoneCtrlWidth; + const int block = row.right() - 7 * kZoneCtrlWidth; const ZoneField expected[7] = { ZoneField::kLowDown, ZoneField::kLowUp, ZoneField::kHighDown, ZoneField::kHighUp, ZoneField::kRootDown, ZoneField::kRootUp, @@ -300,14 +301,14 @@ static void testZoneRowControlsMapToFields() { static void testZoneHitTestMisses() { const KeymapEditorLayout L = layoutKeymapEditor(600, 300); const Rect row = zoneRowRect(L, 0); - const int midY = (row.top + row.bottom) / 2; + const int midY = (row.y + row.bottom()) / 2; // Zero zones -> always miss. - CHECK(zoneHitTest(L, 0, row.left + 2, midY).zoneIndex == -1); + CHECK(zoneHitTest(L, 0, row.x + 2, midY).zoneIndex == -1); // Below the last zone row -> miss. const Rect last = zoneRowRect(L, 2); - CHECK(zoneHitTest(L, 3, row.left + 2, last.bottom + 1).zoneIndex == -1); + CHECK(zoneHitTest(L, 3, row.x + 2, last.bottom() + 1).zoneIndex == -1); // Left of the zone panel (in the sample list) -> miss. - CHECK(zoneHitTest(L, 3, L.sampleList.left + 2, midY).zoneIndex == -1); + CHECK(zoneHitTest(L, 3, L.sampleList.x + 2, midY).zoneIndex == -1); } int main() { diff --git a/tests/test_embed_strip.cpp b/tests/test_embed_strip.cpp index 4ce3c22..9d3ea93 100644 --- a/tests/test_embed_strip.cpp +++ b/tests/test_embed_strip.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::embed_strip — no VST3, no REAPER, no framework. +// Standalone tests for reasampler::instrument::ui::embed_strip — no VST3, no REAPER, no framework. // Same fast assert loop as the sibling pure tests (editor_geometry et al.): assert the // embedded TCP/MCP strip's layout math + zone hit-testing + level fill directly. // @@ -9,11 +9,12 @@ // on overlap, missing on uncovered keys and off-band, and rejecting a null/empty list; // levelFillRect clamping 0..1 and its endpoints. -#include "../src/vst/embed_strip.h" +#include "../src/core/instrument/ui/embed_strip.h" #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -24,34 +25,34 @@ static int g_fail = 0; static void testLayoutNormalArea() { // A comfortable inline strip: keymap band on top, thin level band pinned to the bottom. const EmbedLayout L = layoutEmbed(300, 40); - CHECK(L.keymap.left == 0 && L.keymap.top == 0 && L.keymap.right == 300); - CHECK(L.levelBand.left == 0 && L.levelBand.right == 300); + CHECK(L.keymap.x == 0 && L.keymap.y == 0 && L.keymap.right() == 300); + CHECK(L.levelBand.x == 0 && L.levelBand.right() == 300); // Level band is the fixed height at the very bottom; keymap fills the rest, contiguous. - CHECK(L.levelBand.height() == kEmbedLevelBandHeight); - CHECK(L.levelBand.bottom == 40); - CHECK(L.keymap.bottom == L.levelBand.top); - CHECK(L.keymap.height() == 40 - kEmbedLevelBandHeight); + CHECK(L.levelBand.height == kEmbedLevelBandHeight); + CHECK(L.levelBand.bottom() == 40); + CHECK(L.keymap.bottom() == L.levelBand.y); + CHECK(L.keymap.height == 40 - kEmbedLevelBandHeight); } static void testLayoutTinyAreaKeepsKeymap() { // A very short area: the level band must yield so the keymap keeps its minimum, and no // rect inverts. const EmbedLayout L = layoutEmbed(300, 8); - CHECK(L.keymap.height() >= 0); - CHECK(L.levelBand.height() >= 0); - CHECK(L.keymap.bottom == L.levelBand.top); - CHECK(L.levelBand.bottom == 8); + CHECK(L.keymap.height >= 0); + CHECK(L.levelBand.height >= 0); + CHECK(L.keymap.bottom() == L.levelBand.y); + CHECK(L.levelBand.bottom() == 8); // The keymap is not starved below its floor when the area allows it. - CHECK(L.keymap.height() >= kEmbedKeymapMinHeight || 8 < kEmbedKeymapMinHeight); + CHECK(L.keymap.height >= kEmbedKeymapMinHeight || 8 < kEmbedKeymapMinHeight); } static void testLayoutZeroArea() { const EmbedLayout L = layoutEmbed(0, 0); - CHECK(L.keymap.width() <= 0 && L.keymap.height() <= 0); - CHECK(L.levelBand.width() <= 0 && L.levelBand.height() <= 0); + CHECK(L.keymap.width <= 0 && L.keymap.height <= 0); + CHECK(L.levelBand.width <= 0 && L.levelBand.height <= 0); // Negative dimensions clamp to a zero-area, non-inverted rect. const EmbedLayout N = layoutEmbed(-50, -50); - CHECK(N.keymap.right >= N.keymap.left && N.keymap.bottom >= N.keymap.top); + CHECK(N.keymap.right() >= N.keymap.x && N.keymap.bottom() >= N.keymap.y); } // --- zoneSegmentRect ---------------------------------------------------------- @@ -60,9 +61,9 @@ static void testZoneSegmentFullSpan() { // A zone covering the whole keyboard spans the entire keymap band width. const EmbedLayout L = layoutEmbed(256, 40); const Rect r = zoneSegmentRect(L, 0, 127); - CHECK(r.left == L.keymap.left); - CHECK(r.right == L.keymap.right); - CHECK(r.top == L.keymap.top && r.bottom == L.keymap.bottom); + CHECK(r.x == L.keymap.x); + CHECK(r.right() == L.keymap.right()); + CHECK(r.y == L.keymap.y && r.bottom() == L.keymap.bottom()); } static void testAdjacentZonesTileSeamlessly() { @@ -71,10 +72,10 @@ static void testAdjacentZonesTileSeamlessly() { const EmbedLayout L = layoutEmbed(256, 40); const Rect lo = zoneSegmentRect(L, 0, 59); const Rect hi = zoneSegmentRect(L, 60, 127); - CHECK(lo.left == L.keymap.left); - CHECK(hi.right == L.keymap.right); - CHECK(lo.right == hi.left); // seamless tile — the load-bearing assertion - CHECK(lo.right == L.keymap.left + 60 * 2); // 60 keys * 2px + CHECK(lo.x == L.keymap.x); + CHECK(hi.right() == L.keymap.right()); + CHECK(lo.right() == hi.x); // seamless tile — the load-bearing assertion + CHECK(lo.right() == L.keymap.x + 60 * 2); // 60 keys * 2px } static void testZoneSegmentClampsBadNotes() { @@ -82,9 +83,9 @@ static void testZoneSegmentClampsBadNotes() { // Out-of-range notes clamp into the band; an inverted zone (low > high) collapses to a // zero-or-positive-width rect, never inverts. const Rect over = zoneSegmentRect(L, -10, 200); - CHECK(over.left == L.keymap.left && over.right == L.keymap.right); + CHECK(over.x == L.keymap.x && over.right() == L.keymap.right()); const Rect inv = zoneSegmentRect(L, 100, 20); - CHECK(inv.right >= inv.left); + CHECK(inv.right() >= inv.x); } // --- zoneAtPoint -------------------------------------------------------------- @@ -95,31 +96,31 @@ static void testZoneAtPointHits() { // A point inside the low zone's segment resolves to zone 0; inside the high zone, 1. const Rect lo = zoneSegmentRect(L, 0, 59); const Rect hi = zoneSegmentRect(L, 60, 127); - const int yMid = (L.keymap.top + L.keymap.bottom) / 2; - CHECK(zoneAtPoint(L, zones, 2, lo.left + 1, yMid) == 0); - CHECK(zoneAtPoint(L, zones, 2, hi.right - 1, yMid) == 1); + const int yMid = (L.keymap.y + L.keymap.bottom()) / 2; + CHECK(zoneAtPoint(L, zones, 2, lo.x + 1, yMid) == 0); + CHECK(zoneAtPoint(L, zones, 2, hi.right() - 1, yMid) == 1); } static void testZoneAtPointFirstMatchOnOverlap() { const EmbedLayout L = layoutEmbed(256, 40); // Two overlapping zones; the FIRST in order must win the contested keys. const EmbedZone zones[2] = {{0, 127}, {40, 80}}; - const int yMid = (L.keymap.top + L.keymap.bottom) / 2; + const int yMid = (L.keymap.y + L.keymap.bottom()) / 2; const Rect contested = zoneSegmentRect(L, 40, 80); - CHECK(zoneAtPoint(L, zones, 2, contested.left + 1, yMid) == 0); // zone 0 wins + CHECK(zoneAtPoint(L, zones, 2, contested.x + 1, yMid) == 0); // zone 0 wins } static void testZoneAtPointMisses() { const EmbedLayout L = layoutEmbed(256, 40); const EmbedZone zones[1] = {{60, 72}}; // a narrow zone; most keys uncovered - const int yMid = (L.keymap.top + L.keymap.bottom) / 2; + const int yMid = (L.keymap.y + L.keymap.bottom()) / 2; // A key left of the zone is uncovered -> -1. - CHECK(zoneAtPoint(L, zones, 1, L.keymap.left + 1, yMid) == -1); + CHECK(zoneAtPoint(L, zones, 1, L.keymap.x + 1, yMid) == -1); // A point in the level band (below the keymap) is off the keymap -> -1. - CHECK(zoneAtPoint(L, zones, 1, L.levelBand.left + 4, L.levelBand.top) == -1); + CHECK(zoneAtPoint(L, zones, 1, L.levelBand.x + 4, L.levelBand.y) == -1); // Empty / null list -> -1. - CHECK(zoneAtPoint(L, zones, 0, L.keymap.left + 1, yMid) == -1); - CHECK(zoneAtPoint(L, nullptr, 3, L.keymap.left + 1, yMid) == -1); + CHECK(zoneAtPoint(L, zones, 0, L.keymap.x + 1, yMid) == -1); + CHECK(zoneAtPoint(L, nullptr, 3, L.keymap.x + 1, yMid) == -1); } // --- levelFillRect ------------------------------------------------------------ @@ -127,16 +128,16 @@ static void testZoneAtPointMisses() { static void testLevelFillClamps() { const EmbedLayout L = layoutEmbed(200, 40); // Zero / negative -> empty. - CHECK(levelFillRect(L, 0.0).width() <= 0); - CHECK(levelFillRect(L, -1.0).width() <= 0); + CHECK(levelFillRect(L, 0.0).width <= 0); + CHECK(levelFillRect(L, -1.0).width <= 0); // Full / over-full -> the whole band width. - CHECK(levelFillRect(L, 1.0).width() == L.levelBand.width()); - CHECK(levelFillRect(L, 5.0).width() == L.levelBand.width()); + CHECK(levelFillRect(L, 1.0).width == L.levelBand.width); + CHECK(levelFillRect(L, 5.0).width == L.levelBand.width); // Half -> ~half the band, pinned to the band's left and vertical extent. const Rect half = levelFillRect(L, 0.5); - CHECK(half.left == L.levelBand.left); - CHECK(half.top == L.levelBand.top && half.bottom == L.levelBand.bottom); - CHECK(half.width() == L.levelBand.width() / 2); + CHECK(half.x == L.levelBand.x); + CHECK(half.y == L.levelBand.y && half.bottom() == L.levelBand.bottom()); + CHECK(half.width == L.levelBand.width / 2); } int main() { diff --git a/tests/test_envelope_edit.cpp b/tests/test_envelope_edit.cpp index 6b40cac..37f92a3 100644 --- a/tests/test_envelope_edit.cpp +++ b/tests/test_envelope_edit.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::envelope_edit — no VST3, no REAPER, no framework. +// Standalone tests for reasampler::instrument::ui::envelope_edit — no VST3, no REAPER, no framework. // Same fast assert loop as the sibling pure tests. Assert the S-VIEW-3 draggable-node INVERSE // map: node hit-test + pixel-delta -> clamped/monotonic param set, HARD at the clamp + monotonic // boundaries (the load-bearing "a drag can never produce a param a slider couldn't" invariant). @@ -14,14 +14,15 @@ // pixel delta; zero-fade-out node grabbable at the right edge and draggable inward — FA2); // degenerate area/duration + non-draggable node + cross-mode node -> no motion. -#include "../src/vst/envelope_edit.h" +#include "../src/core/instrument/ui/envelope_edit.h" #include #include #include #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -41,7 +42,7 @@ static bool findNode(const std::vector& poly, EnvNode node, EnvVertex // scale (FA2 param-domain schematic — sample-length-free): (850-1-32)px over the 8.0s schematic // domain => 102.125 px/s, each segment prefixed by the 8px separation base; the gateEnv() nodes // draw at A x@28, H x@47, D x@85, RS x@235, RE x@284. -static Rect wideArea() { return Rect{20, 10, 1020, 110}; } +static Rect wideArea() { return Rect::ltrb(20, 10, 1020, 110); } static constexpr double kTotal = 2.0; static const double kGateSecPerPx = 1.0 / gatePxPerSecond(wideArea()); @@ -71,10 +72,10 @@ static void testHitGrabsDrawnHandle() { const AmpEnvelope e = gateEnv(); const Rect a = wideArea(); // AttackEnd draws at x = left+28 (8px base + 0.2s * 102.125 px/s), y = top (level 1). - NodeHit h = nodeAtPoint(e, a, kTotal, a.left + 28, a.top); + NodeHit h = nodeAtPoint(e, a, kTotal, a.x + 28, a.y); CHECK(h.hit && h.node == EnvNode::AttackEnd); // The sustain node (DecayEnd) at left+85, level 0.5 -> ~top+50. - NodeHit s = nodeAtPoint(e, a, kTotal, a.left + 85, a.top + 50); + NodeHit s = nodeAtPoint(e, a, kTotal, a.x + 85, a.y + 50); CHECK(s.hit && s.node == EnvNode::DecayEnd); } @@ -82,7 +83,7 @@ static void testHitMissesOffEveryNode() { const AmpEnvelope e = gateEnv(); const Rect a = wideArea(); // A point far from any drawn handle (right of the release ramp, well away from a node). - NodeHit h = nodeAtPoint(e, a, kTotal, a.left + 700, a.top + 5); + NodeHit h = nodeAtPoint(e, a, kTotal, a.x + 700, a.y + 5); CHECK(!h.hit); } @@ -90,11 +91,11 @@ static void testHitSkipsNonDraggableAnchors() { const AmpEnvelope e = gateEnv(); const Rect a = wideArea(); // Origin draws at (left, bottom-1). Even a pixel-perfect grab there is NOT a draggable node. - NodeHit o = nodeAtPoint(e, a, kTotal, a.left, a.bottom - 1); + NodeHit o = nodeAtPoint(e, a, kTotal, a.x, a.bottom() - 1); CHECK(!o.hit); // ReleaseStart draws at (left+235, sustain level ~top+50) — the fixed plateau end. It is // drawing-only -> not grabbable; no other node is within the radius, so this grab misses. - NodeHit rs = nodeAtPoint(e, a, kTotal, a.left + 235, a.top + 50); + NodeHit rs = nodeAtPoint(e, a, kTotal, a.x + 235, a.y + 50); CHECK(!rs.hit); } @@ -106,7 +107,7 @@ static void testHitNearestNodeWinsOverDrawOrder() { AmpEnvelope e = gateEnv(); e.holdSeconds = 0.01; const Rect a = wideArea(); - NodeHit h = nodeAtPoint(e, a, kTotal, a.left + 33, a.top); + NodeHit h = nodeAtPoint(e, a, kTotal, a.x + 33, a.y); CHECK(h.hit && h.node == EnvNode::HoldEnd); } @@ -199,11 +200,11 @@ static void testGateTimeOnlyNodeIgnoresY() { static void testGateReleaseEndGrabAndDrag() { // The FA2 fix: ReleaseEnd is a drawn, IN-BOUNDS, grabbable handle (pre-FA2 it mapped past - // area.right and could never be grabbed). gateEnv() draws it at x@284, level 0 (bottom row). + // area.right() and could never be grabbed). gateEnv() draws it at x@284, level 0 (bottom row). const AmpEnvelope e = gateEnv(); const Rect a = wideArea(); EnvClampBounds b; - NodeHit h = nodeAtPoint(e, a, kTotal, a.left + 284, a.bottom - 1); + NodeHit h = nodeAtPoint(e, a, kTotal, a.x + 284, a.bottom() - 1); CHECK(h.hit && h.node == EnvNode::ReleaseEnd); // Dragging it RIGHT lengthens the release at the gate timed scale; only release changes. AmpEnvelope out = resolveNodeDrag(e, EnvNode::ReleaseEnd, a, kTotal, b, 85, 0); @@ -289,14 +290,14 @@ static void testTriggerZeroFadeOutGrabbableAtRightEdge() { e.fadeOutFraction = 0.0; const Rect a = wideArea(); EnvClampBounds b; - NodeHit h = nodeAtPoint(e, a, kTotal, a.right - 1, a.top); + NodeHit h = nodeAtPoint(e, a, kTotal, a.right() - 1, a.y); CHECK(h.hit && h.node == EnvNode::FadeOutStart); // -100px = -0.2s on the 2.0s played span, applied OPPOSITE -> fadeOut 0.0 -> 0.1. AmpEnvelope out = resolveNodeDrag(e, EnvNode::FadeOutStart, a, kTotal, b, -100, 0); CHECK(near(out.fadeOutFraction, 0.1)); CHECK(near(out.lengthFraction, e.lengthFraction)); // length untouched // LengthEnd sits at the same x but level 0 (bottom row) — grabbable at ITS drawn point. - NodeHit le = nodeAtPoint(e, a, kTotal, a.right - 1, a.bottom - 1); + NodeHit le = nodeAtPoint(e, a, kTotal, a.right() - 1, a.bottom() - 1); CHECK(le.hit && le.node == EnvNode::LengthEnd); } @@ -327,7 +328,7 @@ static void testNonDraggableNodeNoMotion() { static void testDegenerateAreaNoMotion() { const AmpEnvelope e = gateEnv(); EnvClampBounds b; - const Rect zeroW = Rect{0, 0, 0, 100}; + const Rect zeroW = Rect::ltrb(0, 0, 0, 100); AmpEnvelope o1 = resolveNodeDrag(e, EnvNode::AttackEnd, zeroW, kTotal, b, 500, 0); CHECK(near(o1.attackSeconds, e.attackSeconds)); AmpEnvelope o2 = resolveNodeDrag(e, EnvNode::AttackEnd, wideArea(), 0.0, b, 500, 0); // no time @@ -347,7 +348,7 @@ static void testCrossModeNodeNoMotion() { out = resolveNodeDrag(g, EnvNode::FadeInEnd, wideArea(), kTotal, b, 50, 0); CHECK(near(out.fadeInFraction, g.fadeInFraction)); // And the zero-height baseline's ReleaseEnd is not even reported grabbable in Trigger mode. - const Rect flat = Rect{0, 0, 100, 0}; + const Rect flat = Rect::ltrb(0, 0, 100, 0); const NodeHit h = nodeAtPoint(t, flat, kTotal, 99, 0); CHECK(!h.hit); } diff --git a/tests/test_envelope_overlay.cpp b/tests/test_envelope_overlay.cpp index bd09245..6839bec 100644 --- a/tests/test_envelope_overlay.cpp +++ b/tests/test_envelope_overlay.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::envelope_overlay — no VST3, no REAPER, no framework. +// Standalone tests for reasampler::instrument::ui::envelope_overlay — no VST3, no REAPER, no framework. // Same fast assert loop as the sibling pure tests. Assert the S-VIEW-3/FA2 amp-envelope -> // polyline FORWARD map: the Gate BOUNDED-SCHEMATIC AHDSR shape (attack ramp / hold plateau / // decay-to-sustain / fixed-width sustain plateau / in-bounds release) and the Trigger @@ -14,12 +14,13 @@ // the played span, overlap clamp, full-length/zero-fade-out nodes in-bounds at right-1); // degenerate flat baseline. -#include "../src/vst/envelope_overlay.h" +#include "../src/core/instrument/ui/envelope_overlay.h" #include #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -27,7 +28,7 @@ static int g_fail = 0; // A comfortable overlay area: 1000px wide, 100px tall, offset so left/top != 0 (catches origin // bugs). Under levelToY the level span is height-1 = 99 rows. -static Rect wideArea() { return Rect{20, 10, 1020, 110}; } // width 1000, height 100 +static Rect wideArea() { return Rect::ltrb(20, 10, 1020, 110); } // width 1000, height 100 // Find the first vertex with a given node in a polyline; asserts presence via the returned bool. static bool findNode(const std::vector& poly, EnvNode node, EnvVertex& out) { @@ -41,32 +42,32 @@ static bool findNode(const std::vector& poly, EnvNode node, EnvVertex static void testTimeToXEndpoints() { const Rect a = wideArea(); - CHECK(timeToX(a, 2.0, 0.0) == a.left); // t=0 -> left - CHECK(timeToX(a, 2.0, 2.0) == a.right - 1); // t=total -> last in-bounds column - CHECK(timeToX(a, 2.0, 1.0) == a.left + 500); // midpoint + CHECK(timeToX(a, 2.0, 0.0) == a.x); // t=0 -> left + CHECK(timeToX(a, 2.0, 2.0) == a.right() - 1); // t=total -> last in-bounds column + CHECK(timeToX(a, 2.0, 1.0) == a.x + 500); // midpoint } static void testTimeToXNegativePinsLeft() { const Rect a = wideArea(); - CHECK(timeToX(a, 2.0, -0.5) == a.left); // t<0 pins left + CHECK(timeToX(a, 2.0, -0.5) == a.x); // t<0 pins left } static void testTimeToXPastEndClamps() { // FA2 bounds invariant: t past total pins to the last in-bounds column, never past right. const Rect a = wideArea(); - CHECK(timeToX(a, 2.0, 3.0) == a.right - 1); - CHECK(timeToX(a, 2.0, 1000.0) == a.right - 1); + CHECK(timeToX(a, 2.0, 3.0) == a.right() - 1); + CHECK(timeToX(a, 2.0, 1000.0) == a.right() - 1); // A HUGE t must clamp in double space, not overflow the integer cast (32-bit long on // Windows would wrap to LONG_MIN and pin to the WRONG edge). - CHECK(timeToX(a, 2.0, 1e15) == a.right - 1); + CHECK(timeToX(a, 2.0, 1e15) == a.right() - 1); } static void testGateTimedWidth() { // 15% of the 1000px canvas is reserved for the sustain plateau -> 850px timed region. CHECK(gateTimedWidth(wideArea()) == 850); // Zero-width area -> 0; a tiny area still yields >= 1 so the px<->s scale never degenerates. - CHECK(gateTimedWidth(Rect{5, 5, 5, 45}) == 0); - CHECK(gateTimedWidth(Rect{0, 0, 1, 10}) == 1); + CHECK(gateTimedWidth(Rect::ltrb(5, 5, 5, 45)) == 0); + CHECK(gateTimedWidth(Rect::ltrb(0, 0, 1, 10)) == 1); } static void testGatePxPerSecond() { @@ -74,30 +75,30 @@ static void testGatePxPerSecond() { // 1000px canvas: (850 - 1 - 32) / 8.0s = 817/8 px/s. Independent of any sample duration. const double expected = 817.0 / (4.0 * kGateStageMaxSeconds); CHECK(gatePxPerSecond(wideArea()) == expected); - CHECK(gatePxPerSecond(Rect{5, 5, 5, 45}) == 0.0); // zero-width area -> 0 - CHECK(gatePxPerSecond(Rect{0, 0, 10, 10}) > 0.0); // tiny area: usable floors at 1px, > 0 + CHECK(gatePxPerSecond(Rect::ltrb(5, 5, 5, 45)) == 0.0); // zero-width area -> 0 + CHECK(gatePxPerSecond(Rect::ltrb(0, 0, 10, 10)) > 0.0); // tiny area: usable floors at 1px, > 0 } static void testTimeToXDegenerate() { const Rect a = wideArea(); - CHECK(timeToX(a, 0.0, 1.0) == a.left); // no duration -> left - const Rect z = Rect{5, 5, 5, 45}; // zero width - CHECK(timeToX(z, 2.0, 1.0) == z.left); + CHECK(timeToX(a, 0.0, 1.0) == a.x); // no duration -> left + const Rect z = Rect::ltrb(5, 5, 5, 45); // zero width + CHECK(timeToX(z, 2.0, 1.0) == z.x); } static void testLevelToYEndpoints() { const Rect a = wideArea(); - CHECK(levelToY(a, 1.0) == a.top); // level 1 -> top row - CHECK(levelToY(a, 0.0) == a.bottom - 1); // level 0 -> bottom row - CHECK(levelToY(a, 0.5) == a.top + 50); // mid: round((1-0.5)*99)=round(49.5)=50 + CHECK(levelToY(a, 1.0) == a.y); // level 1 -> top row + CHECK(levelToY(a, 0.0) == a.bottom() - 1); // level 0 -> bottom row + CHECK(levelToY(a, 0.5) == a.y + 50); // mid: round((1-0.5)*99)=round(49.5)=50 } static void testLevelToYClamps() { const Rect a = wideArea(); - CHECK(levelToY(a, 2.0) == a.top); // >1 clamps to top - CHECK(levelToY(a, -1.0) == a.bottom - 1); // <0 clamps to bottom - const Rect z = Rect{5, 5, 45, 5}; // zero height - CHECK(levelToY(z, 0.5) == z.top); + CHECK(levelToY(a, 2.0) == a.y); // >1 clamps to top + CHECK(levelToY(a, -1.0) == a.bottom() - 1); // <0 clamps to bottom + const Rect z = Rect::ltrb(5, 5, 45, 5); // zero height + CHECK(levelToY(z, 0.5) == z.y); } // --- Gate polyline ------------------------------------------------------------ @@ -149,11 +150,11 @@ static void testGateSchematicPlacement() { const std::vector poly = buildEnvelopePolyline(env, a, 2.0); EnvVertex v; - CHECK(findNode(poly, EnvNode::AttackEnd, v) && v.x == a.left + 28); - CHECK(findNode(poly, EnvNode::HoldEnd, v) && v.x == a.left + 47); - CHECK(findNode(poly, EnvNode::DecayEnd, v) && v.x == a.left + 85); - CHECK(findNode(poly, EnvNode::ReleaseStart, v) && v.x == a.left + 235); - CHECK(findNode(poly, EnvNode::ReleaseEnd, v) && v.x == a.left + 284); + CHECK(findNode(poly, EnvNode::AttackEnd, v) && v.x == a.x + 28); + CHECK(findNode(poly, EnvNode::HoldEnd, v) && v.x == a.x + 47); + CHECK(findNode(poly, EnvNode::DecayEnd, v) && v.x == a.x + 85); + CHECK(findNode(poly, EnvNode::ReleaseStart, v) && v.x == a.x + 235); + CHECK(findNode(poly, EnvNode::ReleaseEnd, v) && v.x == a.x + 284); } static void testGateLayoutIndependentOfSampleDuration() { @@ -195,7 +196,7 @@ static void testGateSustainPlateauFixedWidth() { env.sustainLevel = 0.6; env.releaseSeconds = 0.3; const Rect a = wideArea(); - const int plateauPx = a.width() - gateTimedWidth(a); // 150 + const int plateauPx = a.width - gateTimedWidth(a); // 150 const std::vector poly = buildEnvelopePolyline(env, a, 2.0); EnvVertex decay, plateauEnd; @@ -207,7 +208,7 @@ static void testGateSustainPlateauFixedWidth() { static void testGateReleaseVisibleInBounds() { // The FA2 fix: Release is a VISIBLE, in-bounds segment — ReleaseEnd sits strictly right of - // the plateau end and strictly inside the canvas (pre-FA2 it mapped past area.right and the + // the plateau end and strictly inside the canvas (pre-FA2 it mapped past area.right() and the // shell clipped its handle away). AmpEnvelope env; env.mode = EnvMode::Gate; @@ -223,7 +224,7 @@ static void testGateReleaseVisibleInBounds() { CHECK(findNode(poly, EnvNode::ReleaseStart, plateauEnd)); CHECK(findNode(poly, EnvNode::ReleaseEnd, rel)); CHECK(rel.x > plateauEnd.x); // a visible ramp, not a collapsed point - CHECK(rel.x < a.right); // strictly in-bounds + CHECK(rel.x < a.right()); // strictly in-bounds CHECK(rel.level == 0.0); } @@ -231,7 +232,7 @@ static void testGateOverrunCompressesFromRight() { // Stages BEYOND the schematic domain (4.0s each > kGateStageMaxSeconds): the layout // compresses from the right preserving the minimum gaps — ReleaseEnd pins to the last // in-bounds column, but the trailing nodes stay strictly increasing and individually - // separated (>= kGateNodeSepPx), NOT piled on one pixel. NOTHING maps past area.right. + // separated (>= kGateNodeSepPx), NOT piled on one pixel. NOTHING maps past area.right(). AmpEnvelope env; env.mode = EnvMode::Gate; env.attackSeconds = 4.0; @@ -246,12 +247,12 @@ static void testGateOverrunCompressesFromRight() { EnvVertex plateauEnd, rel; CHECK(findNode(poly, EnvNode::ReleaseStart, plateauEnd)); CHECK(findNode(poly, EnvNode::ReleaseEnd, rel)); - CHECK(rel.x == a.right - 1); // pinned to the last in-bounds column + CHECK(rel.x == a.right() - 1); // pinned to the last in-bounds column CHECK(plateauEnd.level == 0.7); // still at sustain for (size_t i = 1; i < poly.size(); ++i) { CHECK(poly[i].x > poly[i - 1].x); // strictly monotonic CHECK(poly[i].x - poly[i - 1].x >= kGateNodeSepPx - 1); // min gaps survive compression - CHECK(poly[i].x >= a.left && poly[i].x < a.right); // in-bounds + CHECK(poly[i].x >= a.x && poly[i].x < a.right()); // in-bounds } } @@ -279,8 +280,8 @@ static void testGateAllVerticesInBounds() { for (const AmpEnvelope& env : {base, big, zero, trig, huge}) { for (const EnvVertex& v : buildEnvelopePolyline(env, a, 2.0)) { - CHECK(v.x >= a.left && v.x < a.right); - CHECK(v.y >= a.top && v.y < a.bottom); + CHECK(v.x >= a.x && v.x < a.right()); + CHECK(v.y >= a.y && v.y < a.bottom()); } } } @@ -305,9 +306,9 @@ static void testTriggerShape() { CHECK(poly[3].node == EnvNode::LengthEnd); EnvVertex v; - CHECK(findNode(poly, EnvNode::FadeInEnd, v) && v.x == a.left + 100 && v.level == 1.0); - CHECK(findNode(poly, EnvNode::FadeOutStart, v) && v.x == a.left + 350 && v.level == 1.0); - CHECK(findNode(poly, EnvNode::LengthEnd, v) && v.x == a.left + 500 && v.level == 0.0); + CHECK(findNode(poly, EnvNode::FadeInEnd, v) && v.x == a.x + 100 && v.level == 1.0); + CHECK(findNode(poly, EnvNode::FadeOutStart, v) && v.x == a.x + 350 && v.level == 1.0); + CHECK(findNode(poly, EnvNode::LengthEnd, v) && v.x == a.x + 500 && v.level == 0.0); } static void testTriggerFadeOverlapClamp() { @@ -324,7 +325,7 @@ static void testTriggerFadeOverlapClamp() { CHECK(findNode(poly, EnvNode::FadeInEnd, fin)); CHECK(findNode(poly, EnvNode::FadeOutStart, fout)); CHECK(fin.x == fout.x); // fades meet exactly, never cross - CHECK(fin.x == a.left + 800); + CHECK(fin.x == a.x + 800); } static void testTriggerFullLengthZeroFadeOutInBounds() { @@ -342,8 +343,8 @@ static void testTriggerFullLengthZeroFadeOutInBounds() { EnvVertex fout, lend; CHECK(findNode(poly, EnvNode::FadeOutStart, fout)); CHECK(findNode(poly, EnvNode::LengthEnd, lend)); - CHECK(fout.x == a.right - 1); // present + in-bounds at zero fade-out - CHECK(lend.x == a.right - 1); + CHECK(fout.x == a.right() - 1); // present + in-bounds at zero fade-out + CHECK(lend.x == a.right() - 1); CHECK(fout.level == 1.0 && lend.level == 0.0); } @@ -351,7 +352,7 @@ static void testTriggerFullLengthZeroFadeOutInBounds() { static void testDegenerateFlatBaseline() { AmpEnvelope env; // any params - const Rect zeroW = Rect{0, 0, 0, 100}; + const Rect zeroW = Rect::ltrb(0, 0, 0, 100); const std::vector p1 = buildEnvelopePolyline(env, zeroW, 2.0); CHECK(p1.size() == 2); // always a drawable line CHECK(p1.front().level == 0.0 && p1.back().level == 0.0); @@ -360,7 +361,7 @@ static void testDegenerateFlatBaseline() { const std::vector p2 = buildEnvelopePolyline(env, ok, 0.0); // no duration CHECK(p2.size() == 2); CHECK(p2.front().level == 0.0 && p2.back().level == 0.0); - CHECK(p2.front().x == ok.left && p2.back().x == ok.right - 1); // spans the area, in-bounds + CHECK(p2.front().x == ok.x && p2.back().x == ok.right() - 1); // spans the area, in-bounds } int main() { diff --git a/tests/test_file_bytes.cpp b/tests/test_file_bytes.cpp index d0bc358..ba6789a 100644 --- a/tests/test_file_bytes.cpp +++ b/tests/test_file_bytes.cpp @@ -11,6 +11,7 @@ #include using namespace reasampler; +using namespace reasampler::util; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_footer_bar.cpp b/tests/test_footer_bar.cpp index ba8cb4d..73a2462 100644 --- a/tests/test_footer_bar.cpp +++ b/tests/test_footer_bar.cpp @@ -13,11 +13,12 @@ // * Hit-test: Toggle / Tail returned for in-bounds points, None outside AND on the count // label (a passive readout, never a control); half-open bounds; suppressed box claims none. -#include "../src/footer_bar.h" +#include "../src/core/ui/footer_bar.h" #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_guid_diff.cpp b/tests/test_guid_diff.cpp index 08febad..b888ab2 100644 --- a/tests/test_guid_diff.cpp +++ b/tests/test_guid_diff.cpp @@ -10,7 +10,7 @@ // 5. reset() (project switch) re-arms the first-poll guard: the next observe() // re-baselines and reports nothing new — never diffs across projects. -#include "../src/guid_diff.h" +#include "../src/core/view/guid_diff.h" #include #include @@ -18,6 +18,7 @@ #include using namespace reasampler; +using namespace reasampler::view; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_insert_plan.cpp b/tests/test_insert_plan.cpp index 1339ddb..c118288 100644 --- a/tests/test_insert_plan.cpp +++ b/tests/test_insert_plan.cpp @@ -5,12 +5,13 @@ // proof that the forbidden stretch bit is never set and that native-length insert // carries no tempo bits. -#include "../src/insert_plan.h" +#include "../src/core/capture/insert_plan.h" #include #include using namespace reasampler; +using namespace reasampler::capture; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_instrument_drop.cpp b/tests/test_instrument_drop.cpp index 08ffaa6..43467c0 100644 --- a/tests/test_instrument_drop.cpp +++ b/tests/test_instrument_drop.cpp @@ -6,8 +6,8 @@ // container -> the instrument's OWN reader -> assert the capture selected) IS the // cross-artifact contract guard — the same pattern assignment_request_tests uses. -#include "../src/instrument_drop.h" -#include "../src/vst/sample_map.h" // deserializeComponentState — the instrument's OWN reader +#include "../src/core/wire/instrument_drop.h" +#include "../src/core/instrument/map/sample_map.h" // deserializeComponentState — the instrument's OWN reader #include "version_generated.h" // REASAMPLER_CHANNEL_IS_BETA — pins the per-channel class ID @@ -17,6 +17,7 @@ #include using namespace reasampler; +using namespace reasampler::wire; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_json.cpp b/tests/test_json.cpp index 687042c..0c71cbe 100644 --- a/tests/test_json.cpp +++ b/tests/test_json.cpp @@ -16,6 +16,7 @@ #include using namespace reasampler; +using namespace reasampler::json; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_keyboard_strip.cpp b/tests/test_keyboard_strip.cpp index 57596cf..61bbc4b 100644 --- a/tests/test_keyboard_strip.cpp +++ b/tests/test_keyboard_strip.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::keyboard_strip — no VST3, no REAPER, no framework. +// Standalone tests for reasampler::instrument::ui::keyboard_strip — no VST3, no REAPER, no framework. // Same fast assert loop as the sibling pure tests. Assert the capture-first editor's // keyboard-strip layout, root marker, key mapping, zone-bar hit regions, and the drag-delta // note resolver directly — the geometry that backs the single-capture root-set and the opt-in @@ -14,11 +14,12 @@ // no-ops; isNaturalKey across a full octave (C4..B4), at boundary notes 0 and 127, and with // out-of-range inputs that clamp to [0,127]. -#include "../src/vst/keyboard_strip.h" +#include "../src/core/instrument/ui/keyboard_strip.h" #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -32,13 +33,13 @@ static StripLayout wideStrip() { return layoutStrip(1280, 40); } static void testLayoutNormalArea() { const StripLayout L = layoutStrip(640, 40); - CHECK(L.keys.left == 0 && L.keys.top == 0); - CHECK(L.keys.right == 640 && L.keys.bottom == 40); + CHECK(L.keys.x == 0 && L.keys.y == 0); + CHECK(L.keys.right() == 640 && L.keys.bottom() == 40); } static void testLayoutZeroArea() { const StripLayout L = layoutStrip(0, 0); - CHECK(L.keys.width() == 0 && L.keys.height() == 0); + CHECK(L.keys.width == 0 && L.keys.height == 0); } // --- keyLeftX / keyRect / rootMarkerRect -------------------------------------- @@ -46,8 +47,8 @@ static void testLayoutZeroArea() { static void testKeyLeftMonotonicAndBounds() { const StripLayout L = wideStrip(); // Key 0's left edge is the band left; the 128 boundary is the band right. - CHECK(keyLeftX(L, 0) == L.keys.left); - CHECK(keyLeftX(L, 128) == L.keys.right); + CHECK(keyLeftX(L, 0) == L.keys.x); + CHECK(keyLeftX(L, 128) == L.keys.right()); // Strictly non-decreasing across the span. int prev = keyLeftX(L, 0); for (int n = 1; n <= 128; ++n) { @@ -62,17 +63,17 @@ static void testKeyLeftMonotonicAndBounds() { static void testKeyRectHalfOpen() { const StripLayout L = wideStrip(); const Rect k = keyRect(L, 60); - CHECK(k.left == keyLeftX(L, 60)); - CHECK(k.right == keyLeftX(L, 61)); - CHECK(k.top == L.keys.top && k.bottom == L.keys.bottom); - CHECK(k.width() == 10); // 10px/key + CHECK(k.x == keyLeftX(L, 60)); + CHECK(k.right() == keyLeftX(L, 61)); + CHECK(k.y == L.keys.y && k.bottom() == L.keys.bottom()); + CHECK(k.width == 10); // 10px/key } static void testRootMarkerEqualsKeyRect() { const StripLayout L = wideStrip(); const Rect m = rootMarkerRect(L, 64); const Rect k = keyRect(L, 64); - CHECK(m.left == k.left && m.right == k.right && m.top == k.top && m.bottom == k.bottom); + CHECK(m.x == k.x && m.right() == k.right() && m.y == k.y && m.bottom() == k.bottom()); } // --- keyAtPoint --------------------------------------------------------------- @@ -81,17 +82,17 @@ static void testKeyAtPointInverts() { const StripLayout L = wideStrip(); // A point in the middle of key 60's cell resolves to 60. const Rect k = keyRect(L, 60); - CHECK(keyAtPoint(L, k.left + 5, k.top + 2) == 60); + CHECK(keyAtPoint(L, k.x + 5, k.y + 2) == 60); // The very left of the band is key 0; just inside the right edge is key 127. - CHECK(keyAtPoint(L, L.keys.left, 2) == 0); - CHECK(keyAtPoint(L, L.keys.right - 1, 2) == 127); + CHECK(keyAtPoint(L, L.keys.x, 2) == 0); + CHECK(keyAtPoint(L, L.keys.right() - 1, 2) == 127); } static void testKeyAtPointOffBand() { const StripLayout L = wideStrip(); CHECK(keyAtPoint(L, -5, 2) == -1); // left of band - CHECK(keyAtPoint(L, L.keys.right + 5, 2) == -1); // right of band - CHECK(keyAtPoint(L, 100, L.keys.bottom + 5) == -1); // below band + CHECK(keyAtPoint(L, L.keys.right() + 5, 2) == -1); // right of band + CHECK(keyAtPoint(L, 100, L.keys.bottom() + 5) == -1); // below band } // --- zoneBarRect -------------------------------------------------------------- @@ -99,17 +100,17 @@ static void testKeyAtPointOffBand() { static void testZoneBarSpansInclusive() { const StripLayout L = wideStrip(); const Rect bar = zoneBarRect(L, 12, 23); // C1..B1 inclusive - CHECK(bar.left == keyLeftX(L, 12)); - CHECK(bar.right == keyLeftX(L, 24)); // high+1 -> the bar covers key 23 fully - CHECK(bar.width() == 120); // 12 keys * 10px + CHECK(bar.x == keyLeftX(L, 12)); + CHECK(bar.right() == keyLeftX(L, 24)); // high+1 -> the bar covers key 23 fully + CHECK(bar.width == 120); // 12 keys * 10px } static void testZoneBarMalformedCollapses() { const StripLayout L = wideStrip(); // low > high must collapse, never invert. const Rect bar = zoneBarRect(L, 80, 40); - CHECK(bar.width() >= 0); - CHECK(bar.right >= bar.left); + CHECK(bar.width >= 0); + CHECK(bar.right() >= bar.x); } // --- zoneGrabAt --------------------------------------------------------------- @@ -117,23 +118,23 @@ static void testZoneBarMalformedCollapses() { static void testZoneGrabEdgesAndBody() { const StripLayout L = wideStrip(); const Rect bar = zoneBarRect(L, 20, 60); // wide bar with a clear body - const int y = L.keys.top + 2; + const int y = L.keys.y + 2; // Near the left edge -> low; near the right edge -> high; the middle -> body. - CHECK(zoneGrabAt(L, 20, 60, bar.left + 1, y) == ZoneGrab::kLowEdge); - CHECK(zoneGrabAt(L, 20, 60, bar.right - 1, y) == ZoneGrab::kHighEdge); - CHECK(zoneGrabAt(L, 20, 60, bar.left + bar.width() / 2, y) == ZoneGrab::kBody); + CHECK(zoneGrabAt(L, 20, 60, bar.x + 1, y) == ZoneGrab::kLowEdge); + CHECK(zoneGrabAt(L, 20, 60, bar.right() - 1, y) == ZoneGrab::kHighEdge); + CHECK(zoneGrabAt(L, 20, 60, bar.x + bar.width / 2, y) == ZoneGrab::kBody); // Off the bar entirely -> none. - CHECK(zoneGrabAt(L, 20, 60, bar.right + 20, y) == ZoneGrab::kNone); + CHECK(zoneGrabAt(L, 20, 60, bar.right() + 20, y) == ZoneGrab::kNone); } static void testZoneGrabNarrowBarSplitsAtMidpointLowWins() { const StripLayout L = wideStrip(); // A 1-key bar is narrower than 2*edge: no body; the low edge wins the exact midpoint. const Rect bar = zoneBarRect(L, 50, 50); - const int y = L.keys.top + 2; - const int mid = bar.left + bar.width() / 2; + const int y = L.keys.y + 2; + const int mid = bar.x + bar.width / 2; CHECK(zoneGrabAt(L, 50, 50, mid, y) == ZoneGrab::kLowEdge); // tie -> low - CHECK(zoneGrabAt(L, 50, 50, bar.right - 1, y) == ZoneGrab::kHighEdge); + CHECK(zoneGrabAt(L, 50, 50, bar.right() - 1, y) == ZoneGrab::kHighEdge); } // --- zoneBarAtPoint ----------------------------------------------------------- @@ -143,8 +144,8 @@ static void testZoneBarAtPointFirstMatch() { const int lows[2] = {20, 30}; // zone 0 and zone 1 overlap on [30,50] const int highs[2] = {50, 70}; const Rect overlap = zoneBarRect(L, 30, 50); - const int y = L.keys.top + 2; - const int cx = overlap.left + overlap.width() / 2; + const int y = L.keys.y + 2; + const int cx = overlap.x + overlap.width / 2; // A point in the overlap resolves to the FIRST covering zone (draw order). const ZoneBarHit hit = zoneBarAtPoint(L, lows, highs, 2, cx, y); CHECK(hit.zoneIndex == 0); @@ -219,7 +220,7 @@ static void testResolveDragProportionalNonDivisibleWidth() { // a drag from note 0 by (width-1) pixels must land at keyAtPoint(width-1), which is 127. const int width = 544; const StripLayout L = layoutStrip(width, 40); - CHECK(keyAtPoint(L, width - 1, L.keys.top + 1) == 127); + CHECK(keyAtPoint(L, width - 1, L.keys.y + 1) == 127); CHECK(resolveDragNote(L, 0, width - 1) == 127); // Also verify mid-strip coherence: for each key N, a drag from 0 by N's left-edge @@ -227,12 +228,12 @@ static void testResolveDragProportionalNonDivisibleWidth() { // rounding may round down). The critical direction is that it must NOT over-shoot by // more than 0 (it must reach at least the right key). for (int n = 1; n < kStripKeyCount; ++n) { - const int leftPx = keyRect(L, n).left; + const int leftPx = keyRect(L, n).x; const int resolved = resolveDragNote(L, 0, leftPx); // The left edge of key N is the first pixel "in" that key, so we expect resolved == N. // Allow resolved == N-1 only when the pixel is at the exact boundary (keyEdgeToX may // produce the same x for adjacent keys when keys share a pixel). Disallow over-shoot. - const int expected = keyAtPoint(L, leftPx, L.keys.top + 1); + const int expected = keyAtPoint(L, leftPx, L.keys.y + 1); CHECK(resolved >= expected - 1 && resolved <= expected + 1); } } diff --git a/tests/test_knob_deck.cpp b/tests/test_knob_deck.cpp index 695c6fa..4852617 100644 --- a/tests/test_knob_deck.cpp +++ b/tests/test_knob_deck.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::knob_deck — no VST3, no REAPER, no framework. Same fast +// Standalone tests for reasampler::instrument::ui::knob_deck — no VST3, no REAPER, no framework. Same fast // assert loop as the sibling pure tests. Assert the r11 deck layout HARD: // // * group width — caption row vs knob row max + padding; row-toggle and caption-toggle widths. @@ -10,12 +10,13 @@ // * hit-test — knob cell hit (whole cell), toggle segment 0/1 boundaries, blank (-1) cells // and fence padding miss, outside-deck miss. -#include "../src/vst/knob_deck.h" +#include "../src/core/instrument/ui/knob_deck.h" #include #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -75,20 +76,20 @@ static void testWrapAtNarrowWidthIsDeterministic() { CHECK(dl.groups.size() == 5); // Row membership: groups on row 1 share the first top; the wrapped groups sit one row // pitch lower and restart at the left margin. - const int row0Top = dl.groups[0].box.top; + const int row0Top = dl.groups[0].box.y; const int row1Top = row0Top + kDeckGroupH + kDeckRowGap; - CHECK(dl.groups[0].box.top == row0Top); - CHECK(dl.groups[1].box.top == row0Top); + CHECK(dl.groups[0].box.y == row0Top); + CHECK(dl.groups[1].box.y == row0Top); bool sawWrap = false; for (std::size_t i = 1; i < dl.groups.size(); ++i) { - if (dl.groups[i].box.top == row1Top && dl.groups[i - 1].box.top == row0Top) { - CHECK(dl.groups[i].box.left == 8); // wrapped row restarts at the left edge + if (dl.groups[i].box.y == row1Top && dl.groups[i - 1].box.y == row0Top) { + CHECK(dl.groups[i].box.x == 8); // wrapped row restarts at the left edge sawWrap = true; } } CHECK(sawWrap); // Every box stays within the available width (no group straddles the right edge). - for (const auto& g : dl.groups) CHECK(g.box.right <= 8 + 544); + for (const auto& g : dl.groups) CHECK(g.box.right() <= 8 + 544); } static void testFirstGroupAlwaysPlaces() { @@ -103,33 +104,33 @@ static void testGroupInnerGeometry() { const DeckLayout dl = layoutDeck(deck, 8, 50, 824); const DeckGroupLayout& amp = dl.groups[0]; // Caption row at the top padding; caption toggle right-anchored inside the box. - CHECK(amp.caption.top == amp.box.top + kDeckGroupPadY); + CHECK(amp.caption.y == amp.box.y + kDeckGroupPadY); CHECK(amp.captionToggle.id == 100); - CHECK(amp.captionToggle.seg1.right == amp.box.right - kDeckGroupPadX); - CHECK(amp.captionToggle.seg0.right == amp.captionToggle.seg1.left); - CHECK(amp.captionToggle.seg0.width() == 44 && amp.captionToggle.seg1.width() == 44); - CHECK(amp.captionToggle.seg0.height() == kDeckToggleH); + CHECK(amp.captionToggle.seg1.right() == amp.box.right() - kDeckGroupPadX); + CHECK(amp.captionToggle.seg0.right() == amp.captionToggle.seg1.x); + CHECK(amp.captionToggle.seg0.width == 44 && amp.captionToggle.seg1.width == 44); + CHECK(amp.captionToggle.seg0.height == kDeckToggleH); // The caption text rect stops before the toggle. - CHECK(amp.caption.right <= amp.captionToggle.seg0.left); + CHECK(amp.caption.right() <= amp.captionToggle.seg0.x); // Cells: five, fixed size, abutting, inside the box, below the caption row. CHECK(static_cast(amp.cells.size()) == 5); for (std::size_t i = 0; i < amp.cells.size(); ++i) { const DeckCellLayout& c = amp.cells[i]; - CHECK(c.cell.width() == kDeckCellW && c.cell.height() == kDeckCellH); - CHECK(c.cell.top == amp.box.top + kDeckGroupPadY + kDeckCaptionH + kDeckCaptionGap); - if (i > 0) CHECK(c.cell.left == amp.cells[i - 1].cell.right); + CHECK(c.cell.width == kDeckCellW && c.cell.height == kDeckCellH); + CHECK(c.cell.y == amp.box.y + kDeckGroupPadY + kDeckCaptionH + kDeckCaptionGap); + if (i > 0) CHECK(c.cell.x == amp.cells[i - 1].cell.right()); // Knob square centered horizontally, label band beneath it, both inside the cell. - CHECK(c.knob.width() == kDeckKnobSize && c.knob.height() == kDeckKnobSize); - CHECK(c.knob.left - c.cell.left == c.cell.right - c.knob.right); - CHECK(c.label.top >= c.knob.bottom); - CHECK(c.label.bottom <= c.cell.bottom); + CHECK(c.knob.width == kDeckKnobSize && c.knob.height == kDeckKnobSize); + CHECK(c.knob.x - c.cell.x == c.cell.right() - c.knob.right()); + CHECK(c.label.y >= c.knob.bottom()); + CHECK(c.label.bottom() <= c.cell.bottom()); } // VOICE group's row toggle sits after its cell, vertically centered in the cell row. const DeckGroupLayout& voice = dl.groups[3]; CHECK(voice.rowToggle.id == 104); - CHECK(voice.rowToggle.seg0.left == voice.cells[0].cell.right + kDeckToggleGap); - CHECK(voice.rowToggle.seg0.height() == kDeckToggleH); - CHECK(voice.rowToggle.seg0.top > voice.cells[0].cell.top); + CHECK(voice.rowToggle.seg0.x == voice.cells[0].cell.right() + kDeckToggleGap); + CHECK(voice.rowToggle.seg0.height == kDeckToggleH); + CHECK(voice.rowToggle.seg0.y > voice.cells[0].cell.y); // MASTER has no toggles. CHECK(dl.groups[4].captionToggle.id == -1); CHECK(dl.groups[4].rowToggle.id == -1); @@ -142,20 +143,20 @@ static void testHitTest() { // Knob hit: anywhere in the cell (including the label band) resolves to the cell id. const DeckCellLayout& c0 = amp.cells[0]; - DeckHit h = hitTestDeck(dl, c0.cell.left + 1, c0.cell.top + 1); + DeckHit h = hitTestDeck(dl, c0.cell.x + 1, c0.cell.y + 1); CHECK(h.kind == DeckHitKind::Knob && h.id == 1 && h.segment == -1); - h = hitTestDeck(dl, c0.label.left + 2, c0.label.top + 2); + h = hitTestDeck(dl, c0.label.x + 2, c0.label.y + 2); CHECK(h.kind == DeckHitKind::Knob && h.id == 1); // Caption toggle segments 0/1 at their boundary: last px of seg0, first px of seg1. - h = hitTestDeck(dl, amp.captionToggle.seg0.right - 1, amp.captionToggle.seg0.top + 1); + h = hitTestDeck(dl, amp.captionToggle.seg0.right() - 1, amp.captionToggle.seg0.y + 1); CHECK(h.kind == DeckHitKind::CaptionToggle && h.id == 100 && h.segment == 0); - h = hitTestDeck(dl, amp.captionToggle.seg1.left, amp.captionToggle.seg1.top + 1); + h = hitTestDeck(dl, amp.captionToggle.seg1.x, amp.captionToggle.seg1.y + 1); CHECK(h.kind == DeckHitKind::CaptionToggle && h.id == 100 && h.segment == 1); // Row toggle. const DeckGroupLayout& voice = dl.groups[3]; - h = hitTestDeck(dl, voice.rowToggle.seg1.left + 1, voice.rowToggle.seg1.top + 1); + h = hitTestDeck(dl, voice.rowToggle.seg1.x + 1, voice.rowToggle.seg1.y + 1); CHECK(h.kind == DeckHitKind::RowToggle && h.id == 104 && h.segment == 1); // A blank cell (id -1) misses even though its rect exists. @@ -164,11 +165,11 @@ static void testHitTest() { const DeckLayout tl = layoutDeck(trig, 0, 0, 824); const DeckCellLayout& blank = tl.groups[0].cells[4]; CHECK(blank.id == -1); - h = hitTestDeck(tl, blank.cell.left + 5, blank.cell.top + 5); + h = hitTestDeck(tl, blank.cell.x + 5, blank.cell.y + 5); CHECK(h.kind == DeckHitKind::None); // The fence padding inside the box misses; outside the deck misses. - h = hitTestDeck(dl, amp.box.left + 1, amp.box.bottom - 1); + h = hitTestDeck(dl, amp.box.x + 1, amp.box.bottom() - 1); CHECK(h.kind == DeckHitKind::None); h = hitTestDeck(dl, -50, -50); CHECK(h.kind == DeckHitKind::None); diff --git a/tests/test_lane_keys.cpp b/tests/test_lane_keys.cpp index 82a280b..7cfdd0b 100644 --- a/tests/test_lane_keys.cpp +++ b/tests/test_lane_keys.cpp @@ -9,12 +9,13 @@ // 3. Round-trip: managedLaneKey(laneNameForMode(m)) == "reasampler:" + m, so the // Wave-3 minting path and the read path cannot drift. -#include "../src/lane_keys.h" +#include "../src/core/view/lane_keys.h" #include #include using namespace reasampler; +using namespace reasampler::view; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_master_gain.cpp b/tests/test_master_gain.cpp index 16be668..50b098e 100644 --- a/tests/test_master_gain.cpp +++ b/tests/test_master_gain.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::master_gain — no VST3, no REAPER, no framework. Same +// Standalone tests for reasampler::instrument::engine::master_gain — no VST3, no REAPER, no framework. Same // fast assert loop as the sibling pure tests. Assert the FB1 post-mixer gain taper HARD: // // * -inf bottom — norm 0 maps to -infinity dB and TRUE ZERO linear (silence, not an epsilon); @@ -9,13 +9,14 @@ // * monotonicity — more norm never means less gain. // * label — "-inf" at the bottom, signed one-decimal dB elsewhere. -#include "../src/vst/master_gain.h" +#include "../src/core/instrument/engine/master_gain.h" #include #include #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::engine; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_mode_enable.cpp b/tests/test_mode_enable.cpp index 2e8effc..d8fe990 100644 --- a/tests/test_mode_enable.cpp +++ b/tests/test_mode_enable.cpp @@ -7,13 +7,14 @@ // buttons are live and "…: Design" buttons are dead; when Arrange is active, the reverse. An // unrecognized active id fails OPEN (every button live) so a future mode never dead-locks the bar. -#include "../src/mode_enable.h" -#include "../src/view_mode_model.h" // kArrangeModeId / kDesignModeId — the ids the rule keys off +#include "../src/core/ui/mode_enable.h" +#include "../src/core/view/view_mode_model.h" // kArrangeModeId / kDesignModeId — the ids the rule keys off #include #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_mode_switch.cpp b/tests/test_mode_switch.cpp index 3110165..42a84c3 100644 --- a/tests/test_mode_switch.cpp +++ b/tests/test_mode_switch.cpp @@ -8,13 +8,14 @@ // evenly); hit-test hits per segment and misses (outside the band above/below/ // left/right, boundary pixels); degenerate widths and counts. -#include "../src/mode_switch.h" +#include "../src/core/view/mode_switch.h" #include #include #include using namespace reasampler; +using namespace reasampler::view; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_note_entry.cpp b/tests/test_note_entry.cpp index 4990001..1107f36 100644 --- a/tests/test_note_entry.cpp +++ b/tests/test_note_entry.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::note_entry — no VST3, no REAPER, no framework. +// Standalone tests for reasampler::instrument::map::note_entry — no VST3, no REAPER, no framework. // Assert the S12 direct-numeric-entry parse for a zone's low/high/root MIDI note. // // Covers: plain decimal integers (with +/- sign + surrounding whitespace); note names under the @@ -6,11 +6,12 @@ // [0,127] rather than rejecting; empty / whitespace-only / unparseable input returning nullopt; // the integer path taking precedence over the note-name path for a leading digit. -#include "../src/vst/note_entry.h" +#include "../src/core/instrument/map/note_entry.h" #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::map; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_overflow_menu.cpp b/tests/test_overflow_menu.cpp index 0a064bb..c6912c3 100644 --- a/tests/test_overflow_menu.cpp +++ b/tests/test_overflow_menu.cpp @@ -8,11 +8,12 @@ // a degenerate band reserves nothing; hit-test in/out/edge (half-open bounds); an empty button // claims no point; draw and hit-test agree over the whole rect. -#include "../src/overflow_menu.h" +#include "../src/core/ui/overflow_menu.h" #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_owned_manifest.cpp b/tests/test_owned_manifest.cpp index 77ecf17..e5f27f8 100644 --- a/tests/test_owned_manifest.cpp +++ b/tests/test_owned_manifest.cpp @@ -7,12 +7,13 @@ // semantics, insertion-order preservation, malformed-parse -> nullopt (the persist // shell's warn+fallback hinges on it), and round-trip of paths with JSON metacharacters. -#include "../src/owned_manifest.h" +#include "../src/core/model/owned_manifest.h" #include #include using namespace reasampler; +using namespace reasampler::model; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_param_slider.cpp b/tests/test_param_slider.cpp index 35005a4..48dfaed 100644 --- a/tests/test_param_slider.cpp +++ b/tests/test_param_slider.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::param_slider — no VST3, no REAPER, no framework. +// Standalone tests for reasampler::instrument::ui::param_slider — no VST3, no REAPER, no framework. // Same fast assert loop as the sibling pure editor tests (capture_browser / keyboard_strip): // assert the S12/S15/S16 control-surface layout, toggle-segment split + hit-test, slider // value<->pixel mapping (round-trip + clamping + endpoints), and point->control routing. @@ -16,13 +16,14 @@ // o'clock), wrap-boundary + un-normalized arc inputs, the needle endpoint on the circle, and // the vertical-drag delta->value map (up = increase) with clamping at 0/1. -#include "../src/vst/param_slider.h" +#include "../src/core/instrument/ui/param_slider.h" #include #include #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -33,7 +34,7 @@ static bool approx(double a, double b) { return (a - b) < 1e-9 && (b - a) < 1e-9 // --- layoutControls ----------------------------------------------------------- static void testLayoutStacksRows() { - const Rect panel{0, 100, 300, 400}; + const Rect panel = Rect::ltrb(0, 100, 300, 400); std::vector ctl{ {1, ControlKind::Toggle}, {2, ControlKind::Slider}, @@ -42,58 +43,58 @@ static void testLayoutStacksRows() { const std::vector rows = layoutControls(panel, ctl); CHECK(rows.size() == 3); // Row 0 sits at the panel top; each subsequent row is one row-height + gap below. - CHECK(rows[0].row.top == 100); - CHECK(rows[0].row.bottom == 100 + kControlRowHeight); - CHECK(rows[1].row.top == rows[0].row.bottom + kControlRowGap); - CHECK(rows[2].row.top == rows[1].row.bottom + kControlRowGap); + CHECK(rows[0].row.y == 100); + CHECK(rows[0].row.bottom() == 100 + kControlRowHeight); + CHECK(rows[1].row.y == rows[0].row.bottom() + kControlRowGap); + CHECK(rows[2].row.y == rows[1].row.bottom() + kControlRowGap); // Ids + kinds carried through in order. CHECK(rows[0].id == 1 && rows[0].kind == ControlKind::Toggle); CHECK(rows[1].id == 2 && rows[1].kind == ControlKind::Slider); // Label column then control column, contiguous, spanning the panel width. - CHECK(rows[0].label.left == panel.left); - CHECK(rows[0].control.left == rows[0].label.right); - CHECK(rows[0].control.right == panel.right); - CHECK(rows[0].label.width() == kControlLabelWidth); + CHECK(rows[0].label.x == panel.x); + CHECK(rows[0].control.x == rows[0].label.right()); + CHECK(rows[0].control.right() == panel.right()); + CHECK(rows[0].label.width == kControlLabelWidth); } static void testLayoutEmptyAndDegenerate() { - CHECK(layoutControls(Rect{0, 0, 300, 300}, {}).empty()); + CHECK(layoutControls(Rect::ltrb(0, 0, 300, 300), {}).empty()); std::vector ctl{{1, ControlKind::Slider}}; - CHECK(layoutControls(Rect{0, 0, 0, 0}, ctl).empty()); - CHECK(layoutControls(Rect{0, 0, 300, 0}, ctl).empty()); + CHECK(layoutControls(Rect::ltrb(0, 0, 0, 0), ctl).empty()); + CHECK(layoutControls(Rect::ltrb(0, 0, 300, 0), ctl).empty()); } static void testLayoutNarrowPanelClampsLabel() { // A panel narrower than 2*labelWidth clamps the label column to half so a control column // survives. - const Rect panel{0, 0, 100, 200}; + const Rect panel = Rect::ltrb(0, 0, 100, 200); const std::vector rows = layoutControls(panel, {{1, ControlKind::Slider}}); CHECK(rows.size() == 1); - CHECK(rows[0].label.width() <= panel.width() / 2 + 1); - CHECK(rows[0].control.width() > 0); + CHECK(rows[0].label.width <= panel.width / 2 + 1); + CHECK(rows[0].control.width > 0); } // --- toggle ------------------------------------------------------------------- static void testToggleSegmentsTile() { - const Rect control{100, 0, 300, 22}; // width 200 + const Rect control = Rect::ltrb(100, 0, 300, 22); // width 200 const Rect s0 = toggleSegmentRect(control, 0); const Rect s1 = toggleSegmentRect(control, 1); - CHECK(s0.left == 100 && s0.right == 200); - CHECK(s1.left == 200 && s1.right == 300); // last absorbs remainder -> reaches control.right + CHECK(s0.x == 100 && s0.right() == 200); + CHECK(s1.x == 200 && s1.right() == 300); // last absorbs remainder -> reaches control.right() // Out of range. - CHECK(toggleSegmentRect(control, 2).width() == 0); - CHECK(toggleSegmentRect(control, -1).width() == 0); + CHECK(toggleSegmentRect(control, 2).width == 0); + CHECK(toggleSegmentRect(control, -1).width == 0); } static void testToggleSegmentRemainderInLast() { - const Rect control{0, 0, 201, 22}; // odd width -> seg0 = 100, seg1 = 101 (absorbs remainder) - CHECK(toggleSegmentRect(control, 0).width() == 100); - CHECK(toggleSegmentRect(control, 1).right == 201); + const Rect control = Rect::ltrb(0, 0, 201, 22); // odd width -> seg0 = 100, seg1 = 101 (absorbs remainder) + CHECK(toggleSegmentRect(control, 0).width == 100); + CHECK(toggleSegmentRect(control, 1).right() == 201); } static void testToggleHitTest() { - const Rect control{100, 0, 300, 22}; + const Rect control = Rect::ltrb(100, 0, 300, 22); CHECK(toggleSegmentHitTest(control, 150, 10) == 0); CHECK(toggleSegmentHitTest(control, 250, 10) == 1); CHECK(toggleSegmentHitTest(control, 50, 10) == -1); // left of control @@ -103,59 +104,59 @@ static void testToggleHitTest() { // --- slider ------------------------------------------------------------------- static void testSliderTrackInsetsHalfHandle() { - const Rect control{100, 0, 300, 22}; + const Rect control = Rect::ltrb(100, 0, 300, 22); const Rect track = sliderTrackRect(control); - CHECK(track.left == control.left + kSliderHandleWidth / 2); - CHECK(track.right == control.right - kSliderHandleWidth / 2); + CHECK(track.x == control.x + kSliderHandleWidth / 2); + CHECK(track.right() == control.right() - kSliderHandleWidth / 2); // A control too narrow for a handle yields an empty track. - CHECK(sliderTrackRect(Rect{0, 0, kSliderHandleWidth - 1, 22}).width() == 0); + CHECK(sliderTrackRect(Rect::ltrb(0, 0, kSliderHandleWidth - 1, 22)).width == 0); } static void testSliderHandleAtEndpointsAndMid() { - const Rect control{100, 0, 300, 22}; + const Rect control = Rect::ltrb(100, 0, 300, 22); const Rect track = sliderTrackRect(control); const int half = kSliderHandleWidth / 2; - // Value 0 -> handle centered at track.left. + // Value 0 -> handle centered at track.x. const Rect h0 = sliderHandleRect(control, 0.0); - CHECK(h0.left + half == track.left); - // Value 1 -> handle centered at track.right. + CHECK(h0.x + half == track.x); + // Value 1 -> handle centered at track.right(). const Rect h1 = sliderHandleRect(control, 1.0); - CHECK(h1.left + half == track.right); + CHECK(h1.x + half == track.right()); // Value 0.5 -> centered at the track middle. const Rect hm = sliderHandleRect(control, 0.5); - CHECK(hm.left + half == track.left + track.width() / 2); + CHECK(hm.x + half == track.x + track.width / 2); } static void testSliderHandleClampsOutOfRange() { - const Rect control{0, 0, 200, 22}; - CHECK(sliderHandleRect(control, -0.5).left == sliderHandleRect(control, 0.0).left); - CHECK(sliderHandleRect(control, 5.0).left == sliderHandleRect(control, 1.0).left); + const Rect control = Rect::ltrb(0, 0, 200, 22); + CHECK(sliderHandleRect(control, -0.5).x == sliderHandleRect(control, 0.0).x); + CHECK(sliderHandleRect(control, 5.0).x == sliderHandleRect(control, 1.0).x); } static void testValueAtPointEndpointsSaturate() { - const Rect control{100, 0, 300, 22}; + const Rect control = Rect::ltrb(100, 0, 300, 22); const Rect track = sliderTrackRect(control); - CHECK(approx(valueAtPoint(control, track.left - 20), 0.0)); - CHECK(approx(valueAtPoint(control, track.left), 0.0)); - CHECK(approx(valueAtPoint(control, track.right + 20), 1.0)); - CHECK(approx(valueAtPoint(control, track.right), 1.0)); + CHECK(approx(valueAtPoint(control, track.x - 20), 0.0)); + CHECK(approx(valueAtPoint(control, track.x), 0.0)); + CHECK(approx(valueAtPoint(control, track.right() + 20), 1.0)); + CHECK(approx(valueAtPoint(control, track.right()), 1.0)); } static void testValueAtPointIsHandleInverse() { // Round-trip: a value -> handle center -> valueAtPoint recovers (within one pixel quantum). - const Rect control{50, 0, 450, 22}; // wide track for pixel resolution + const Rect control = Rect::ltrb(50, 0, 450, 22); // wide track for pixel resolution const Rect track = sliderTrackRect(control); for (double v : {0.1, 0.25, 0.5, 0.75, 0.9}) { const Rect h = sliderHandleRect(control, v); - const int centerX = h.left + kSliderHandleWidth / 2; + const int centerX = h.x + kSliderHandleWidth / 2; const double back = valueAtPoint(control, centerX); CHECK(back >= v - 0.01 && back <= v + 0.01); - CHECK(centerX >= track.left && centerX <= track.right); + CHECK(centerX >= track.x && centerX <= track.right()); } } static void testValueAtPointDegenerateTrack() { - CHECK(approx(valueAtPoint(Rect{0, 0, kSliderHandleWidth - 1, 22}, 5), 0.0)); + CHECK(approx(valueAtPoint(Rect::ltrb(0, 0, kSliderHandleWidth - 1, 22), 5), 0.0)); } // --- knob (FA4) ----------------------------------------------------------------- @@ -164,21 +165,21 @@ static bool nearWithin(double a, double b, double tol) { return (a - b) < tol && static void testKnobGeometryInscribesCell() { // A 44x44 cell at (100,0): center (122,22), radius 22. - const KnobGeometry g = computeKnob(Rect{100, 0, 144, 44}); + const KnobGeometry g = computeKnob(Rect::ltrb(100, 0, 144, 44)); CHECK(approx(g.centerX, 122.0)); CHECK(approx(g.centerY, 22.0)); CHECK(approx(g.radius, 22.0)); // A wide cell inscribes on the smaller (vertical) dimension. - const KnobGeometry w = computeKnob(Rect{0, 0, 200, 22}); + const KnobGeometry w = computeKnob(Rect::ltrb(0, 0, 200, 22)); CHECK(approx(w.radius, 11.0)); CHECK(approx(w.centerX, 100.0)); // Degenerate cells yield radius 0. - CHECK(computeKnob(Rect{0, 0, 0, 22}).radius == 0.0); - CHECK(computeKnob(Rect{0, 0, 22, 0}).radius == 0.0); + CHECK(computeKnob(Rect::ltrb(0, 0, 0, 22)).radius == 0.0); + CHECK(computeKnob(Rect::ltrb(0, 0, 22, 0)).radius == 0.0); } static void testKnobHitTestCircle() { - const KnobGeometry g = computeKnob(Rect{100, 0, 144, 44}); // center (122,22), r 22 + const KnobGeometry g = computeKnob(Rect::ltrb(100, 0, 144, 44)); // center (122,22), r 22 CHECK(knobHitTest(g, 122, 22)); // center — always hits CHECK(!knobHitTest(g, 122 + 22, 22)); // exactly on the boundary — boundary exclusive CHECK(!knobHitTest(g, 122 + 22, 44)); // cell corner: inside the rect, outside the circle @@ -223,7 +224,7 @@ static void testKnobArcWrapBoundary() { } static void testKnobNeedlePointOnCircle() { - const KnobGeometry g = computeKnob(Rect{100, 0, 144, 44}); // center (122,22), r 22 + const KnobGeometry g = computeKnob(Rect::ltrb(100, 0, 144, 44)); // center (122,22), r 22 // Default arc, value 0 -> 7 o'clock -> needle points down-left from center. const KnobPoint p7 = knobNeedlePoint(g, KnobArc{}, 0.0); // 210° clockwise from 12: sin(210°)=-0.5, cos(210°)=-√3/2 -> x = cx - r/2, y = cy + r*√3/2 @@ -265,7 +266,7 @@ static void testKnobDragClamps() { // --- controlAtPoint routing --------------------------------------------------- static void testControlAtPointRoutes() { - const Rect panel{0, 0, 300, 400}; + const Rect panel = Rect::ltrb(0, 0, 300, 400); std::vector ctl{ {10, ControlKind::Toggle}, {20, ControlKind::Slider}, @@ -274,26 +275,26 @@ static void testControlAtPointRoutes() { const std::vector rows = layoutControls(panel, ctl); // A point in the toggle's control area routes to the toggle id. const Rect tctl = rows[0].control; - CHECK(controlAtPoint(rows, (tctl.left + tctl.right) / 2, (tctl.top + tctl.bottom) / 2) == 10); + CHECK(controlAtPoint(rows, (tctl.x + tctl.right()) / 2, (tctl.y + tctl.bottom()) / 2) == 10); // A point on the slider's track routes to the slider id. const Rect strack = sliderTrackRect(rows[1].control); - CHECK(controlAtPoint(rows, (strack.left + strack.right) / 2, - (strack.top + strack.bottom) / 2) == 20); + CHECK(controlAtPoint(rows, (strack.x + strack.right()) / 2, + (strack.y + strack.bottom()) / 2) == 20); // A point at the knob's center routes to the knob id; the control-rect corner (outside // the circle) is a miss. const KnobGeometry kg = computeKnob(rows[2].control); CHECK(controlAtPoint(rows, static_cast(kg.centerX), static_cast(kg.centerY)) == 30); - CHECK(controlAtPoint(rows, rows[2].control.left + 1, rows[2].control.top + 1) == -1); + CHECK(controlAtPoint(rows, rows[2].control.x + 1, rows[2].control.y + 1) == -1); } static void testControlAtPointMisses() { - const Rect panel{0, 0, 300, 400}; + const Rect panel = Rect::ltrb(0, 0, 300, 400); const std::vector rows = layoutControls(panel, {{10, ControlKind::Toggle}, {20, ControlKind::Slider}}); // The label column is not interactive. - CHECK(controlAtPoint(rows, rows[0].label.left + 2, rows[0].label.top + 4) == -1); + CHECK(controlAtPoint(rows, rows[0].label.x + 2, rows[0].label.y + 4) == -1); // The gap between rows is a miss. - const int gapY = rows[0].row.bottom + kControlRowGap / 2; + const int gapY = rows[0].row.bottom() + kControlRowGap / 2; CHECK(controlAtPoint(rows, 200, gapY) == -1); // Off-panel below. CHECK(controlAtPoint(rows, 200, 5000) == -1); diff --git a/tests/test_peaks.cpp b/tests/test_peaks.cpp index 713f70b..4ac91b4 100644 --- a/tests/test_peaks.cpp +++ b/tests/test_peaks.cpp @@ -11,13 +11,14 @@ // merge, steep disjoint-span merge, no-bin-dropped spike sweep, no-column-empty // coverage, col clamp, degenerate inputs. -#include "../src/peaks.h" +#include "../src/core/audio/peaks.h" #include #include #include using namespace reasampler; +using namespace reasampler::audio; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_pitch_shift.cpp b/tests/test_pitch_shift.cpp index a92c2fe..768e45e 100644 --- a/tests/test_pitch_shift.cpp +++ b/tests/test_pitch_shift.cpp @@ -27,13 +27,14 @@ // the master's splice decision (jump/lag/frac/fadeLen AND firing frame) exactly, on // decorrelated stereo content where an independent per-channel search provably diverges. -#include "../src/vst/pitch_shift.h" +#include "../src/core/instrument/engine/pitch_shift.h" #include #include #include using namespace reasampler; +using namespace reasampler::instrument::engine; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_provenance.cpp b/tests/test_provenance.cpp index a2b19f3..5c77186 100644 --- a/tests/test_provenance.cpp +++ b/tests/test_provenance.cpp @@ -15,15 +15,16 @@ // The Sample-JSON round-trip of the fingerprint (leveraging M1's existing provenance // round-trip) is exercised in test_bank_model.cpp — see the fingerprint case there. -#include "../src/provenance.h" +#include "../src/core/model/provenance.h" -#include "../src/bank_model.h" // recipe-through-Sample-JSON round-trip (M1 seam) +#include "../src/core/model/bank_model.h" // recipe-through-Sample-JSON round-trip (M1 seam) #include #include #include using namespace reasampler; +using namespace reasampler::model; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -246,7 +247,7 @@ static void testHugeGuidCountRejectedBeforeReserve() { // --- recorded-recipe model round-trips through the Sample JSON ---------------- // The fingerprint rides in Provenance.fxChainSnapshot (one string), which M1's -// BankIndex JSON already round-trips. Prove a real recipe survives that path intact. +// BankModel JSON already round-trips. Prove a real recipe survives that path intact. static void testRecipeThroughSampleJson() { const CaptureRecipe r = baseRecipe(); @@ -260,10 +261,10 @@ static void testRecipeThroughSampleJson() { prov.fxChainSnapshot = buildFingerprint(r); s.provenance = prov; - BankIndex idx; + BankModel idx; CHECK(idx.add(s) == AddResult::Added); - auto back = BankIndex::deserialize(idx.serialize()); + auto back = BankModel::deserialize(idx.serialize()); CHECK(back.has_value()); const Sample* child = back ? back->query("child-1") : nullptr; CHECK(child != nullptr); diff --git a/tests/test_prune_button.cpp b/tests/test_prune_button.cpp index 19ff0aa..2803d64 100644 --- a/tests/test_prune_button.cpp +++ b/tests/test_prune_button.cpp @@ -7,11 +7,12 @@ // tail-label inset or is degenerate; hit-test in/out/edge (half-open bounds); a // suppressed/empty button claims no point; draw and hit-test agree over the whole rect. -#include "../src/prune_button.h" +#include "../src/core/ui/prune_button.h" #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_prune_reconcile.cpp b/tests/test_prune_reconcile.cpp index 8a283d1..2fabda3 100644 --- a/tests/test_prune_reconcile.cpp +++ b/tests/test_prune_reconcile.cpp @@ -15,8 +15,8 @@ // Plus: determinism (present-order output), duplicate-`present` de-dup, exact-string // (non-normalizing) match, and the referencedPaths() union query directly. -#include "../src/bank_book.h" -#include "../src/prune_reconcile.h" +#include "../src/core/model/bank_book.h" +#include "../src/core/reclaim/prune_reconcile.h" #include #include @@ -26,6 +26,8 @@ #include using namespace reasampler; +using namespace reasampler::model; +using namespace reasampler::reclaim; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_realtime_record.cpp b/tests/test_realtime_record.cpp index c904ecc..1da6e96 100644 --- a/tests/test_realtime_record.cpp +++ b/tests/test_realtime_record.cpp @@ -3,12 +3,13 @@ // record-mode/recipe bookkeeping (channel count + tap -> I_RECMODE / I_RECMODE_FLAGS) // and the wet/dry -> tap decision, plus the recorded-file -> Sample mapping. -#include "../src/realtime_record.h" +#include "../src/core/capture/realtime_record.h" #include #include using namespace reasampler; +using namespace reasampler::capture; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_render_settings.cpp b/tests/test_render_settings.cpp index 62001c1..fc30425 100644 --- a/tests/test_render_settings.cpp +++ b/tests/test_render_settings.cpp @@ -6,7 +6,7 @@ // FX-bypass plan (corrects the "items captured through parent FX" defect), and the // capture-action taxonomy table (stable ids, scope x tail-variant matrix). -#include "../src/render_settings.h" +#include "../src/core/capture/render_settings.h" #include #include @@ -14,6 +14,7 @@ #include using namespace reasampler; +using namespace reasampler::capture; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_sample_map.cpp b/tests/test_sample_map.cpp index 427cbcb..1a7a96d 100644 --- a/tests/test_sample_map.cpp +++ b/tests/test_sample_map.cpp @@ -22,7 +22,7 @@ // stride contract across the seam (that the byte stride wav_trim reports matches the // channel-count stride downmixToMono divides by). -#include "../src/vst/sample_map.h" +#include "../src/core/instrument/map/sample_map.h" #include #include @@ -30,18 +30,21 @@ #include #include -#include "../src/bank_book.h" -#include "../src/bank_model.h" -#include "../src/vst/master_gain.h" // masterGainMaxLinear (the v8 master-gain wire cap) +#include "../src/core/model/bank_book.h" +#include "../src/core/model/bank_model.h" +#include "../src/core/instrument/engine/master_gain.h" // masterGainMaxLinear (the v8 master-gain wire cap) using namespace reasampler; +using namespace reasampler::instrument::engine; +using namespace reasampler::capture; // wav_trim (WavLayout) — sample_map re-exports live in reasampler until Q-W2v +using namespace reasampler::model; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) // Build a Sample with the fields sample_map reads. Relative path is required by -// BankIndex::add (relative-only invariant); a content hash is set so dedup does not +// BankModel::add (relative-only invariant); a content hash is set so dedup does not // collapse distinct entries. static Sample makeSample(const std::string& id, const std::string& name, const std::string& rel, std::optional root) { @@ -62,7 +65,7 @@ static std::string bookJson(const std::vector& poolSamples, for (const Sample& s : poolSamples) book.pool().index.add(s); if (!drumSamples.empty()) { book.createBank("drums-id", "Drums"); - BankIndex* di = book.index("drums-id"); + BankModel* di = book.index("drums-id"); for (const Sample& s : drumSamples) di->add(s); } return book.serialize(); @@ -1305,7 +1308,7 @@ static void testComponentStateMasterGainWriterClamps() { hi.masterGainLinear = 1000.0; CHECK(std::fabs(deserializeComponentState(serializeComponentState(hi), 44100.0) .masterGainLinear - - vst::masterGainMaxLinear()) < 1e-9); + instrument::engine::masterGainMaxLinear()) < 1e-9); ComponentState lo; lo.masterGainLinear = -5.0; CHECK(deserializeComponentState(serializeComponentState(lo), 44100.0).masterGainLinear == @@ -1686,7 +1689,7 @@ static void testVelocityCurveRoundTrip() { // A second zone left at the flat default proves the field is per-record and defaults to flat y=1. PerformanceMap m; PerformanceZone z = zone("lead", 20, 100); - z.velocityCurve = vst::VelocityCurve::linear(); + z.velocityCurve = VelocityCurve::linear(); z.velocityCurve.addPoint(60.0, 0.3); // an interior knot to exercise multi-point round-trip m.zones.push_back(z); m.zones.push_back(zone("pad", 0, 19)); // default flat curve @@ -1694,7 +1697,7 @@ static void testVelocityCurveRoundTrip() { CHECK(back.zones.size() == 2); if (back.zones.size() != 2) return; CHECK(back.zones[0].velocityCurve.equals(z.velocityCurve)); // exact point round-trip - CHECK(back.zones[1].velocityCurve.equals(vst::VelocityCurve::flat())); // default preserved + CHECK(back.zones[1].velocityCurve.equals(VelocityCurve::flat())); // default preserved // And the flat default really is unity everywhere (R10-F1 Option A), not the old linear ramp. CHECK(back.zones[1].velocityCurve.eval(1.0) == 1.0); CHECK(back.zones[1].velocityCurve.eval(64.0) == 1.0); @@ -1706,12 +1709,12 @@ static void testVelocityCurveThroughComponentEnvelope() { ComponentState s; s.selectionId = "pick"; PerformanceZone z = zone("pick", 0, 127); - z.velocityCurve = vst::VelocityCurve::linear(); + z.velocityCurve = VelocityCurve::linear(); s.map.zones.push_back(z); const ComponentState back = deserializeComponentState(serializeComponentState(s), 44100.0); CHECK(back.map.zones.size() == 1); if (back.map.zones.size() != 1) return; - CHECK(back.map.zones[0].velocityCurve.equals(vst::VelocityCurve::linear())); + CHECK(back.map.zones[0].velocityCurve.equals(VelocityCurve::linear())); } static void testVelocityCurveResolvesToZone() { @@ -1720,12 +1723,12 @@ static void testVelocityCurveResolvesToZone() { const std::string json = bookJson({makeSample("a", "Kick", "b/a.wav", 36)}, {}); PerformanceMap m; PerformanceZone z = zone("a", 0, 127); - z.velocityCurve = vst::VelocityCurve::linear(); + z.velocityCurve = VelocityCurve::linear(); m.zones.push_back(z); const ResolvedPerformance r = resolvePerformance(json, m); CHECK(r.zones.size() == 1); if (r.zones.size() != 1) return; - CHECK(r.zones[0].velocityCurve.equals(vst::VelocityCurve::linear())); + CHECK(r.zones[0].velocityCurve.equals(VelocityCurve::linear())); } // FA1 bug 3a — the COMPOSED end-to-end regression, mirroring the processor's reload composition @@ -1740,7 +1743,7 @@ static void testVelocityCurveEndToEndThroughReloadComposition() { ComponentState s; s.selectionId = "a"; PerformanceZone z = zone("a", 0, 127); - z.velocityCurve = vst::VelocityCurve::linear(); + z.velocityCurve = VelocityCurve::linear(); s.map.zones.push_back(z); const ComponentState back = deserializeComponentState(serializeComponentState(s), 48000.0); CHECK(back.map.zones.size() == 1); @@ -1817,7 +1820,7 @@ static void testVelocityCurveV6BackCompatLiftsToFlat() { CHECK(back.zones[0].sampleId == "v6saved"); CHECK(back.zones[0].keyTrack == 0.5); // the v6 field still read correctly // No curve tail -> flat y=1 default (the deliberate behavior change). - CHECK(back.zones[0].velocityCurve.equals(vst::VelocityCurve::flat())); + CHECK(back.zones[0].velocityCurve.equals(VelocityCurve::flat())); CHECK(back.zones[0].velocityCurve.eval(20.0) == 1.0); // a soft hit now plays at unity } diff --git a/tests/test_sample_usage.cpp b/tests/test_sample_usage.cpp index c2d84b0..8c48da5 100644 --- a/tests/test_sample_usage.cpp +++ b/tests/test_sample_usage.cpp @@ -22,7 +22,7 @@ // the pure identity matcher (UID hex / module filename base / display name, beta // over-protect), and the composed pruneOrphans exclusion proof. -#include "../src/sample_usage.h" +#include "../src/core/wire/sample_usage.h" #include #include @@ -30,9 +30,11 @@ #include #include -#include "../src/prune_reconcile.h" // mergeReferenced + pruneOrphans (composed proof) +#include "../src/core/reclaim/prune_reconcile.h" // mergeReferenced + pruneOrphans (composed proof) using namespace reasampler; +using namespace reasampler::reclaim; +using namespace reasampler::wire; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_sampler_core.cpp b/tests/test_sampler_core.cpp index 279953c..ec968c9 100644 --- a/tests/test_sampler_core.cpp +++ b/tests/test_sampler_core.cpp @@ -17,7 +17,7 @@ // by the CMake target linking neither SDK — this file includes only sampler_core.h + // the standard library, which is itself the compile-time proof. -#include "../src/vst/sampler_core.h" +#include "../src/core/instrument/engine/sampler_core.h" #include #include @@ -25,6 +25,7 @@ #include using namespace reasampler; +using namespace reasampler::instrument::engine; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ @@ -426,7 +427,7 @@ static void testNoteOffReleasesNewestSameNote() { // A LINEAR velocity curve keeps the two velocities distinguishable (velocity/127). The default // flat y=1 curve (S-VIEW-9 R10-F1) would render both at unity, collapsing the distinction this // note-off-selection test relies on — so we opt this zone back to the linear response. - km.zones[0].velocityCurve = vst::VelocityCurve::linear(); + km.zones[0].velocityCurve = VelocityCurve::linear(); VoiceEngine eng(8, km); std::size_t first = eng.noteOn(60, velOld); // older voice, lower gain @@ -751,7 +752,7 @@ static void testVelocityDefaultCurveIsFlatUnity() { // (not a hardcoded map) drives the gain, and that eval is applied at note-on. static void testVelocityLinearCurveReproducesRamp() { Keymap km = Keymap::singleSampleChromatic(dcSample(100, 60)); // DC 1.0 - km.zones[0].velocityCurve = vst::VelocityCurve::linear(); + km.zones[0].velocityCurve = VelocityCurve::linear(); { VoiceEngine eng(1, km); eng.noteOn(60, 127); @@ -776,7 +777,7 @@ static void testVelocityLinearCurveReproducesRamp() { // curve's shaped value, not the linear one. Proves the whole curve, not just the endpoints, applies. static void testVelocityShapedCurveDrivesGain() { Keymap km = Keymap::singleSampleChromatic(dcSample(100, 60)); // DC 1.0 - vst::VelocityCurve curve = vst::VelocityCurve::linear(); + VelocityCurve curve = VelocityCurve::linear(); curve.addPoint(64.0, 0.9); // pull the mid-velocity response UP to 0.9 km.zones[0].velocityCurve = curve; VoiceEngine eng(1, km); @@ -1429,7 +1430,7 @@ static void testVelocityCurveAppliesUnderPreserve() { SampleData s = dcSample(4000, 60); s.play.pitchEngine = PitchEngine::Preserve; Keymap km = Keymap::singleSampleChromatic(std::move(s)); - km.zones[0].velocityCurve = vst::VelocityCurve::linear(); + km.zones[0].velocityCurve = VelocityCurve::linear(); VoiceEngine eng(1, km, /*preserveCap=*/0, /*window=*/256); eng.noteOn(62, vel); // transposed: the genuine shifter path (not the unity demotion) std::vector out; @@ -1578,7 +1579,7 @@ static void testMonoRepressHeldNoteMovesToTop() { static void testMonoRetriggerFallbackUsesOriginalVelocity() { SampleData s = dcLevelSample(200000, 1.0f, 60); Keymap km = Keymap::singleSampleChromatic(std::move(s)); - km.zones[0].velocityCurve = vst::VelocityCurve::linear(); // gain = velocity/127 + km.zones[0].velocityCurve = VelocityCurve::linear(); // gain = velocity/127 VoiceEngine eng(4, km, 0, 0, VoiceMode::Mono, MonoTrigger::Retrigger); eng.noteOn(60, 32); // soft first note CHECK(approx(probeFrame(eng), 32.0 / 127.0, 1e-4)); @@ -1645,7 +1646,7 @@ static void testMonoLegatoRetunesWithoutReadRestart() { s.rootNote = 60; s.play.adsr = flatAdsr(); Keymap km = Keymap::singleSampleChromatic(std::move(s)); - km.zones[0].velocityCurve = vst::VelocityCurve::linear(); + km.zones[0].velocityCurve = VelocityCurve::linear(); VoiceEngine eng(2, km, 0, 0, VoiceMode::Mono, MonoTrigger::Legato); eng.noteOn(60, 127); // unity: read advances 1/frame, full gain std::vector out; diff --git a/tests/test_tab_strip.cpp b/tests/test_tab_strip.cpp index ab32613..833cfb5 100644 --- a/tests/test_tab_strip.cpp +++ b/tests/test_tab_strip.cpp @@ -10,13 +10,14 @@ // left/right chevron precedence at the ends, dead space between visible tabs, // outside the band above/below/left/right, half-open boundary pixels). -#include "../src/tab_strip.h" +#include "../src/core/ui/tab_strip.h" #include #include #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_tail_control.cpp b/tests/test_tail_control.cpp index 20738ad..20e6e27 100644 --- a/tests/test_tail_control.cpp +++ b/tests/test_tail_control.cpp @@ -3,12 +3,13 @@ // (None -> Auto -> Manual -> None), the manual-length clamp to the 8 s cap, and the // toggle label text. The drawing / click hit-testing is DAW-verified in bank_panel. -#include "../src/tail_control.h" +#include "../src/core/capture/tail_control.h" #include #include using namespace reasampler; +using namespace reasampler::capture; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_theme.cpp b/tests/test_theme.cpp index 3885752..545d91c 100644 --- a/tests/test_theme.cpp +++ b/tests/test_theme.cpp @@ -8,7 +8,7 @@ // interaction-state transform behaves, the WCAG math is correct against known anchors, and // the Direction C spectral ramp interpolates its endpoints. -#include "../src/theme.h" +#include "../src/core/ui/theme.h" #include #include @@ -16,6 +16,7 @@ #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_tooltip.cpp b/tests/test_tooltip.cpp index 2d7cf11..fef6765 100644 --- a/tests/test_tooltip.cpp +++ b/tests/test_tooltip.cpp @@ -5,12 +5,13 @@ // match); placement BELOW the anchor centred; horizontal clamp at both client edges; the // bottom-edge flip to ABOVE; the both-clip clamp for a tall tooltip; degenerate inputs -> empty. -#include "../src/tooltip.h" +#include "../src/core/ui/tooltip.h" #include #include using namespace reasampler; +using namespace reasampler::ui; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_trigger_seam.cpp b/tests/test_trigger_seam.cpp index 58cbcb5..702c44d 100644 --- a/tests/test_trigger_seam.cpp +++ b/tests/test_trigger_seam.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::trigger_seam — no VST3, no REAPER, no framework. +// Standalone tests for reasampler::instrument::map::trigger_seam — no VST3, no REAPER, no framework. // Same fast assert loop as the sibling pure tests. // // Covers: triggerPlayLength (zero play length, startFrame set, startFrame past frameCount, @@ -6,11 +6,12 @@ // (zero play length, rounding); round-trip fidelity; the Finding 1 regression (start-point // set — the case that was broken before this module existed). -#include "../src/vst/trigger_seam.h" +#include "../src/core/instrument/map/trigger_seam.h" #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::map; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_velocity_curve.cpp b/tests/test_velocity_curve.cpp index 9baeaab..7e642cc 100644 --- a/tests/test_velocity_curve.cpp +++ b/tests/test_velocity_curve.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::velocity_curve — no VST3, no REAPER, no framework. Same fast +// Standalone tests for reasampler::instrument::engine::velocity_curve — no VST3, no REAPER, no framework. Same fast // assert loop as the sibling pure tests. Assert the S-VIEW-9 velocity->amp transfer curve HARD: // // * eval — flat y=1 default (R10-F1 Option A: EVERY velocity -> 1.0), linear ramp, curved shape @@ -11,12 +11,13 @@ // * fromPoints — the deserialization repair: sorts by X, box-clamps, forces endpoints, and falls // back to flat() for a sub-2-point list. -#include "../src/vst/velocity_curve.h" +#include "../src/core/instrument/engine/velocity_curve.h" #include #include -using namespace reasampler::vst; +using namespace reasampler; +using namespace reasampler::instrument::engine; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_view_mode_model.cpp b/tests/test_view_mode_model.cpp index 94ce056..34bb1b3 100644 --- a/tests/test_view_mode_model.cpp +++ b/tests/test_view_mode_model.cpp @@ -15,14 +15,15 @@ // (park-while-parked) so untagged leaves return to visible after toggling back; // guards the in-DAW "all leaves hidden after toggling twice" regression. -#include "../src/view_mode_model.h" -#include "../src/lane_keys.h" // laneNameForMode — assert the minting plan's durable keys +#include "../src/core/view/view_mode_model.h" +#include "../src/core/view/lane_keys.h" // laneNameForMode — assert the minting plan's durable keys #include #include #include using namespace reasampler; +using namespace reasampler::view; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_view_tree.cpp b/tests/test_view_tree.cpp index f52c131..9a7e06e 100644 --- a/tests/test_view_tree.cpp +++ b/tests/test_view_tree.cpp @@ -3,12 +3,13 @@ // one genuinely pure piece: the I_FOLDERDEPTH walk that turns REAPER's linear // track stream into the parent<->child FolderTree the pure model consumes. -#include "../src/view_tree.h" +#include "../src/core/view/view_tree.h" #include #include using namespace reasampler; +using namespace reasampler::view; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_wav_trim.cpp b/tests/test_wav_trim.cpp index a615be2..10b651d 100644 --- a/tests/test_wav_trim.cpp +++ b/tests/test_wav_trim.cpp @@ -7,7 +7,7 @@ // extraction (whole / tail window / clamp / out-of-range); truncate plan (kept #include @@ -15,6 +15,7 @@ #include using namespace reasampler; +using namespace reasampler::capture; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ diff --git a/tests/test_waveform_view.cpp b/tests/test_waveform_view.cpp index 2969cd9..d5cfd60 100644 --- a/tests/test_waveform_view.cpp +++ b/tests/test_waveform_view.cpp @@ -1,4 +1,4 @@ -// Standalone tests for reasampler::vst::waveform_view — no VST3, no REAPER, no framework. +// Standalone tests for reasampler::instrument::ui::waveform_view — no VST3, no REAPER, no framework. // Same fast assert loop as the sibling pure tests. Assert the S11 waveform surface's // frame<->pixel mapping, marker grab regions, drag-delta frame resolver (with clamps), and // the zero-crossing snap — the geometry + snap that back the draggable start/loop markers. @@ -9,61 +9,62 @@ // no-ops); nearestZeroCrossing (nearest sign-change, sample-on-zero, equidistant-tie-to-lower, // no-crossing keeps target, target clamp, degenerate buffers). -#include "../src/vst/waveform_view.h" +#include "../src/core/instrument/ui/waveform_view.h" #include #include -using namespace reasampler::vst; -using reasampler::AudioSample; +using namespace reasampler; +using namespace reasampler::instrument::ui; +using reasampler::audio::AudioSample; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) // A comfortable waveform area: 1000px wide, offset so left != 0 (catches origin bugs). -static Rect wideArea() { return Rect{20, 10, 1020, 90}; } // width 1000 +static Rect wideArea() { return Rect::ltrb(20, 10, 1020, 90); } // width 1000 // --- frameToX / xToFrame ------------------------------------------------------ static void testFrameToXEndpoints() { const Rect a = wideArea(); - CHECK(frameToX(a, 1000, 0) == a.left); // frame 0 -> left edge - CHECK(frameToX(a, 1000, 1000) == a.right); // frameCount -> right edge - CHECK(frameToX(a, 1000, 500) == a.left + 500); // midpoint (1:1 here) + CHECK(frameToX(a, 1000, 0) == a.x); // frame 0 -> left edge + CHECK(frameToX(a, 1000, 1000) == a.right()); // frameCount -> right edge + CHECK(frameToX(a, 1000, 500) == a.x + 500); // midpoint (1:1 here) } static void testFrameToXClampsOutOfRange() { const Rect a = wideArea(); - CHECK(frameToX(a, 1000, -50) == a.left); // below 0 pins left - CHECK(frameToX(a, 1000, 5000) == a.right); // above count pins right + CHECK(frameToX(a, 1000, -50) == a.x); // below 0 pins left + CHECK(frameToX(a, 1000, 5000) == a.right()); // above count pins right } static void testFrameToXDegenerate() { const Rect a = wideArea(); - CHECK(frameToX(a, 0, 100) == a.left); // no frames -> left - const Rect z = Rect{5, 5, 5, 45}; // zero width - CHECK(frameToX(z, 1000, 500) == z.left); + CHECK(frameToX(a, 0, 100) == a.x); // no frames -> left + const Rect z = Rect::ltrb(5, 5, 5, 45); // zero width + CHECK(frameToX(z, 1000, 500) == z.x); } static void testXToFrameInverse() { const Rect a = wideArea(); - CHECK(xToFrame(a, 1000, a.left) == 0); - CHECK(xToFrame(a, 1000, a.right) == 1000); - CHECK(xToFrame(a, 1000, a.left + 250) == 250); // 1:1 map here + CHECK(xToFrame(a, 1000, a.x) == 0); + CHECK(xToFrame(a, 1000, a.right()) == 1000); + CHECK(xToFrame(a, 1000, a.x + 250) == 250); // 1:1 map here } static void testXToFrameClampsOutside() { const Rect a = wideArea(); - CHECK(xToFrame(a, 1000, a.left - 100) == 0); // left of area -> 0 - CHECK(xToFrame(a, 1000, a.right + 100) == 1000); // right of area -> frameCount - CHECK(xToFrame(a, 0, a.left + 10) == 0); // no frames -> 0 + CHECK(xToFrame(a, 1000, a.x - 100) == 0); // left of area -> 0 + CHECK(xToFrame(a, 1000, a.right() + 100) == 1000); // right of area -> frameCount + CHECK(xToFrame(a, 0, a.x + 10) == 0); // no frames -> 0 } static void testFrameToXRoundTrip() { // Round-trip at a non-1:1 scale: 800px area over 2000 frames (2.5 frames/px). frameToX then // xToFrame should land within a couple frames (rounding both directions). - const Rect a = Rect{0, 0, 800, 60}; + const Rect a = Rect::ltrb(0, 0, 800, 60); for (std::int64_t f = 0; f <= 2000; f += 137) { const int x = frameToX(a, 2000, f); const std::int64_t back = xToFrame(a, 2000, x); @@ -77,39 +78,39 @@ static void testMarkerAtPointGrabsWithinBand() { const Rect a = wideArea(); // Markers at frames 100, 500, 900 -> x = left+100, left+500, left+900. const std::int64_t frames[3] = {100, 500, 900}; - const int midY = a.top + a.height() / 2; - CHECK(markerAtPoint(a, 1000, frames, 3, a.left + 100, midY) == 0); - CHECK(markerAtPoint(a, 1000, frames, 3, a.left + 500, midY) == 1); - CHECK(markerAtPoint(a, 1000, frames, 3, a.left + 900, midY) == 2); + const int midY = a.y + a.height / 2; + CHECK(markerAtPoint(a, 1000, frames, 3, a.x + 100, midY) == 0); + CHECK(markerAtPoint(a, 1000, frames, 3, a.x + 500, midY) == 1); + CHECK(markerAtPoint(a, 1000, frames, 3, a.x + 900, midY) == 2); // Within the grab band on either side of the line. - CHECK(markerAtPoint(a, 1000, frames, 3, a.left + 500 + kMarkerGrabWidth, midY) == 1); - CHECK(markerAtPoint(a, 1000, frames, 3, a.left + 500 - kMarkerGrabWidth, midY) == 1); + CHECK(markerAtPoint(a, 1000, frames, 3, a.x + 500 + kMarkerGrabWidth, midY) == 1); + CHECK(markerAtPoint(a, 1000, frames, 3, a.x + 500 - kMarkerGrabWidth, midY) == 1); } static void testMarkerAtPointMissesBetween() { const Rect a = wideArea(); const std::int64_t frames[3] = {100, 500, 900}; - const int midY = a.top + a.height() / 2; + const int midY = a.y + a.height / 2; // Well away from any marker line. - CHECK(markerAtPoint(a, 1000, frames, 3, a.left + 300, midY) == -1); + CHECK(markerAtPoint(a, 1000, frames, 3, a.x + 300, midY) == -1); // Off the area vertically. - CHECK(markerAtPoint(a, 1000, frames, 3, a.left + 500, a.top - 5) == -1); + CHECK(markerAtPoint(a, 1000, frames, 3, a.x + 500, a.y - 5) == -1); } static void testMarkerAtPointFirstMatchOnOverlap() { const Rect a = wideArea(); // Two markers at the same frame -> first in order wins. const std::int64_t frames[2] = {400, 400}; - const int midY = a.top + a.height() / 2; - CHECK(markerAtPoint(a, 1000, frames, 2, a.left + 400, midY) == 0); + const int midY = a.y + a.height / 2; + CHECK(markerAtPoint(a, 1000, frames, 2, a.x + 400, midY) == 0); } static void testMarkerAtPointRejectsNullEmpty() { const Rect a = wideArea(); - const int midY = a.top + a.height() / 2; - CHECK(markerAtPoint(a, 1000, nullptr, 3, a.left + 100, midY) == -1); + const int midY = a.y + a.height / 2; + CHECK(markerAtPoint(a, 1000, nullptr, 3, a.x + 100, midY) == -1); const std::int64_t frames[1] = {100}; - CHECK(markerAtPoint(a, 1000, frames, 0, a.left + 100, midY) == -1); + CHECK(markerAtPoint(a, 1000, frames, 0, a.x + 100, midY) == -1); } // --- resolveDragFrame --------------------------------------------------------- @@ -130,7 +131,7 @@ static void testResolveDragFrameClamps() { static void testResolveDragFrameRounds() { // 500px area over 1000 frames -> 2 frames/px. A +3px drag -> round(6.0)=6; the rounding is // at the frame centre. Use a scale where a fractional result appears. - const Rect a = Rect{0, 0, 300, 60}; // 1000 frames / 300px = 3.33 frames/px + const Rect a = Rect::ltrb(0, 0, 300, 60); // 1000 frames / 300px = 3.33 frames/px // +3px -> 3*1000/300 = 10.0 -> 10 frames. CHECK(resolveDragFrame(a, 1000, 100, 3) == 110); // +1px -> 1000/300 = 3.33 -> rounds to 3. @@ -138,7 +139,7 @@ static void testResolveDragFrameRounds() { } static void testResolveDragFrameDegenerate() { - const Rect z = Rect{0, 0, 0, 60}; // zero width + const Rect z = Rect::ltrb(0, 0, 0, 60); // zero width CHECK(resolveDragFrame(z, 1000, 300, 100) == 300); // pinned to start const Rect a = wideArea(); CHECK(resolveDragFrame(a, 0, 300, 100) == 0); // no frames -> clamp(start)=0 diff --git a/tests/test_wire.cpp b/tests/test_wire.cpp index 3f58399..d2420b4 100644 --- a/tests/test_wire.cpp +++ b/tests/test_wire.cpp @@ -14,6 +14,7 @@ #include using namespace reasampler; +using namespace reasampler::wire; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \