cmake_minimum_required(VERSION 3.19) # --------------------------------------------------------------------------- # Version — SINGLE SOURCE OF TRUTH (Phase V, V1). Edit REASAMPLER_VERSION here and # nowhere else: it flows to the binary constant, the ext-state writing-version stamp, # and the "show version" action via a configure_file'd header (below). The string is # authoritative verbatim — leading zero preserved (Daniel-fixed: exactly "0.9.01", # two-digit zero-padded patch). We deliberately do NOT reconstruct the display string # from project(VERSION)'s numeric components, since CMake may normalize a numeric patch # field; the string variable is what renders. project(VERSION ...) is still set (with a # normalized 0.9.1 triple) for CMake hygiene / any downstream numeric use, but it is NOT # the rendered source of truth. set(REASAMPLER_VERSION "0.9.01") project(reaper_reasampler VERSION 0.9.1 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) # --------------------------------------------------------------------------- # Channel — SINGLE SOURCE OF TRUTH for beta-in-isolation (Phase V, V4). One flag, # `-DREASAMPLER_CHANNEL=beta`, forks the whole channel identity from one build tree: # absent (or `stable`) = today's build with BYTE-IDENTICAL identity (binary name, # ext-state namespace, command-id strings, version render); `beta` = a fully isolated # `reaper_reasampler_beta` that coexists with stable in one REAPER. The flag reduces to # ONE bit (REASAMPLER_CHANNEL_IS_BETA) threaded through the SAME configure_file'd header # as the version string, so the pure app_version module derives every channel-qualified # identity from it — no scattered #ifdefs. Any value other than exactly `beta` is treated # as stable (a typo must not silently produce a half-forked build), and we hard-error on # an unrecognized non-empty value so a misspelled `-DREASAMPLER_CHANNEL=betaa` is caught # at configure time rather than shipping stable identity under a beta intent. set(REASAMPLER_CHANNEL "stable" CACHE STRING "Build channel: stable (default) or beta") if(REASAMPLER_CHANNEL STREQUAL "beta") set(REASAMPLER_CHANNEL_IS_BETA 1) set(REASAMPLER_OUTPUT_NAME "reaper_reasampler_beta") elseif(REASAMPLER_CHANNEL STREQUAL "stable") set(REASAMPLER_CHANNEL_IS_BETA 0) set(REASAMPLER_OUTPUT_NAME "reaper_reasampler") else() message(FATAL_ERROR "REASAMPLER_CHANNEL must be 'stable' or 'beta' (got '${REASAMPLER_CHANNEL}')") endif() # Generate version_generated.h from the one REASAMPLER_VERSION variable + the channel bit. # 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_BINARY_DIR}/generated/version_generated.h @ONLY) # --------------------------------------------------------------------------- # Vendored dependencies — add as git submodules (see README): # git submodule add https://github.com/justinfrankel/reaper-sdk vendor/reaper-sdk # git submodule add https://github.com/justinfrankel/WDL vendor/WDL # --------------------------------------------------------------------------- set(SDK_INC ${CMAKE_CURRENT_SOURCE_DIR}/vendor/reaper-sdk/sdk) set(WDL_INC ${CMAKE_CURRENT_SOURCE_DIR}/vendor/WDL/WDL) set(SWELL ${WDL_INC}/swell) # --------------------------------------------------------------------------- # 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) target_include_directories(bank_model PUBLIC src) # --------------------------------------------------------------------------- # 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) target_include_directories(peaks PUBLIC src) # --------------------------------------------------------------------------- # 2b) Pure capture-path arithmetic — NO REAPER, NO SWELL. Bank-folder / unique # 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) target_include_directories(capture_paths PUBLIC src) # --------------------------------------------------------------------------- # 2c) Pure bank-grid layout — NO REAPER, NO SWELL. Grid tiling math (panel WxH + # cell size + N -> cell rects, wrapping, partial last row) and the thumbnail # 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) target_include_directories(bank_grid PUBLIC src) # --------------------------------------------------------------------------- # 2c') Pure mode_switch layout — NO REAPER, NO SWELL. The Design-View mode-switch # geometry (D5): header rect + N modes -> N equal segment rects (exact tiling), # and point -> segment hit-test. Split out so the switch's layout math is # 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) target_include_directories(mode_switch PUBLIC src) # --------------------------------------------------------------------------- # 2c'') Pure tab_strip layout — NO REAPER, NO SWELL. The named-banks tab-strip # geometry (B4): strip rect + N tabs at a fixed tab width + scroll offset -> # per-tab rects (overflow-clipped), overflow chevron reservation + maxScroll, # and point -> tab / chevron hit-test. Split out so the strip's layout + # 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) target_include_directories(tab_strip PUBLIC src) # --------------------------------------------------------------------------- # 2d) Pure view_mode_model library — NO REAPER, NO SWELL. The Design View heart # (Phase D1): mode registry + GUID-keyed membership index + folder-tree-aware # 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) target_include_directories(view_mode_model PUBLIC src) # The pure lane-minting decision (planLaneMinting) names managed lanes via the ONE # durable-key convention in lane_keys (laneNameForMode), so the model depends on that # pure sibling. PUBLIC so every consumer (tests + module) resolves the symbol. target_link_libraries(view_mode_model PUBLIC lane_keys) # --------------------------------------------------------------------------- # 2d) Pure view_tree library — NO REAPER, NO SWELL. The one testable-outside-DAW # piece of the D2 view shell: turning REAPER's linear I_FOLDERDEPTH stream into # the parent<->child FolderTree the model consumes. The REAPER reads stay in # 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) target_include_directories(view_tree PUBLIC src) target_link_libraries(view_tree PUBLIC view_mode_model) # --------------------------------------------------------------------------- # 2d'') Pure guid_diff library — NO REAPER, NO SWELL. The D2 Wave-2 new-content # detection core: current \ previous GUID diff + the first-poll-after-open # baseline guard (and per-project reset). Split out so the fiddly baseline/diff # logic is unit-tested outside the DAW; the bank_panel timer that reads REAPER's # 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) target_include_directories(guid_diff PUBLIC src) # --------------------------------------------------------------------------- # 2d''') Pure lane_keys library — NO REAPER, NO SWELL. The managed/manual fixed-lane # heuristic (D2 Wave-2): a lane whose durable P_LANENAME:n carries the # "reasampler:" prefix is tool-managed and keyed by that stable name; any other # lane is user-minted manual and off-limits. Resolves design point #1 (auto-tag # 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) target_include_directories(lane_keys PUBLIC src) # --------------------------------------------------------------------------- # 2e) Pure insert_plan library — NO REAPER, NO SWELL. The InsertMedia `mode` # bitmask arithmetic behind the `insert` shell (M6). Split out so the # 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) target_include_directories(insert_plan PUBLIC src) # --------------------------------------------------------------------------- # 2f) Pure render_settings library — NO REAPER, NO SWELL. The M7 capture-family # logic: source-mode + wet/dry -> RENDER_SETTINGS bit value, P_RAZOREDITS # string -> time ranges + union bound, and the capture-action taxonomy table. # Split out so the fiddly bit-mapping / razor-parsing is unit-tested outside # 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) target_include_directories(render_settings PUBLIC src) target_link_libraries(render_settings PUBLIC bank_model) # --------------------------------------------------------------------------- # 2f') Pure tail_control library — NO REAPER, NO SWELL. The docked bank_panel's # tail-mode toggle logic (T1 exposure): TailSetting state, cycle order # (None->Auto->Manual->None), the manual-length clamp to the 8 s cap, and the # toggle label text. Split out so the toggle's cycle/clamp/label is unit-tested # 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) target_include_directories(tail_control PUBLIC src) target_link_libraries(tail_control PUBLIC render_settings) # --------------------------------------------------------------------------- # 2g') Pure bank_book library — NO REAPER, NO SWELL. The multi-bank phase heart # (Phase B1): an ordered registry of banks (pool seeded as bank-zero + named # banks), each wrapping a BankIndex; create/rename/reorder/delete named banks, # pool privileges enforced in-model, active-bank id, index-only move/copy of a # 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) target_include_directories(bank_book PUBLIC src) target_link_libraries(bank_book PUBLIC bank_model) # --------------------------------------------------------------------------- # 2g'') Pure owned_manifest library — NO REAPER, NO SWELL. The owned-file manifest # seam (Phase B B-cap): the set of project-relative files the capture path # itself created, so Phase R prune can tell the bank system's own orphans from # hand-dropped files. Deliberately DECOUPLED from bank_book — it tracks files # CREATED, not index membership (sample-remove is not manifest-remove). Small # 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) target_include_directories(owned_manifest PUBLIC src) # --------------------------------------------------------------------------- # 2g) Pure realtime_record library — NO REAPER, NO SWELL. The M8 realtime-record # logic: capture scope + FX-tap point -> I_RECMODE / I_RECMODE_FLAGS values, # wet/dry -> tap point, and the recorded-file -> Sample mapping. Split out so # the fiddly record-mode bit values + Sample population are unit-tested outside # 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) target_include_directories(realtime_record PUBLIC src) target_link_libraries(realtime_record PUBLIC bank_model) # --------------------------------------------------------------------------- # 2h) Pure wav_trim library — NO REAPER, NO SWELL. The realtime tail's (T2) PCM # decay-scan trim needs to TRUNCATE the recorded 32-bit-float WAV at a frame # boundary without corrupting the RIFF container. This module holds the fiddly, # easy-to-get-wrong part unit-tested outside the DAW: parse the WAV geometry # (fmt/data chunk walk + 32-bit-float verification), extract the tail-region # floats to scan, and compute the truncate plan (kept byte length + the two # 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) target_include_directories(wav_trim PUBLIC src) target_link_libraries(wav_trim PUBLIC peaks) # --------------------------------------------------------------------------- # 2i) Pure app_version library — NO REAPER, NO SWELL. The Phase V (V1) version-identity # core: re-exports the ONE CMake-sourced version string (via the configure_file'd # version_generated.h) and owns the pure parse/compare/classify logic — the semver # ordering a within-channel forward migration needs, and the three-way writing-version # classification (PreVersioning / Unknown / Stamped) persist reads back from ext state. # Split out so the exact-string fidelity + parse/compare are unit-tested outside the # 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) target_include_directories(app_version PUBLIC src ${CMAKE_CURRENT_BINARY_DIR}/generated) # --------------------------------------------------------------------------- # 2j) Pure provenance library — NO REAPER, NO SWELL. The Milestone 10 core: the # recorded capture recipe (CaptureRecipe) + the thin drift-fingerprint that # rides in Provenance.fxChainSnapshot (P1=a), its build/parse round-trip, the # FX-chain identity fold, and the pure parent-detection decision (resample-from- # sample by resolved file path). Split out so the encoding + decision logic are # unit-tested outside the DAW; the FX-chain query, capture re-run, and action # 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) target_include_directories(provenance PUBLIC src) # --------------------------------------------------------------------------- # 3) Standalone tests for the pure modules (run without launching REAPER). # --------------------------------------------------------------------------- enable_testing() add_executable(bank_model_tests tests/test_bank_model.cpp) target_link_libraries(bank_model_tests PRIVATE bank_model) add_test(NAME bank_model_tests COMMAND bank_model_tests) add_executable(peaks_tests tests/test_peaks.cpp) target_link_libraries(peaks_tests PRIVATE peaks) add_test(NAME peaks_tests COMMAND peaks_tests) add_executable(capture_paths_tests tests/test_capture_paths.cpp) target_link_libraries(capture_paths_tests PRIVATE capture_paths) add_test(NAME capture_paths_tests COMMAND capture_paths_tests) add_executable(bank_grid_tests tests/test_bank_grid.cpp) target_link_libraries(bank_grid_tests PRIVATE bank_grid) add_test(NAME bank_grid_tests COMMAND bank_grid_tests) add_executable(mode_switch_tests tests/test_mode_switch.cpp) target_link_libraries(mode_switch_tests PRIVATE mode_switch) add_test(NAME mode_switch_tests COMMAND mode_switch_tests) add_executable(tab_strip_tests tests/test_tab_strip.cpp) target_link_libraries(tab_strip_tests PRIVATE tab_strip) add_test(NAME tab_strip_tests COMMAND tab_strip_tests) add_executable(view_mode_model_tests tests/test_view_mode_model.cpp) target_link_libraries(view_mode_model_tests PRIVATE view_mode_model) add_test(NAME view_mode_model_tests COMMAND view_mode_model_tests) add_executable(view_tree_tests tests/test_view_tree.cpp) target_link_libraries(view_tree_tests PRIVATE view_tree) add_test(NAME view_tree_tests COMMAND view_tree_tests) add_executable(guid_diff_tests tests/test_guid_diff.cpp) target_link_libraries(guid_diff_tests PRIVATE guid_diff) add_test(NAME guid_diff_tests COMMAND guid_diff_tests) add_executable(lane_keys_tests tests/test_lane_keys.cpp) target_link_libraries(lane_keys_tests PRIVATE lane_keys) add_test(NAME lane_keys_tests COMMAND lane_keys_tests) add_executable(insert_plan_tests tests/test_insert_plan.cpp) target_link_libraries(insert_plan_tests PRIVATE insert_plan) add_test(NAME insert_plan_tests COMMAND insert_plan_tests) add_executable(render_settings_tests tests/test_render_settings.cpp) target_link_libraries(render_settings_tests PRIVATE render_settings) add_test(NAME render_settings_tests COMMAND render_settings_tests) add_executable(tail_control_tests tests/test_tail_control.cpp) target_link_libraries(tail_control_tests PRIVATE tail_control) add_test(NAME tail_control_tests COMMAND tail_control_tests) add_executable(realtime_record_tests tests/test_realtime_record.cpp) target_link_libraries(realtime_record_tests PRIVATE realtime_record) add_test(NAME realtime_record_tests COMMAND realtime_record_tests) add_executable(bank_book_tests tests/test_bank_book.cpp) target_link_libraries(bank_book_tests PRIVATE bank_book) add_test(NAME bank_book_tests COMMAND bank_book_tests) add_executable(wav_trim_tests tests/test_wav_trim.cpp) target_link_libraries(wav_trim_tests PRIVATE wav_trim) add_test(NAME wav_trim_tests COMMAND wav_trim_tests) add_executable(owned_manifest_tests tests/test_owned_manifest.cpp) target_link_libraries(owned_manifest_tests PRIVATE owned_manifest) add_test(NAME owned_manifest_tests COMMAND owned_manifest_tests) add_executable(app_version_tests tests/test_app_version.cpp) target_link_libraries(app_version_tests PRIVATE app_version) add_test(NAME app_version_tests COMMAND app_version_tests) # The provenance test links bank_model too — it proves the recorded recipe survives # the Sample-JSON round-trip (Provenance.fxChainSnapshot), the M1 seam M10 rides on. add_executable(provenance_tests tests/test_provenance.cpp) target_link_libraries(provenance_tests PRIVATE provenance bank_model) add_test(NAME provenance_tests COMMAND provenance_tests) # --------------------------------------------------------------------------- # 4) The REAPER extension — a loadable module (dlopen'd by REAPER, not linked). # --------------------------------------------------------------------------- # LICE sources the bank_panel draws with: lice.cpp (LICE_SysBitmap, FillRect, # Clear, Blit) + lice_line.cpp (Line, DrawRect). lice_line.cpp's bezier helpers # call LICE_FillCircle from lice_arc.cpp, so that TU is required to link even # though the panel draws no arcs. LICE routes GDI through native Win32 or, on # mac/linux, the host SWELL (SWELL_PROVIDED_BY_APP). set(LICE_SRC ${WDL_INC}/lice/lice.cpp ${WDL_INC}/lice/lice_line.cpp ${WDL_INC}/lice/lice_arc.cpp ) add_library(reaper_reasampler MODULE src/main.cpp src/capture.cpp src/capture_realtime.cpp src/realtime_record.cpp src/persist.cpp src/bank_panel.cpp src/mode_switch.cpp src/tab_strip.cpp src/insert.cpp src/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/actions.cpp src/bank_book.cpp src/owned_manifest.cpp ) target_link_libraries(reaper_reasampler PRIVATE bank_model capture_paths peaks bank_grid mode_switch tab_strip view_mode_model insert_plan render_settings tail_control realtime_record bank_book wav_trim owned_manifest app_version provenance) target_include_directories(reaper_reasampler PRIVATE ${SDK_INC} ${WDL_INC}) # OUTPUT_NAME is channel-derived (Phase V, V4): "reaper_reasampler" (stable, default) or # "reaper_reasampler_beta" (beta). REAPER dlopen's any reaper_* module, so both channels' # artifacts load side-by-side. The CMake TARGET name stays "reaper_reasampler" for both # configs — one source tree, one target; only the emitted file name forks by channel. set_target_properties(reaper_reasampler PROPERTIES PREFIX "" OUTPUT_NAME "${REASAMPLER_OUTPUT_NAME}") if(WIN32) # Native Win32. REAPER provides nothing extra to link. The bank_panel dialog # template (M5) is compiled from src/resource.rc by the platform RC compiler. target_sources(reaper_reasampler PRIVATE src/resource.rc) elseif(APPLE) # macOS: use REAPER's OWN SWELL at runtime via the modstub. # Do NOT build full SWELL. SWELL_PROVIDED_BY_APP routes calls to the host. target_sources(reaper_reasampler PRIVATE ${SWELL}/swell-modstub.mm) target_compile_definitions(reaper_reasampler PRIVATE SWELL_PROVIDED_BY_APP) target_link_libraries(reaper_reasampler PRIVATE "-framework AppKit") set_target_properties(reaper_reasampler PROPERTIES SUFFIX ".dylib") # bank_panel dialog (M5): SWELL can't read a Win32 .rc directly. Run resgen # once to turn src/resource.rc into a C++ source, then add it here: # php ${WDL_INC}/swell/mac_resgen.php src/resource.rc # target_sources(reaper_reasampler PRIVATE src/resource.rc_mac_dlg.h) # generated else() # Linux: REAPER's libSwell.so is used at runtime via the generic modstub. # With SWELL_PROVIDED_BY_APP you can drop pkg-config / -lX11 entirely. target_sources(reaper_reasampler PRIVATE ${SWELL}/swell-modstub-generic.cpp) target_compile_definitions(reaper_reasampler PRIVATE SWELL_PROVIDED_BY_APP) set_target_properties(reaper_reasampler PROPERTIES SUFFIX ".so") # bank_panel dialog (M5): reuse the macOS resgen output (see README / # CLAUDE.md §SWELL dialog resources), then add the generated source: # php ${WDL_INC}/swell/mac_resgen.php src/resource.rc # target_sources(reaper_reasampler PRIVATE src/resource.rc_mac_dlg.h) # generated endif()