SDK v2
Docs Home DSP Plugins Crossfader

♫ Mcaster1AMP Plugin SDK

Stable C ABI plugin interface for extending Mcaster1AMP with DSP processing, custom decoders, crossfader algorithms, and visualization. API version 2 — binary-compatible across compilers and C++ runtimes.

API Version: 2
ABI: C (extern "C")
Header: plugins/plugin_api.h
License: GPL-2.0-or-later
Author: David St. John
Plugin Types
5
Required Exports
3
Param API
SDK v2
UI API
Qt Shared
1

Overview

The Mcaster1AMP Plugin SDK provides a stable C ABI for extending the player without recompiling the host. Plugins are loaded via dlopen on macOS/Linux and LoadLibrary on Windows. Because the interface is pure C, plugins can be written in any language that can export C functions and remain binary-compatible across C++ compiler versions and standard library implementations.

The constant AMP_PLUGIN_API_VERSION is bumped whenever the ABI changes in an incompatible way. The host rejects any plugin whose api_version field does not match exactly.

Plugin Types

ConstantValueDescription
AMP_PLUGIN_GENERAL0Misc utility — no specific audio callback required. Use for settings panels, metadata scrapers, broadcast integrations.
AMP_PLUGIN_DSP1In-place audio processing. Receives interleaved float PCM, modifies in-place. Realtime-safe callback required.
AMP_PLUGIN_INPUT2File decoder. Reports which extensions it handles; host delegates decode to it when a matching file is opened.
AMP_PLUGIN_VISUALIZATION3Receives a copy of the output PCM stream for spectrum/waveform display. Must not modify the buffer.
AMP_PLUGIN_CROSSFADER4Blends Deck A and Deck B into a single output according to a fader position and curve algorithm. Replaces the built-in crossfader when loaded.
ⓘ
Only one Crossfader plugin (type 4) may be active at a time. If multiple Crossfader plugins are loaded, the host activates the first and warns about the rest. DSP plugins (type 1) are chained in load order.
2

Required Exports

Every plugin, regardless of type, must export these three C functions. If any is missing, the host will refuse to load the plugin and log an error.

/* Return a pointer to a static AmpPluginInfo struct.
 * Must remain valid for the lifetime of the plugin. */
const AmpPluginInfo* amp_plugin_info(void);

/* Initialize the plugin. ctx is valid only during this call —
 * copy any needed fields (sample_rate, data_dir, log) to plugin-static storage.
 * Return 0 on success, non-zero on fatal error (host will dlclose). */
int amp_plugin_init(const AmpHostContext* ctx);

/* Shutdown: flush state, write config, free resources.
 * Called on the GUI thread before dlclose. */
void amp_plugin_shutdown(void);
⚠
Export visibility: On macOS and Linux, symbols default to hidden when compiled with -fvisibility=hidden. Mark each exported function with __attribute__((visibility("default"))) or use a .def file on Windows to ensure dlsym/GetProcAddress can find them.

Loading Sequence

The host follows this sequence when loading a plugin:

  1. dlopen(path, RTLD_LAZY | RTLD_LOCAL) — load the shared library
  2. dlsym("amp_plugin_info") — fetch and call; read api_version and type
  3. Reject if api_version != AMP_PLUGIN_API_VERSION
  4. Probe for type-specific symbols; log a warning for missing optional ones
  5. dlsym("amp_plugin_init") — call with a populated AmpHostContext
  6. If init returns non-zero: dlsym("amp_plugin_shutdown"), then dlclose
  7. Register plugin in active chain / crossfader slot
3

AmpPluginInfo Struct

Returned by amp_plugin_info(). Must be a static constant — the host may cache the pointer and dereference it at any time while the plugin is loaded.

typedef struct {
    const char* name;           /* "MC1 Crossfader"                      */
    const char* author;         /* "Mcaster1 Audio"                      */
    const char* description;    /* Human-readable one-line description   */
    const char* version;        /* Semantic version string "1.0.0"       */
    int         type;           /* AMP_PLUGIN_* constant                 */
    int         api_version;    /* Must equal AMP_PLUGIN_API_VERSION (2) */
} AmpPluginInfo;
FieldTypeNotes
nameconst char*Short display name shown in Plugin Manager. ASCII, max 64 chars recommended.
authorconst char*Author or organization name. Shown in plugin detail view.
descriptionconst char*One-sentence description of what the plugin does.
versionconst char*SemVer string (e.g., "1.0.0"). Parsed for update checking in future SDK versions.
typeintOne of the AMP_PLUGIN_* constants. Determines which additional symbols the host looks for.
api_versionintMust be exactly AMP_PLUGIN_API_VERSION (currently 2). Plugin rejected if mismatched.
4

AmpHostContext Struct

Passed to amp_plugin_init(). The pointer is only valid during the init call — copy any fields you need into plugin-static storage before returning.

typedef void (*AmpLogCallback)(int level, const char* message);
/* Log levels: 1=CRITICAL  2=ERROR  3=WARNING  4=INFO  5=DEBUG */

typedef struct {
    int            sample_rate;      /* Host audio sample rate (e.g. 44100, 48000)      */
    int            channels;         /* Host channel count (1=mono, 2=stereo)           */
    int            buffer_size;      /* Frames per dsp_process/blend call               */
    const char*    app_version;      /* Host version string, e.g. "1.0.0"              */
    const char*    plugin_data_dir;  /* Writable per-plugin config directory           */
    AmpLogCallback log;              /* Host logging callback (thread-safe)            */
} AmpHostContext;
FieldNotes
sample_rateMatches the PortAudio stream sample rate. If your plugin does internal FFT, size your buffers here.
channelsAlmost always 2 (stereo interleaved). Your DSP callback must handle channels == 1 too for future mono support.
buffer_sizeThe frames parameter passed to DSP/blend callbacks. Allocate scratch buffers sized to this during init, not during the audio callback.
app_versionUseful for gating features only available on newer host versions.
plugin_data_dirGuaranteed to exist and be writable. Path example: ~/Library/Application Support/Mcaster1AMP/plugins/dsp_mc1crossfader/. Store YAML config, presets, and caches here. Never hardcode paths.
logThread-safe. Call from any thread including the realtime audio thread (but minimize calls on the audio thread to avoid latency spikes).

Storing Context Fields

/* Plugin-static storage — copy during amp_plugin_init() */
static int          g_sample_rate = 44100;
static int          g_channels    = 2;
static std::string  g_data_dir;
static AmpLogCallback g_log       = nullptr;

int amp_plugin_init(const AmpHostContext* ctx)
{
    if (!ctx) return -1;
    g_sample_rate = ctx->sample_rate;
    g_channels    = ctx->channels;
    g_data_dir    = ctx->plugin_data_dir ? ctx->plugin_data_dir : "/tmp/myplugin";
    g_log         = ctx->log;
    return 0;  /* 0 = success */
}
5

Parameter API SDK v2

The Parameter API lets the host build a generic settings UI for any plugin without requiring a custom Qt widget. All parameter names use snake_case ASCII strings. The API is optional — plugins that provide a full custom UI via amp_plugin_create_ui() may omit it.

Exported Functions

/* Set a named parameter. Plugin may ignore unknown names silently. */
void   amp_plugin_set_param(const char* name, double value);

/* Get a named parameter value. Returns 0.0 for unknown names. */
double amp_plugin_get_param(const char* name);

/* Return the number of parameters this plugin exposes. */
int    amp_plugin_get_param_count(void);

/* Return a descriptor for parameter at index.
 * Pointer must remain valid for the lifetime of the plugin. */
const AmpParamInfo* amp_plugin_get_param_info(int param_index);

AmpParamInfo Struct

typedef struct {
    const char* name;           /* Internal key: "gain_db", "curve", etc.     */
    const char* label;          /* Display name: "Gain (dB)", "Curve Type"    */
    const char* unit;           /* "dB", "%", "Hz", "" (empty if unitless)    */
    double      min_value;
    double      max_value;
    double      default_value;
    int         is_enum;        /* 1 = integer index, show as dropdown        */
    const char* enum_names;     /* Pipe-separated: "Linear|Constant Power|..." */
} AmpParamInfo;
FieldNotes
nameThe key used in set_param/get_param. Must be stable — host may serialize it to settings files.
labelHuman-readable string for the host's auto-generated UI.
unitAppended to the value in the UI. Use "" for dimensionless values.
min_value / max_valueInclusive range. Host clamps sliders to these bounds.
default_valueUsed when the host generates a "Reset to Default" action.
is_enumIf 1, the host renders a dropdown. value is always a non-negative integer index into enum_names.
enum_namesPipe (|) separated list of option labels. nullptr when is_enum == 0. Example: "Linear|Constant Power|S-Curve".

Example: Defining Parameters

static const AmpParamInfo kParams[] = {
    { "gain_db",  "Gain",       "dB",   -24.0, 24.0,  0.0, 0, nullptr },
    { "enabled",  "Enabled",    "",      0.0,   1.0,   1.0, 1, "Off|On" },
    { "freq_hz",  "Frequency",  "Hz",   20.0,  20000.0, 1000.0, 0, nullptr },
};

int amp_plugin_get_param_count() { return 3; }

const AmpParamInfo* amp_plugin_get_param_info(int i) {
    if (i < 0 || i >= 3) return nullptr;
    return &kParams[i];
}
6

Custom Qt UI API SDK v2

Plugins may provide a fully custom QWidget-based settings panel shown in the Plugin Manager's "Configure" dialog. The host calls amp_plugin_create_ui(), embeds the returned widget, and calls amp_plugin_destroy_ui() when the dialog closes or the plugin is unloaded.

/* Create and return a QWidget* for this plugin's settings panel.
 * parent_qwidget: host passes QWidget* cast to void*.
 * Return new QWidget* cast to void*, or NULL if no UI available.
 * Host takes ownership and will call destroy_ui before unload. */
void* amp_plugin_create_ui(void* parent_qwidget);

/* Destroy the widget returned by create_ui. */
void  amp_plugin_destroy_ui(void* ui_widget);
⚠
Qt shared runtime requirement: These functions are only safe when the plugin and host share the same Qt dylib/so at runtime. If your plugin bundles its own Qt, do not export these symbols. Violating this causes crashes and undefined behavior from dual QWidget vtables. The MC1 Crossfader plugin ships as part of the Mcaster1AMP bundle and is therefore safe to use this API.

Thread Safety

Both create_ui and destroy_ui are called on the GUI thread. The widget must not touch audio-thread state without an atomics barrier or QMetaObject::invokeMethod(..., Qt::QueuedConnection).

/* Correct pattern: update audio-thread state from widget signal */
QObject::connect(g_widget, &MyWidget::gainChanged,
    [](double db) {
        g_gain_linear.store(static_cast<float>(std::pow(10.0, db / 20.0)));
    });
7

Plugin Types — Additional Symbols

Type 1 — DSP Plugin (AMP_PLUGIN_DSP)

SymbolRequired?Signature
amp_plugin_dsp_processRequiredvoid(float* samples, int frames, int channels)
amp_plugin_set_paramOptionalvoid(const char* name, double value)
amp_plugin_get_paramOptionaldouble(const char* name)
amp_plugin_get_param_countOptionalint(void)
amp_plugin_get_param_infoOptionalconst AmpParamInfo*(int index)
amp_plugin_create_uiOptionalvoid*(void* parent)
amp_plugin_destroy_uiOptionalvoid(void* widget)

DSP callbacks are called on the realtime audio thread. REALTIME-SAFE — no heap allocation, no mutex lock, no file I/O, no blocking system call.

Type 2 — Input Plugin (AMP_PLUGIN_INPUT)

SymbolRequired?Signature
amp_plugin_input_can_decodeRequiredint(const char* ext) — return 1 if this plugin handles the extension
amp_plugin_input_openRequiredvoid*(const char* path, int* out_sample_rate, int* out_channels)
amp_plugin_input_readRequiredint(void* handle, float* buffer, int max_frames) — return frames decoded, 0=EOF, negative=error
amp_plugin_input_closeRequiredvoid(void* handle)

Type 3 — Visualization Plugin (AMP_PLUGIN_VISUALIZATION)

SymbolRequired?Signature
amp_plugin_vis_pushRequiredvoid(const float* samples, int frames, int channels) — buffer is read-only, do not modify

Called on the realtime audio thread with a copy of the post-DSP output PCM. Use a non-blocking ring buffer to hand data off to the GUI thread for rendering.

Type 4 — Crossfader Plugin (AMP_PLUGIN_CROSSFADER)

SymbolRequired?Signature
amp_plugin_crossfader_blendRequiredvoid(const float* deck_a, const float* deck_b, float* out, int frames, int channels, float position)
amp_plugin_set_paramOptionalsame as DSP — for curve type, smoothing, etc.
amp_plugin_create_uiOptionalCustom fader widget

position: 0.0 = full Deck A, 0.5 = equal blend, 1.0 = full Deck B. Pass -1.0f to use the last value set via set_param("position", ...).

8

C ABI Symbol Name Constants

Use these #define constants with dlsym()/GetProcAddress() to avoid typos when probing for optional symbols. They are defined in plugins/plugin_api.h.

/* Required — all plugin types */
#define AMP_SYM_INFO             "amp_plugin_info"
#define AMP_SYM_INIT             "amp_plugin_init"
#define AMP_SYM_SHUTDOWN         "amp_plugin_shutdown"

/* DSP (type 1) */
#define AMP_SYM_DSP_PROCESS      "amp_plugin_dsp_process"

/* Parameter API (types 1 and 4) */
#define AMP_SYM_SET_PARAM        "amp_plugin_set_param"
#define AMP_SYM_GET_PARAM        "amp_plugin_get_param"
#define AMP_SYM_GET_PARAM_COUNT  "amp_plugin_get_param_count"
#define AMP_SYM_GET_PARAM_INFO   "amp_plugin_get_param_info"

/* Custom Qt UI (shared Qt runtime only) */
#define AMP_SYM_CREATE_UI        "amp_plugin_create_ui"
#define AMP_SYM_DESTROY_UI       "amp_plugin_destroy_ui"

/* Crossfader (type 4) */
#define AMP_SYM_CROSSFADER_BLEND "amp_plugin_crossfader_blend"

/* Input decoder (type 2) */
#define AMP_SYM_INPUT_CAN        "amp_plugin_input_can_decode"
#define AMP_SYM_INPUT_OPEN       "amp_plugin_input_open"
#define AMP_SYM_INPUT_READ       "amp_plugin_input_read"
#define AMP_SYM_INPUT_CLOSE      "amp_plugin_input_close"

/* Visualization (type 3) */
#define AMP_SYM_VIS_PUSH         "amp_plugin_vis_push"

Usage in Plugin Host

void* lib = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
auto info_fn = (AmpPluginInfoFn)dlsym(lib, AMP_SYM_INFO);
auto init_fn = (AmpPluginInitFn)dlsym(lib, AMP_SYM_INIT);
/* optional — check before calling */
auto set_p   = (AmpPluginSetParamFn)dlsym(lib, AMP_SYM_SET_PARAM);
9

Building a Plugin

Plugins are shared libraries. Include plugins/plugin_api.h (no other Mcaster1AMP headers are needed for a basic plugin). Link against any libraries your plugin uses. Do not link against libmcaster1amp — all communication goes through the C ABI at runtime.

macOS (dylib)

# Makefile excerpt — macOS ARM64 / x86_64
PLUGIN_NAME = dsp_myplugin
SRCS        = dsp_myplugin.cpp
SDK_INCLUDE = /path/to/Mcaster1AMP/src/Mcaster1AMP/plugins

QT6_CFLAGS  := $(shell pkg-config --cflags Qt6Widgets)
QT6_LIBS    := $(shell pkg-config --libs   Qt6Widgets)
YAML_LIBS   := $(shell pkg-config --libs   yaml-0.1)

CXXFLAGS = -std=c++17 -O2 -fvisibility=hidden \
           -I$(SDK_INCLUDE) $(QT6_CFLAGS)

$(PLUGIN_NAME).dylib: $(SRCS)
	$(CXX) $(CXXFLAGS) -dynamiclib -install_name @rpath/$@.dylib \
	    -o $@ $^ $(QT6_LIBS) $(YAML_LIBS)

Linux (shared object)

$(PLUGIN_NAME).so: $(SRCS)
	$(CXX) $(CXXFLAGS) -shared -fPIC -o $@ $^ $(QT6_LIBS) $(YAML_LIBS)

Windows (DLL, MinGW)

$(PLUGIN_NAME).dll: $(SRCS)
	$(CXX) $(CXXFLAGS) -shared -o $@ $^ $(QT6_LIBS) $(YAML_LIBS) \
	    -Wl,--out-implib,$(PLUGIN_NAME).lib

Installation Path

Drop the built .dylib / .so / .dll into one of the following directories — the Plugin Manager scans all of them at startup:

✓
Quick install on macOS: cp dsp_myplugin.dylib ~/Library/Application\ Support/Mcaster1AMP/plugins/ then relaunch Mcaster1AMP and open Plugins > Plugin Manager.
10

AI Agent Prompts

The following prompts are designed for use with Claude Code or any AI coding agent. Each prompt gives the agent enough context to generate a working plugin without further clarification. Paste them directly into your agent session with the Mcaster1AMP repo open.

Prompt 1 — New DSP Gain Plugin
Using the Mcaster1AMP Plugin SDK defined in src/Mcaster1AMP/plugins/plugin_api.h, write a complete DSP plugin (AMP_PLUGIN_DSP, type 1) called "dsp_mc1gain" that implements a stereo gain stage with soft-clip limiting. The plugin should export the full C ABI including amp_plugin_info, amp_plugin_init, amp_plugin_shutdown, amp_plugin_dsp_process, and the complete Parameter API (set_param, get_param, get_param_count, get_param_info). Parameters: "gain_db" (-24 to +24 dB, default 0), "clip_threshold" (0.0 to 1.0, default 0.9), "enabled" (enum Off|On, default On). The dsp_process callback must be realtime-safe (no alloc, no lock). Store gain_db as a std::atomic in linear form. Save/load config to plugin_data_dir/dsp_mc1gain.yaml using libyaml. Place the plugin in src/Mcaster1AMP/plugins/dsp_mc1gain/ and add it to the Makefile.am.
Prompt 2 — Crossfader Plugin with Custom Curve
Using src/Mcaster1AMP/plugins/plugin_api.h and looking at the existing MC1 Crossfader in src/Mcaster1AMP/plugins/dsp_mc1crossfader/dsp_mc1crossfader.cpp as a reference, create a new crossfader plugin (AMP_PLUGIN_CROSSFADER, type 4) called "dsp_mc1hamster" that implements only hamster/reverse mode: it wraps position (1.0 - pos) before delegating to a Constant Power blend. The plugin should be minimal — no custom Qt UI, just the C ABI exports plus Parameter API for a single "sensitivity" parameter (0.5 to 2.0) that scales how aggressively the position reversal is applied. Write the full .cpp file with all required exports, proper atomic state, and a brief YAML config for the sensitivity value.
Prompt 3 — Input Decoder Plugin (AIFF)
Using src/Mcaster1AMP/plugins/plugin_api.h, write an Input plugin (AMP_PLUGIN_INPUT, type 2) called "input_mc1aiff" that decodes AIFF and AIFC files to interleaved float PCM using libsndfile. Export amp_plugin_info, amp_plugin_init, amp_plugin_shutdown, amp_plugin_input_can_decode (return 1 for "aif", "aiff", "aifc" extensions), amp_plugin_input_open, amp_plugin_input_read (convert to float [-1,1] and return decoded frame count), and amp_plugin_input_close. Handle both 16-bit and 32-bit AIFF files. The open function should fill out_sample_rate and out_channels. Use SF_INFO from sndfile.h. Write the complete plugin as a single .cpp file ready to build with -shared -fPIC on Linux or -dynamiclib on macOS, linking against -lsndfile.
Prompt 4 — Visualization Plugin (Peak Meter Logger)
Using src/Mcaster1AMP/plugins/plugin_api.h, write a Visualization plugin (AMP_PLUGIN_VISUALIZATION, type 3) called "vis_mc1peaklog" that logs peak dBFS values to a rolling CSV file at plugin_data_dir/peaks.csv once per second. The amp_plugin_vis_push callback runs on the realtime audio thread — use an SPSC ring buffer (lockless) to pass peak data to a background std::thread that does the file writes. The ring buffer should hold 128 float peak values. The background thread wakes on a std::condition_variable. Include a "log_interval_ms" parameter (100 to 5000, default 1000). Write the complete plugin implementation in a single .cpp file with all required and type-specific exports.
Prompt 5 — General Plugin with Qt Settings Panel
Using src/Mcaster1AMP/plugins/plugin_api.h, write a General plugin (AMP_PLUGIN_GENERAL, type 0) called "gen_mc1nowplaying" that exports the now-playing track metadata (title, artist, album) to a plain UTF-8 text file on every track change. The plugin should include a custom Qt UI (amp_plugin_create_ui / amp_plugin_destroy_ui) with a QWidget containing a QLineEdit for the output file path, a QCheckBox "Append timestamp", and a QPushButton "Choose...". The widget uses QFileDialog for path selection. Store state in plugin_data_dir/nowplaying.yaml. The host will call set_param("title", ...) and set_param("artist", ...) from the main window when the track changes — use QMetaObject::invokeMethod to update the UI label safely. Write the complete plugin with all C ABI exports, the Qt widget subclass, and the YAML config save/load.