Skip to content

modular_effects_system

github-actions[bot] edited this page Nov 2, 2026 · 1 revision

Polyend Tracker Modular Effects System & Developer SDK (PTFX)

This guide covers everything needed to use, author, compile, and understand custom DSP audio effects and sample generators (.ptfx plugins) for Original Polyend Tracker (MK66), Polyend Tracker+ (IMX), and Polyend Tracker Mini (IMX).


Table of Contents

  1. Overview & Installing .ptfx Plugins
  2. Bundled Reference Plugins (editor-fx-sdk/effects/)
  3. Quick Start & Anatomy of a .ptfx Plugin
  4. Metadata Directives & Capability Flags (PtfxFlags)
  5. Parameter Descriptors & UI Controls (0–12 Parameters)
  6. Host API Reference (PtfxHostApi)
  7. Plugin Lifecycle & Audio Processing Patterns
  8. The .ptfx Binary Specification (PtfxHeader)
  9. Firmware Architecture, Memory Layout & Cache Coherency

1. Overview & Installing .ptfx Plugins

The Modular Effects System (PTFX) introduces dynamically loadable DSP and synthesis plugins for the Polyend Tracker family across all three hardware platforms:

  • Original Polyend Tracker - NXP Kinetis MK66 (ARM Cortex-M4F @ 180 MHz, mono sample engine)
  • Polyend Tracker+ - NXP i.MX RT1051/RT1064 (ARM Cortex-M7F @ 600 MHz, stereo sample engine)
  • Polyend Tracker Mini - NXP i.MX RT1051/RT1064 (ARM Cortex-M7F @ 600 MHz, stereo sample engine)

External .ptfx position-independent binary modules placed on the SD card under /Effects/*.ptfx are automatically discovered when entering the Sample Editor, appended to the built-in effect list (up to 128 custom plugins alongside the 17 built-in effects), loaded on demand into executable RAM, auditioned via Preview, and applied to existing samples or used to generate new samples/wavetables onto empty or active instrument slots.

Installing Plugins on Your SD Card

Place .ptfx files in the /Effects/ directory at the root of your microSD card:

SD Card Root/
└── Effects/
    ├── add_silence.ptfx
    ├── analog_kick_gen.ptfx
    ├── chiptune_gen.ptfx
    ├── multimode_filter.ptfx
    ├── stereo_delay.ptfx
    └── stereo_to_mono.ptfx

2. Bundled Reference Plugins (editor-fx-sdk/effects/)

All reference plugins in editor-fx-sdk/effects/ can be compiled locally via make ptfx (outputting to editor-fx-sdk/build/Effects/*.ptfx) or downloaded ready-to-use from the polyend-tracker-ce-effects GitHub Actions build artifact:

Plugin Source Display Name Capability Flags Scratch RAM Parameters & Description
add_silence.c Add Silence EFFECT | STEREO 0 B Where (List: Beginning|End), Length (10–5000 ms, default 2500 ms). Prepends or appends silence via host->set_extra_samples().
analog_kick_gen.c Analog Kick Gen GENERATOR 0 B TopFreq (60–250 Hz), SubFreq (30–80 Hz), Decay (100–900 ms), Click (0.0–1.0). Synthesizes analog-style kick drums onto any slot.
chiptune_gen.c Chiptune Voice Gen GENERATOR 0 B Voice (List: Kick|Snare|HiHat|Pulse12|Pulse25|Pulse50|Triangle|NESNoise), Note (24–84 st), Arp (List), Speed (10–60 Hz), Decay (30–1500 ms), Sweep (-24..+24 st), Crush (List), Length (List: DecayLen|SingleCyc).
multimode_filter.c Multi-Mode Filter EFFECT | STEREO 0 B Mode (List: LPF 24dB|LPF 12dB|BPF 12dB|HPF 12dB|Notch|Peak), Cutoff (40–16000 Hz), Reso (0–95 %), Drive (List: Clean|SoftSat|WarmTube|HardClip|Foldback). Zero-delay feedback Cytomic SVF filter.
stereo_delay.c Stereo Delay EFFECT | STEREO 176,404 B Time (10–1000 ms), Fdbk (0–95 %), Mix (0–100 %), Tail (List: Ring Out|Cut Off). Stereo delay line with optional tail ring-out.
stereo_to_mono.c Stereo to Mono STEREO | CONVERT_TO_MONO 0 B Source (List: Merge L+R|Left Only|Right Only|Side L-R), Gain (-12.0 to +6.0 dB). Converts a stereo sample on Tracker+ / Mini into a true mono sample, reclaiming 50% sample RAM.

3. Quick Start & Anatomy of a .ptfx Plugin

A .ptfx plugin is a single freestanding C/C++ source file compiled into a position-independent ARM Thumb-2 binary with a 472-byte metadata header (PtfxHeader).

SDK Contents

Building Plugins

Build all plugins in editor-fx-sdk/effects/ from the repository root:

make ptfx

Or compile an individual plugin with ptfx_builder.py:

python3 editor-fx-sdk/ptfx_builder.py editor-fx-sdk/effects/multimode_filter.c \
    -o editor-fx-sdk/build/Effects/multimode_filter.ptfx \
    --toolchain agt/10.3/bin/arm-none-eabi-

ptfx_builder.py CLI Options

usage: ptfx_builder.py [-h] -o OUTPUT [--name NAME] [--author AUTHOR]
                       [--flags FLAGS] [--scratch SCRATCH]
                       [--toolchain TOOLCHAIN]
                       source

positional arguments:
  source                Input C source file (e.g. my_effect.c)

optional arguments:
  -o, --output OUTPUT   Output .ptfx file path (e.g. editor-fx-sdk/build/Effects/my_effect.ptfx)
  --name NAME           Override PTFX_NAME (max 31 chars)
  --flags FLAGS         Override PTFX_FLAGS (comma-separated)
  --scratch SCRATCH     Override PTFX_SCRATCH size in bytes
  --toolchain TOOLCHAIN Toolchain prefix (e.g. agt/10.3/bin/arm-none-eabi-)

Minimal Effect Template

Every .ptfx plugin source file consists of four parts:

  1. Top-of-file metadata comments (// PTFX_NAME:, // PTFX_AUTHOR:, // PTFX_FLAGS:, // PTFX_SCRATCH:)
  2. Parameter definitions (const PluginParam PLUGIN_PARAMS[...])
  3. Initialization & parameter handlers (plugin_init, plugin_set_param)
  4. Block audio processor (plugin_process_block)
// PTFX_NAME: My Custom Effect
// PTFX_AUTHOR: YourName
// PTFX_FLAGS: EFFECT, STEREO
// PTFX_SCRATCH: 0

#include "tracker_plugin.h"

const PluginParam PLUGIN_PARAMS[2] = {
    PTFX_PARAM_LIST("Mode", 0, "Clean|Warm|Clip"),
    PTFX_PARAM_FLOAT("Drive", 1.0f, 10.0f, 2.0f, 0.1f, "x")
};

static const PtfxHostApi* s_host = 0;
static uint8_t s_mode = 0;
static float   s_drive = 2.0f;

void plugin_init(void* scratch_mem, uint32_t scratch_size, const PtfxHostApi* host) {
    (void)scratch_mem;
    (void)scratch_size;
    s_host = host;
    s_mode = 0;
    s_drive = 2.0f;
}

void plugin_set_param(uint8_t index, float value) {
    if (index == 0)      s_mode  = (uint8_t)(value + 0.5f);
    else if (index == 1) s_drive = value;
}

int32_t plugin_process_block(const int16_t** in, int16_t** out, uint32_t samples, uint8_t channels) {
    if (!in || !out || !in[0] || !out[0]) return 0;

    for (uint8_t ch = 0; ch < channels; ch++) {
        if (!in[ch] || !out[ch]) continue;
        for (uint32_t i = 0; i < samples; i++) {
            float x = (float)in[ch][i] / 32768.0f;
            x *= s_drive;
            if (s_mode == 1 && s_host && s_host->tanhf) {
                x = s_host->tanhf(x);
            } else if (s_mode == 2) {
                if (x > 1.0f) x = 1.0f;
                if (x < -1.0f) x = -1.0f;
            }
            out[ch][i] = (int16_t)SATURATE_16BIT((int32_t)(x * 32767.0f));
        }
    }
    return (int32_t)samples;
}

4. Metadata Directives & Capability Flags (PtfxFlags)

editor-fx-sdk/ptfx_builder.py parses the header comments at the top of your .c file:

Directive Max Length / Limit Description
// PTFX_NAME: 31 ASCII chars Display name shown in the Sample Editor effect list and title bar.
// PTFX_FLAGS: Comma/pipe list Combination of EFFECT, GENERATOR, STEREO, WAVETABLE, CONVERT_TO_MONO, IMX_ONLY.
// PTFX_SCRATCH: Up to 640 KB (655,360 B) Working scratch RAM size in bytes requested by the plugin (pre-zeroed before plugin_init).
// PTFX_AUTHOR: 15 ASCII chars Optional author handle or credit stored in the header.

Capability Flags (PtfxFlags)

Flag Constant Behavior
EFFECT PTFX_FLAG_EFFECT (0x0000) Standard audio effect transforming in to out across the selected sample range (plus optional prepended/appended samples via set_extra_samples). Disabled on empty sample slots.
GENERATOR PTFX_FLAG_GENERATOR (0x0001) Audio generator/synthesizer. Can run on empty instrument slots (in is NULL), produces up to 88,200 samples (2.0 s, or stops early if plugin_process_block returns <= 0), activates inactive instruments upon apply, and names them "Gen XX".
STEREO PTFX_FLAG_STEREO (0x0002) Processes independent Left/Right channels when channels == 2 on Tracker+ / Tracker Mini. On Original Tracker (MK66), channels is always 1.
WAVETABLE PTFX_FLAG_WAVETABLE (0x0004) Automatically sets instrument.playMode = playModeWavetable when committed.
CONVERT_TO_MONO PTFX_FLAG_CONVERT_TO_MONO (0x0008) Signals cSampleEditor::isCurrentSampleStereo() on IMX platforms to export only the Left channel (memoryAddressR = memoryAddressL) when saving back to project sample memory, reclaiming 50% sample RAM.
IMX_ONLY PTFX_FLAG_IMX_ONLY (0x0010) Requires Cortex-M7 speeds or > 32 KB code/BSS size; blocked from executing on Original Tracker (MK66).

5. Parameter Descriptors & UI Controls (0–12 Parameters)

A plugin can define between 0 and 12 parameters in const PluginParam PLUGIN_PARAMS[]:

  • 0 parameters: Selecting the plugin in the Sample Editor immediately shows "Apply" on the main screen (like built-in Reverse or Fade In).
  • 1 to 4 parameters: Displayed on a single parameter screen across columns 1–4.
  • 5 to 12 parameters: A "More" button with a page dot indicator (1/2, 2/2 or 1/3, 2/3, 3/3) automatically appears on Screen Button 5 to cycle through Page 1 (parameters 0–3), Page 2 (parameters 4–7), and Page 3 (parameters 8–11).

Parameter Macros

Defined in editor-fx-sdk/tracker_plugin.h:

1. Continuous Float Bar (PTFX_PARAM_FLOAT)

PTFX_PARAM_FLOAT(label, min_val, max_val, default_val, step, unit)
  • Renders a vertical progress bar (cBar) with a 2-decimal floating-point readout (e.g., -3.50 dB).
  • label: Up to 7 characters (e.g., "Cutoff", "Gain").
  • unit: Up to 5 characters (e.g., " dB", " Hz", " s").

2. Integer Bar (PTFX_PARAM_INT)

PTFX_PARAM_INT(label, min_val, max_val, default_val, step, unit)
  • Renders a vertical progress bar (cBar) with an integer readout (e.g., 2500 ms, 40 %).
  • label: Up to 7 characters.
  • unit: Up to 5 characters.

3. Scrollable String List (PTFX_PARAM_LIST)

PTFX_PARAM_LIST(label, default_index, "Option 0|Option 1|Option 2|...")
  • Renders a native Tracker scrollable string list (cList) in that parameter's column and displays the selected option text in the bottom parameter box.
  • Users can scroll options with the encoder, step with Up/Down arrow keys, press Delete to reset to default_index, or press the screen button below an already-selected list column to cycle through options with wrap-around.
  • label: Up to 7 characters.
  • default_index: Zero-based initial item index (0 to N - 1).
  • options: Pipe-separated (|) string options (up to 16 options per list parameter, max 10 characters per option label to fit cleanly inside the 100 px column width).
  • Inside plugin_set_param(uint8_t index, float value), value is the zero-based option index (0.0f, 1.0f, 2.0f, ...). Convert it to an integer index with (uint8_t)(value + 0.5f).

6. Host API Reference (PtfxHostApi)

Because .ptfx binaries are compiled freestanding (-nostdlib), plugins do not link libm or libc. Instead, plugin_init() receives a const PtfxHostApi* host pointer (core/effector/ptfx_loader.cpp) providing hardware-accelerated single-precision FPU math, memory utilities, and host callbacks:

typedef struct {
    uint32_t sample_rate; // Always 44100 Hz

    // Hardware FPU Single-Precision Math
    float (*sinf)(float);
    float (*cosf)(float);
    float (*tanf)(float);
    float (*tanhf)(float);
    float (*powf)(float, float);
    float (*expf)(float);
    float (*sqrtf)(float);
    float (*logf)(float);
    float (*log10f)(float);
    float (*fminf)(float, float);
    float (*fmaxf)(float, float);
    float (*fabsf)(float);

    // Memory & Utility Primitives
    void* (*memcpy)(void*, const void*, size_t);
    void* (*memset)(void*, int, size_t);
    int   (*rand)(void);

    // Sample Length Extension (Prepend / Append Samples to Selection)
    void  (*set_extra_samples)(uint32_t prepend_samples, uint32_t append_samples);
} PtfxHostApi;

Helper Macro: 16-Bit Signed Saturation (SATURATE_16BIT)

editor-fx-sdk/tracker_plugin.h provides SATURATE_16BIT(x), which compiles to a single-cycle hardware SSAT instruction on ARM Cortex-M4/M7:

int32_t sample32 = (int32_t)(float_sample * 32767.0f);
out[ch][i] = (int16_t)SATURATE_16BIT(sample32);

7. Plugin Lifecycle & Audio Processing Patterns

7.1 Lifecycle Order

  1. Plugin Selection: When the user highlights your plugin in the Sample Editor list, the firmware loads the .ptfx binary, zeroes scratch_mem, calls plugin_init(), and calls plugin_set_param() for each parameter with its default_val.
  2. Parameter Adjustment: Turning the encoder or changing a list item calls plugin_set_param(index, new_value).
  3. Preview / Apply (startProcess): When the user presses Preview or Apply, the firmware zeroes scratch_mem, calls plugin_init() to reset DSP state, and immediately replays plugin_set_param(i, current_value) for all parameters before calling plugin_process_block() in chunks of up to 8192 samples.

Tip

Because plugin_init() is called at the start of every render pass followed by plugin_set_param() for every parameter, reset your DSP state in plugin_init() and compute derived coefficients (or call host->set_extra_samples()) inside plugin_set_param().

7.2 Pattern A: Standard In-Place Audio Effect (PTFX_FLAG_EFFECT)

For effects that keep the same sample length (filters, distortion, bitcrushers, EQ):

  • in[ch] points to the input selection (int16_t signed 16-bit PCM at 44100 Hz).
  • out[ch] points to the output buffer (int16_t signed 16-bit PCM).
  • Loop ch from 0 to channels - 1 and i from 0 to samples - 1, write to out[ch][i], and return (int32_t)samples.
  • See editor-fx-sdk/effects/multimode_filter.c.

7.3 Pattern B: Extending Sample Length (host->set_extra_samples)

Effects that insert audio before the selection (add_silence.c) or let a tail ring out after the selection (stereo_delay.c) can call:

host->set_extra_samples(prepend_samples, append_samples);

inside plugin_init() and plugin_set_param().

When prepend_samples > 0 or append_samples > 0:

  1. Before rendering begins, the firmware checks that original_length + prepend_samples + append_samples fits within the Sample Editor buffer (EDIT_BUFF_SIZE / 2) and project sample RAM (mtProject.max_memory).
  2. During rendering, mtEffectModular::processBlock() automatically splits plugin_process_block() calls at region boundaries:
    • Prepend region ([0, prepend_samples)): in[ch] is NULL.
    • Selection body ([prepend_samples, prepend_samples + input_length)): in[ch] points to the source sample data in confirmed[ch].selection.addr.
    • Append / tail region ([prepend_samples + input_length, total_length)): in[ch] is NULL.
  3. Any unselected audio before the selection's Start point and after its End point is automatically preserved around the extended selection.
for (uint8_t ch = 0; ch < channels; ch++) {
    const int16_t* src = (in != 0) ? in[ch] : 0;
    for (uint32_t i = 0; i < samples; i++) {
        int32_t dry = src ? (int32_t)src[i] : 0; // 0 during prepend/append regions
        // Process dry + wet delay/reverb tank...
    }
}

7.4 Pattern C: Sample & Wavetable Generators (PTFX_FLAG_GENERATOR)

Generators synthesize audio from scratch (in is NULL) and can run even when an instrument slot is completely empty:

  • Maximum generation window is 88,200 samples (2.0 seconds at 44.1 kHz).
  • To generate a shorter sample (such as a 300 ms drum hit or a single-cycle oscillator waveform of 689 samples), stop generating once your target sample count is reached and return 0 on the next call (or return the exact remaining sample count < samples and 0 thereafter).
  • When plugin_process_block() finishes early, the firmware automatically trims processed[ch].selection.length and processed[ch].area.length to the exact number of samples generated.
  • Write identical samples to all channels (for (uint8_t c = 0; c < channels; c++) out[c][i] = pcm;) so stereo instruments on Tracker+ / Tracker Mini receive both Left and Right channels.
  • See editor-fx-sdk/effects/analog_kick_gen.c and editor-fx-sdk/effects/chiptune_gen.c.

7.5 Pattern D: Using Working Scratch RAM (PTFX_SCRATCH)

Because plugin .bss shares the executable code buffer (32 KB limit on Original Tracker, 128 KB on Tracker+ / Tracker Mini), never declare large arrays as static global variables. Instead:

  1. Request scratch memory via // PTFX_SCRATCH: <bytes>.
  2. Cast scratch_mem to your state struct inside plugin_init():
// PTFX_SCRATCH: 176404
typedef struct {
    int16_t  buffer[2][44100];
    uint32_t write_idx;
} DelayState;

static DelayState* s_state = 0;

void plugin_init(void* scratch_mem, uint32_t scratch_size, const PtfxHostApi* host) {
    if (scratch_mem && scratch_size >= sizeof(DelayState)) {
        s_state = (DelayState*)scratch_mem;
    } else {
        s_state = 0;
    }
}

8. The .ptfx Binary Specification (PtfxHeader)

Defined in core/effector/tracker_plugin.h and editor-fx-sdk/tracker_plugin.h, each .ptfx file consists of a fixed 472-byte header (PtfxHeader) followed immediately at byte offset 472 (0x1D8) by a flat position-independent ARMv7E-M Thumb-2 binary payload (.text, .rodata, .data), with .bss zero-initialized in RAM immediately following code_size.

8.1 Binary Layout (PtfxHeader - 472 Bytes)

+-----------------------------------------------------------------------+
| PtfxHeader (472 bytes total, 4-byte aligned)                          |
|   +0x00: uint32_t magic                = 0x58465450 ("PTFX" LE)       |
|   +0x04: uint16_t format_version       = 1                            |
|   +0x06: uint16_t flags                = PtfxFlags bitfield           |
|   +0x08: char     display_name[32]     = UI effect list name          |
|   +0x28: char     author[16]           = Author / creator handle      |
|   +0x38: uint8_t  num_params           = Active parameter count (0-12)|
|   +0x39: uint8_t  reserved[3]          = Alignment padding            |
|   +0x3C: uint32_t scratch_size         = Working RAM requested (bytes)|
|   +0x40: uint32_t code_offset          = 472 (0x1D8)                  |
|   +0x44: uint32_t code_size            = Payload size in file (bytes) |
|   +0x48: uint32_t bss_size             = Zero-init .bss size (bytes)  |
|   +0x4C: uint32_t init_offset          = Offset of plugin_init()      |
|   +0x50: uint32_t set_param_offset     = Offset of plugin_set_param() |
|   +0x54: uint32_t process_block_offset = Offset of process_block()    |
|   +0x58: PtfxParamDef params[12]       = 12 * 32B param descriptors   |
+-----------------------------------------------------------------------+
| Position-Independent Thumb-2 Code Payload (starts at +0x1D8 / 472)    |
|   - .text (functions)                                                 |
|   - .rodata (constants, lookup tables, .ptfx_strings)                 |
|   - .data (initialized static variables)                              |
+-----------------------------------------------------------------------+
| Zero-Initialized .bss Region (allocated in RAM after code_size)       |
+-----------------------------------------------------------------------+

8.2 Parameter Descriptor (PtfxParamDef - 32 Bytes)

All float fields in PtfxParamDef are strictly 4-byte aligned so Cortex-M4/M7 hardware FPU instructions (VLDR/VSTR) never fault on unaligned accesses:

Offset Type Field Description
+0x00 char[8] label Parameter name (max 7 chars + \0, e.g. "Time", "Mode")
+0x08 float min_val Minimum knob value (0.0f for PARAM_LIST)
+0x0C float max_val Maximum knob value (item_count - 1 for PARAM_LIST)
+0x10 float default_val Default initial knob value / default list index
+0x14 float step Value change per encoder step (1.0f for PARAM_LIST)
+0x18 char[6] unit Unit suffix string (" ms", " dB", " %"), or for PARAM_LIST: uint16_t list_offset (+0x18..+0x19) + uint8_t item_count (+0x1A) pointing to .ptfx_strings in the code payload
+0x1E uint8_t display_type 'f' (PARAM_FLOAT, decimal bar), 'd' (PARAM_INT, integer bar), or 'l' (PARAM_LIST, scrollable string list cList)
+0x1F uint8_t padding Struct padding to 32 bytes

9. Firmware Architecture, Memory Layout & Cache Coherency

9.1 End-to-End Architecture

flowchart TD
    subgraph Storage ["SD Card (/Effects/*.ptfx)"]
        FX1["analog_kick_gen.ptfx (Generator)"]
        FX2["stereo_delay.ptfx (Stereo Effect)"]
        FX3["stereo_to_mono.ptfx (Stereo-to-Mono Converter)"]
    end

    subgraph Manager ["PtfxManager & PtfxLoader"]
        Scan["PtfxManager::scanEffects()"]
        Header["PtfxLoader::readHeader() (472B aligned read)"]
        Load["PtfxLoader::loadPlugin() (256B chunked FatFs read)"]
        Cache["PtfxLoader::syncInstructionCache() (L1 D/I-Cache & MPU)"]
    end

    subgraph Memory ["Platform Execution & Scratch Memory"]
        CodeBuf["Executable Code Buffer (.text + .rodata + .data + .bss)"]
        Scratch["Scratch RAM (Delay Lines / Filter State)"]
        HostApi["s_ptfxHostApi (Math & Memory Function Table)"]
        Entries["s_ptfxEntries[128] (Catalog Cache: 128 Plugins)"]
    end

    subgraph Effector ["Effector Engine (mtEffect & mtEffectModular)"]
        ModularEff["sampleEditorModular (mtEffectModular)"]
        StartProc["startProcess()"]
        ProcessBlock["processBlock()"]
        Apply["apply() & onApplyCommitted()"]
    end

    subgraph UI ["Sample Editor UI (cSampleEditor)"]
        List["Effect Selector List (17 Built-in + up to 128 PTFX)"]
        Knobs["Dynamic Parameter Screen (Up to 12 Knobs across 3 Pages)"]
        Wave["Waveform, Selection Endpoints & Preview Player"]
    end

    Storage --> Scan
    Scan --> Header
    Header --> Entries
    Entries --> List
    List -->|Select Plugin| Load
    Load --> CodeBuf
    Load --> Cache
    Load --> Scratch
    Load --> HostApi
    Load --> ModularEff
    ModularEff --> Knobs
    Knobs -->|Preview / Apply| StartProc
    StartProc --> ProcessBlock
    ProcessBlock --> Wave
    ProcessBlock --> Apply
Loading

9.2 Platform Resource & Memory Allocation

Resource / Limit Original Tracker (MK66) Tracker+ & Tracker Mini (IMX)
MCU & Core NXP MK66FX1M0 (Cortex-M4F @ 180 MHz) NXP i.MX RT1051/RT1064 (Cortex-M7F @ 600 MHz)
Max Discovered Plugins 128 .ptfx files in /Effects/ 128 .ptfx files in /Effects/
Max Parameters per Plugin 12 (3 pages of 4) 12 (3 pages of 4)
Executable Code + BSS Limit 32,768 B (32 KB at base of externalRamScratchBuff) 131,072 B (128 KB at base of externalRamScratchBuff, 0x80000000)
Plugin Scratch Buffer (scratch_mem) externalRamScratchBuff + 128 KB ([128 KB .. 768 KB], 655,360 B / 640 KB) externalRamScratchBuff + 128 KB ([128 KB .. 768 KB], 655,360 B / 640 KB)
Sample Editor Buffers (confirmed / processed) externalRamEffectsBuff (1 channel, 2 × 1.875 MB halves) externalRamEffectsBuff (2 channels, 4 × 1.875 MB halves)

Important

Two-Stage Lazy Caching & Shared Scratch Buffer (kPtfxScratchTopOffset):

  • Stage 1 (Catalog Cache): s_ptfxEntries[128] stores 76 bytes per plugin (filename[42], displayName[32], flags), enabling 60 FPS rotary jog-wheel scrolling across up to 128 custom plugins with zero SD card seeks during scrolling.
  • Stage 2 (Single-Pass Loader): When the user selects a modular plugin, PtfxLoader::loadPlugin() opens the .ptfx file once and reads the 472-byte s_activeHeader and executable code payload in the same f_open session.
  • Shared Scratch Cap (768 KB): kPtfxScratchTopOffset = 768 * 1024 in core/effector/ptfx_loader.h caps .ptfx scratch usage at offset 768 KB (SHARED_SCRATCH_MEMORY_SIZE) of externalRamScratchBuff.

9.3 Cortex-M7 L1 Cache Coherency & FatFs Loading (PLATFORM_IMX)

On the i.MX RT1051/RT1064 (Cortex-M7), 0x80000000 (EXTERNAL_RAM) is configured in the MPU as Write-Back, Write-Allocate cached memory (DisableExec = 0), with separate L1 Data Cache (D-Cache) and L1 Instruction Cache (I-Cache):

  1. Chunked File Reads (<= 256 bytes):
    • Because sizeof(PtfxHeader) is 472 bytes (not a multiple of the 512-byte SD sector size), PtfxLoader::loadPlugin() reads the code payload in 256-byte chunks (chunk <= 256), guaranteeing that FatFs (cc = btr / 512 == 0) always reads SD sectors through its aligned internal DTCM bounce buffer (fp->buf).
  2. Cortex-M7 SCB Cache Maintenance:
    • After reading the code payload and zeroing .bss, PtfxLoader::syncInstructionCache() cleans and invalidates D-Cache lines over [codeBuf, codeBuf + totalCodeSize) via SCB->DCCIMVAC (0xE000EF70), executes dsb, invalidates the entire I-Cache via SCB->ICIALLU (0xE000EF50), and executes dsb + isb.
  3. MK66 System MPU (!PLATFORM_IMX):
    • On Original Tracker (MK66), syncInstructionCache() disables the Kinetis System MPU (MPU_CESR = 0 at 0x4000D000) to permit instruction fetches from externalRamScratchBuff.

9.4 Sample Editor Buffer Lifecycle

In core/effector/mtEffect.cpp:

  • Each channel has two buffer halves inside externalRamEffectsBuff: confirmedBuffer and processedBuffer.
  • When an effect is Previewed (processSelection()), audio is read from confirmed[ch] and written to processed[ch].
  • When an effect is Applied (mtEffect::apply()), the confirmed[ch] and processed[ch] descriptors are swapped in-place and processing.isLoadedData is set to true. This allows seamless chaining of built-in effects, generators, and .ptfx plugins within the same Sample Editor session before saving back to mtProject.sampleMemory.

Clone this wiki locally