1446 lines
48 KiB
C++
1446 lines
48 KiB
C++
|
|
#include "PDClass_Base.h"
|
|
|
|
#include "FrameLib_Global.h"
|
|
#include "FrameLib_Context.h"
|
|
#include "FrameLib_Parameters.h"
|
|
#include "FrameLib_DSP.h"
|
|
#include "FrameLib_Multistream.h"
|
|
#include "FrameLib_SerialiseGraph.h"
|
|
|
|
#include "g_canvas.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
extern "C"
|
|
{
|
|
t_signal *signal_newfromcontext(int borrowed);
|
|
void signal_makereusable(t_signal *sig);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////// PD Globals Class ////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
class FrameLib_PDGlobals : public PDClass_Base
|
|
{
|
|
|
|
public:
|
|
|
|
// Sync Check Class
|
|
|
|
class SyncCheck
|
|
{
|
|
|
|
public:
|
|
|
|
enum Mode { kDownOnly, kDown, kAcross };
|
|
enum Action { kSyncComplete, kSync, kAttachAndSync };
|
|
|
|
SyncCheck() : mGlobal(get()), mObject(nullptr), mTime(-1), mMode(kDownOnly) {}
|
|
~SyncCheck() { mGlobal->release(); }
|
|
|
|
Action operator()(void *object, bool handlesAudio, bool isOutput)
|
|
{
|
|
const SyncCheck *info = mGlobal->getSyncCheck();
|
|
|
|
if (info && (info->mTime != mTime || info->mObject != mObject))
|
|
{
|
|
set(info->mObject, info->mTime, info->mMode);
|
|
return handlesAudio && object != mObject && (mMode != kAcross || isOutput) ? kAttachAndSync : kSync;
|
|
}
|
|
|
|
if (info && mMode == kAcross && info->mMode == kDown)
|
|
{
|
|
mMode = kDown;
|
|
return handlesAudio && object != mObject && !isOutput ? kAttachAndSync : kSync;
|
|
}
|
|
|
|
return kSyncComplete;
|
|
}
|
|
|
|
void sync(void *object = nullptr, long time = -1, Mode mode = kDownOnly)
|
|
{
|
|
set(object, time, mode);
|
|
mGlobal->setSyncCheck(object ? this : nullptr);
|
|
}
|
|
|
|
bool upwardsMode() { return setMode(mGlobal->getSyncCheck(), kAcross); }
|
|
void restoreMode() { setMode(mGlobal->getSyncCheck(), mMode); }
|
|
|
|
private:
|
|
|
|
void set(void *object, long time, Mode mode)
|
|
{
|
|
mObject = object;
|
|
mTime = time;
|
|
mMode = mode;
|
|
}
|
|
|
|
bool setMode(SyncCheck *info, Mode mode) { return info && info->mMode != kDownOnly && ((info->mMode = mode) == mode); }
|
|
|
|
FrameLib_PDGlobals *mGlobal;
|
|
void *mObject;
|
|
long mTime;
|
|
Mode mMode;
|
|
};
|
|
|
|
// ConnectionInfo Struct
|
|
|
|
struct ConnectionInfo
|
|
{
|
|
enum Mode { kConnect, kConfirm, kDoubleCheck };
|
|
|
|
ConnectionInfo(t_object *object, unsigned long index, Mode mode) : mObject(object), mIndex(index), mMode(mode) {}
|
|
|
|
t_object *mObject;
|
|
unsigned long mIndex;
|
|
Mode mMode;
|
|
|
|
};
|
|
|
|
// Convenience Pointer for automatic deletion and RAII
|
|
|
|
struct ManagedPointer
|
|
{
|
|
ManagedPointer() : mPointer(get()) {}
|
|
~ManagedPointer() { mPointer->release(); }
|
|
|
|
FrameLib_PDGlobals *operator->() { return mPointer; }
|
|
|
|
private:
|
|
|
|
// Deleted
|
|
|
|
ManagedPointer(const ManagedPointer&) = delete;
|
|
ManagedPointer& operator=(const ManagedPointer&) = delete;
|
|
|
|
FrameLib_PDGlobals *mPointer;
|
|
};
|
|
|
|
// Constructor and Destructor (public for the PD API, but use the ManagedPointer for use from outside this class)
|
|
|
|
FrameLib_PDGlobals(t_symbol *sym, long ac, t_atom *av)
|
|
: mGlobal(nullptr), mConnectionInfo(nullptr), mSyncCheck(nullptr) {}
|
|
~FrameLib_PDGlobals() { if (mGlobal) FrameLib_Global::release(&mGlobal); }
|
|
|
|
// Getters and setters for max global items
|
|
|
|
FrameLib_Global *getGlobal() const { return mGlobal; }
|
|
|
|
const ConnectionInfo *getConnectionInfo() const { return mConnectionInfo; }
|
|
void setConnectionInfo(ConnectionInfo *info = nullptr) { mConnectionInfo = info; }
|
|
|
|
SyncCheck *getSyncCheck() const { return mSyncCheck; }
|
|
void setSyncCheck(SyncCheck *check = nullptr) { mSyncCheck = check; }
|
|
|
|
private:
|
|
|
|
void retain() { FrameLib_Global::get(&mGlobal); }
|
|
void release() { FrameLib_Global::release(&mGlobal); }
|
|
|
|
static FrameLib_PDGlobals **getPDGlobalsPtr()
|
|
{
|
|
return (FrameLib_PDGlobals **) &gensym("__fl.pd_global_items")->s_thing;
|
|
}
|
|
|
|
// Get and release the max global items (singleton)
|
|
|
|
static FrameLib_PDGlobals *get()
|
|
{
|
|
// Make sure the max globals class exists
|
|
|
|
FrameLib_PDGlobals *x = *getPDGlobalsPtr();
|
|
|
|
if (!x)
|
|
{
|
|
makeClass<FrameLib_PDGlobals>("__fl.pd_global_items");
|
|
x = (FrameLib_PDGlobals *) pd_new(*FrameLib_PDGlobals::getClassPointer<FrameLib_PDGlobals>());
|
|
*getPDGlobalsPtr() = x;
|
|
}
|
|
|
|
if (x)
|
|
x->retain();
|
|
|
|
return x;
|
|
}
|
|
|
|
// Pointers
|
|
|
|
FrameLib_Global *mGlobal;
|
|
ConnectionInfo *mConnectionInfo;
|
|
SyncCheck *mSyncCheck;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
////////////////////// Mutator for Synchronisation ///////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
class Mutator : public PDClass_Base
|
|
{
|
|
|
|
public:
|
|
|
|
Mutator(t_symbol *sym, long ac, t_atom *av)
|
|
{
|
|
mObject = ac ? atom_getobj(av) : nullptr;
|
|
mMode = object_method(mObject, gensym("__fl.is_output")) ? FrameLib_PDGlobals::SyncCheck::kDownOnly : FrameLib_PDGlobals::SyncCheck::kDown;
|
|
}
|
|
|
|
static void classInit(t_class *c, const char *classname)
|
|
{
|
|
addMethod<Mutator, &Mutator::mutate>(c, "signal");
|
|
}
|
|
|
|
void mutate(t_symbol *sym, long ac, t_atom *av)
|
|
{
|
|
// FIX - clock_getlogicaltime??
|
|
mSyncChecker.sync(mObject, gettime(), mMode);
|
|
object_method(mObject, gensym("sync"));
|
|
mSyncChecker.sync();
|
|
}
|
|
|
|
private:
|
|
|
|
FrameLib_PDGlobals::SyncCheck mSyncChecker;
|
|
FrameLib_PDGlobals::SyncCheck::Mode mMode;
|
|
void *mObject;
|
|
};
|
|
*/
|
|
//////////////////////////////////////////////////////////////////////////
|
|
////////////////////// Wrapper for Synchronisation ///////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
template <class T>
|
|
class Wrapper : public PDClass_Base
|
|
{
|
|
|
|
public:
|
|
|
|
// Initialise Class
|
|
|
|
static t_method *sigMethodCache()
|
|
{
|
|
static t_method sigMethod;
|
|
|
|
return &sigMethod;
|
|
}
|
|
|
|
static void classInit(t_class *c, const char *classname)
|
|
{
|
|
addMethod<Wrapper<T>, &Wrapper<T>::subpatcher>(c, "subpatcher");
|
|
addMethod<Wrapper<T>, &Wrapper<T>::assist>(c, "assist");
|
|
addMethod<Wrapper<T>, &Wrapper<T>::anything>(c, "anything");
|
|
addMethod<Wrapper<T>, &Wrapper<T>::sync>(c, "sync");
|
|
addMethod<Wrapper<T>, &Wrapper<T>::dsp>(c);
|
|
addMethod(c, (t_method) &externalPatchLineUpdate, "patchlineupdate");
|
|
addMethod(c, (t_method) &externalConnectionAccept, "connectionaccept");
|
|
addMethod(c, (t_method) &externalWrapperInternalObject, "__fl.wrapper_internal_object");
|
|
|
|
// N.B. MUST add signal handling after dspInit to override the builtin responses
|
|
|
|
dspInit(c);
|
|
*sigMethodCache() = class_method(c, gensym("signal"));
|
|
addMethod<Wrapper<T>, &Wrapper<T>::anything>(c, "signal");
|
|
|
|
// Make sure the mutator class exists
|
|
|
|
const char mutatorClassName[] = "__fl.signal.mutator";
|
|
|
|
if (!class_findbyname(CLASS_NOBOX, gensym(mutatorClassName)))
|
|
Mutator::makeClass<Mutator>(CLASS_NOBOX, mutatorClassName);
|
|
}
|
|
|
|
// Constructor and Destructor
|
|
|
|
Wrapper(t_symbol *s, long argc, t_atom *argv)
|
|
{
|
|
// Create patcher (you must report this as a subpatcher to get audio working)
|
|
|
|
t_dictionary *d = dictionary_new();
|
|
t_atom a;
|
|
t_atom *av = nullptr;
|
|
long ac = 0;
|
|
|
|
atom_setparse(&ac, &av, "@defrect 0 0 300 300");
|
|
attr_args_dictionary(d, ac, av);
|
|
atom_setobj(&a, d);
|
|
mPatch = (t_object *)object_new_typed(CLASS_NOBOX, gensym("jpatcher"),1, &a);
|
|
|
|
// Get box text (and strip object name from the top - relace with stored name in case the object name is an alias)
|
|
|
|
t_object *textfield = nullptr;
|
|
const char *text = nullptr;
|
|
std::string newObjectText = accessClassName<Wrapper>()->c_str();
|
|
|
|
object_obex_lookup(this, gensym("#B"), &textfield);
|
|
|
|
if ((textfield = jbox_get_textfield(textfield)))
|
|
{
|
|
text = (char *)object_method(textfield, gensym("getptr"));
|
|
text = strchr(text, ' ');
|
|
|
|
if (text)
|
|
newObjectText += text;
|
|
}
|
|
|
|
// Make internal object
|
|
|
|
mObject = jbox_get_object((t_object *) newobject_sprintf(mPatch, "@maxclass newobj @text \"unsynced.%s\" @patching_rect 0 0 30 10", newObjectText.c_str()));
|
|
|
|
// Make Mutator (with argument referencing the internal object)
|
|
|
|
atom_setobj(&a, mObject);
|
|
mMutator = (t_object *) object_new_typed(CLASS_NOBOX, gensym("__fl.signal.mutator"), 1, &a);
|
|
|
|
// Free the dictionary
|
|
|
|
object_free(d);
|
|
|
|
// Get the object itself (typed)
|
|
|
|
T *internal = internalObject();
|
|
|
|
long numIns = internal->getNumIns() + (internal->supportsOrderingConnections() ? 1 : 0);
|
|
long numOuts = internal->getNumOuts();
|
|
long numAudioIns = internal->getNumAudioIns();
|
|
long numAudioOuts = internal->getNumAudioOuts();
|
|
|
|
internal->mUserObject = *this;
|
|
|
|
// Create I/O
|
|
|
|
mInOutlets.resize(numIns + numAudioIns - 1);
|
|
mProxyIns.resize(numIns + numAudioIns - 1);
|
|
mAudioOuts.resize(numAudioOuts - 1);
|
|
mOuts.resize(numOuts);
|
|
|
|
// Inlets for messages/signals (we need one audio in for the purposes of sync)
|
|
|
|
dspSetup(1);
|
|
|
|
for (long i = numIns + numAudioIns - 2; i >= 0 ; i--)
|
|
{
|
|
mInOutlets[i] = (t_object *) outlet_new(nullptr, nullptr);
|
|
mProxyIns[i] = (t_object *) (i ? proxy_new(this, i, &mProxyNum) : nullptr);
|
|
}
|
|
|
|
// Outlets for messages/signals
|
|
|
|
for (long i = numOuts - 1; i >= 0 ; i--)
|
|
mOuts[i] = (t_object *) outlet_new(this, nullptr);
|
|
for (long i = numAudioOuts - 2; i >= 0 ; i--)
|
|
mAudioOuts[i] = (t_object *) outlet_new(this, "signal");
|
|
|
|
// Connect first signal outlet to the mutator
|
|
|
|
outlet_add(outlet_nth(mObject, 0), inlet_nth(mMutator, 0));
|
|
|
|
// Connect inlets (all types)
|
|
|
|
for (long i = 0; i < numAudioIns + numIns - 1; i++)
|
|
outlet_add(mInOutlets[i], inlet_nth(mObject, i + 1));
|
|
|
|
// Connect non-audio outlets
|
|
|
|
for (long i = 0; i < numOuts; i++)
|
|
outlet_add(outlet_nth(mObject, i + numAudioOuts), mOuts[i]);
|
|
}
|
|
|
|
~Wrapper()
|
|
{
|
|
// Delete ins and proxies
|
|
|
|
for (auto it = mProxyIns.begin(); it != mProxyIns.end(); it++)
|
|
object_free(*it);
|
|
|
|
for (auto it = mInOutlets.begin(); it != mInOutlets.end(); it++)
|
|
object_free(*it);
|
|
|
|
// Free objects - N.B. - free the patch, but not the object within it (which will be freed by deleting the patch)
|
|
|
|
object_free(mMutator);
|
|
object_free(mPatch);
|
|
}
|
|
|
|
// Standard methods
|
|
|
|
void *subpatcher(long index, void *arg)
|
|
{
|
|
return ((t_ptr_uint) arg > 1 && !NOGOOD(arg) && index == 0) ? (void *) mPatch : nullptr;
|
|
}
|
|
|
|
void assist(void *b, long m, long a, char *s)
|
|
{
|
|
internalObject()->assist(b, m, a + 1, s);
|
|
}
|
|
|
|
void sync()
|
|
{
|
|
// Must set the order of the wrapper after the internal object before calling internal sync
|
|
|
|
(*sigMethodCache())(this);
|
|
|
|
internalObject()->sync();
|
|
}
|
|
|
|
void dsp(t_object *dsp64, short *count, double samplerate, long maxvectorsize, long flags)
|
|
{
|
|
if (internalObject()->getType() == kOutput)
|
|
addPerform<Wrapper, &Wrapper<T>::perform>(dsp64);
|
|
}
|
|
|
|
void perform(t_object *dsp64, double **ins, long numins, double **outs, long numouts, long vec_size, long flags, void *userparam)
|
|
{
|
|
std::vector<double*> &internalOuts = internalObject()->getAudioOuts();
|
|
|
|
// Copy to output
|
|
|
|
for (long i = 0; i < internalOuts.size(); i++)
|
|
std::copy(internalOuts[i], internalOuts[i] + vec_size, outs[i]);
|
|
}
|
|
|
|
void anything(t_symbol *sym, long ac, t_atom *av)
|
|
{
|
|
outlet_anything(mInOutlets[getInlet()], sym, ac, av);
|
|
}
|
|
|
|
// External methods (A_CANT)
|
|
|
|
static t_max_err externalPatchLineUpdate(Wrapper *x, t_object *patchline, long updatetype, t_object *src, long srcout, t_object *dst, long dstin)
|
|
{
|
|
if ((t_object *) x == dst)
|
|
return T::externalPatchLineUpdate(x->internalObject(), patchline, updatetype, src, srcout, x->mObject, dstin + 1);
|
|
else
|
|
return T::externalPatchLineUpdate(x->internalObject(), patchline, updatetype, x->mObject, srcout + 1, dst, dstin);
|
|
}
|
|
|
|
static t_ptr_int externalConnectionAccept(Wrapper *src, t_object *dst, long srcout, long dstin, t_object *outlet, t_object *inlet)
|
|
{
|
|
// Only called for sources / account for internal sync connections
|
|
|
|
return T::externalConnectionAccept(src->internalObject(), dst, srcout + 1, dstin, outlet, inlet);
|
|
}
|
|
|
|
static void *externalWrapperInternalObject(Wrapper *x)
|
|
{
|
|
return x->mObject;
|
|
}
|
|
|
|
private:
|
|
|
|
T *internalObject() { return (T *) mObject; }
|
|
|
|
// Objects (need freeing except the internal object which is owned by the patch)
|
|
|
|
t_object *mPatch;
|
|
t_object *mObject;
|
|
t_object *mMutator;
|
|
|
|
// Inlets (must be freed)
|
|
|
|
std::vector<t_object *> mInOutlets;
|
|
std::vector<t_object *> mProxyIns;
|
|
|
|
// Outlets (don't need to free)
|
|
|
|
std::vector<t_object *> mAudioOuts;
|
|
std::vector<t_object *> mOuts;
|
|
|
|
// Dummy for stuffloc on proxies
|
|
|
|
long mProxyNum;
|
|
};
|
|
*/
|
|
//////////////////////////////////////////////////////////////////////////
|
|
/////////////////////// FrameLib PD Object Class /////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
enum PDObjectArgsMode { kAsParams, kAllInputs, kDistribute };
|
|
|
|
struct FrameLib_PDProxy : public virtual FrameLib_Proxy
|
|
{
|
|
t_object *mMaxObject;
|
|
};
|
|
|
|
template <class T, PDObjectArgsMode argsMode = kAsParams>
|
|
class FrameLib_PDClass : public PDClass_Base
|
|
{
|
|
typedef FrameLib_Object<FrameLib_Multistream>::Connection FrameLibConnection;
|
|
typedef FrameLib_Object<t_object>::Connection PDConnection;
|
|
typedef FrameLib_PDGlobals::ConnectionInfo ConnectionInfo;
|
|
|
|
static t_atomtype atom_gettype(t_atom* a) { return a->a_type; }
|
|
|
|
struct PDProxy : public PDClass_Base
|
|
{
|
|
static t_pd *create(FrameLib_PDClass *owner, int index)
|
|
{
|
|
t_pd *proxy = pd_new(*PDProxy::getClassPointer<PDProxy>());
|
|
|
|
((PDProxy *)proxy)->mOwner = owner;
|
|
((PDProxy *)proxy)->mIndex = index;
|
|
|
|
return proxy;
|
|
}
|
|
|
|
static void classInit(t_class *c, const char *classname)
|
|
{
|
|
addMethod<PDProxy, &PDProxy::frame>(c, "frame");
|
|
}
|
|
|
|
PDProxy(t_symbol *sym, long ac, t_atom *av) : mOwner(nullptr), mIndex(-1) {}
|
|
|
|
void frame()
|
|
{
|
|
mOwner->frameInlet(mIndex);
|
|
}
|
|
|
|
FrameLib_PDClass *mOwner;
|
|
int mIndex;
|
|
};
|
|
|
|
public:
|
|
|
|
// Class Initialisation (must explicitly give U for classes that inherit from FrameLib_PDClass<>)
|
|
|
|
template <class U = FrameLib_PDClass<T, argsMode>>
|
|
static void makeClass(const char *className)
|
|
{
|
|
// If handles audio/scheduler then make wrapper class and name the inner object differently..
|
|
|
|
std::string internalClassName = className;
|
|
std::string proxyClassName;
|
|
|
|
if (T::handlesAudio())
|
|
{
|
|
//Wrapper<U>:: template makeClass<Wrapper<U>>(CLASS_BOX, className);
|
|
internalClassName.insert(0, "unsynced.");
|
|
}
|
|
|
|
proxyClassName.append(".proxy");
|
|
PDClass_Base::makeClass<U>(internalClassName.c_str());
|
|
PDClass_Base::makeClass<PDProxy>(proxyClassName.c_str());
|
|
}
|
|
|
|
static void classInit(t_class *c, const char *classname)
|
|
{
|
|
addMethod<FrameLib_PDClass<T>, &FrameLib_PDClass<T>::info>(c, "info");
|
|
addMethod<FrameLib_PDClass<T>, &FrameLib_PDClass<T>::frame>(c, "frame");
|
|
addMethod<FrameLib_PDClass<T>, &FrameLib_PDClass<T>::sync>(c, "sync");
|
|
addMethod<FrameLib_PDClass<T>, &FrameLib_PDClass<T>::dsp>(c);
|
|
addMethod(c, (t_method) &externalResolveConnections, "__fl.resolve_connections");
|
|
addMethod(c, (t_method) &externalAutoOrderingConnections, "__fl.auto_ordering_connections");
|
|
addMethod(c, (t_method) &externalClearAutoOrderingConnections, "__fl.clear_auto_ordering_connections");
|
|
addMethod(c, (t_method) &externalIsConnected, "__fl.is_connected");
|
|
addMethod(c, (t_method) &externalConnectionConfirm, "__fl.connection_confirm");
|
|
addMethod(c, (t_method) &externalGetInternalObject, "__fl.get_internal_object");
|
|
addMethod(c, (t_method) &externalIsOutput, "__fl.is_output");
|
|
addMethod(c, (t_method) &externalGetNumAudioIns, "__fl.get_num_audio_ins");
|
|
addMethod(c, (t_method) &externalGetNumAudioOuts, "__fl.get_num_audio_outs");
|
|
class_addmethod(c, (t_method) &codeexport, gensym("export"), A_SYMBOL, A_SYMBOL, 0);
|
|
|
|
dspInit(c);
|
|
}
|
|
|
|
// Constructor and Destructor
|
|
|
|
FrameLib_PDClass(t_symbol *s, long argc, t_atom *argv, FrameLib_PDProxy *proxy = new FrameLib_PDProxy()) : mFrameLibProxy(proxy), mConfirmObject(nullptr), mConfirmInIndex(-1), mConfirmOutIndex(-1), mConfirm(false), mCanvas(canvas_getcurrent()), mSyncIn(nullptr), mNeedsResolve(true), mUserObject(*this)
|
|
{
|
|
// Object creation with parameters and arguments (N.B. the object is not a member due to size restrictions)
|
|
|
|
unsigned long nStreams = 1;
|
|
|
|
if (argc && getStreamCount(argv))
|
|
{
|
|
nStreams = getStreamCount(argv);
|
|
argv++;
|
|
argc--;
|
|
}
|
|
|
|
FrameLib_Parameters::AutoSerial serialisedParameters;
|
|
parseParameters(serialisedParameters, argc, argv);
|
|
mFrameLibProxy->mMaxObject = *this;
|
|
mObject.reset(new T(FrameLib_Context(mGlobal->getGlobal(), mCanvas), &serialisedParameters, mFrameLibProxy.get(), nStreams));
|
|
parseInputs(argc, argv);
|
|
|
|
long numIns = getNumIns() + (supportsOrderingConnections() ? 1 : 0);
|
|
|
|
mInputs.resize(numIns);
|
|
mOutputs.resize(getNumOuts());
|
|
|
|
dspSetup(getNumAudioIns(), getNumAudioOuts());
|
|
|
|
// Create frame inlets - N.B. - we create a proxy if the inlet is not the first inlet (not the first frame input or the object handles audio)
|
|
|
|
for (long i = 0; i < numIns; i++)
|
|
{
|
|
if (i || handlesAudio())
|
|
{
|
|
mInputs[i] = PDProxy::create(this, (int) (getNumAudioIns() + i));
|
|
inlet_new(*this, mInputs[i], gensym("frame"), gensym("frame"));
|
|
}
|
|
else
|
|
mInputs[i] = nullptr;
|
|
}
|
|
|
|
// Create frame outlets
|
|
|
|
for (unsigned long i = 0; i < getNumOuts(); i++)
|
|
mOutputs[i] = outlet_new(*this, gensym("frame"));
|
|
|
|
// Add a sync outlet if we need to handle audio
|
|
|
|
if (handlesAudio())
|
|
{
|
|
//mSyncIn = (t_object *) outlet_new(nullptr, nullptr);
|
|
//outlet_add(mSyncIn, inlet_nth(*this, 0));
|
|
}
|
|
}
|
|
|
|
~FrameLib_PDClass()
|
|
{
|
|
for (auto it = mInputs.begin(); it != mInputs.end(); it++)
|
|
if (*it)
|
|
pd_free(*it);
|
|
|
|
//object_free(mSyncIn);
|
|
}
|
|
|
|
static void codeexport(FrameLib_PDClass *x, t_symbol *className, t_symbol *path)
|
|
{
|
|
char conformedPath[MAXPDSTRING];
|
|
|
|
if (strlen(path->s_name) > MAXPDSTRING)
|
|
conformedPath[0] = 0;
|
|
else
|
|
sys_bashfilename(path->s_name, conformedPath);
|
|
|
|
ExportError error = exportGraph(x->mObject.get(), conformedPath, className->s_name);
|
|
|
|
if (error == kExportPathError)
|
|
pd_error(x->mUserObject, "couldn't write to or find specified path");
|
|
else if (error == kExportWriteError)
|
|
pd_error(x->mUserObject, "couldn't write file");
|
|
}
|
|
|
|
void info(t_symbol *sym, long ac, t_atom *av)
|
|
{
|
|
// Determine what to post
|
|
|
|
enum InfoFlags { kInfoDesciption = 0x01, kInfoInputs = 0x02, kInfoOutputs = 0x04, kInfoParameters = 0x08 };
|
|
bool verbose = true;
|
|
long flags = 0;
|
|
|
|
while (ac--)
|
|
{
|
|
t_symbol *type = atom_getsymbol(av++);
|
|
|
|
if (type == gensym("description")) flags |= kInfoDesciption;
|
|
else if (type == gensym("inputs")) flags |= kInfoInputs;
|
|
else if (type == gensym("outputs")) flags |= kInfoOutputs;
|
|
else if (type == gensym("io")) flags |= kInfoInputs | kInfoOutputs;
|
|
else if (type == gensym("parameters")) flags |= kInfoParameters;
|
|
else if (type == gensym("quick")) verbose = false;
|
|
}
|
|
|
|
flags = !flags ? (kInfoDesciption | kInfoInputs | kInfoOutputs | kInfoParameters) : flags;
|
|
|
|
// Start Tag
|
|
|
|
// FIX - user object should be used here....
|
|
|
|
post("********* %s *********", class_getname(*getClassPointer<FrameLib_PDClass>()));
|
|
|
|
// Description
|
|
|
|
if (flags & kInfoDesciption)
|
|
{
|
|
post("--- Description ---");
|
|
postSplit(mObject->objectInfo(verbose).c_str(), "", "-");
|
|
}
|
|
|
|
// IO
|
|
|
|
if (flags & kInfoInputs)
|
|
{
|
|
post("--- Input List ---");
|
|
if (argsMode == kAllInputs)
|
|
post("N.B. - arguments set the fixed array values for all inputs.");
|
|
if (argsMode == kDistribute)
|
|
post("N.B - arguments are distributed one per input.");
|
|
for (long i = 0; i < mObject->getNumAudioIns(); i++)
|
|
post("Audio Input %ld: %s", i + 1, mObject->audioInfo(i, verbose).c_str());
|
|
for (long i = 0; i < mObject->getNumIns(); i++)
|
|
post("Frame Input %ld [%s]: %s", i + 1, frameTypeString(mObject->inputType(i)), mObject->inputInfo(i, verbose).c_str());
|
|
if (supportsOrderingConnections())
|
|
post("Ordering Input [%s]: Connect to ensure ordering", frameTypeString(kFrameAny));
|
|
}
|
|
|
|
if (flags & kInfoOutputs)
|
|
{
|
|
post("--- Output List ---");
|
|
for (long i = 0; i < mObject->getNumAudioOuts(); i++)
|
|
post("Audio Output %ld: %s", i + 1, mObject->audioInfo(i, verbose).c_str());
|
|
for (long i = 0; i < mObject->getNumOuts(); i++)
|
|
post("Frame Output %ld [%s]: %s", i + 1, frameTypeString(mObject->outputType(i)), mObject->outputInfo(i, verbose).c_str());
|
|
}
|
|
|
|
// Parameters
|
|
|
|
if (flags & kInfoParameters)
|
|
{
|
|
post("--- Parameter List ---");
|
|
|
|
const FrameLib_Parameters *params = mObject->getParameters();
|
|
if (!params || !params->size()) post("< No Parameters >");
|
|
|
|
// Loop over parameters
|
|
|
|
for (long i = 0; params && i < params->size(); i++)
|
|
{
|
|
FrameLib_Parameters::Type type = params->getType(i);
|
|
FrameLib_Parameters::NumericType numericType = params->getNumericType(i);
|
|
std::string defaultStr = params->getDefaultString(i);
|
|
|
|
// Name, type and default value
|
|
|
|
if (defaultStr.size())
|
|
post("Parameter %ld: %s [%s] (default: %s)", i + 1, params->getName(i).c_str(), params->getTypeString(i).c_str(), defaultStr.c_str());
|
|
else
|
|
post("Parameter %ld: %s [%s]", i + 1, params->getName(i).c_str(), params->getTypeString(i).c_str());
|
|
|
|
// Verbose - arguments, range (for numeric types), enum items (for enums), array sizes (for arrays), description
|
|
|
|
if (verbose)
|
|
{
|
|
if (argsMode == kAsParams && params->getArgumentIdx(i) >= 0)
|
|
post("- Argument: %ld", params->getArgumentIdx(i) + 1);
|
|
if (numericType == FrameLib_Parameters::kNumericInteger || numericType == FrameLib_Parameters::kNumericDouble)
|
|
{
|
|
switch (params->getClipMode(i))
|
|
{
|
|
case FrameLib_Parameters::kNone: break;
|
|
case FrameLib_Parameters::kMin: post("- Min Value: %lg", params->getMin(i)); break;
|
|
case FrameLib_Parameters::kMax: post("- Max Value: %lg", params->getMax(i)); break;
|
|
case FrameLib_Parameters::kClip: post("- Clipped: %lg-%lg", params->getMin(i), params->getMax(i)); break;
|
|
}
|
|
}
|
|
if (type == FrameLib_Parameters::kEnum)
|
|
for (long j = 0; j <= params->getMax(i); j++)
|
|
post(" [%ld] - %s", j, params->getItemString(i, j).c_str());
|
|
else if (type == FrameLib_Parameters::kArray)
|
|
post("- Array Size: %ld", params->getArraySize(i));
|
|
else if (type == FrameLib_Parameters::kVariableArray)
|
|
post("- Array Max Size: %ld", params->getArrayMaxSize(i));
|
|
postSplit(params->getInfo(i).c_str(), "- ", "-");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// IO Helpers
|
|
|
|
bool supportsOrderingConnections() { return mObject->supportsOrderingConnections(); }
|
|
|
|
bool handlesAudio() { return T::handlesAudio(); }
|
|
|
|
long getNumAudioIns() { return (long) mObject->getNumAudioIns() + (handlesAudio() ? 1 : 0); }
|
|
long getNumAudioOuts() { return (long) mObject->getNumAudioOuts() + (handlesAudio() ? 1 : 0); }
|
|
long getNumIns() { return (long) mObject->getNumIns(); }
|
|
long getNumOuts() { return (long) mObject->getNumOuts(); }
|
|
|
|
// Perform and DSP
|
|
|
|
void perform(int vec_size)
|
|
{
|
|
// FIX - use alloca
|
|
|
|
// Copy Audio In
|
|
|
|
for (int i = 0; i < (getNumAudioIns() - 1); i++)
|
|
for (int j = 0; j < vec_size; j++)
|
|
mSigIns[i][j] = getAudioIn(i + 1)[j];
|
|
|
|
// N.B. Plus one due to sync inputs
|
|
|
|
mObject->blockUpdate(mSigIns.data(), mSigOuts.data(), vec_size);
|
|
|
|
for (int i = 0; i < (getNumAudioOuts() - 1); i++)
|
|
for (int j = 0; j < vec_size; j++)
|
|
getAudioOut(i + 1)[j] = mSigOuts[i][j];
|
|
}
|
|
|
|
void dsp(t_signal **sp)
|
|
{
|
|
// Resolve connections (in case there are no schedulers left in the patch) and mark unresolved for next time
|
|
|
|
iterateCanvas(mCanvas, gensym("__fl.resolve_connections"));
|
|
//resolveConnections();
|
|
mNeedsResolve = true;
|
|
|
|
// Reset DSP
|
|
|
|
t_signal *temp = signal_newfromcontext(0);
|
|
double samplingRate = temp->s_sr;
|
|
int vec_size = temp->s_vecsize;
|
|
signal_makereusable(temp);
|
|
|
|
mObject->reset(samplingRate, vec_size);
|
|
|
|
// Add a perform routine to the chain if the object handles audio
|
|
|
|
if (handlesAudio())
|
|
{
|
|
addPerform<FrameLib_PDClass, &FrameLib_PDClass<T>::perform>(sp);
|
|
|
|
mTemp.resize(vec_size * (getNumAudioIns() + getNumAudioOuts() -2));
|
|
mSigIns.resize(getNumAudioIns() - 1);
|
|
mSigOuts.resize(getNumAudioOuts() - 1);
|
|
|
|
double *inVecs = mTemp.data();
|
|
double *outVecs = inVecs + ((getNumAudioIns() - 1) * vec_size);
|
|
|
|
for (int i = 0; i < getNumAudioIns() - 1; i++)
|
|
mSigIns[i] = inVecs + (i * vec_size);
|
|
for (int i = 0; i < getNumAudioOuts() - 1; i++)
|
|
mSigOuts[i] = outVecs + (i * vec_size);
|
|
}
|
|
}
|
|
|
|
// Get Audio Outputs
|
|
|
|
std::vector<double *> &getAudioOuts()
|
|
{
|
|
return mSigOuts;
|
|
}
|
|
|
|
// Type
|
|
|
|
ObjectType getType()
|
|
{
|
|
return mObject->getType();
|
|
}
|
|
|
|
// Audio Synchronisation
|
|
|
|
void sync()
|
|
{
|
|
FrameLib_PDGlobals::SyncCheck::Action action = mSyncChecker(this, handlesAudio(), externalIsOutput(this));
|
|
|
|
if (action != FrameLib_PDGlobals::SyncCheck::kSyncComplete && handlesAudio() && mNeedsResolve)
|
|
{
|
|
iterateCanvas(mCanvas, gensym("__fl.resolve_connections"));
|
|
iterateCanvas(mCanvas, gensym("__fl.clear_auto_ordering_connections"));
|
|
iterateCanvas(mCanvas, gensym("__fl.auto_ordering_connections"));
|
|
}
|
|
|
|
// FIX
|
|
|
|
//if (action == FrameLib_PDGlobals::SyncCheck::kAttachAndSync)
|
|
// outlet_anything(mSyncIn, gensym("signal"), 0, nullptr);
|
|
|
|
if (action != FrameLib_PDGlobals::SyncCheck::kSyncComplete)
|
|
{
|
|
for (unsigned long i = getNumOuts(); i > 0; i--)
|
|
outlet_anything(mOutputs[i - 1], gensym("sync"), 0, nullptr);
|
|
|
|
if (mSyncChecker.upwardsMode())
|
|
{
|
|
for (unsigned long i = 0; i < getNumIns(); i++)
|
|
if (isConnected(i))
|
|
callMethod(getConnection(i).mObject, gensym("sync"));
|
|
|
|
if (supportsOrderingConnections())
|
|
for (unsigned long i = 0; i < getNumOrderingConnections(); i++)
|
|
callMethod(getOrderingConnection(i).mObject, gensym("sync"));
|
|
|
|
mSyncChecker.restoreMode();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Connection Routines
|
|
|
|
void frame()
|
|
{
|
|
frameInlet(0);
|
|
}
|
|
|
|
void frameInlet(long index)
|
|
{
|
|
const ConnectionInfo *info = mGlobal->getConnectionInfo();
|
|
index -= getNumAudioIns();
|
|
|
|
if (!info)
|
|
return;
|
|
|
|
switch (info->mMode)
|
|
{
|
|
case ConnectionInfo::kConnect:
|
|
connect(info->mObject, info->mIndex, index);
|
|
break;
|
|
|
|
case ConnectionInfo::kConfirm:
|
|
case ConnectionInfo::kDoubleCheck:
|
|
|
|
if (index == mConfirmInIndex && mConfirmObject == info->mObject && mConfirmOutIndex == info->mIndex)
|
|
{
|
|
mConfirm = true;
|
|
if (info->mMode == ConnectionInfo::kDoubleCheck)
|
|
pd_error(mUserObject, "extra connection to input %ld", index + 1);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// External methods (A_CANT)
|
|
|
|
static void externalResolveConnections(FrameLib_PDClass *x)
|
|
{
|
|
x->resolveConnections();
|
|
}
|
|
|
|
static void externalAutoOrderingConnections(FrameLib_PDClass *x)
|
|
{
|
|
x->mObject->autoOrderingConnections();
|
|
}
|
|
|
|
static void externalClearAutoOrderingConnections(FrameLib_PDClass *x)
|
|
{
|
|
x->mObject->clearAutoOrderingConnections();
|
|
}
|
|
|
|
static uintptr_t externalIsConnected(FrameLib_PDClass *x, unsigned long index)
|
|
{
|
|
return x->confirmConnection(index, ConnectionInfo::kConfirm);
|
|
}
|
|
|
|
static void externalConnectionConfirm(FrameLib_PDClass *x, unsigned long index, FrameLib_PDGlobals::ConnectionInfo::Mode mode)
|
|
{
|
|
x->makeConnection(index, mode);
|
|
}
|
|
|
|
static FrameLib_Multistream *externalGetInternalObject(FrameLib_PDClass *x)
|
|
{
|
|
return x->mObject.get();
|
|
}
|
|
|
|
static uintptr_t externalIsOutput(FrameLib_PDClass *x)
|
|
{
|
|
return x->handlesAudio() && (x->getNumAudioOuts() > 1);
|
|
}
|
|
|
|
static uintptr_t externalGetNumAudioIns(FrameLib_PDClass *x)
|
|
{
|
|
return x->getNumAudioIns();
|
|
}
|
|
|
|
static uintptr_t externalGetNumAudioOuts(FrameLib_PDClass *x)
|
|
{
|
|
return x->getNumAudioOuts();
|
|
}
|
|
|
|
static uintptr_t intMethod(t_object *object, t_symbol *methodName)
|
|
{
|
|
// FIX - look at vmess
|
|
|
|
typedef uintptr_t (*func)(t_object *);
|
|
|
|
t_gotfn f = zgetfn(&object->ob_pd, methodName);
|
|
|
|
if (!f)
|
|
return 0;
|
|
|
|
func f2 = (func)f;
|
|
return f2(object);
|
|
}
|
|
|
|
static void *ptrMethod(t_object *object, t_symbol *methodName)
|
|
{
|
|
// FIX - look at vmess
|
|
|
|
typedef void *(*func)(t_object *);
|
|
|
|
t_gotfn f = zgetfn(&object->ob_pd, methodName);
|
|
|
|
if (!f)
|
|
return 0;
|
|
|
|
func f2 = (func)f;
|
|
return f2(object);
|
|
}
|
|
|
|
static void callMethod(t_object *object, t_symbol *methodName)
|
|
{
|
|
//vmess((t_pd *) g, method, "");
|
|
|
|
// FIX - look at vmess
|
|
|
|
typedef void (*func)(t_object *);
|
|
|
|
t_gotfn f = zgetfn(&object->ob_pd, methodName);
|
|
|
|
if (!f)
|
|
return;
|
|
|
|
func f2 = (func)f;
|
|
return f2(object);
|
|
}
|
|
|
|
static void confirmMethod(t_object *object, t_symbol *methodName, long index, ConnectionInfo::Mode mode)
|
|
{
|
|
//vmess((t_pd *) g, method, "");
|
|
|
|
// FIX - look at vmess
|
|
|
|
typedef void (*func)(t_object *, long, ConnectionInfo::Mode);
|
|
|
|
t_gotfn f = zgetfn(&object->ob_pd, methodName);
|
|
|
|
if (!f)
|
|
return;
|
|
|
|
func f2 = (func)f;
|
|
return f2(object, index, mode);
|
|
}
|
|
|
|
private:
|
|
|
|
// Unwrapping connections
|
|
|
|
void unwrapConnection(t_object *& object, long& connection)
|
|
{
|
|
/*t_object *wrapped = (t_object *) object_method(object, gensym("__fl.wrapper_internal_object"));
|
|
|
|
if (wrapped)
|
|
{
|
|
object = wrapped;
|
|
connection++;
|
|
}*/
|
|
}
|
|
|
|
// Get an internal object from a generic pointer safely
|
|
|
|
FrameLib_Multistream *getInternalObject(t_object *x)
|
|
{
|
|
return (FrameLib_Multistream *) ptrMethod(x, gensym("__fl.get_internal_object"));
|
|
}
|
|
|
|
// Private connection methods
|
|
|
|
void iterateCanvas(t_glist *gl, t_symbol *method)
|
|
{
|
|
// Search for subpatchers, and call method on objects that don't have subpatchers
|
|
|
|
for (t_gobj *g = gl->gl_list; g; g = g->g_next)
|
|
{
|
|
if (zgetfn((t_pd *) g, method))
|
|
mess0((t_pd *) g, method);
|
|
}
|
|
}
|
|
|
|
void resolveConnections()
|
|
{
|
|
if (mNeedsResolve)
|
|
{
|
|
// Confirm input connections
|
|
|
|
for (unsigned long i = 0; i < getNumIns(); i++)
|
|
confirmConnection(i, ConnectionInfo::kConfirm);
|
|
|
|
// Confirm ordering connections
|
|
|
|
for (unsigned long i = 0; i < getNumOrderingConnections(); i++)
|
|
confirmConnection(getOrderingConnection(i), getNumIns(), ConnectionInfo::kConfirm);
|
|
|
|
// Make output connections
|
|
|
|
for (unsigned long i = getNumOuts(); i > 0; i--)
|
|
makeConnection(i - 1, ConnectionInfo::kConnect);
|
|
|
|
mNeedsResolve = false;
|
|
}
|
|
}
|
|
|
|
void makeConnection(unsigned long index, ConnectionInfo::Mode mode)
|
|
{
|
|
ConnectionInfo info(*this, index, mode);
|
|
|
|
mGlobal->setConnectionInfo(&info);
|
|
outlet_anything(mOutputs[index], gensym("frame"), 0, nullptr);
|
|
mGlobal->setConnectionInfo();
|
|
}
|
|
|
|
bool confirmConnection(unsigned long inIndex, ConnectionInfo::Mode mode)
|
|
{
|
|
if (!validInput(inIndex))
|
|
return false;
|
|
|
|
return confirmConnection(getConnection(inIndex), inIndex, mode);
|
|
}
|
|
|
|
bool confirmConnection(PDConnection connection, unsigned long inIndex, ConnectionInfo::Mode mode)
|
|
{
|
|
if (!validInput(inIndex))
|
|
return false;
|
|
|
|
mConfirm = false;
|
|
mConfirmObject = connection.mObject;
|
|
mConfirmInIndex = inIndex;
|
|
mConfirmOutIndex = connection.mIndex;
|
|
|
|
// Check for connection *only* if the internal object is connected (otherwise assume the previously connected object has been deleted)
|
|
|
|
if (mConfirmObject)
|
|
confirmMethod(mConfirmObject, gensym("__fl.connection_confirm"), mConfirmOutIndex, mode);
|
|
|
|
if (mConfirmObject && !mConfirm)
|
|
disconnect(mConfirmObject, mConfirmOutIndex, mConfirmInIndex);
|
|
|
|
bool result = mConfirm;
|
|
mConfirm = false;
|
|
mConfirmObject = nullptr;
|
|
mConfirmInIndex = mConfirmOutIndex = -1;
|
|
|
|
return result;
|
|
}
|
|
|
|
bool validInput(long index, FrameLib_Multistream *object) const { return object && index >= 0 && index < object->getNumIns(); }
|
|
bool validOutput(long index, FrameLib_Multistream *object) const { return object && index >= 0 && index < object->getNumOuts(); }
|
|
bool isOrderingInput(long index, FrameLib_Multistream *object) const { return object && object->supportsOrderingConnections() && index == object->getNumIns(); }
|
|
bool validInput(long index) const { return validInput(index, mObject.get()); }
|
|
bool validOutput(long index) const { return validOutput(index, mObject.get()); }
|
|
bool isOrderingInput(long index) const { return isOrderingInput(index, mObject.get()); }
|
|
|
|
bool isConnected(long index) const { return mObject->isConnected(index); }
|
|
|
|
PDConnection getPDConnection(const FrameLibConnection& connection) const
|
|
{
|
|
FrameLib_PDProxy *proxy = dynamic_cast<FrameLib_PDProxy *>(connection.mObject->getProxy());
|
|
t_object *object = proxy->mMaxObject;
|
|
return PDConnection(object, connection.mIndex);
|
|
}
|
|
|
|
PDConnection getConnection(long index) const
|
|
{
|
|
if (isConnected(index))
|
|
return getPDConnection(mObject->getConnection(index));
|
|
else
|
|
return PDConnection();
|
|
}
|
|
|
|
unsigned long getNumOrderingConnections() const
|
|
{
|
|
return mObject->getNumOrderingConnections();
|
|
}
|
|
|
|
PDConnection getOrderingConnection(long index) const
|
|
{
|
|
return getPDConnection(mObject->getOrderingConnection(index));
|
|
}
|
|
|
|
bool matchConnection(t_object *src, long outIdx, long inIdx) const
|
|
{
|
|
PDConnection connection = getConnection(inIdx);
|
|
return connection.mObject == src && connection.mIndex == outIdx;
|
|
}
|
|
|
|
void connect(t_object *src, long outIdx, long inIdx)
|
|
{
|
|
FrameLib_Multistream *object = getInternalObject(src);
|
|
|
|
if (!isOrderingInput(inIdx) && (!validInput(inIdx) || !validOutput(outIdx, object) || matchConnection(src, outIdx, inIdx) || confirmConnection(inIdx, ConnectionInfo::kDoubleCheck)))
|
|
return;
|
|
|
|
ConnectionResult result;
|
|
|
|
if (isOrderingInput(inIdx))
|
|
result = mObject->addOrderingConnection(FrameLibConnection(object, outIdx));
|
|
else
|
|
result = mObject->addConnection(FrameLibConnection(object, outIdx), inIdx);
|
|
|
|
switch (result)
|
|
{
|
|
case kConnectFeedbackDetected:
|
|
pd_error(mUserObject, "feedback loop detected");
|
|
break;
|
|
|
|
case kConnectWrongContext:
|
|
pd_error(mUserObject, "cannot connect objects from different top-level patchers");
|
|
break;
|
|
|
|
case kConnectSelfConnection:
|
|
pd_error(mUserObject, "direct feedback loop detected");
|
|
break;
|
|
|
|
case kConnectSuccess:
|
|
case kConnectNoOrderingSupport:
|
|
case kConnectAliased:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void disconnect(t_object *src, long outIdx, long inIdx)
|
|
{
|
|
FrameLib_Multistream *object = getInternalObject(src);
|
|
|
|
if (!isOrderingInput(inIdx) && (!validInput(inIdx) || !matchConnection(src, outIdx, inIdx)))
|
|
return;
|
|
|
|
if (isOrderingInput(inIdx))
|
|
mObject->deleteOrderingConnection(FrameLibConnection(object, outIdx));
|
|
else
|
|
mObject->deleteConnection(inIdx);
|
|
}
|
|
|
|
// Info Utilities
|
|
|
|
void postSplit(const char *text, const char *firstLineTag, const char *lineTag)
|
|
{
|
|
std::string str(text);
|
|
size_t oldPos, pos;
|
|
|
|
for (oldPos = 0, pos = str.find_first_of(":."); oldPos < str.size(); pos = str.find_first_of(":.", pos + 1))
|
|
{
|
|
pos = pos == std::string::npos ? str.size() : pos;
|
|
post("%s%s", oldPos ? lineTag : firstLineTag, str.substr(oldPos, (pos - oldPos) + 1).c_str());
|
|
oldPos = pos + 1;
|
|
}
|
|
}
|
|
|
|
const char *frameTypeString(FrameType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case kFrameAny: return "either";
|
|
case kFrameNormal: return "vector";
|
|
case kFrameTagged: return "tagged";
|
|
}
|
|
}
|
|
|
|
// Parameter Parsing
|
|
|
|
unsigned long safeCount(char *str, unsigned long maxCount)
|
|
{
|
|
unsigned long number = std::max(1, atoi(str));
|
|
return std::min(maxCount, number);
|
|
}
|
|
|
|
long getStreamCount(t_atom *a)
|
|
{
|
|
if (atom_gettype(a) == A_SYMBOL)
|
|
{
|
|
t_symbol *sym = atom_getsymbol(a);
|
|
|
|
if (strlen(sym->s_name) > 1 && sym->s_name[0] == '=')
|
|
return safeCount(sym->s_name + 1, 1024);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool isParameterTag(t_symbol *sym)
|
|
{
|
|
return strlen(sym->s_name) > 1 && sym->s_name[0] == '/';
|
|
}
|
|
|
|
bool isInputTag(t_symbol *sym)
|
|
{
|
|
size_t len = strlen(sym->s_name);
|
|
|
|
if (len > 2)
|
|
return (sym->s_name[0] == '[' && sym->s_name[len - 1] == ']');
|
|
|
|
return false;
|
|
}
|
|
|
|
bool isTag(t_atom *a)
|
|
{
|
|
t_symbol *sym = atom_getsymbol(a);
|
|
return atom_gettype(a) == A_SYMBOL && (isParameterTag(sym) || isInputTag(sym));
|
|
}
|
|
|
|
long parseNumericalList(std::vector<double> &values, t_atom *argv, long argc, long idx)
|
|
{
|
|
values.resize(0);
|
|
|
|
// Collect doubles
|
|
|
|
for ( ; idx < argc; idx++)
|
|
{
|
|
if (isTag(argv + idx))
|
|
break;
|
|
|
|
if (atom_gettype(argv + idx) == A_SYMBOL)
|
|
pd_error(mUserObject, "string %s in entry list where value expected", atom_getsymbol(argv + idx)->s_name);
|
|
|
|
values.push_back(atom_getfloat(argv + idx));
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
void parseParameters(FrameLib_Parameters::AutoSerial& serialisedParameters, long argc, t_atom *argv)
|
|
{
|
|
t_symbol *sym = nullptr;
|
|
std::vector<double> values;
|
|
long i;
|
|
|
|
// Parse arguments
|
|
|
|
for (i = 0; i < argc; i++)
|
|
{
|
|
if (isTag(argv + i))
|
|
break;
|
|
|
|
if (argsMode == kAsParams)
|
|
{
|
|
char argNames[64];
|
|
sprintf(argNames, "%ld", i);
|
|
|
|
if (atom_gettype(argv + i) == A_SYMBOL)
|
|
{
|
|
t_symbol *str = atom_getsymbol(argv + i);
|
|
serialisedParameters.write(argNames, str->s_name);
|
|
}
|
|
else
|
|
{
|
|
double value = atom_getfloat(argv + i);
|
|
serialisedParameters.write(argNames, &value, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse parameters
|
|
|
|
while (i < argc)
|
|
{
|
|
// Strip stray items
|
|
|
|
for (long j = 0; i < argc; i++, j++)
|
|
{
|
|
if (isTag(argv + i))
|
|
{
|
|
sym = atom_getsymbol(argv + i);
|
|
break;
|
|
}
|
|
|
|
if (j == 0)
|
|
pd_error(mUserObject, "stray items after entry %s", sym->s_name);
|
|
}
|
|
|
|
// Check for lack of values or end of list
|
|
|
|
if ((++i >= argc) || isTag(argv + i))
|
|
{
|
|
if (i < (argc + 1))
|
|
pd_error(mUserObject, "no values given for entry %s", sym->s_name);
|
|
continue;
|
|
}
|
|
|
|
if (isParameterTag(sym))
|
|
{
|
|
// Do strings or values
|
|
|
|
if (atom_gettype(argv + i) == A_SYMBOL && atom_getsymbol(argv + i))
|
|
serialisedParameters.write(sym->s_name + 1, atom_getsymbol(argv + i++)->s_name);
|
|
else
|
|
{
|
|
i = parseNumericalList(values, argv, argc, i);
|
|
serialisedParameters.write(sym->s_name + 1, values.data(), values.size());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Input Parsing
|
|
|
|
unsigned long inputNumber(t_symbol *sym)
|
|
{
|
|
return safeCount(sym->s_name + 1, 16384) - 1;
|
|
}
|
|
|
|
void parseInputs(long argc, t_atom *argv)
|
|
{
|
|
std::vector<double> values;
|
|
long i = 0;
|
|
|
|
// Parse arguments if used to set inputs
|
|
|
|
if (argsMode == kAllInputs || argsMode == kDistribute)
|
|
{
|
|
i = parseNumericalList(values, argv, argc, 0);
|
|
if(argsMode == kAllInputs)
|
|
{
|
|
for (unsigned long j = 0; i && j < getNumIns(); j++)
|
|
mObject->setFixedInput(j, values.data(), values.size());
|
|
}
|
|
else
|
|
{
|
|
for (unsigned long j = 0; j < i && (j + 1) < getNumIns(); j++)
|
|
mObject->setFixedInput(j + 1, &values[j], 1);
|
|
}
|
|
}
|
|
|
|
// Parse tags
|
|
|
|
while (i < argc)
|
|
{
|
|
// Advance to next input tag
|
|
|
|
for ( ; i < argc && !isInputTag(atom_getsymbol(argv + i)); i++);
|
|
|
|
// If there are values to read then do so
|
|
|
|
if ((i + 1) < argc && !isTag(argv + i + 1))
|
|
{
|
|
t_symbol *sym = atom_getsymbol(argv + i);
|
|
i = parseNumericalList(values, argv, argc, i + 1);
|
|
mObject->setFixedInput(inputNumber(sym), values.data(), values.size());
|
|
}
|
|
}
|
|
}
|
|
|
|
protected:
|
|
|
|
std::unique_ptr<FrameLib_PDProxy> mFrameLibProxy;
|
|
|
|
private:
|
|
|
|
// Data - N.B. - the order is crucial for safe deconstruction
|
|
|
|
FrameLib_PDGlobals::ManagedPointer mGlobal;
|
|
FrameLib_PDGlobals::SyncCheck mSyncChecker;
|
|
|
|
std::unique_ptr<FrameLib_Multistream> mObject;
|
|
|
|
std::vector<t_pd *> mInputs;
|
|
std::vector<t_outlet *> mOutputs;
|
|
|
|
std::vector<double *> mSigIns;
|
|
std::vector<double *> mSigOuts;
|
|
std::vector<double> mTemp;
|
|
|
|
long mProxyNum;
|
|
t_object *mConfirmObject;
|
|
long mConfirmInIndex;
|
|
long mConfirmOutIndex;
|
|
bool mConfirm;
|
|
|
|
t_glist *mCanvas;
|
|
t_object *mSyncIn;
|
|
|
|
bool mNeedsResolve;
|
|
|
|
public:
|
|
|
|
t_object *mUserObject;
|
|
};
|
|
|
|
// Convenience for Objects Using FrameLib_Expand (use FrameLib_PDClass_Expand<T>::makeClass() to create)
|
|
|
|
template <class T, PDObjectArgsMode argsMode = kAsParams>
|
|
using FrameLib_PDClass_Expand = FrameLib_PDClass<FrameLib_Expand<T>, argsMode>;
|