From 2173e366ea88c7fa77ef11bde23c69248e30ee57 Mon Sep 17 00:00:00 2001 From: Paul Lipscomb Date: Wed, 22 Jul 2026 18:49:25 -0400 Subject: [PATCH] Reorganize extension-reaper-macos into per-module subfolders, add hello module Splits socket/tracking into their own subdirectories and pulls the hello action registration out into its own module, matching the one-module-per- folder pattern; build.sh now outputs into build/macos instead of the repo root. Co-Authored-By: Claude Sonnet 5 --- extension-reaper-macos/.gitignore | 2 +- extension-reaper-macos/build.sh | 14 +- extension-reaper-macos/src/example.cpp | 63 ++++++++ extension-reaper-macos/src/hello/hello.cpp | 70 ++++++++ extension-reaper-macos/src/hello/hello.h | 14 ++ extension-reaper-macos/src/main.cpp | 149 ++++++++---------- .../src/{ => socket}/socket.cpp | 4 +- .../src/{ => socket}/socket.h | 0 .../src/{ => tracking}/tracking.cpp | 2 +- .../src/{ => tracking}/tracking.h | 0 10 files changed, 231 insertions(+), 87 deletions(-) create mode 100644 extension-reaper-macos/src/example.cpp create mode 100644 extension-reaper-macos/src/hello/hello.cpp create mode 100644 extension-reaper-macos/src/hello/hello.h rename extension-reaper-macos/src/{ => socket}/socket.cpp (98%) rename extension-reaper-macos/src/{ => socket}/socket.h (100%) rename extension-reaper-macos/src/{ => tracking}/tracking.cpp (99%) rename extension-reaper-macos/src/{ => tracking}/tracking.h (100%) diff --git a/extension-reaper-macos/.gitignore b/extension-reaper-macos/.gitignore index e5d0467..567609b 100644 --- a/extension-reaper-macos/.gitignore +++ b/extension-reaper-macos/.gitignore @@ -1 +1 @@ -*.dylib +build/ diff --git a/extension-reaper-macos/build.sh b/extension-reaper-macos/build.sh index 9c7add5..a497a3d 100755 --- a/extension-reaper-macos/build.sh +++ b/extension-reaper-macos/build.sh @@ -3,7 +3,11 @@ set -e cd "$(dirname "$0")" -OUT="reaper_extension-reaper-macos.dylib" +NAME="reaper_extension-reaper-macos.dylib" +OUT_DIR="build/macos" +OUT="$OUT_DIR/$NAME" + +mkdir -p "$OUT_DIR" clang++ \ -std=c++17 \ @@ -12,13 +16,15 @@ clang++ \ -arch arm64 \ -Ivendor/reaper-sdk/sdk \ -Ivendor/reaper-sdk/WDL \ + -Isrc \ -o "$OUT" \ src/main.cpp \ - src/tracking.cpp \ - src/socket.cpp + src/hello/hello.cpp \ + src/tracking/tracking.cpp \ + src/socket/socket.cpp echo "Built $OUT" -DEST="$HOME/Library/Application Support/REAPER/UserPlugins/$OUT" +DEST="$HOME/Library/Application Support/REAPER/UserPlugins/$NAME" cp "$OUT" "$DEST" echo "Copied to $DEST (restart REAPER to reload)" diff --git a/extension-reaper-macos/src/example.cpp b/extension-reaper-macos/src/example.cpp new file mode 100644 index 0000000..428f53d --- /dev/null +++ b/extension-reaper-macos/src/example.cpp @@ -0,0 +1,63 @@ +#define REAPERAPI_MINIMAL +#define REAPERAPI_WANT_ShowConsoleMsg + +#include "reaper_plugin.h" +#include "reaper_plugin_functions.h" + +#include + +static bool isOurAction; +static bool handled; + +static int helloActionId = 0; +static const char *kHelloActionIdStr = "EXTENSION_REAPER_MACOS_HELLO"; +static const char *kHelloActionName = "Hello Action List Display Name"; + +static bool HelloAction(KbdSectionInfo *sec, int command, int val, int val2, int relmode, HWND hwnd); + +void RegisterHello(reaper_plugin_info_t *rec) +{ + custom_action_register_t actionDescription = { + 0, + kHelloActionIdStr, + kHelloActionName, + NULL, + }; + helloActionId = rec->Register("custom_action", &actionDescription); + + char buf[128]; + snprintf(buf, sizeof(buf), "[extension-reaper-macos] helloActionId = %d\n", helloActionId); + ShowConsoleMsg(buf); + + rec->Register("hookcommand2", (void *)HelloAction); +} + +static void PerformHelloAction() +{ + ShowConsoleMsg("[extension-reaper-macos] Hello action triggered\n"); +} + +static void RunHelloActionLogic(int command) +{ + char buf[128]; + snprintf(buf, sizeof(buf), "[extension-reaper-macos] HelloAction called: command=%d helloActionId=%d\n", command, helloActionId); + ShowConsoleMsg(buf); + + isOurAction = (command == helloActionId); + + if (isOurAction == true) + { + PerformHelloAction(); + handled = true; + return; + } + + ShowConsoleMsg("[extension-reaper-macos] ignored — not ours\n"); + handled = false; +} + +static bool HelloAction(KbdSectionInfo *sec, int command, int val, int val2, int relmode, HWND hwnd) +{ + RunHelloActionLogic(command); + return handled; +} diff --git a/extension-reaper-macos/src/hello/hello.cpp b/extension-reaper-macos/src/hello/hello.cpp new file mode 100644 index 0000000..390f04f --- /dev/null +++ b/extension-reaper-macos/src/hello/hello.cpp @@ -0,0 +1,70 @@ +#define REAPERAPI_MINIMAL +#define REAPERAPI_WANT_ShowConsoleMsg + +#include "reaper_plugin.h" +#include "reaper_plugin_functions.h" +#include "hello/hello.h" + +#include + +static bool isOurAction; +static bool handled; + +static int helloActionId = 0; +static const char *kHelloActionIdStr = "EXTENSION_REAPER_MACOS_HELLO"; +static const char *kHelloActionName = "Hello Action List Display Name"; + +static bool HelloAction(KbdSectionInfo *sec, int command, int val, int val2, int relmode, HWND hwnd); +static void HandleHelloAction(int command); +static void RunHelloAction(); + +//step 1 register +void RegisterHello(reaper_plugin_info_t *rec) +{ + custom_action_register_t actionDescription = { + 0, + kHelloActionIdStr, + kHelloActionName, + NULL, + }; + helloActionId = rec->Register("custom_action", &actionDescription); + + char buf[128]; + snprintf(buf, sizeof(buf), "[extension-reaper-macos] helloActionId = %d\n", helloActionId); + ShowConsoleMsg(buf); + + rec->Register("hookcommand2", (void *)HelloAction); +} + +//step 2 callback from REAPER +static bool HelloAction(KbdSectionInfo *sec, int command, int val, int val2, int relmode, HWND hwnd) +{ + HandleHelloAction(command); + return handled; +} +//step 3 check whether the ID REAPER gave us is our registered action, and dispatch to step 4 if so +static void HandleHelloAction(int command) +{ + char buf[128]; + snprintf(buf, sizeof(buf), "[extension-reaper-macos] HelloAction called: command=%d helloActionId=%d\n", command, helloActionId); + ShowConsoleMsg(buf); + + isOurAction = (command == helloActionId); + + if (isOurAction == true) + { + RunHelloAction(); + handled = true; + return; + } + + ShowConsoleMsg("[extension-reaper-macos] ignored — not ours\n"); + handled = false; +} +//step 4 perform action +static void RunHelloAction() +{ + ShowConsoleMsg("[extension-reaper-macos] Hello action triggered\n"); +} + + diff --git a/extension-reaper-macos/src/hello/hello.h b/extension-reaper-macos/src/hello/hello.h new file mode 100644 index 0000000..c3f9567 --- /dev/null +++ b/extension-reaper-macos/src/hello/hello.h @@ -0,0 +1,14 @@ +// The "Hello" test action — the first thing built in this extension, to +// prove custom_action registration + hookcommand2 callbacks work end to +// end. Kept around as a working reference, not load-bearing for the real +// feature. + +#ifndef EXTENSION_REAPER_MACOS_HELLO_H +#define EXTENSION_REAPER_MACOS_HELLO_H + +#include "reaper_plugin.h" + +// Call once from the entrypoint, after REAPERAPI_LoadAPI has succeeded. +void RegisterHello(reaper_plugin_info_t *rec); + +#endif diff --git a/extension-reaper-macos/src/main.cpp b/extension-reaper-macos/src/main.cpp index 3186f99..05e9dea 100644 --- a/extension-reaper-macos/src/main.cpp +++ b/extension-reaper-macos/src/main.cpp @@ -1,27 +1,21 @@ -// extension-reaper-macos — bare-minimum REAPER extension. +// extension-reaper-macos — REAPER extension entry point. // -// Loads, registers one test action ("extension-reaper-macos: Hello"), and -// prints a confirmation to the REAPER console. +// This file's only job: establish REAPER_PLUGIN_ENTRYPOINT (the one +// function name REAPER looks for), do the required load/unload/version +// checks, then delegate everything else to its own module — hello/, +// tracking/, socket/. // // Top-level story: -// - We tell the compiler which REAPER functions we need a box for. +// - We tell the compiler which REAPER functions we need a box for. This +// file has to cover everything used anywhere in the project, since it's +// the one with REAPERAPI_IMPLEMENT — the one that owns the real storage. // - REAPER loads our .dylib and calls our one required function, once. // - We check we're actually loading, not unloading, and the version matches. // - We fill our function box(es) with their real address. -// - We describe a new action and hand it to REAPER — it creates it, gives -// us back a number. -// - We subscribe our own function to REAPER's "every action, any trigger" -// stream. +// - We delegate to each module's own Register___(rec) function. // - We print "loaded successfully" — proof of everything up to that point, -// but NOT proof the action/subscription actually works. -// - Setup's done. We now just sit in memory, doing nothing. -// - Later, any action anywhere in REAPER (click, key, MIDI, OSC) calls our -// subscribed function. -// - It checks if the action was ours. If yes, react. If no, ignore. +// but NOT proof any individual module actually works. -// We tell the compiler which REAPER functions we need a box for. This file -// owns the real storage (REAPERAPI_IMPLEMENT), so this list has to cover -// everything used anywhere in the project, including tracking.cpp. #define REAPERAPI_MINIMAL #define REAPERAPI_WANT_ShowConsoleMsg #define REAPERAPI_WANT_CountAutomationItems @@ -34,79 +28,76 @@ #include "reaper_plugin.h" #include "reaper_plugin_functions.h" -#include "tracking.h" -#include "socket.h" +#include "hello/hello.h" +#include "tracking/tracking.h" +#include "socket/socket.h" -#include +// Return values REAPER expects back from us. +const int kUnloadOrIncompatible = 0; +const int kLoadedSuccessfully = 1; -static int action1_id = 0; -static const char *kAction1IdStr = "EXTENSION_REAPER_MACOS_HELLO"; -static const char *kAction1Name = "Hello Action List Display Name"; - -// Testing hookcommand2 again (REAPER's docs specifically recommend it for -// custom_action-registered actions), this time with debug prints kept in. -static bool Action1(KbdSectionInfo *sec, int command, int val, int val2, int relmode, HWND hwnd) -{ - // Unconditional — proves whether this callback is being reached at all, - // and for which command IDs, regardless of whether it's ours. - char buf[128]; - snprintf(buf, sizeof(buf), "[extension-reaper-macos] Action1 called: command=%d action1_id=%d\n", command, action1_id); - ShowConsoleMsg(buf); - - if (command == action1_id) - { - ShowConsoleMsg("[extension-reaper-macos] Hello action triggered\n"); - return true; - } - - ShowConsoleMsg("[extension-reaper-macos] ignored — not ours\n"); - return false; -} +static bool CheckerExtLoadingOrUnloading(reaper_plugin_info_t *rec); +static bool CheckerExtVersionMatches(reaper_plugin_info_t *rec); +static bool CheckerExtFunctionsLoaded(reaper_plugin_info_t *rec); +static void RegisterActions(reaper_plugin_info_t *rec); // REAPER loads our .dylib and calls this once. extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(REAPER_PLUGIN_HINSTANCE hInstance, reaper_plugin_info_t *rec) { - // Check we're actually loading, not unloading, and the version matches. - if (!rec) - return 0; - if (rec->caller_version != REAPER_PLUGIN_VERSION) - return 0; + if (CheckerExtLoadingOrUnloading(rec) == true) + { + return kUnloadOrIncompatible; + } - // Fill our function box(es) with their real address. - if (REAPERAPI_LoadAPI(rec->GetFunc) != 0) - return 0; + if (CheckerExtVersionMatches(rec) == false) + { + return kUnloadOrIncompatible; + } - // Describe a new action and hand it to REAPER — it creates it, gives us - // back a number, which we save. - custom_action_register_t actionDescription = { - 0, - kAction1IdStr, - kAction1Name, - NULL, - }; - action1_id = rec->Register("custom_action", &actionDescription); + if (CheckerExtFunctionsLoaded(rec) == false) + { + return kUnloadOrIncompatible; + } - // Verify registration actually succeeded — Register returns 0 on failure, - // and we've never checked that until now. - char buf[128]; - snprintf(buf, sizeof(buf), "[extension-reaper-macos] action1_id = %d\n", action1_id); - ShowConsoleMsg(buf); + RegisterActions(rec); - // Subscribe Action1 via hookcommand2 — REAPER's documented pairing for - // custom_action-registered actions specifically. - rec->Register("hookcommand2", (void *)Action1); - - // Envelope/automation-item polling now lives in tracking.cpp. - RegisterTracking(rec); - - // Basic UDP socket listener lives in socket.cpp. - RegisterSocket(rec); - - // Print "loaded successfully" — proof of everything above, but NOT proof - // the action/subscription actually works. + // Prove we got this far — but NOT proof any individual module works. ShowConsoleMsg("[extension-reaper-macos] loaded successfully\n"); - // Setup's done. Return 1 = "keep me loaded." We now just sit in memory, - // doing nothing, until Action1 gets called later. - return 1; + return kLoadedSuccessfully; +} + +// Checks whether REAPER is unloading us (rec is NULL) rather than loading +// us. Safe to call no matter what — this IS the check for whether rec is +// even safe to use for anything else. +static bool CheckerExtLoadingOrUnloading(reaper_plugin_info_t *rec) +{ + bool isUnloading = (rec == NULL); + return isUnloading; +} + +// Checks whether this REAPER's plugin format matches what we expect. Only +// call after CheckerExtLoadingOrUnloading has confirmed rec is real. +static bool CheckerExtVersionMatches(reaper_plugin_info_t *rec) +{ + bool versionMatches = (rec->caller_version == REAPER_PLUGIN_VERSION); + return versionMatches; +} + +// Fetches every REAPER function we WANT'd above, and checks whether all of +// them were found. Only call once we know rec is real. +static bool CheckerExtFunctionsLoaded(reaper_plugin_info_t *rec) +{ + int missingFunctionCount = REAPERAPI_LoadAPI(rec->GetFunc); + bool allFunctionsLoaded = (missingFunctionCount == 0); + return allFunctionsLoaded; +} + +// Delegates to each module's own Register___(rec) function — one place +// that lists every module this extension is made of. +static void RegisterActions(reaper_plugin_info_t *rec) +{ + RegisterHello(rec); + RegisterTracking(rec); + RegisterSocket(rec); } diff --git a/extension-reaper-macos/src/socket.cpp b/extension-reaper-macos/src/socket/socket.cpp similarity index 98% rename from extension-reaper-macos/src/socket.cpp rename to extension-reaper-macos/src/socket/socket.cpp index 06501d5..fc64673 100644 --- a/extension-reaper-macos/src/socket.cpp +++ b/extension-reaper-macos/src/socket/socket.cpp @@ -16,8 +16,8 @@ #include "reaper_plugin.h" #include "reaper_plugin_functions.h" -#include "socket.h" -#include "tracking.h" +#include "socket/socket.h" +#include "tracking/tracking.h" #include #include diff --git a/extension-reaper-macos/src/socket.h b/extension-reaper-macos/src/socket/socket.h similarity index 100% rename from extension-reaper-macos/src/socket.h rename to extension-reaper-macos/src/socket/socket.h diff --git a/extension-reaper-macos/src/tracking.cpp b/extension-reaper-macos/src/tracking/tracking.cpp similarity index 99% rename from extension-reaper-macos/src/tracking.cpp rename to extension-reaper-macos/src/tracking/tracking.cpp index f755e36..a83917b 100644 --- a/extension-reaper-macos/src/tracking.cpp +++ b/extension-reaper-macos/src/tracking/tracking.cpp @@ -20,7 +20,7 @@ #include "reaper_plugin.h" #include "reaper_plugin_functions.h" -#include "tracking.h" +#include "tracking/tracking.h" #include #include diff --git a/extension-reaper-macos/src/tracking.h b/extension-reaper-macos/src/tracking/tracking.h similarity index 100% rename from extension-reaper-macos/src/tracking.h rename to extension-reaper-macos/src/tracking/tracking.h