That book looks great. Did you order it from the publisher? Frustratingly hard to track down otherwise!

Yes, I ordered it from the publisher.

I dove into that recently, but due to other things, haven’t gotten past the first two chapters. I did successfully build an external, which was pretty exciting. I’m curious if anyone has any insight into any fundamental differences in performance between a DSP routine contained in an external, and a DSP routine written in codebox in gen~. I find the quickness and ease of prototyping in gen~ really encouraging. Is the code easily adaptable from gen~ over to C?

Wish I could share some tips, and not just ask questions- but my experience in the topic is limited. I am interested to hear what others have to say!

1 Like

gen~ performance is supposed to be similar to C performance (I don’t know if it is identical, but it is supposedly comparable).

My motivations for exploring externals aren’t really about performance. Has more to do with things such as code sharing between Max and non-Max projects, unit testing, structured programming, and to be honest, a preference for text over patching. So, for me, it’s more about workflow and collaboration and maintainability than it is about performance. YMMV.

3 Likes

In order to get the first example from the Designing Audio Objects book to build, I needed to use the current version of the Max SDK, which @tehn links to above, and then replace the c74support directory located in the Code/MaxMSP Code/ folder from the book’s DVD.

I also needed to edit the Build Settings in XCode 7 so that it used a 32-bit Intel architecture, and then I needed to change the values under Base SDK to Latest OS X.

Then I needed to ensure that Max 7 started up in 32-bit mode (Get Info on the Max 7.app file to do this)

At that point the build succeeded and the resulting mirror~ external worked as expected in Max 7.

At some point I’ll read the DVD’s Updates/Max 6/Writing Externals for Max 6.pdf and understand how to target 64-bit architectures, which I believe will work in Max 7 regardless of whether Max was started in 32-bit or 64-bit mode.

1 Like

I totally agree with everything you said. My interest in text based programming came about because there are ideas and programming techniques that are more easily achieved with a text based approach.

I got the examples working with max 7, with some help from HERE

1 Like

A one line change? So easy! Thanks @Evan

OK, nice, if I use the files available at the link @Evan provided, there’s no need to change the architecture Build Settings in XCode. It comes pre-configured for a Universal build with the Latest SDK.

But I did still need to open Max in 32-bit mode, so the Updates/Max 6/Writing Externals for Max 6.pdf is still relevant if you want your externals to work in 64-bit mode.

1 Like

I’m curious now that I read this…

Would some of the older apps like mlrv possibly work correctly if I used Max 7 in 32 bit mode? Would that make any difference?

It’s worth a try. There’s a checkbox for it in Max’s Get Info dialog (Cmd-I with Max.app selected)

1 Like

EYYYYYYY! EVVVVVAAAAAAANNNNNNN! Welcome! So glad to see you here :smile:

(sorry, i’ve nothing to add to this thread, just excited to see Evan from the C74 forums here :sunny: )

If those apps are dependent on any externals, there’s a chance running in 32 bit mode could be helpful.

YOOOOO. Nice to see a familiar face (or avatar, whatever)!

…no wait… i guess i do have some things to add to this thread:

Yes, although gen~ code is based on the language of ‘LUA’, the basic conditionals and other such syntax of C which are used in many languages like Java, C++, etc. are also in gen~ so it can be pretty easily adaptable(LUA is based on C same as many of these other languages).

The fundamental differences in performance are minimal(the DSP routines themselves work pretty much the same calculating things at sample rate and allowing you to hide functions from that calculation within conditional statements wherever necessary). There are some advantages to writing an external in C which have to do with having direct access to the internal ‘struct’(or memory of an external) and performing efficient checks as to whether a signal or a message is attached to the inlets of an external, then catering the DSP method to those(whereas in gen~ it’s all assumed as signals by the time it hits the DSP method within, but certain types of messages are only calculated once per vector…), however, most beginners won’t find these differences to be huge gains or losses.
The main advantages to writing externals in C are found in creating access to some of Max’s more esoteric and special features and functionalities(as mentioned before, you can separate a DSP routine that works specifically for messages as opposed to signals(detecting what’s connected to each inlet then moving from there), but other examples of extending behavior using a C external are: gaining access to specialized functionality within poly~, doing specialized checks and reformatting for changes in external buffer~s, creating behaviors specific to Windows as opposed to MacOSX within the same code, doing special things like handling files, creating a UI within the same audio-handling object, creating different typed outlets(for example in karma~ there’s a regular set of signal outlets, but then an internal scheduler-clock polls certain information to be sent out the last outlet in messaged format), etc.).

Having said all that, if you’re just starting with coding, gen~ is best since it offers a bridge between visual patching and coding(with codebox), and is still highly efficient, but eventually i’ve actually found writing externals in C to be a bit easier simply because i know the SDK and it allows me more modular access to different kinds of functionality and efficiency within one external/code(easier in the sense that i can separate things out to be more readable and develop them in a more organized easy-to-keep-track-of sort of way).

Anyways, best of luck and have fun!

4 Likes

This time-worn tutorial, MaxMSPExternalsTutorial2.41, while a bit dated (errr, 2002 and Code Warrior?!?!) saved my bacon last night when I was puzzling through proxies.

Good cycling74 forum post here with some tips on reading inlets and working with atoms:

1 Like

didn’t find the setup section from the SDK super helpful but this hella dummy-prove tutorial for mac had my back

also not mentioned, took me way too long to figure out: you gotta restart max for changes to propogate :grimacing:

1 Like

ok weird thing folks: I managed to get an external doing a thing I want it to (sample rate reduction) but only when the input signal is patched to any other object (which doesn’t need to be patched to anything!). then the output signal comes out of the object and works just fine.

I’m guessing I’m just doing something wrong in the setup process but that looks fine? maybe I’m not cleaning up my memory correctly? idk

le repo :pray:

it does’t look to me like this code will produce any output without input. (but maybe i don’t understand the question.)

also, well, it’s not a realtime resampler ehh, nevermind, i see how it works. “rate” is a divisor and it only acts as an integer. (i maintain that this is weird and unpredictable, i see several ways you could go out of bounds on the internal buffer, &c, but that’s as it may be.)

if you want to do SR reduction in realtime, you need interpolated writes. with anything but zero-order interpolation this is quite a bit trickier than interpolated reads, which is why softcut::Resampler exists. (it only deals with writes) - same for some of the hairier parts of karma~ or anything with similar duties.

basically something like this, which of course i have not tested at all:

// these headers are from softcut
// you could of course rip out the relevant bits if you don't want cpp
#include "Resampler.h"
#include "Interpolate.h"

/// Resampler handles interpolated writing.
softcut::Resampler resamp;

// assumption: Resampler::sample_t == float

// we need to do interpolated reads ourselves.
// this requires a second buffer.
// this only needs to be big enough for our interpolation window.
// (if we allowed upsampling things would get more complicated, but why bother?)
#define BUFSIZE 4; 
#define BUFMASK 3;
float buf[BUFSIZE];
unsigned int idx = 0;
double phase = 0.0; // current "playback" phase in [0,1]
double inc; // phase increment per sample;

// write a new value to the buffer, update the index
void writeToBuf(const float x) { 
    buf[idx] = x;
    idx = (idx + 1) & BUFMASK;
}

// this could use other interpolation modes if you want more dirt.
float readFromBuf() {
    // assumptions: 
    // - we always read after write, so `idx` will be the _oldest_ location
    // - idx is already wrapped
    float y0 = buf[(idx + 3) & BUFMASK];
    float y_1 = buf[(idx + 2) & BUFMASK];
    float y_2 = buf[(idx + 1) & BUFMASK];
    float y_3 = buf[idx];
    float y = softcut::Interpolate::hermite(phase, y_3, y_2, y_1, y0);
    phase += inc;
    while (phase > 1.0) { phase -= 1.0; }
    return y;
}

void setRate(double r) { 
    if (r > 1.f) { 
        rate = 1.f;
    } else {
        rate = val;
    }
    calcRate();
}

void setSampleRate(double val) { 
    sr = val;
    calcRate();
}

void calcRate() { 
    inc = rate / sr;
    resamp.setRate(rate);
}

void processSample(const float* in, float* out) { 
    // currently, this returns how many samples were written.
    // if rate < 1, nframes will either be zero or one.
    // if rate > 1, nframes will be >= 1. 
    int nframes = resamp_.processFrame(*in);    

    // this returns a pointer to Resampler's output buffer.
    // after `processFrame()`, the output buf contains N samples.
    
    // in softcut, we'd capture all these in a buffer, and interpolate playback separately.
    // here, we just want to immediately "play back" at the same rate we "recorded" with.

    // write... 
    const float* resampBuf = resamp_.output();
    for(int i=0; i<nframes; ++i) { 
        writeBuf(resampBuf[i]);
    }
    // read out the last value with interpolation
    *out = readBuf();
}
1 Like

to illustrate: it works as pictured, when I delete the cable fromcycle~ to cycle~ (second one being arbitrary), the output from colloquial~ disappears. I would guess it’s a pretty max-specific thing I messed up on. I also wouldn’t be super upset if it only worked this way tho.

ya the vague goal here was to make something like an interpolated version of downsamp~ but there’s a definite vibe of “I messed with the numbers and it sounded cool” If you can’t already tell. maybe needs a new description if I roll with it. and yea technically 1/rate i guess lol. not highly creative with variable names.

I’ll try it out using writes tho ! would this end up writing to the buffer in a pitch-independent way ? (& without a delay I assume, although if it’s really small I don’t really care that much)

oh hell yea this works & sounds great (!!)

(also I’m very here for this writing code straight into discourse business)

edit:

whoaaa, ok, that recording was actually still my wonky version - I compiled wrong (but it still sounds absolutely dank)

here’s ezra’s version:

I think if I mess with the size (spread?) of the interpolation window I can get the best of both worlds tho. brb

didn’t work! anyway I like these both, I think they compliment each other nicely


2 Likes