Jamoma API  0.6.0.a19
j.unpack_equal/j.unpack.cpp
Go to the documentation of this file.
1 /** @file
2  *
3  * @ingroup implementationMaxExternalsAudioGraph
4  *
5  * @brief j.unpack= : Unpack AudioGraph multichannel signal to several MSP audio signals
6  *
7  * @details A 2D/3D trajectory generator at signal rate
8  *
9  * @authors Tim Place, Trond Lossius
10  *
11  * @copyright © 2008, Tim Place @n
12  * This code is licensed under the terms of the "New BSD License" @n
13  * http://creativecommons.org/licenses/BSD/
14  */
15 
16 
17 #include "maxAudioGraph.h"
18 #include "jpatcher_api.h"
19 //#define DEBUG_NOTIFICATIONS
20 
21 // Data Structure for this object
22 typedef struct _unpack {
23  t_pxobject obj;
24  TTAudioGraphObjectBasePtr audioGraphObject;
25  TTAudioSignalPtr audioSignal;
26  TTUInt16 maxNumChannels; // the number of inlets or outlets, which is an argument at instantiation
27  TTUInt16 numChannels; // the actual number of channels to use, set by the dsp method
28  TTUInt16 vectorSize; // cached by the DSP method
29  //TTFloat32 gain; // gain multiplier
30  TTBoolean hasReset; // flag indicating that reset has been called already, so we don't need to reset again
31  TTBoolean hasConnections; // flag indicating that we have connections so we can mute MSP output
32  t_object* patcher; // the patcher -- cached for iterating to make connections
33  t_object* patcherview; // first view of the top-level patcher (for dirty notifications)
34  TTPtr qelem; // for clumping patcher dirty notifications
35  TTAudioGraphPreprocessData initData; // for the preprocess method
36  TTUInt64 sampleStamp; // current count of samples processed since DSP was started
37 } t_unpack;
38 
39 
40 // Prototypes for methods
41 t_unpack* UnpackNew(t_symbol *msg, long argc, t_atom* argv);
42 void UnpackFree(t_unpack* self);
43 t_max_err UnpackNotify(t_unpack* self, t_symbol *s, t_symbol *msg, t_object* sender, TTPtr data);
44 void UnpackQFn(t_unpack* self);
45 void UnpackAssist(t_unpack* self, void* b, long msg, long arg, char* dst);
46 TTErr UnpackReset(t_unpack* self, long vectorSize);
47 TTErr UnpackConnect(t_unpack* self, TTAudioGraphObjectBasePtr audioSourceObject, long sourceOutletNumber);
48 void UnpackIterateResetCallback(t_unpack* self, t_object* obj);
49 void UnpackIterateSetupCallback(t_unpack* self, t_object* obj);
50 void UnpackAttachToPatchlinesForPatcher(t_unpack* self, t_object* patcher);
51 void UnpackDsp64(t_unpack* self, t_object* dsp64, short *count, double samplerate, long maxvectorsize, long flags);
52 //t_max_err UnpackSetGain(t_unpack* self, void* attr, long argc, t_atom* argv);
53 
54 
55 // Globals
56 static t_class* sUnpackClass;
57 
58 
59 /************************************************************************************/
60 // Main() Function
61 
62 int C74_EXPORT main(void)
63 {
64  t_class *c;
65 
66  TTAudioGraphInit();
67  common_symbols_init();
68 
69  c = class_new("j.unpack=", (method)UnpackNew, (method)UnpackFree, sizeof(t_unpack), (method)0L, A_GIMME, 0);
70 
71  class_addmethod(c, (method)UnpackNotify, "notify", A_CANT, 0);
72  class_addmethod(c, (method)UnpackReset, "audio.reset", A_CANT, 0);
73  class_addmethod(c, (method)UnpackConnect, "audio.connect", A_OBJ, A_LONG, 0);
74  class_addmethod(c, (method)MaxAudioGraphDrop, "audio.drop", A_CANT, 0);
75  class_addmethod(c, (method)MaxAudioGraphObject, "audio.object", A_CANT, 0);
76  class_addmethod(c, (method)UnpackDsp64, "dsp64", A_CANT, 0);
77  class_addmethod(c, (method)UnpackAssist, "assist", A_CANT, 0);
78  class_addmethod(c, (method)object_obex_dumpout, "dumpout", A_CANT, 0);
79 
80  //CLASS_ATTR_FLOAT(c, "gain", 0, Unpack, gain);
81  //CLASS_ATTR_ACCESSORS(c, "gain", NULL, UnpackSetGain);
82 
83  class_dspinit(c);
84  class_register(_sym_box, c);
85  sUnpackClass = c;
86  return 0;
87 }
88 
89 
90 /************************************************************************************/
91 // Object Creation Method
92 
93 t_unpack* UnpackNew(t_symbol *msg, long argc, t_atom* argv)
94 {
95  t_unpack* self;
96  TTValue sr(sys_getsr());
97  long attrstart = attr_args_offset(argc, argv); // support normal arguments
98  short i;
99  TTValue v;
100  TTErr err;
101 
102  self = (t_unpack*)object_alloc(sUnpackClass);
103  if (self) {
104  self->maxNumChannels = 2; // An initial argument to this object will set the maximum number of channels
105  if (attrstart && argv)
106  self->maxNumChannels = atom_getlong(argv);
107 
108  ttEnvironment->setAttributeValue(kTTSym_sampleRate, sr);
109 
110  v.resize(2);
111  v[0] = "thru";
112  v[1] = 1; // arg is the number of inlets
113  err = TTObjectBaseInstantiate(TT("audio.object"), (TTObjectBasePtr*)&self->audioGraphObject, v);
114  //self->audioGraphObject->getUnitGenerator()->setAttributeValue(TT("linearGain"), 1.0);
115 
116  attr_args_process(self, argc, argv);
117 
118  object_obex_store((void*)self, _sym_dumpout, (object*)outlet_new(self, NULL)); // dumpout
119  dsp_setup((t_pxobject*)self, 1);
120  for (i=0; i < self->maxNumChannels; i++)
121  outlet_new((t_pxobject*)self, "signal");
122 
123  self->qelem = qelem_new(self, (method)UnpackQFn);
124  self->obj.z_misc = Z_NO_INPLACE | Z_PUT_LAST;
125  }
126  return self;
127 }
128 
129 // Memory Deallocation
130 void UnpackFree(t_unpack* self)
131 {
132  dsp_free((t_pxobject*)self);
133  if (self->patcherview) {
134  object_detach_byptr(self, self->patcherview);
135  self->patcherview = NULL;
136  }
137  TTObjectBaseRelease((TTObjectBasePtr*)&self->audioGraphObject);
138  qelem_free(self->qelem);
139 }
140 
141 
142 /************************************************************************************/
143 // Methods bound to input/inlets
144 
145 t_max_err UnpackNotify(t_unpack* self, t_symbol *s, t_symbol *msg, t_object* sender, TTPtr data)
146 {
147  if (sender == self->patcherview) {
148  if (msg == _sym_attr_modified) {
149  t_symbol *name = (t_symbol*)object_method((t_object*)data, _sym_getname);
150  if (name == _sym_dirty) {
151  qelem_set(self->qelem);
152  }
153  }
154  else if (msg == _sym_free)
155  self->patcherview = NULL;
156  }
157  else {
158  if (msg == _sym_free) {
159  t_object* sourceBox;
160  t_object* sourceObject;
161  long sourceOutlet;
162  t_object* destBox;
163  t_object* destObject;
164  long destInlet;
165 
166  #ifdef DEBUG_NOTIFICATIONS
167  object_post(SELF, "patch line deleted");
168  #endif // DEBUG_NOTIFICATIONS
169 
170  // get boxes and inlets
171  sourceBox = jpatchline_get_box1(sender);
172  if (!sourceBox)
173  goto out;
174  sourceObject = jbox_get_object(sourceBox);
175  sourceOutlet = jpatchline_get_outletnum(sender);
176  destBox = jpatchline_get_box2(sender);
177  if (!destBox)
178  goto out;
179  destObject = jbox_get_object(destBox);
180  destInlet = jpatchline_get_inletnum(sender);
181 
182  // if both boxes are audio graph objects
183  if ( zgetfn(sourceObject, gensym("audio.object")) && zgetfn(destObject, gensym("audio.object")) ) {
184  #ifdef DEBUG_NOTIFICATIONS
185  object_post(SELF, "deleting audio graph patchline!");
186  #endif // DEBUG_NOTIFICATIONS
187 
188  object_method(destObject, gensym("audio.drop"), destInlet, sourceObject, sourceOutlet);
189  UnpackReset(self, self->vectorSize); // attempt for http://redmine.jamoma.org/issues/1248
190  }
191  out:
192  ;
193  }
194  }
195  return MAX_ERR_NONE;
196 }
197 
198 
199 // Qelem function, which clumps together dirty notifications before making the new connections
200 void UnpackQFn(t_unpack* self)
201 {
202  t_atom result;
203 
204  #ifdef DEBUG_NOTIFICATIONS
205  object_post(SELF, "patcher dirtied");
206  #endif // DEBUG_NOTIFICATIONS
207 
208  object_method(self->patcher, gensym("iterate"), (method)UnpackIterateSetupCallback, self, PI_DEEP, &result);
209 
210  // attach to all of the patch cords so we will know if one is deleted
211  // we are not trying to detach first -- hopefully this is okay and multiple attachments will be filtered (?)
212  UnpackAttachToPatchlinesForPatcher(self, self->patcher);
213 }
214 
215 
216 // Method for Assistance Messages
217 void UnpackAssist(t_unpack* self, void* b, long msg, long arg, char* dst)
218 {
219  if (msg==1) // Inlets
220  strcpy(dst, "multichannel audio connection and control messages");
221  else if (msg==2) { // Unpacklets
222  if (arg == self->maxNumChannels)
223  strcpy(dst, "dumpout");
224  else
225  snprintf(dst, 256, "(signal) single-channel output Nr. %ld", arg + 1);
226  }
227 }
228 
229 TTErr UnpackReset(t_unpack* self, long vectorSize)
230 {
231  self->hasReset = true;
232  self->hasConnections = false;
233  return self->audioGraphObject->resetAudio();
234 }
235 
236 
237 TTErr UnpackConnect(t_unpack* self, TTAudioGraphObjectBasePtr audioSourceObject, long sourceOutletNumber)
238 {
239  self->hasConnections = true;
240  return self->audioGraphObject->connectAudio(audioSourceObject, sourceOutletNumber);
241 }
242 
243 
244 void UnpackIterateResetCallback(t_unpack* self, t_object* obj)
245 {
246  t_max_err err = MAX_ERR_NONE;
247  method audioResetMethod = zgetfn(obj, gensym("audio.reset"));
248 
249  if (audioResetMethod)
250  err = (t_max_err)audioResetMethod(obj, self->vectorSize);
251 }
252 
253 
254 void UnpackIterateSetupCallback(t_unpack* self, t_object* obj)
255 {
256  t_max_err err = MAX_ERR_NONE;
257  method audioSetupMethod = zgetfn(obj, gensym("audio.setup"));
258 
259  if (audioSetupMethod)
260  err = (t_max_err)audioSetupMethod(obj);
261 }
262 
263 
264 void UnpackAttachToPatchlinesForPatcher(t_unpack* self, t_object* patcher)
265 {
266  t_object* patchline = object_attr_getobj(patcher, _sym_firstline);
267  t_object* box = jpatcher_get_firstobject(patcher);
268 
269  while (patchline) {
270  object_attach_byptr_register(self, patchline, _sym_nobox);
271  patchline = object_attr_getobj(patchline, _sym_nextline);
272  }
273 
274  while (box) {
275  t_symbol *classname = jbox_get_maxclass(box);
276 
277  if (classname == _sym_jpatcher) {
278  t_object* subpatcher = jbox_get_object(box);
279 
280  UnpackAttachToPatchlinesForPatcher(self, subpatcher);
281  }
282  box = jbox_get_nextobject(box);
283  }
284 }
285 
286 
287 // Perform (signal) Method
288 void UnpackPerform64(t_unpack* self, t_object* dsp64, double **ins, long numins, double **outs, long numouts, long sampleframes, long flags, void *userparam)
289 {
290  TTUInt16 numChannels;
291 
292  if (self->hasConnections) {
293  self->audioGraphObject->lockProcessing();
294  self->audioGraphObject->preprocess(self->initData);
295  self->audioGraphObject->process(self->audioSignal, self->sampleStamp);
296  self->sampleStamp += sampleframes;
297  self->audioGraphObject->unlockProcessing();
298 
299  numChannels = TTClip<TTUInt16>(self->numChannels, 0, self->audioSignal->getNumChannelsAsInt());
300  for (TTUInt16 channel=0; channel < numChannels; channel++) //TODO: what happens if the incomming multicable has 100 channels and we only want to unpack the first two, are we looping 100 times ?
301  self->audioSignal->getVectorCopy(channel, self->vectorSize, outs[channel]);
302 
303  if (numChannels < self->maxNumChannels) { // in case the incomming smart signal has less channels than j.unpack has outlets
304  for (TTUInt16 channel=numChannels; channel < self->maxNumChannels; channel++)
305  for (int i=0 ; i < self->vectorSize; i++)
306  outs[channel][i] = 0.0;
307  }
308  }
309  else {
310  for (TTUInt16 channel=0; channel < self->numChannels; channel++) {
311  for (int i=0 ; i < self->vectorSize; i++)
312  outs[channel][i] = 0.0;
313  }
314  }
315 
316  self->hasReset = false;
317 }
318 
319 
320 // DSP Method
321 void UnpackDsp64(t_unpack* self, t_object* dsp64, short *count, double samplerate, long maxvectorsize, long flags)
322 {
323  TTUInt16 i;
324  t_max_err err;
325  long result = 0;
326 
327  self->vectorSize = maxvectorsize;
328 
329 #ifdef DEBUG_NOTIFICATIONS
330  object_post(SELF, "dsp method called");
331 #endif // DEBUG_NOTIFICATIONS
332 
333  t_object* patcher = NULL;
334  t_object* parent = NULL;
335  t_object* patcherview = NULL;
336 
337  // first find the top-level patcher
338  err = object_obex_lookup(self, gensym("#P"), &patcher);
339  parent = patcher;
340  while (parent) {
341  patcher = parent;
342  parent = object_attr_getobj(patcher, _sym_parentpatcher);
343  }
344 
345 
346  /* We need to figure out what objects are connected to what inlets to build the graph:
347 
348  1. Broadcast 'audio.reset' to every object in the patcher, to remove all existing connections.
349  2. Broadcast 'audio.setup' to every object in the patcher, to tell objects to then send
350  'audio.connect' messages to any objects below them.
351  3. When an object received 'audio.connect', then it makes the connection.
352 
353  At this point, the graph is configured and we just need to execute it.
354  We execute the graph from our perform method, which MSP calls once per signal vector.
355 
356  5. Crawl the graph from bottom to top, calling the audio graph preprocess method (prepare for process)
357  6. Crawl the graph from bottom to top, calling the audio graph process method (calculate the samples)
358  7. (Maybe) crawl the graph from bottom to top, calling a audio graph postprocess method
359 
360  For steps 1 & 2, we have to traverse thge patcher twice,
361  because we have to clear all connections first, then add connections.
362  It won't work to do them both during the same traversal because situations arise
363  Where we setup the chain and then it gets reset again by another object
364  (since the order in which we traverse objects is undefined).
365  */
366 
367  if (!self->hasReset) {
368  // now iterate recursively from the top-level patcher down through all of the subpatchers
369  object_method(patcher, gensym("iterate"), (method)UnpackIterateResetCallback, self, PI_DEEP, &result);
370  object_method(patcher, gensym("iterate"), (method)UnpackIterateSetupCallback, self, PI_DEEP, &result);
371  }
372 
373  // now let's attach to the patcherview to get notifications about any further changes to the patch cords
374  // the patcher 'dirty' attribute is not modified for each change, but the patcherview 'dirty' attribute is
375  if (!self->patcherview) {
376  patcherview = jpatcher_get_firstview(patcher);
377  self->patcherview = patcherview;
378  self->patcher = patcher;
379  object_attach_byptr_register(self, patcherview, _sym_nobox);
380  }
381 
382  // now we want to go a step further and attach to all of the patch cords
383  // this is how we will know if one is deleted
384  UnpackAttachToPatchlinesForPatcher(self, self->patcher);
385 
386  self->numChannels = 0;
387  for (i=1; i <= self->maxNumChannels; i++) {
388  self->numChannels++;
389  }
390 
391  self->audioGraphObject->getUnitGenerator().set(kTTSym_sampleRate, samplerate);
392  self->audioGraphObject->resetSampleStamp();
393  self->sampleStamp = 0;
394 
395  self->initData.vectorSize = self->vectorSize;
396 
397  object_method(dsp64, gensym("dsp_add64"), self, UnpackPerform64, 0, NULL);
398  //dsp_add64(dsp64, (t_object*)self, (t_perfroutine64)UnpackPerform64, 0, NULL);
399 }
400 
401 
402 /*t_max_err UnpackSetGain(t_unpack* self, void* attr, long argc, t_atom* argv)
403 {
404  if (argc) {
405  self->gain = atom_getfloat(argv);
406  self->audioGraphObject->getUnitGenerator()->setAttributeValue(TT("linearGain"), self->gain);
407  }
408  return MAX_ERR_NONE;
409 }*/
410 
bool TTBoolean
Boolean flag, same as Boolean on the Mac.
Definition: TTBase.h:167
std::uint16_t TTUInt16
16 bit unsigned integer
Definition: TTBase.h:176
TTErr TTObjectBaseRelease(TTObjectBasePtr *anObject)
DEPRECATED.
std::uint64_t TTUInt64
64 bit unsigned integer
Definition: TTBase.h:180
TTFOUNDATION_EXPORT TTEnvironment * ttEnvironment
The environment object has one instance, which is global in scope.
Base class for all first-class Jamoma objects.
Definition: TTObjectBase.h:109
TTErr setAttributeValue(const TTSymbol name, TTValue &value)
Set an attribute value for an object.
int C74_EXPORT main(void)
Set up this class as a Max external the first time an object of this kind is instantiated.
#define TT
This macro is defined as a shortcut for doing a lookup in the symbol table.
Definition: TTSymbol.h:155
void * TTPtr
A generic pointer.
Definition: TTBase.h:248
TTErr TTObjectBaseInstantiate(const TTSymbol className, TTObjectBasePtr *returnedObjectPtr, const TTValue arguments)
DEPRECATED.
The TTAudioSignal class represents N vectors of audio samples for M channels.
Definition: TTAudioSignal.h:57
A thin wrapper of Jamoma AudioGraph for use in the Cycling '74 Max/MSP environment.
TTErr MaxAudioGraphObject(t_object *x, TTAudioGraphObjectBasePtr *returnedAudioGraphObject)
Returns a pointer to the Jamoma Audio Graph object that is wrapped by this Max object.
TTErr
Jamoma Error Codes Enumeration of error codes that might be returned by any of the TTBlue functions a...
Definition: TTBase.h:342
The TTAudioGraphObjectBase wraps a TTDSP object such that it is possible to build a dynamic graph of ...
[doxygenAppendixC_bitmaskExample]
Definition: TTAudioGraph.h:81
TTErr MaxAudioGraphDrop(t_object *x, long inletNumber, t_object *sourceMaxObject, long sourceOutletNumber)
Method called when a connection from an upstream node is dropped.
void resize(size_type n)
Change the number of elements.
[doxygenAppendixC_copyExample]
Definition: TTValue.h:34