Skip to content

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:

\[\text{buffer}[\text{writePos}] = x\]

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:

\[L_{\text{beat}} = \frac{f_s \cdot 60}{\text{BPM}}\]
\[L_{\text{loop}} = L_{\text{beat}} \cdot M_{\text{div}}\]
\[\text{loopStart} = \text{writePos} - L_{\text{loop}}\]

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:

\[\text{absPos} = \text{loopStart} + \text{readPos}\]
\[y_{\text{loop}} = \text{buffer.readInterp}(\text{absPos})\]

5. Crossfade Envelope

A cosine crossfade window (64 samples) is applied at both ends of the loop to eliminate clicks:

\[\text{fadeLen} = \min(64, L_{\text{loop}} / 4)\]
\[w(p) = \begin{cases} \frac{1}{2}(1 - \cos(\pi \cdot p / \text{fadeLen})) & \text{if } p < \text{fadeLen} \\ \frac{1}{2}(1 - \cos(\pi \cdot (L - p) / \text{fadeLen})) & \text{if } p > L - \text{fadeLen} \\ 1 & \text{otherwise} \end{cases}\]
\[y_{\text{loop}} \mathrel{\times}= w(\text{readPos}) \cdot \text{amplitude}\]

6. Position Advancement

The read position advances by the rate parameter each sample:

\[\text{readPos} \mathrel{+}= \text{rate}\]

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:

\[\text{readPos} = \begin{cases} \text{readPos} - L_{\text{loop}} & \text{if rate} \geq 0 \text{ and readPos} \geq L \\ \text{readPos} + L_{\text{loop}} & \text{if rate} < 0 \text{ and readPos} < 0 \end{cases}\]

On each wrap, the iteration count increments (capped at 255) and amplitude decays:

\[\text{amplitude} \mathrel{\times}= \text{decay}\]

Playback stops when \(\text{amplitude} < 0.001\).

8. Dry/Wet Mix

\[y = x \cdot (1 - \text{mix}) + y_{\text{loop}} \cdot \text{mix}\]

where \(\text{mix} = \text{mixPct} / 100\).

Core Equations

\[L_{\text{loop}} = \frac{f_s \cdot 60}{\text{BPM}} \cdot M_{\text{div}}\]
\[y_{\text{loop}} = \text{buffer}[\text{loopStart} + \text{readPos}] \cdot w(\text{readPos}) \cdot \text{amplitude}\]
\[\text{amplitude}_{n+1} = \text{amplitude}_n \cdot \text{decay}\]

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 ParamSmoother for 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