Max for Live device for macOS & Windows · by BitNet01
Update: now includes Apple Silicon externals.
A clone of the Clouds module by Mutable Instruments.
This free Max for Live device is built on the vb.mi.clds~ external by Volker Böhm, whom we thank for porting the original code to Max/MSP.
We only designed the M4L interface: during quarantine we were experimenting with objects like pictctrl, and wanted everyone to be able to fit this tool into their own setups.
A huge thanks goes to Émilie Gillet, the original author of the Mutable Instruments module family, for making the source code public so people can explore, learn and create new musical textures!
We made it for passion, not for profit, so there may be bugs. We'll find them together!
- Original code: Émilie Gillet – https://mutable-instruments.net
- Max/MSP external: Volker Böhm – https://vboehm.net
- Unzip all the contents into the same folder.
- Go to
User / Music / Ableton / User Library. - Drag and drop the whole folder there.
You'll find the .amxd file in Ableton's browser, under User Library in the left panel.
If the device loads without a UI, a step went wrong: repeat the whole procedure.
Every knob can be MIDI-mapped like any Live control: enter MIDI Map Mode (Cmd+M / Ctrl+M), click the number box below the knob (not the knob itself), move a control on your controller, then exit MIDI Map Mode. See the animation above.
Tested with Ableton Live 12.3.7 & Max 9.1.2 on macOS Sequoia.
Nube_gen/NUBE - Texture_Synthesizer gen~.amxd is a parallel version of the device with the same interface. The compiled vb.mi.clds~ external is replaced by a port of its DSP to pure gen~ code (nube.genexpr). With no compiled externals it can run on Push 3 standalone, which can't load third-party externals.
This is not a re-creation "in the style of" Clouds. It is a line-by-line port of the code vb.mi.clds~ runs (Volker Böhm's version of Émilie Gillet's Clouds): same algorithms, same tables, same random number generator and same 32-sample block structure. All four modes are included:
- Granular
- Stretch, with the WSOLA correlator
- Looping delay, with its pitch shifter
- Spectral, the phase vocoder with its own FFT
Also included: feedback, reverb, diffuser, freeze, lo-fi (8-bit μ-law with 2× sample-rate conversion), trigger and tap tempo. Messages and inlets are the same as vb.mi.clds~, so the rest of the patch is unchanged.
Status: verified sample by sample against the original binary (see below). It still needs to be tried in Live and on Push 3 hardware. Feedback is welcome!
The comparison uses the real vb.mi.clds~ binary shipped in this repository (the same one frozen in the original device):
- The binary is loaded outside Max, with stand-ins for the few Max API functions it calls.
- The gen~ code is exported to C++ from Max.
- Both are fed the same audio, parameters and triggers, and their outputs are compared sample by sample.
The original itself is not bit-exact across platforms: its Apple Silicon and Intel builds already drift apart through floating-point rounding (FMA instructions, float32 vs double). So the pass criterion is: the gen~ port must stay as close to the Apple Silicon build as the Intel build of the same device is.
21 scenarios were tested at 48 kHz: every mode, freeze, trigger/tap, lo-fi, feedback + reverb, mode switching (including to and from Spectral), bypass and in_gain, plus a scenario with everything moving at once. Selected results:
| Scenario | gen~ vs original (Apple Silicon) | Original Intel vs Apple Silicon |
|---|---|---|
| Granular, random grains | −112 dB | −59 dB |
| Granular, trigger | −111 dB | −156 dB |
| Stretch (WSOLA) | −116 dB | −135 dB |
| Looping delay + freeze | −113 dB | −69 dB |
| Spectral | −94 dB | −102 dB |
| Lo-fi granular | −136 dB | −54 dB |
| Mode switching | −108 dB | −70 dB |
| Everything at once (feedback, reverb, freeze, lo-fi…) | −65 dB | −54 dB |
In most scenarios no output sample differs from the original by more than 0.0001 (−80 dBFS). The exceptions are the three scenarios where feedback or a frozen spectrum amplifies rounding differences over time (feedback + reverb, spectral freeze, everything at once), and in those the Intel build of the original drifts by the same amount or more. Spot checks at 44.1 kHz and 96 kHz, with vector sizes of 128 and 256, give the same results.
-
Latency: the original processes each 32-sample block inside the same audio vector. A gen~ codebox sees one sample at a time, so each block is rendered once it has been captured, and the output (bypass included) is delayed by 32 samples (0.7 ms at 48 kHz).
-
Undefined behaviour: where the original C++ reads outside its buffers (for example extreme grain sizes and pitches at high sample rates), the gen~ version reads zeros instead of whatever memory happened to be there.
-
Sample rate: recording buffers are sized for up to 96 kHz, 4 seconds, like
[vb.mi.clds~ 4000]. -
CPU: gen~ has no 32-bit float type, so float32 rounding is emulated wherever it changes the result (grain timing, table indices, correlator decisions…). This makes it heavier than the compiled external. Measured on one Apple Silicon core, as a fraction of real time:
Mode gen~ vb.mi.clds~ Granular (density 0.95) ~9% ~1% Stretch ~8% ~1% Looping delay ~5% ~1% Spectral ~17% ~1%
After editing Nube_gen/nube.genexpr, rebuild the device with:
python3 Nube_gen/build.pyThe script reads the original Nube/NUBE - Texture_Synthesizer.amxd and swaps vb.mi.clds~ for the gen~ code. It drops the frozen externals and keeps the images.
The top of nube.genexpr lists the gen~ compiler pitfalls found during the port; check it before editing.
