Skip to content

Instantly share code, notes, and snippets.

@weefuzzy
Last active September 6, 2020 16:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save weefuzzy/024f8784d0e4d44203e18728e6fb6200 to your computer and use it in GitHub Desktop.
Save weefuzzy/024f8784d0e4d44203e18728e6fb6200 to your computer and use it in GitHub Desktop.
Baby steps towards running Max DSP graphs on buffer~s
#include <stdlib.h>
#include "ext.h"
#include "ext_obex.h"
#include "z_dsp.h"
#include "ext_buffer.h"
#include "ext_dictobj.h"
typedef struct _bufproc_binding
{
t_buffer_obj* buf;
t_atom_long offset;
t_atom_long num_frames;
t_atom_long num_chans;
t_atom_long start_chan;
t_atom_long process_count;
} t_bufproc_binding;
typedef struct _bufproc
{
t_pxobject ob;
t_symbol* output_buffer;
t_buffer_ref** inputs;
t_buffer_ref* output;
void* resultOutlet;
void* lambda_outlet;
long inlet_num;
long num_ins;
long num_outs;
void** inlet_proxies;
t_object* processor;
t_object* processor_copy;
long latency; //TODO
long tail; //TODO
long num_hops;
long hop_count;
long remainder;
void* old_scheduler;
void* new_scheduler;
t_bufproc_binding* input_bindings;
t_bufproc_binding* output_bindings;
t_int32_atomic working;
t_dspchain* chain;
double shift;
} t_bufproc;
typedef struct _bufproc_inproxy
{
t_pxobject x_obj;
long num;
t_bufproc_binding* binding;
} t_bufproc_inproxy;
typedef struct _bufproc_outproxy
{
t_pxobject x_obj;
long num;
t_bufproc_binding* binding;
} t_bufproc_outproxy;
void bufproc_bang(t_bufproc *x);
void* bufproc_new(t_symbol* s, long ac, t_atom* av);
void bufproc_free(t_bufproc* x);
void bufproc_anything(t_bufproc* x, t_symbol *s, long argc, t_atom *argv);
void* bufproc_subpatcher(t_bufproc *x, long index, void *arg);
void bufproc_dsp(t_bufproc *x, t_object *chain, short* count, double sr, long bs, long flags){}
t_max_err bufproc_patchlineupdate(t_bufproc *x, t_object *patchline, long updatetype, t_object *src, long srcout, t_object *dst, long dstin);
t_ptr_int bufproc_connectionaccept(t_bufproc *src, t_object *dst, long srcout, long dstin, t_object *outlet, t_object *inlet);
t_max_err bufproc_notify(t_bufproc *x, t_symbol *s, t_symbol *msg, void *sender, void *data);
t_max_err bufproc_get_ownsdspchain(t_object *pv, t_object *attr, long *argc, t_atom **argv);
void* bufproc_inproxy_new(t_symbol* s, long ac, t_atom* av);
void bufproc_inproxy_dsp(t_bufproc_inproxy *x, t_object *chain, short* count, double sr, long bs, long flags);
void bufproc_inproxy_free(t_bufproc_inproxy* x);
void* bufproc_outproxy_new(t_symbol* s, long ac, t_atom* av);
void bufproc_outproxy_dsp(t_bufproc_outproxy *x, t_object *chain, short* count, double sr, long bs, long flags);
void bufproc_outproxy_free(t_bufproc_outproxy* x);
t_class *bufproc_class;
t_class *bufproc_inproxy_class;
t_class *bufproc_outproxy_class;
void ext_main(void *r)
{
bufproc_class = class_new("bufproc", (method)bufproc_new, (method)bufproc_free, sizeof(t_bufproc), 0L, A_GIMME, 0);
class_addmethod(bufproc_class, (method)bufproc_bang, "bang", 0);
class_addmethod(bufproc_class, (method) bufproc_patchlineupdate, "patchlineupdate",A_CANT,0);
class_addmethod(bufproc_class, (method)bufproc_connectionaccept, "connectionaccept",A_CANT,0);
class_addmethod(bufproc_class, (method)bufproc_anything, "anything", A_GIMME, 0);
class_addmethod(bufproc_class, (method)bufproc_notify, "notify", A_CANT, 0);
class_addmethod(bufproc_class, (method)bufproc_dsp, "dsp64", A_CANT, 0);
class_addmethod(bufproc_class, (method)bufproc_subpatcher, "subpatcher", A_CANT, 0);
CLASS_ATTR_LONG(bufproc_class, "numins", 0, t_bufproc, num_ins);
CLASS_ATTR_LONG(bufproc_class, "numouts", 0, t_bufproc, num_outs);
CLASS_ATTR_MIN(bufproc_class, "numins", 0, "0");
CLASS_ATTR_MIN(bufproc_class, "numouts", 0, "0");
CLASS_ATTR_OBJ(bufproc_class, "ownsdspchain", ATTR_SET_OPAQUE | ATTR_SET_OPAQUE_USER, t_bufproc, ob);
CLASS_ATTR_ACCESSORS(bufproc_class, "ownsdspchain", (method) bufproc_get_ownsdspchain, (method) 0);
CLASS_ATTR_INVISIBLE(bufproc_class, "ownsdspchain", 0);
class_dspinit(bufproc_class);
class_register(CLASS_BOX, bufproc_class);
bufproc_inproxy_class = class_new("bufproc_inproxy", (method)bufproc_inproxy_new, (method)bufproc_inproxy_free, sizeof(t_bufproc_inproxy),0L, A_GIMME, 0);
class_dspinit(bufproc_inproxy_class);
class_addmethod(bufproc_inproxy_class, (method)bufproc_inproxy_dsp, "dsp64", A_CANT, 0);
class_register(CLASS_BOX, bufproc_inproxy_class);
bufproc_outproxy_class = class_new("bufproc_outproxy", (method)bufproc_outproxy_new,(method)bufproc_outproxy_free, sizeof(t_bufproc_outproxy), 0L, A_GIMME, 0);
class_dspinit(bufproc_outproxy_class);
class_addmethod(bufproc_outproxy_class, (method)bufproc_outproxy_dsp, "dsp64", A_CANT, 0);
class_register(CLASS_BOX, bufproc_outproxy_class);
}
t_max_err bufproc_get_ownsdspchain(t_object *pv, t_object *attr, long *argc, t_atom **argv)
{
char alloc = false;
if (atom_alloc(argc, argv, &alloc))
return MAX_ERR_GENERIC;
// This prevents getdspchain on child patchers from walking up past this object
atom_setlong(*argv,1);
return MAX_ERR_NONE;
}
void* bufproc_inproxy_new(t_symbol* s, long ac, t_atom* av)
{
if(ac < 1) return NULL;
t_bufproc_inproxy* obj = (t_bufproc_inproxy*)object_alloc(bufproc_inproxy_class);
dsp_setup((t_pxobject*)obj, 0);
outlet_new((t_object*)obj, "signal");
t_atom_long arg = atom_getlong(av);
obj->num = arg > 0 ? arg : 1;
return obj;
}
void bufproc_inproxy_free(t_bufproc_inproxy* x)
{
dsp_free((t_pxobject*)x);
}
void bufproc_inproxy_perform(t_bufproc_inproxy* x, t_object* dsp64,double** ins, long nIns, double **outs, long nOuts, long bs, long flags, void* user)
{
t_bufproc_binding* binding = x->binding;
long n = binding->offset + (binding->process_count * binding->num_chans);
long offset = binding->offset + binding->start_chan;
long stride = binding->num_chans;
t_float* samps = buffer_locksamples(binding->buf);
if(!samps) return;
t_double* output = outs[0];
//buffers are interleaved, so we have to use this strided copy
for(long i = offset, j=0; i < n; i+=stride, ++j)
output[j] = samps[i];
buffer_unlocksamples(binding->buf);
}
void bufproc_inproxy_dsp(t_bufproc_inproxy *x, t_object *chain, short* count, double sr, long bs, long flags)
{
if(x->binding && x->binding->buf)
object_method(chain,gensym("dsp_add64"),x,bufproc_inproxy_perform,0, NULL);
}
void* bufproc_outproxy_new(t_symbol* s, long ac, t_atom* av)
{
if(ac < 1) return NULL;
t_bufproc_outproxy* obj = (t_bufproc_outproxy*)object_alloc(bufproc_outproxy_class);
obj->num = atom_getlong(av);
dsp_setup((t_pxobject*)obj,1);
t_atom_long arg = atom_getlong(av);
obj->num = arg > 0 ? arg : 1;
return obj;
}
void bufproc_outproxy_free(t_bufproc_outproxy* x)
{
dsp_free((t_pxobject*)x);
}
void bufproc_outproxy_perform(t_bufproc_outproxy* x, t_object* dsp64,double** ins, long nIns,
double **outs, long nOuts, long bs, long flags, void* user)
{
t_bufproc_binding* binding = x->binding;
long n = binding->offset + (binding->process_count * binding->num_chans);
long offset = binding->offset + binding->start_chan;
long stride = binding->num_chans;
t_float* samps = buffer_locksamples(binding->buf);
if(!samps) return;
t_double* input = ins[0];
//buffers are interleaved, so we have to use this strided copy
for(long i = offset, j=0; i < n; i+=stride, ++j)
samps[i] += input[j];
buffer_unlocksamples(binding->buf);
}
void bufproc_outproxy_dsp(t_bufproc_outproxy *x, t_object *chain, short* count, double sr, long bs, long flags)
{
if(x->binding && x->binding->buf)
object_method(chain,gensym("dsp_add64"),x,bufproc_outproxy_perform,0, NULL);
}
void* bufproc_new(t_symbol* s, long ac, t_atom* av)
{
t_bufproc *x;
x = (t_bufproc *)object_alloc(bufproc_class);
x->num_ins = 1;
x->num_outs = 1;
x->processor = NULL;
attr_args_process(x, ac, av);
x->inputs = (t_buffer_ref**)sysmem_newptr(sizeof(t_buffer_ref*) * x->num_ins);
for(int i = 0; i < x->num_ins;++i)
{
x->inputs[i] = buffer_ref_new((t_object*)x, NULL);
}
if(x->num_ins > 1)
{
x->inlet_proxies = (void**)sysmem_newptr(sizeof(void*) * (x->num_ins - 1));
for(long i = x->num_ins - 2; i >= 0; --i)
{
x->inlet_proxies[i] = proxy_new(x, i+1, &x->inlet_num);
}
} else x->inlet_proxies = NULL;
//Make the output buffer
x->output_buffer = symbol_unique();
t_atom buffer_name;
atom_setsym(&buffer_name, x->output_buffer);
object_new_typed(CLASS_BOX, gensym("buffer~"), 1, &buffer_name);
x->output = buffer_ref_new((t_object*)x, x->output_buffer);
x->lambda_outlet = outlet_new(x,NULL);
x->resultOutlet = outlet_new(x, NULL);
x->input_bindings = (t_bufproc_binding*) sysmem_newptr(sizeof(t_bufproc_binding) * x->num_ins);
x->output_bindings = (t_bufproc_binding*) sysmem_newptr(sizeof(t_bufproc_binding) * x->num_outs);
x->working = 0;
return(x);
}
void bufproc_free(t_bufproc* x)
{
dsp_free((t_pxobject*)x);
if(x->inlet_proxies)
{
for(int i = 0; i < x->num_ins - 1;++i)
{
object_free(x->inlet_proxies[i]);
object_free(x->inputs[i]);
}
sysmem_freeptr(x->inlet_proxies);
}
object_free(x->output);
sysmem_freeptr(x->inputs);
sysmem_freeptr(x->input_bindings);
sysmem_freeptr(x->output_bindings);
}
void bufproc_anything(t_bufproc* x, t_symbol *s, long argc, t_atom *argv)
{
long inlet = proxy_getinlet((t_object*) x);
buffer_ref_set(x->inputs[inlet], s);
}
void* bufproc_subpatcher(t_bufproc *x, long index, void *arg)
{
if ((t_ptr_uint) arg > 1)
if (!NOGOOD(arg))
if (ob_sym(arg) == gensym("dspchain"))
return NULL;
return x->processor_copy;
}
t_max_err bufproc_patchlineupdate(t_bufproc *x, t_object *patchline, long updatetype, t_object *src, long srcout, t_object *dst, long dstin)
{
if((t_object*)x == src && srcout == 1)
{
switch(updatetype)
{
case JPATCHLINE_CONNECT:
x->processor = dst;
break;
case JPATCHLINE_DISCONNECT:
x->processor = NULL;
break;
}
}
return MAX_ERR_NONE;
}
t_ptr_int bufproc_connectionaccept(t_bufproc *src, t_object *dst, long srcout, long dstin, t_object *outlet, t_object *inlet)
{
t_symbol* dstclass = object_classname(dst);
//Connections can only be made from lambda outlet if not already connected, and target is a patcher
return (srcout == 0 || (src->processor == NULL && dstclass == gensym("jpatcher") && srcout == 1));
}
void map_io_objects(t_bufproc *x, t_object* patcher)
{
t_symbol* bufproc_inproxy_name = gensym("bufproc_inproxy");
t_symbol* bufproc_outproxy_name = gensym("bufproc_outproxy");
for (t_object* box = jpatcher_get_firstobject(patcher); box; box = jbox_get_nextobject(box))
{
t_object* obj = jbox_get_object(box);
t_symbol* cn = object_classname(obj);
if(cn == bufproc_inproxy_name)
{
t_bufproc_inproxy* inprox = (t_bufproc_inproxy*) obj;
long idx = inprox->num - 1;
if(idx < x->num_ins)
{
if(x->input_bindings[idx].buf)
{
inprox->binding = &x->input_bindings[idx];
}
}
else
object_error((t_object*)x, "Input %d out of range (%d)", idx + 1,x->num_ins);
}
if(cn == bufproc_outproxy_name)
{
t_bufproc_outproxy* outprox = (t_bufproc_outproxy*) obj;
long idx = outprox->num - 1;
if(idx < x->num_outs)
outprox->binding = &x->output_bindings[idx];
else
object_error((t_object*)x, "Output %d out of range (%d)", idx + 1,x->num_outs);
}
}
}
t_max_err bufproc_notify(t_bufproc *x, t_symbol *s, t_symbol *msg, void *sender, void *data)
{
for(int i = 0; i < x->num_ins; ++i) buffer_ref_notify(x->inputs[i], s, msg, sender, data);
buffer_ref_notify(x->output, s, msg, sender, data);
if(x->processor_copy)
{
object_method((t_object*)x->processor_copy,gensym("notify"),s,msg,sender,data);
}
return 0;
}
t_object* copy_patcher(t_object* patcher)
{
t_dictionary* d = dictionary_new();
object_method(patcher,gensym("appendtodictionary"),d);
t_symbol* name = symbol_unique();
t_object* p2 = (t_object*) jpatcher_load_fromdictionary(name->s_name, 0, (t_object*)d, 0, NULL);
object_free(d);
return p2;
}
void bufproc_docleanup (t_bufproc* x, t_symbol *s, short argc, t_atom *argv)
{
scheduler_set(x->old_scheduler);
sched_resume(); //TODO needed?
object_free(x->new_scheduler);
x->new_scheduler = NULL;
object_free(x->chain);
x->chain = NULL;
object_method(x->output_bindings[0].buf, gensym("dirty"));
object_free(x->processor_copy);
x->processor_copy = NULL;
outlet_anything(x->resultOutlet, x->output_buffer, 0, NULL);
ATOMIC_DECREMENT(&x->working);
}
void bufproc_dotick (t_bufproc* x, t_symbol *s, short argc, t_atom *argv)
{
t_atom_long i = x->hop_count;
double next_logical_time = x->shift * i;
//still work to do?
if(i <= x->num_hops)
{
t_atom_long process_count = i == x->num_hops ? x->remainder : 64;
for(int j = 0; j < x->num_ins; ++j)
{
x->input_bindings[j].offset = i * 64 * buffer_getchannelcount(x->input_bindings[j].buf);
x->input_bindings[j].process_count = process_count;
}
for(int j = 0; j < x->num_outs; ++j)
{
x->output_bindings[j].offset = i * 64 * buffer_getchannelcount(x->output_bindings[j].buf);
x->output_bindings[j].process_count = process_count;
}
dspchain_tick(x->chain);
scheduler_shift(x->new_scheduler,next_logical_time);
x->hop_count++;
}
//all done?
if(x->hop_count > x->num_hops)
{
defer(x,(method)bufproc_docleanup, NULL,0,NULL); //TODO: Must I defer this?
}
else //keep going
{
schedulef(x,(method)bufproc_dotick,next_logical_time, NULL,0,NULL);
}
}
int setSubAssoc(t_patcher *p, t_object *x)
{
t_object *assoc;
object_method(p, gensym("getassoc"), &assoc);
if (!assoc)
object_method(p, gensym("setassoc"), x);
return 0;
}
void bufproc_bang(t_bufproc *x)
{
if(x->working)
{
object_error((t_object*)x, "Still working");
return;
}
ATOMIC_INCREMENT(&x->working);
if(!x->processor)
{
object_error((t_object*)x, "No processing patch!");
return;
}
t_atom_long numframes = 0;
t_atom_float sample_rate = 0;
for(int i = 0; i < x->num_ins; ++i)
{
if(!buffer_ref_exists(x->inputs[i]))
{
object_error((t_object*)x, "No buffer~ found for input %d",i);
ATOMIC_DECREMENT(&x->working);
return;
}
x->input_bindings[i].buf = buffer_ref_getobject(x->inputs[i]);
if(!x->input_bindings[i].buf)
{
object_error((t_object*)x, "buffer~ at input %d not valid",i);
ATOMIC_DECREMENT(&x->working);
return;
}
}
//hygiene check
for(int i = 0; i < x->num_ins; ++i)
{
//For now, just use the minimum frame count and sr of first valid buf
t_buffer_obj* buf = x->input_bindings[i].buf;
if(!buf) continue;
x->input_bindings[i].num_chans = buffer_getchannelcount(buf);
t_atom_long f = buffer_getframecount(buf);
t_atom_float sr = buffer_getsamplerate(buf);
if(numframes == 0) numframes = f;
if(numframes > 0 && f != numframes)
{
object_warn((t_object*)x, "frame count mismatch at buffer~ %d",i);
if(f < numframes)
{
numframes = f;
object_warn((t_object *)x, "Going with new smaller frame count (%d)",numframes);
}
}
if(sample_rate > 0)
{
if(sample_rate != sr) object_warn((t_object *)x, "Sample rate mistmatch at input %d",i);
} else sample_rate = sr;
}
if(numframes == 0 || !(sample_rate > 0))
{
object_error((t_object *)x, "Failed to get valid frame count or sample rate from inputs");
ATOMIC_DECREMENT(&x->working);
return;
}
//resize output and set sr
t_buffer_obj* output_buffer_object = buffer_ref_getobject(x->output);
t_symbol* resize_msg = gensym("sizeinsamps");
t_atom outputsize[2];
atom_setlong(&outputsize[0], numframes);
atom_setlong(&outputsize[1], x->num_outs);
object_method_typed(output_buffer_object, resize_msg, 2, outputsize, NULL);
t_atom sr;
atom_setfloat(&sr, sample_rate);
t_symbol* sr_msg = gensym("sr");
object_method_typed(output_buffer_object, sr_msg, 1, &sr, NULL);
//make sure output is 0'd
t_symbol* clear_msg = gensym("clear");
object_method(output_buffer_object, clear_msg);
buffer_setdirty(output_buffer_object);
//initalize bindings
for(int i = 0; i < x->num_ins; ++i)
{
x->input_bindings[i].num_frames = numframes;
x->input_bindings[i].offset = 0;
x->input_bindings[i].process_count = 0;
x->input_bindings[i].start_chan = 0;
}
for(int i = 0; i < x->num_outs; ++i)
{
x->output_bindings[i].buf = buffer_ref_getobject(x->output);
x->output_bindings[i].offset = 0;
x->output_bindings[i].start_chan = i;
x->output_bindings[i].num_frames = numframes;
x->output_bindings[i].num_chans = buffer_getchannelcount(output_buffer_object);
}
//setup new scheduler
x->new_scheduler = scheduler_new();
x->old_scheduler = scheduler_set(x->new_scheduler);
//copy the processing patch
x->processor_copy = copy_patcher(x->processor);
object_method(x->processor_copy, gensym("setassoc"), x);
long result = 0;
object_method(x->processor_copy, gensym("traverse"), setSubAssoc, x, &result);
object_method(x->processor_copy, gensym("loadbang"));
//assign proxies from patcher
map_io_objects(x, x->processor_copy);
//compile DSP
x->chain = dspchain_compile(x->processor_copy, 64, sample_rate);
//setup processing loop
x->num_hops = numframes / 64;
x->remainder = numframes % 64;
x->shift = 64 * (1000/sample_rate);
x->hop_count = 0;
for(int i = 0; i < x->num_ins; ++i)
{
x->input_bindings[i].offset = 0;
x->input_bindings[i].process_count = 0;
}
for(int i = 0; i < x->num_outs; ++i)
{
x->output_bindings[i].offset = 0;
x->output_bindings[i].process_count = 0;
}
//we seem to need an initial tick to make the scheduler in the proecssing patch come alive. Don't underdtand why
dspchain_tick(x->chain);
schedulef(x, (method)bufproc_dotick, 0, NULL, 0, NULL);
}
<pre><code>
----------begin_max5_patcher----------
4253.3oc6cs0iihiE94p9UXEsR6tiRGgs459zrR6Kyaq1KOM8nRDvIgoIPVt
zUU8no+su9FI.IPbHgJPF5VJoBlPN9iiO93i+NG9smeZ1x32Hoy.+MvOCd5o
e64mdheH1AdR94mls08MuP2T9oMKh7Z7xec1bQSYj2x3GdGH0aCwOOjjTzVf
OuE5Y+IjcwAix2FDERx3WLM4A24l4sIHZ8KIDuLgvfrWXLG.Q3EZyAVP1qHz
BMvub35DmmcxKDUBDhuT9eZ1pfPxWIIoAwQrlfykG2c2tRG9oReEVm9Wi4WH
646OTPj3Pv8GJg70fhuOd+QcSncmLZeIOgvwf2L0mc3xD6SRhxC3WIwA+8mK
DINPG4tkjty0S7kY2OJZ9..owgDSN7XawdyzFd.fn2FWGF68EheI74oYw6HQ
AQ6RHojnL2LojuuYexJ27vrWVEGkkF7M9uOjB6mp8UR46jMx5Abg+umD3FtW
7WmD3GGwDhJ2GXGt3m6mAPCVug85gNC+Lhb2chuLU+hBJMzXJsSlmtzMgcaZ
YH+W.UzXVbbX0l1+8BIqxjMuKHJpFJlEuq4FSBVuoku6xXZiaa6ZyaI8k7HQ
quP0HxdI08qUQ6L2vP4XwpW92biB15lQxBD2BPZ6ajD4R6naR8RhCCqzeEs7
0SzhOUC2i7Zfe1F9OTYkA5oGrqPIZ196x9AqIoYUOVl65zpGIM6cAnW5P4Kk
ifeIircWHsWT6D1D+JU6xcYk6iUrgUdPbYaYUNdMaZzQaotqI6GhV1vF01QH
EM.TaEguCxnpyQqCIfMtgqJN3Z2fHpzH+T7pUojLvmfkubkrEBsK2PE6gnRM
bBahXKa9fd9HRpER85VEOgkQXoVDGN68cxAZylA9khV+8met3Ol2QTj7MeWu
u2Pu1nqcZjCah.CH2dmtQwqszk0tg8o1zLn1VRxZ5lrSm6u7atXaG9ang0s3
JS9WyAfP22+NvOeK.0DlX1Hl.OClv060M428sTQsG2FljFrNhMoT4+ZIcX8s
EqVlSMzG0DVnOqRWNgNeYF0rmvNbYK5WJVA03fEDycM.oW7ZWUft8.SKJQYf
kr+2DnAmckfhk.TDCttFUHNpLu162TTpMKOteKXaNc93FfIstaq0pzDLFvQi
wmk4qVQR9NPBLfeL0c6tTpqq55XmFfI8tpLULMrNerEz1TAXB0FLsJL1Mqez
hT.y903n07WBeegavpFPKbWQKc9XMK9z2PDZ.iUdwa2RWUzomcKI1iNfj10.
79XSC8rJ2vx7k0VWwkqpI7F.ZxWMLzTAq4ZenpOM.DHsNap1AK77QWLm+3bv
EcE1zkh5CnqDnADx9JmKSBPVCLODaaZKlQl4.wTlmd3SmWkfDTjybIbTbb31
Lvc6xfctM5nXmM7JmLuPOAq.jnU+JdHTZU6dMFRslBqVsu9oBuVCgXq4vrc9
Ps0T31pDxMEB6VsPuoiEdSpyAXcwaXCspX6oi.mBQgSkHwc1nwoXD4ZIpbpE
YtyDctyFgtyFktyDotyGstyFwNEhZmJQt6RhdWKQv6rQwq8H40dz7ZOhdsFU
ulhr2oitWCQ3Son7wOoltUeTr9paYotM4iZ+rSXUasVDe.D7IcCJPAfK3gBj
dHD3vQp+caXQFsNoViVwghUvKb8qgEaz9ramcFtJyxUcltqEcOZlupfK02L+
Z9Cz1BOZcNvFQPLV.c7kfXXeq.vZ9Sd+.Qpm3rkm7BUBou+16UB.WqAgqS3I
zxfsXDrF2UKHDpHfpM.Qp1F5VwYzNgTF1CVfRMqd+InBl3fWuMNSQzAvbDxA
8PYhK0yMj.9DjiiexTCXp0FXhZELMUFLcDtnpY7XAlTeHS2Dm8c.rUTDdiTI
M4S2ZCuUfnHXDCAjjspSehGcsQTrr8YLzZEL0U0NHRL7FpoJZhNGZdz93bzE
j42s+Kh0e7haVVRvx7Lg+iO0HNRWToWVvWKVyCUjqecehYe7E9Ha9s8ptPKV
PYbbh+K78Jt9RqKeBoumR8A9jm.aWk4W8iu3zuII4DJbxVdkvVLiT7Yfs1Bb
0+cbGp.VO5J916eqZOsR6UTduipxjHtpbKpwN2lYyYd+bQZw5WrV77OJM6tn
VShXaSI8G7EeNILLa7b7xSjK5cApgSYefM5iwO9KoKBOHSb8MNp0fTNt0vOO
qY2HOxKrnGcxwEsL7Lf041EGtOxNP0G.OhGGV3cMKxVmaYHsuJDMkmfFK7bT
S+gxMm5HYateitVjzfG9RqQINx6ns46x0BNN6wF7d8sa.zT6TJ14w5QASXLw
m7VsXWeDdVMbrgAQmKFY7tJ67ZGrSiyS7Jja4rnfiA.eRZVPzdyc+7AGGYmb
mzCtFADdIBH7NHfnKQ.Q2AADeIBH9CV.MuDcP3cPGTenOHAeABn9cP9PWf74
bGjOn9PW.uDDDdOtEWLvTMIDcOjPsKYpDy6kDpLFZbVIrTik2cbUVq4ucs6f
35v3ktgx8Eb+dU2vlL97wcfa.KMdk1MWEmrsINMYVkaWL5OsWPycLwZZZFNM
y6IC0n0ijhpF72LampWlJQ7oV9iP5xbYu+QR5P1Ja.KiWB9QN7jRcv4GEcqz
loN90xKHGQ3ZzruV5j0LZcHYkNx+6C.3oFpWBFOZHtc8Q3ML59HehJEhAkty
1WBF5HaTy1F3uKNHJSBQPnEeaxLj6vsf9eU+DTvdIMD+LYe5z8Us6ZeE1xcA
TuHYPmKTz5GoPU.RuOEBSEEBi9VHfJJDv9RHLTUonOQBcUEhdUJf0WscSRgc
eKEJgEF8sTnhtY8kjdakBsKP4r2FgnnLzqJEpd2.2nPHOXgu5myO8eqK4sa6
9kW2mbgvweUfQWUEE3yyBh1wbKzMxGv3rC8u+7rSUdAz6V4EvVrWdHioxKPc
NNOUdAlJu.SkWfSS43aal.q0WYBrgf2qvAah.e1xtPZPDv9hSeyykjqhTgQl
mY5NitjbkOoXQJtpq4zTBth5blCISVHIgsLLGoIsYBImmwlBWG.TAdIc5XvO
jluM8GnvXVLHaiaFvaiK0RXXCvnSmySXA4AEg0SvGSj1GVpapBvHzjJvksD2
nTJhP.TUKBPntA7nFDoWGfmaDXIAv9pDe5YkDmudCU1At.YxwdwU9CkvOAOO
zcbFP32+5.9w4mQg+o7OT.mAT+VCCiek3OmhkYfMto.+.FlxfSB8OntUPuXk
9AXKevKNWvD.zMNQrEkRKgutXwGv3AAbduRtZGoSt50lm7AN4pEYtnrKOkb0
M3yjDeFS4VM2TdOkb0RGIEzf2zdDka0xHHT3AfaXfKyLymmc6y1ZcTYiIX8o
zsteR2ZCCAzh0mR25ozsdJcqKeacDjt0mKAuBV+cvmzVXzmYxAFYcnpxZbyx
Pt8opw8m.4+v2YYcQafn8UmgbhkPHJBkBSyOXfn26dgjuCPskogV2HXTuzZc
evfwKJoqu5rSWhmxJM1nL6zUIqOt5bSGZ3zGo8wXUuS+ZwSc6CA6aTo0Qmp.
14xuARUfQXeC+Hae65SVKkUyFyiZmRYqAbJacOXwt8.W9rt.4y9NHeFWf7gu
CI71iTxFgmxghdLGJflNrbnvznq4Pggjs35+QKEJfGRgB3EmBEpskH3QZFTn
LgS0al79Z2YpvZ7QmcGpRdaTORP1+.j5Kl77YA4Hx0EjLUkp7I4duoyyQF9m
9PUQGDoRvDasGCr0doaZf2oHmMrajyVF3.qItYOwM6ItYOwM66.2rw81SoIr
vOZcM8w5SoolfrtS2SIjniuQKz397rYhS3nlxicLtqbvRW7zbPW7zOSef8nY
p0QQF80fHwTf5xRw3iyXHqYWGfHFAAO8tsLLFAcKnCL51RGXCArYHnc9HhNv
c+4pH1obW9wkNvP6qbRIIB8XQHXTmqpJESUKlLZTwH3szkozDdbEOi6rJONB
Nw82aE2egBvD5Hdv+J+zQ648D2em396D2eG4b+8RpezS7eaPx+MKqI9u0B+2
fSDfah.bSDf6Cl.bSDjZhfTCFBR01R1SIYfVCeLpyovqLxfxErOvhd74HMFp
UViYfgLViYi6JqwJhwiHevF0zF6rHoUKH4wpdWBJJzvdD.QkJKQUdpGVAhs6
bsJFK2hGby4k3vuvDgJpLQr5gcSXjS2KfEhHsoI1hCS73Lj0moLX2DGNwZWY
Dak3VuxhydYODQ586lHJe5.+.sIhHz0sKhHYFTNV2EweZEuTVIp+GxZYUHIZ
c1lTfaBArMHcK2QP+4ffLPdJQT6qn3Tv17sf+haXFaxQ1yKuTfWbdnOq9W8Z
h6t4f3DvNWe16KVr3ut.7uH6hSxX6XYX75.O.cNX5EKPTDn.KoW+kwIeg3u3
yQeN5m.anSSG8my.9wQDfaz6Yr6..2krxoUBw0m8oUIwaoBJ865l7d0tgrdk
kBdu5NdUo7Q4TCT7hCEg6mweLHxQGBsL0snNNYilq7gzMpnETdYqc1ltgoYY
tvHMsyxQ++3VapLJpMU36ylQ2Fj7eb+BgoVeH3xrhDGU0KiUX3hAHQ0PiJVx
xqGi1VrY3neEdbV.k.00zwSa3EPMWfe91sLUc52bAfMRQ7KUV2mdQRYWDwuj
.N4m5+cWbDeyNmS+81EHGLePDYGd6tfPZC+i+8+j+CljyK2c7BY2bfnhtIq1
cof3UU+cmSu8mVpx2ApG0K4NuTpVEKpvfGEFV4IJ6gTgTd1xA2TKFdTa.Lrj
co1RQNF5Q+H+wJIE9BeeN30fLJpAxiB9e4D.yEdAVS+1rM5RX8fg.qBRRyJt
c75FRTYvmd1LSPMZCABaPaW+5lawtDucfFOzUbMIEKPlnoJtVadlJAnoRtVE
TAZ4L5XXAyNozH4MuJqIIZgTYw1XhnESDsXhnESDsXbPzhaNgJN1K85N0jQW
eZeVJ0f1lGph5PCsSUjluS66+4.GQcZltda1MqT1xIp5H9wUE7icPF00JClJ
PqoUIJUHdr.VufMOZf1h.dxQ.PeV.vJfMApoOfTHm3qyXiuNSz0YhtNSz04C
ltNSzgYhNLUoCyE+fYQIxvfL3AQvPebQFlV4BCVyRSSyrkLBSMtvfQNO7bgA
eNpvbcLg4Q.BuOOftjb7PN9b5AzUyHjXSBG1OftFPUxrhsZPNxbHRBFF1bLQ
Xn8iil993M0Gq6nw2j95+ArVOrLVBuDd0Pc6NlBmgAdeoNbubcIYgubRsEV1
1HrAm1.bFDbrjrWUPsgBgj0tduWoiCqc0hipKGUdoxu9x0UNasEFbg8vq0+B
Mu+dE7GRSgmRYkB1+r7TREonb2IINm5XN+dkYk.ZNqbvwgmUAcNa9rrJKIf8
6xQquRRZFJK2HCd35TEubDX558k0bQV8Ju1vbLgCdXLl.NVGSHdLFLMj3wYH
A1bXLjPezNMg3wC1dNtMMlX7Ol.MLFSfG2SSTT9llFRz7PhdovH6nXYUEOUi
ee5JpwuXnnz85vs7isEzAr5mLfhQBhxArA7isH+ZoXssE0mEXWKUAbidTHTs
ZGa0ixvPnzayJhiJoRfw8sTnDTzmUeZrp2Pv8olIFqpTnUMC1usRglplJ5U8
Bkqa98oAKjxSQ3zmRgxiQ5SaVHkMZ0mVsPHEkBm9VHfp+3lneDB3kXsn2pY9
JOB4C28NkEMnoh92YH3nuzitZeBYTx+N9mldHNL8Pb3oa+CwAdmqdtJvDhZ4
lPsbR33bQn4bPndtGvghlx0fm+8m++7got3.
-----------end_max5_patcher-----------
</code></pre>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment