Micro Looper
Tier: Algorithms | ComponentType: 33 | Params: 7
Clock-divided buffer capture with four playback modes (OneShot, Loop, Gate, Stutter), variable-rate playback, cosine crossfade at loop boundaries, and per-iteration amplitude decay.
Overview
MicroLooper captures segments of live audio into a circular buffer and plays them back in musically useful ways. The capture length is tempo-synced via BPM and a beat division selector, giving loop lengths from 1/16th notes to 8 beats. A rising edge on the Capture parameter triggers the capture, grabbing a window of the most recently buffered audio.
Four playback modes offer different behaviors: OneShot plays the captured loop once and stops. Loop repeats continuously with optional amplitude decay per iteration. Gate plays while the capture trigger is held and stops when released. Stutter retriggers from the start on each rising edge, creating rhythmic glitch effects.
The Rate parameter controls playback speed, including negative values for reverse playback. This allows pitch-shifted and time-reversed variations of the captured material. A cosine crossfade at loop boundaries (64 samples) eliminates clicks at the loop seam.
Decay controls how much the amplitude reduces after each complete iteration. At 1.0, the loop sustains indefinitely. Below 1.0, it gradually fades -- useful for echo-like trails. Playback stops automatically when amplitude drops below 0.001.
File Locations
| Path | |
|---|---|
| Header | Sources/FolioDSP/include/FolioDSP/Algorithms/MicroLooper.h |
| Implementation | Sources/FolioDSP/src/Algorithms/MicroLooper.cpp |
| Tests | Tests/FolioDSPTests/MicroLooperTests.swift |
| Bridge | Sources/FolioDSPBridge/src/FolioDSPBridge.mm (MicroLooperBridge) |
Parameters
| Index | Name | Description | Min | Max | Default Min | Default Max | Default | Unit |
|---|---|---|---|---|---|---|---|---|
| 0 | BPM | Tempo for beat-sync loop lengths | 20.0 | 400.0 | 40.0 | 300.0 | 120.0 | BPM |
| 1 | Division | Beat division (0=1/16..7=8 beats) | 0.0 | 7.0 | 0.0 | 7.0 | 4.0 | |
| 2 | Capture | Trigger capture (0->1 edge) | 0.0 | 1.0 | 0.0 | 1.0 | 0.0 | |
| 3 | Rate | Playback speed (negative = reverse) | -4.0 | 4.0 | -2.0 | 2.0 | 1.0 | |
| 4 | Mode | Playback mode (0=OneShot, 1=Loop, 2=Gate, 3=Stutter) | 0.0 | 3.0 | 0.0 | 3.0 | 1.0 | |
| 5 | Decay | Amplitude decay per iteration | 0.0 | 1.0 | 0.0 | 1.0 | 1.0 | |
| 6 | Mix | Dry/wet blend | 0.0 | 100.0 | 0.0 | 100.0 | 100.0 | % |
Division multipliers:
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| Division | 1/16 | 1/8 | 1/4 | 1/2 | 1 beat | 2 beats | 4 beats | 8 beats |
| Multiplier | 0.0625 | 0.125 | 0.25 | 0.5 | 1.0 | 2.0 | 4.0 | 8.0 |
Processing Algorithm
The process() function executes these steps for each input sample:
1. Continuous Recording
Input is always written to the circular buffer, regardless of playback state:
The CircularBuffer<524288> provides ~11.9 seconds of recording at 44100 Hz.
2. Capture Trigger Detection
A rising edge (transition from \(< 0.5\) to \(\geq 0.5\)) on the Capture parameter initiates a new capture:
The read position, amplitude, and iteration count are reset. The loop captures the most recently buffered audio of length \(L_{\text{loop}}\).
3. Mode-Dependent Playback Control
- OneShot (\(\text{mode} = 0\)): Plays once. Stops after the first complete iteration (\(\text{iterationCount} > 0\)).
- Loop (\(\text{mode} = 1\)): Repeats continuously. Amplitude decays by \(\text{decay}\) factor per iteration.
- Gate (\(\text{mode} = 2\)): Plays while Capture is held high. Stops when Capture goes low.
- Stutter (\(\text{mode} = 3\)): Each rising edge on Capture retriggers playback from the start.
4. Loop Readout with Interpolation
Audio is read from the captured region using linear interpolation:
5. Crossfade Envelope
A cosine crossfade window (64 samples) is applied at both ends of the loop to eliminate clicks:
6. Position Advancement
The read position advances by the rate parameter each sample:
Negative rate values produce reverse playback.
7. Loop Boundary Handling
When the read position exceeds the loop length (forward) or goes below zero (reverse), it wraps around:
On each wrap, the iteration count increments (capped at 255) and amplitude decays:
Playback stops when \(\text{amplitude} < 0.001\).
8. Dry/Wet Mix
where \(\text{mix} = \text{mixPct} / 100\).
Core Equations
Snapshot Fields
| Field | Type | Range | Unit | Description |
|---|---|---|---|---|
| Input Level | Float | 0--1 | Smoothed input amplitude | |
| Output Level | Float | 0--1 | Smoothed output amplitude | |
| Is Looping | Bool | 0--1 | Whether playback is active | |
| Loop Progress | Float | 0--1 | Normalized position within the loop | |
| Iteration | Uint8 | 0--255 | Number of completed loop iterations | |
| Playback Rate | Float | -4 to 4 | Current playback speed |
Implementation Notes
- CircularBuffer<524288> provides ~11.9 seconds at 44100 Hz. At the slowest tempo (20 BPM) with 8-beat division, the maximum loop length is \(44100 \times 60 / 20 \times 8 = 1{,}058{,}400\) samples, which exceeds the buffer. The loop length is clamped to
kBufferSize - 1. - Cosine crossfade uses 64 samples by default, but adapts to \(L/4\) for very short loops. This prevents the crossfade from exceeding half the loop length.
- Continuous recording means the buffer always has fresh audio available for capture, regardless of whether playback is active. The loopStart calculation looks backward from the current write position.
- BPM and Division are not smoothed (discrete parameters), but Rate, Decay, and Mix use
ParamSmootherfor glitch-free changes during playback. - Level tracking uses exponential smoothing with coefficient 0.01 for display-rate visualization.
- All parameters use
std::atomic<float>for lock-free thread safety. - Snapshot emission is decimated to ~60 fps (every 735 samples at 44.1 kHz).
Equation Summary
y = buf[capture] @ rate +/- decay