max/msp port of @zebra’s softcut looping engine (part of norns)

! a formal thanks to ezra for taking the time to develop and assist with the port !

the current release is beta, feel free to use this thread to post issues if you’d like to contribute to development.


  • help patcher
  • phase output
  • write-amplitude dead zone
  • soft clipper fixes (disabled in this version)


Max/Msp or Max for Live



drop softcut~.mxo (softcut~.mxe or softcut~.mxe64 for Windows) from the .zip into your project folder, or add it to your search path. now softcut~ will be an available object


reference the softcut luadocs for the list of softcut commands. here they take the form of a message contaning: " command value ". for now, softcut~ only supports mono buffers.



build form source:


Hi @andrew.

I’ve built the Windows externals based on that cpp file in case anyone is interested in working on Windows.

softcut~.mxe (81.5 KB)
softcut~.mxe64 (107 KB)

1 Like

oh awesome !! do they work? I’ll add them to the official release. need to put a hard drive in my windows machine one of these days.

I didn’t get a chance test them, but the last version worked fine through the Visual Studio Project. I’ll run the test patch tonight to confirm.

1 Like

some more norns/max dev questions for this thread:

I’ve been spending the last few days studying up and working on an external for softcut (using MaxCpp per @zebra’s suggestion above). It’s been pretty approachable, I think I’m almost ready to get a successful build but I’m getting these kind of confounding Undefined Symbol errors from the linker.


That looks like it’s coming from the sofcut code, but we know that works so I’m wondering if it’s somewhere in my Xcode target config or c++ versions or somewhere on that side of thing. From what I’ve gathered this error is supposed to mean a function is declared and not defined, but seems like it can also point to trickier issues.

This is the only code I’ve actually written, doesn’t really seem like it’s the source of the problem though.

#include "maxcpp6.h"
#include "softcut/SoftCut.h"
#include "softcut/Types.h"
#include "Utilities.h"

// inherit from the MSP base class, template-specialized for myself:

class Softcut_msp : public MspCpp6<Softcut_msp> {

enum { MaxBlockFrames = 2048, NumVoices = 6 };
enum { BufFrames = 16777216 };

softcut::SoftCut<NumVoices> cut;

float buf[2][BufFrames];

bool enabled[NumVoices];
softcut::phase_t quantPhase[NumVoices];

Softcut_msp(t_symbol * sym, long ac, t_atom * av) {
    setupIO(NumVoices, NumVoices);
    post("object created");
    for(int i=0; i<NumVoices; ++i) {
        cut.setVoiceBuffer(i, buf[i&1], BufFrames);

~Softcut_msp() {
    post("object freed");

// methods:
void bang(long inlet) {
    post("bang in inlet %i!", inlet);
void test(long inlet, t_symbol * s, long ac, t_atom * av) {
    post("%s in inlet %i (%i args)", s->s_name, inlet, ac);

// default signal processing method is called 'perform'
void perform(double **ins, long numins, double **outs, long numouts, long sampleframes) {
    // example code to invert inputs
    for (long v = 0; v < numouts; v++) {
        double * in = ins[v];
        double * out = outs[v];
        if (!enabled[v]) {
        cut.processBlock(v, (const float *) in, (float*) out, (int) sampleframes);

C74_EXPORT int main(void) {
    // create a class with the given name:
    REGISTER_METHOD(Softcut_msp, bang);
    REGISTER_METHOD_GIMME(Softcut_msp, test);

anywho, I’ll keep digging but wanted to post in case anyone had any quick pointers

1 Like

it looks like you are not compiling SoftCutVoice.cpp

is your xcode project on github? that might be a better place to share stuff (there could be quite a bit more of this) and i can probably help

(that said, i’ve found setting up xcode to build max externals to be a serious PITA in the past. i just end up customizing sample projects and leaving them in the SDK directory tree.)

@andrew oh but real quick, take a look at the CMake targets for crone:

you will probably want to compile src/softcut/*.cpp

IIRC, softcut doesn’t have any library dependencies, but does need a standard like c++11 or c++14 or something.

also, thanks for taking that on!


ahhh yes compiling the code would be useful !

here’s a quick repo

just a straight modification of the msp example right now ( maxcpp-master goes in the sdk folder which you probably already know that )

I added boost ? couldn’t seem to get it going without ?

anyway that’s probably that tidbit of info I needed to move forward tomorrow but feel free to poke with the repo ofc


oh, good good - sorry yes, just using BOOST_ASSERT i think? should be fine to lose those lines at this point

TBH i will probably not take time to work with this myself (unless needed, no prob) but happy to answer such q’s

1 Like

hey @andrew

just a heads-up, and sorry this is kinda off topic, but
i forked your softcut port to check it out, and ended just starting over:
[ ]

spoiler: doesn’t work yet. something is borked with the buffer format or i dunno. i will continue to poke at this, but wanted to let you know asap, in case it saves duplicate work.

some differences with this project:

  • uses max8 sdk, not max6
  • doesn’t use maxcpp, which doesn’t seem necessary anymore
  • uses new softcut sources from norns repo, not stale ones from the standalone version
  • i cleaned out the boost dependencies
  • the external only implements one voice
  • takes buffer argument from MSP instead of managing a huge buffer itself

(these last two things make it a much better citizen of max-land i think. i would probably make some more changes eventually, like a signal-rate position reset trigger.)


ah nice nice, thnx for the info. the last two were in the plan once I got some output happening but sounds like yr taking it in a better direction that what I could attempt to throw together !

rlly excited to have this in my patches - appreciate the contribution as always @zebra


took a look at this myself, short of really finding anywhere close to the problem I * did * make a nice picture to see what it’s doing to a sine wave loaded into the buffer

scope patch


so uhhhh, maybe that helps point to something, idk ! seems like a sample rate thing. tried x->scv.setSampleRate(44100.0) instead of grabbing it from max but no dice. maybe something missing in Resampler.h, or sample rate data missing elsewhere.

anywayyy, happy to throw time & limited knowledge at this. the good news is the current version is pretty good at turning low frequency inputs into idm beats.

1 Like

it’s not a samplerate problem and it’s not Resampler (which is used for varispeed writes, only comes into play when rate != 1.0, and is really a pretty simple thing.)

i just went ahead and did a quick test of my next suspicion, which is that i’m somehow not setting / using the max-supplied audio buffer correctly, and that was fruitful.

i used a ~21-second internal, self-allocated buffer instead, and it seems to work. (so far - this was an extremely quick test without changing any parameters or anything.)

you can see that branch test-internal-buffer

here are the relevant lines that were changed in the test:

[ ]
[ ]
[ ]
[ ]

somehow, if i use the max-supplied buffer, it’s like the same value is written to each index in each processing block. i’m sure it’s something stupid. im using the index~ example project for reference. th one lazy corner i was cutting is assuming the buffer is mono - softcut expects this, so to do otherwise would mean de-interleaving into a temp ringbuffer, processing, and re-interleaving - more bookkeeeping there. (it would have to be a ringbuffer for the varispeed stuff to work, i think.)

but anyways digging into that is next step. i don’t have time to do more this weekend but might be able to over next two weeks while i’m off the clock and travelling.

one tip i can offer is that you can in fact attach xcode to the Max process and set breakpoints for debugging. (so you can for example esaily check that SR and other params are geting set, that phase is getting incremented correctly, &c.)


Oh oh probably shouldn’t be re-setting the buffer every processing block like index~ does?

Gotta take better look at buffer functions in sdk

like i assumed buffer_locksamples always returns same address but that assumption is worth checking

1 Like

rad rad, this is all great info for me to dive back in with. the processing block thing makes sense - that’s why we got terraces instead of slopes !

1 Like

ok ! so I have it working, but just at a specific buffer size. the test patch plays through included long sample just fine, and recording looks good so far as well.

looks like the problem was softcut needing the buffer framecount as a power of 2 (?), so here I’m just setting framecount manually and sticking with a buffer size that rounds up a bit higher (rolling with 2^23 atm). since the buffer size is set in ms in max the conversion never comes out to a rational, so I’m guessing I have to set framecount internally in samples, then just make sure the max buffer is higher.

aaaaanyway this definitely feels good enough to use - I think I will go ahead and publish !

1 Like

ah ok. if that is really the issue, then the fix is pretty easy: wrapping the buffer frame index is broken out to a utility function for exactly this reason:

change it to use a while-loop instead of a bitmask. (and bufMask_ variable should then not be needed.) (and yea, the assert we disabled would have caught this! woops)

using a bitmask is a tiny optimization that is not worth it in MSP world. (but i’m used to SuperCollider world where buffer allocation uses frames and it’s normal to use nextPowerOfTwo and bitmask tricks. sorry about that.)

but, i am not 100% convinced this is the problem really… when i tried with large buffers in MSP, i saw a very consitent artifact that didn’t just “kick in” when the buffer was wrapped.

(i’m sorry i’m armchair right now, i’m away from a Mac.)

if that works, seems like a fine workaround and definitely indicates that the wrapping func is the issue. you could just set internal framecount to the next highest power-of-two smaller than the actual buffer size:

int highestPowerof2(int n) 
    int res = 0; 
    for (int i=n; i>=1; i--) 
        // If i is a power of 2 
        if ((i & (i-1)) == 0) 
            res = i; 
    return res; 
1 Like

I’ll let this lovely response evolve and look into it some more, but for reference, the bug I was fighting actually granulated the buffer in a specific way. output was quite beautiful at times. only artifacts I noticed were clicks between those grains.


andrew! this rocks! can’t wait to see where this ends up.


kyle! no way I’m eating kimchi rn !!!


yeah, damn, this is gorgeous. able to pass a non-xcode-havin’ bud the max-sdk-8.0.3 folder you built all this in?