Repository navigation
modular_effects_system
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).
- Overview & Installing
.ptfxPlugins - Bundled Reference Plugins (
editor-fx-sdk/effects/) -
Quick Start & Anatomy of a
.ptfxPlugin - Metadata Directives & Capability Flags (
PtfxFlags) -
Parameter Descriptors & UI Controls (
0–12Parameters) - Host API Reference (
PtfxHostApi) - Plugin Lifecycle & Audio Processing Patterns
-
The
.ptfxBinary Specification (PtfxHeader) - Firmware Architecture, Memory Layout & Cache Coherency
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.
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
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. |
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).
-
editor-fx-sdk/tracker_plugin.h- Standalone C header for plugin authors. -
editor-fx-sdk/ptfx_builder.py- Python build tool that parses plugin metadata comments, compiles C/C++ code viaarm-none-eabi-gcc, packs.ptfx_stringstables, resolves entry point offsets, and outputs a.ptfxfile. -
editor-fx-sdk/effects/- Reference effect and generator source files.
Build all plugins in editor-fx-sdk/effects/ from the repository root:
make ptfxOr 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-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-)
Every .ptfx plugin source file consists of four parts:
-
Top-of-file metadata comments (
// PTFX_NAME:,// PTFX_AUTHOR:,// PTFX_FLAGS:,// PTFX_SCRATCH:) -
Parameter definitions (
const PluginParam PLUGIN_PARAMS[...]) -
Initialization & parameter handlers (
plugin_init,plugin_set_param) -
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;
}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. |
| 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). |
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-inReverseorFade 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/2or1/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).
Defined in editor-fx-sdk/tracker_plugin.h:
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").
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.
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
Deleteto reset todefault_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 (0toN - 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),valueis the zero-based option index (0.0f,1.0f,2.0f, ...). Convert it to an integer index with(uint8_t)(value + 0.5f).
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;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);-
Plugin Selection: When the user highlights your plugin in the Sample Editor list, the firmware loads the
.ptfxbinary, zeroesscratch_mem, callsplugin_init(), and callsplugin_set_param()for each parameter with itsdefault_val. -
Parameter Adjustment: Turning the encoder or changing a list item calls
plugin_set_param(index, new_value). -
Preview / Apply (
startProcess): When the user presses Preview or Apply, the firmware zeroesscratch_mem, callsplugin_init()to reset DSP state, and immediately replaysplugin_set_param(i, current_value)for all parameters before callingplugin_process_block()in chunks of up to8192samples.
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().
For effects that keep the same sample length (filters, distortion, bitcrushers, EQ):
-
in[ch]points to the input selection (int16_tsigned 16-bit PCM at44100 Hz). -
out[ch]points to the output buffer (int16_tsigned 16-bit PCM). - Loop
chfrom0tochannels - 1andifrom0tosamples - 1, write toout[ch][i], and return(int32_t)samples. - See
editor-fx-sdk/effects/multimode_filter.c.
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:
- Before rendering begins, the firmware checks that
original_length + prepend_samples + append_samplesfits within the Sample Editor buffer (EDIT_BUFF_SIZE / 2) and project sample RAM (mtProject.max_memory). - During rendering,
mtEffectModular::processBlock()automatically splitsplugin_process_block()calls at region boundaries:-
Prepend region (
[0, prepend_samples)):in[ch]isNULL. -
Selection body (
[prepend_samples, prepend_samples + input_length)):in[ch]points to the source sample data inconfirmed[ch].selection.addr. -
Append / tail region (
[prepend_samples + input_length, total_length)):in[ch]isNULL.
-
Prepend region (
- Any unselected audio before the selection's
Startpoint and after itsEndpoint 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...
}
}Generators synthesize audio from scratch (in is NULL) and can run even when an instrument slot is completely empty:
- Maximum generation window is
88,200samples (2.0 secondsat44.1 kHz). - To generate a shorter sample (such as a
300 msdrum hit or a single-cycle oscillator waveform of689samples), stop generating once your target sample count is reached and return0on the next call (or return the exact remaining sample count< samplesand0thereafter). - When
plugin_process_block()finishes early, the firmware automatically trimsprocessed[ch].selection.lengthandprocessed[ch].area.lengthto 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.candeditor-fx-sdk/effects/chiptune_gen.c.
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:
- Request scratch memory via
// PTFX_SCRATCH: <bytes>. - Cast
scratch_memto your state struct insideplugin_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;
}
}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.
+-----------------------------------------------------------------------+
| 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) |
+-----------------------------------------------------------------------+
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 |
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
| 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.ptfxfile once and reads the 472-bytes_activeHeaderand executable code payload in the samef_opensession. -
Shared Scratch Cap (
768 KB):kPtfxScratchTopOffset = 768 * 1024incore/effector/ptfx_loader.hcaps.ptfxscratch usage at offset768 KB(SHARED_SCRATCH_MEMORY_SIZE) ofexternalRamScratchBuff.
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):
-
Chunked File Reads (
<= 256bytes):- Because
sizeof(PtfxHeader)is472bytes (not a multiple of the 512-byte SD sector size),PtfxLoader::loadPlugin()reads the code payload in256-byte chunks (chunk <= 256), guaranteeing that FatFs (cc = btr / 512 == 0) always reads SD sectors through its aligned internal DTCM bounce buffer (fp->buf).
- Because
-
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)viaSCB->DCCIMVAC(0xE000EF70), executesdsb, invalidates the entire I-Cache viaSCB->ICIALLU(0xE000EF50), and executesdsb+isb.
- After reading the code payload and zeroing
-
MK66 System MPU (
!PLATFORM_IMX):- On Original Tracker (
MK66),syncInstructionCache()disables the Kinetis System MPU (MPU_CESR = 0at0x4000D000) to permit instruction fetches fromexternalRamScratchBuff.
- On Original Tracker (
In core/effector/mtEffect.cpp:
- Each channel has two buffer halves inside
externalRamEffectsBuff:confirmedBufferandprocessedBuffer. - When an effect is Previewed (
processSelection()), audio is read fromconfirmed[ch]and written toprocessed[ch]. - When an effect is Applied (
mtEffect::apply()), theconfirmed[ch]andprocessed[ch]descriptors are swapped in-place andprocessing.isLoadedDatais set totrue. This allows seamless chaining of built-in effects, generators, and.ptfxplugins within the same Sample Editor session before saving back tomtProject.sampleMemory.