Jamoma API  0.6.0.a19
j.unsig.cpp
Go to the documentation of this file.
1 /** @file
2  *
3  * @ingroup implementationMaxExternalsAudioGraph
4  *
5  * @brief j.unsig= - converts Jamoma AudioGraph signal to a Max message list.
6  *
7  * @details
8  *
9  * @authors Nils Peters, Tim Place
10  *
11  * @copyright Copyright © 2011, Nils Peters, 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 #include "maxAudioGraph.h"
17 #include "jpatcher_api.h"
18 //#define DEBUG_NOTIFICATIONS
19 
20 // Data Structure for this object
21 struct Out {
22  t_pxobject obj;
23  TTAudioGraphObjectBasePtr audioGraphObject;
24  TTAudioSignalPtr audioSignal;
25  TTUInt16 maxNumChannels; // the number of inlets or outlets, which is an argument at instantiation
26  TTUInt16 numChannels; // the actual number of channels to use, set by the dsp method
27  TTUInt16 vectorSize; // cached by the DSP method
28  //TTFloat32 gain; // gain multiplier
29  TTBoolean hasReset; // flag indicating that reset has been called already, so we don't need to reset again
30  TTBoolean hasConnections; // flag indicating that we have connections so we can mute MSP output
31  t_object* patcher; // the patcher -- cached for iterating to make connections
32  t_object* patcherview; // first view of the top-level patcher (for dirty notifications)
33  void *s_out; // for the list outlet
34  TTPtr qelem; // for clumping patcher dirty notifications
35  TTAudioGraphPreprocessData initData; // for the preprocess method
36  t_atom *output_buffer; // allocating list for output samples
37  void* clock; ///< clock for pushing the data onto the scheduler from the audio thread
38  TTUInt64 sampleCount;
39 };
40 typedef Out* OutPtr;
41 
42 
43 // Prototypes for methods
44 OutPtr OutNew(t_symbol *msg, long argc, t_atom* argv);
45 void OutFree(OutPtr self);
46 t_max_err OutNotify(OutPtr self, t_symbol *s, t_symbol *msg, t_object* sender, TTPtr data);
47 void OutQFn(OutPtr self);
48 void OutAssist(OutPtr self, void* b, long msg, long arg, char* dst);
49 TTErr OutReset(OutPtr self, long vectorSize);
50 TTErr OutConnect(OutPtr self, TTAudioGraphObjectBasePtr audioSourceObject, long sourceOutletNumber);
51 void OutIterateResetCallback(OutPtr self, t_object* obj);
52 void OutIterateSetupCallback(OutPtr self, t_object* obj);
53 void OutAttachToPatchlinesForPatcher(OutPtr self, t_object* patcher);
54 t_int* OutPerform(t_int* w);
55 void OutTick(OutPtr self);
56 void OutDsp(OutPtr self, t_signal** sp, short* count);
57 //t_max_err OutSetGain(OutPtr self, void* attr, long argc, t_atom* argv);
58 
59 
60 // Globals
61 static t_class* sOutClass;
62 
63 
64 /************************************************************************************/
65 // Main() Function
66 
67 int C74_EXPORT main(void)
68 {
69  t_class *c;
70 
71  TTAudioGraphInit();
72  common_symbols_init();
73 
74  c = class_new("j.unsig=", (method)OutNew, (method)OutFree, sizeof(Out), (method)0L, A_GIMME, 0);
75 
76  class_addmethod(c, (method)OutNotify, "notify", A_CANT, 0);
77  class_addmethod(c, (method)OutReset, "audio.reset", A_CANT, 0);
78  class_addmethod(c, (method)OutConnect, "audio.connect", A_OBJ, A_LONG, 0);
79  class_addmethod(c, (method)MaxAudioGraphDrop, "audio.drop", A_CANT, 0);
80  class_addmethod(c, (method)MaxAudioGraphObject, "audio.object", A_CANT, 0);
81  class_addmethod(c, (method)OutDsp, "dsp", A_CANT, 0);
82  class_addmethod(c, (method)OutAssist, "assist", A_CANT, 0);
83  class_addmethod(c, (method)object_obex_dumpout, "dumpout", A_CANT, 0);
84 
85  //CLASS_ATTR_FLOAT(c, "gain", 0, Out, gain);
86  //CLASS_ATTR_ACCESSORS(c, "gain", NULL, OutSetGain);
87 
88  class_dspinit(c);
89  class_register(_sym_box, c);
90  sOutClass = c;
91  return 0;
92 }
93 
94 
95 /************************************************************************************/
96 // Object Creation Method
97 
98 OutPtr OutNew(t_symbol *msg, long argc, t_atom* argv)
99 {
100  OutPtr self;
101  TTValue sr(sys_getsr());
102  long attrstart = attr_args_offset(argc, argv); // support normal arguments
103  //short i;
104  TTValue v;
105  TTErr err;
106 
107  self = OutPtr(object_alloc(sOutClass));
108  if (self) {
109  self->maxNumChannels = 2; // An initial argument to this object will set the maximum number of channels
110  if (attrstart && argv)
111  self->maxNumChannels = atom_getlong(argv);
112 
113  ttEnvironment->setAttributeValue(kTTSym_sampleRate, sr);
114  // setup the output_buffer according to channnel number
115  self->output_buffer = (t_atom *)malloc(self->maxNumChannels * sizeof(t_atom));
116 
117  v.resize(2);
118  v[0] = "thru";
119  v[1] = 1; // arg is the number of inlets
120  err = TTObjectBaseInstantiate(TT("audio.object"), (TTObjectBasePtr*)&self->audioGraphObject, v);
121  //self->audioGraphObject->getUnitGenerator()->setAttributeValue(TT("linearGain"), 1.0);
122 
123  attr_args_process(self, argc, argv);
124 
125  object_obex_store((void*)self, _sym_dumpout, (object*)outlet_new(self, NULL)); // dumpout
126  self->s_out = listout((t_pxobject *)self); // the list outlet
127  dsp_setup((t_pxobject*)self, 1);
128  self->clock = clock_new(self, (method)OutTick);
129 
130  self->qelem = qelem_new(self, (method)OutQFn);
131  self->obj.z_misc = Z_NO_INPLACE | Z_PUT_LAST;
132  }
133  return self;
134 }
135 
136 // Memory Deallocation
137 void OutFree(OutPtr self)
138 {
139  dsp_free((t_pxobject*)self);
140  object_free(self->clock);
141  if (self->patcherview) {
142  object_detach_byptr(self, self->patcherview);
143  self->patcherview = NULL;
144  }
145  if (self->output_buffer)
146  free(self->output_buffer);
147 
148  TTObjectBaseRelease((TTObjectBasePtr*)&self->audioGraphObject);
149  qelem_free(self->qelem);
150 }
151 
152 
153 /************************************************************************************/
154 // Methods bound to input/inlets
155 
156 t_max_err OutNotify(OutPtr self, t_symbol *s, t_symbol *msg, t_object* sender, TTPtr data)
157 {
158  if (sender == self->patcherview) {
159  if (msg == _sym_attr_modified) {
160  t_symbol *name = (t_symbol*)object_method((t_object*)data, _sym_getname);
161  if (name == _sym_dirty) {
162  qelem_set(self->qelem);
163  }
164  }
165  else if (msg == _sym_free)
166  self->patcherview = NULL;
167  }
168  else {
169  if (msg == _sym_free) {
170  t_object* sourceBox;
171  t_object* sourceObject;
172  long sourceOutlet;
173  t_object* destBox;
174  t_object* destObject;
175  long destInlet;
176 
177  #ifdef DEBUG_NOTIFICATIONS
178  object_post(SELF, "patch line deleted");
179  #endif // DEBUG_NOTIFICATIONS
180 
181  // get boxes and inlets
182  sourceBox = jpatchline_get_box1(sender);
183  if (!sourceBox)
184  goto out;
185  sourceObject = jbox_get_object(sourceBox);
186  sourceOutlet = jpatchline_get_outletnum(sender);
187  destBox = jpatchline_get_box2(sender);
188  if (!destBox)
189  goto out;
190  destObject = jbox_get_object(destBox);
191  destInlet = jpatchline_get_inletnum(sender);
192 
193  // if both boxes are audio graph objects
194  if ( zgetfn(sourceObject, gensym("audio.object")) && zgetfn(destObject, gensym("audio.object")) ) {
195  #ifdef DEBUG_NOTIFICATIONS
196  object_post(SELF, "deleting audio graph patchline!");
197  #endif // DEBUG_NOTIFICATIONS
198 
199  object_method(destObject, gensym("audio.drop"), destInlet, sourceObject, sourceOutlet);
200  }
201  out:
202  ;
203  }
204  }
205  return MAX_ERR_NONE;
206 }
207 
208 
209 // Qelem function, which clumps together dirty notifications before making the new connections
210 void OutQFn(OutPtr self)
211 {
212  t_atom result;
213 
214  #ifdef DEBUG_NOTIFICATIONS
215  object_post(SELF, "patcher dirtied");
216  #endif // DEBUG_NOTIFICATIONS
217 
218  object_method(self->patcher, gensym("iterate"), (method)OutIterateSetupCallback, self, PI_DEEP, &result);
219 
220  // attach to all of the patch cords so we will know if one is deleted
221  // we are not trying to detach first -- hopefully this is okay and multiple attachments will be filtered (?)
222  OutAttachToPatchlinesForPatcher(self, self->patcher);
223 }
224 
225 
226 // Method for Assistance Messages
227 void OutAssist(OutPtr self, void* b, long msg, long arg, char* dst)
228 {
229  if (msg==1) // Inlets
230  strcpy(dst, "multichannel audio connection");
231  else if (msg==2) { // Outlets
232  if (arg == 1)
233  strcpy(dst, "dumpout");
234  else if (arg == 0)
235  strcpy(dst, "unsig list");
236  //else
237  // snprintf(dst, 256, "(signal) single-channel output Nr. %ld", arg + 1);
238  }
239 }
240 
241 TTErr OutReset(OutPtr self, long vectorSize)
242 {
243  self->hasReset = true;
244  self->hasConnections = false;
245  return self->audioGraphObject->resetAudio();
246 }
247 
248 
249 TTErr OutConnect(OutPtr self, TTAudioGraphObjectBasePtr audioSourceObject, long sourceOutletNumber)
250 {
251  self->hasConnections = true;
252  return self->audioGraphObject->connectAudio(audioSourceObject, sourceOutletNumber);
253 }
254 
255 
256 void OutIterateResetCallback(OutPtr self, t_object* obj)
257 {
258  t_max_err err = MAX_ERR_NONE;
259  method audioResetMethod = zgetfn(obj, gensym("audio.reset"));
260 
261  if (audioResetMethod)
262  err = (t_max_err)audioResetMethod(obj, self->vectorSize);
263 }
264 
265 
266 void OutIterateSetupCallback(OutPtr self, t_object* obj)
267 {
268  t_max_err err = MAX_ERR_NONE;
269  method audioSetupMethod = zgetfn(obj, gensym("audio.setup"));
270 
271  if (audioSetupMethod)
272  err = (t_max_err)audioSetupMethod(obj);
273 }
274 
275 
276 void OutAttachToPatchlinesForPatcher(OutPtr self, t_object* patcher)
277 {
278  t_object* patchline = object_attr_getobj(patcher, _sym_firstline);
279  t_object* box = jpatcher_get_firstobject(patcher);
280 
281  while (patchline) {
282  object_attach_byptr_register(self, patchline, _sym_nobox);
283  patchline = object_attr_getobj(patchline, _sym_nextline);
284  }
285 
286  while (box) {
287  t_symbol *classname = jbox_get_maxclass(box);
288 
289  if (classname == _sym_jpatcher) {
290  t_object* subpatcher = jbox_get_object(box);
291 
292  OutAttachToPatchlinesForPatcher(self, subpatcher);
293  }
294  box = jbox_get_nextobject(box);
295  }
296 }
297 
298 
299 // Perform (signal) Method for generating list outlet
300 t_int* OutPerform(t_int* w)
301 {
302  OutPtr self = (OutPtr)(w[1]);
303  TTUInt16 numChannels;
304 
305  if (!self->obj.z_disabled) {
306  if (self->hasConnections) {
307  self->audioGraphObject->lockProcessing();
308  self->audioGraphObject->preprocess(self->initData);
309  self->audioGraphObject->process(self->audioSignal, self->sampleCount);
310  self->sampleCount += self->vectorSize;
311  self->audioGraphObject->unlockProcessing();
312 
313  numChannels = TTClip<TTUInt16>(self->maxNumChannels, 0, self->audioSignal->getNumChannelsAsInt());
314 
315  for (TTUInt16 channel=0; channel<numChannels; channel++) {
316  atom_setfloat(self->output_buffer+channel, self->audioSignal->getSample(channel, 0));
317  }
318  self->numChannels = numChannels;
319  clock_delay(self->clock, 0);
320  //outlet_list(self->s_out, NULL, numChannels, self->output_buffer); //list output
321  }
322  }
323 
324  self->hasReset = false;
325  return (w + 2);
326 
327 }
328 
329 void OutTick(OutPtr self)
330 {
331  outlet_list(self->s_out, NULL, self->numChannels, self->output_buffer); //list output
332 }
333 
334 // DSP Method
335 void OutDsp(OutPtr self, t_signal** sp, short* count)
336 {
337  //TTUInt16 i;
338  TTUInt16 k=0;
339  void **audioVectors = NULL;
340  t_max_err err;
341  long result = 0;
342 
343  self->vectorSize = sp[0]->s_n;
344 
345  #ifdef DEBUG_NOTIFICATIONS
346  object_post(SELF, "dsp method called");
347  #endif // DEBUG_NOTIFICATIONS
348 
349  /* We need to figure out what objects are connected to what inlets to build the graph:
350 
351  1. Broadcast 'audio.reset' to every object in the patcher, to remove all existing connections.
352  2. Broadcast 'audio.setup' to every object in the patcher, to tell objects to then send
353  'audio.connect' messages to any objects below them.
354  3. When an object received 'audio.connect', then it makes the connection.
355 
356  At this point, the graph is configured and we just need to execute it.
357  We execute the graph from our perform method, which MSP calls once per signal vector.
358 
359  5. Crawl the graph from bottom to top, calling the audio graph preprocess method (prepare for process)
360  6. Crawl the graph from bottom to top, calling the audio graph process method (calculate the samples)
361  7. (Maybe) crawl the graph from bottom to top, calling a audio graph postprocess method
362 
363  For steps 1 & 2, we have to traverse thge patcher twice,
364  because we have to clear all connections first, then add connections.
365  It won't work to do them both during the same traversal because situations arise
366  Where we setup the chain and then it gets reset again by another object
367  (since the order in which we traverse objects is undefined).
368  */
369 
370  if (!self->hasReset) {
371  t_object* patcher = NULL;
372  t_object* parent = NULL;
373  t_object* patcherview = NULL;
374 
375  // first find the top-level patcher
376  err = object_obex_lookup(self, gensym("#P"), &patcher);
377  parent = patcher;
378  while (parent) {
379  patcher = parent;
380  parent = object_attr_getobj(patcher, _sym_parentpatcher);
381  }
382 
383  // now iterate recursively from the top-level patcher down through all of the subpatchers
384  object_method(patcher, gensym("iterate"), (method)OutIterateResetCallback, self, PI_DEEP, &result);
385  object_method(patcher, gensym("iterate"), (method)OutIterateSetupCallback, self, PI_DEEP, &result);
386 
387  // now let's attach to the patcherview to get notifications about any further changes to the patch cords
388  // the patcher 'dirty' attribute is not modified for each change, but the patcherview 'dirty' attribute is
389  if (!self->patcherview) {
390  patcherview = jpatcher_get_firstview(patcher);
391  self->patcherview = patcherview;
392  self->patcher = patcher;
393  object_attach_byptr_register(self, patcherview, _sym_nobox);
394  }
395  }
396 
397  // now we want to go a step further and attach to all of the patch cords
398  // this is how we will know if one is deleted
399  OutAttachToPatchlinesForPatcher(self, self->patcher);
400 
401  // Setup the perform method
402  //audioVectors = (void**)sysmem_newptr(sizeof(void*) * (self->maxNumChannels + 1));
403  audioVectors = (void**)sysmem_newptr(sizeof(void*) * (1));
404 
405  audioVectors[k] = self;
406  k++;
407 
408  //self->numChannels = 0;
409  /*for (i=1; i <= self->maxNumChannels; i++) {
410  self->numChannels++;
411  audioVectors[k] = sp[i]->s_vec;
412  k++;
413  }*/
414  self->numChannels = self->maxNumChannels; //[np]
415  self->audioGraphObject->getUnitGenerator().get(kTTSym_sampleRate, sp[0]->s_sr);
416  self->audioGraphObject->resetSampleStamp();
417  self->sampleCount = 0;
418 
419  dsp_addv(OutPerform, k, audioVectors);
420  sysmem_freeptr(audioVectors);
421 
422  self->initData.vectorSize = self->vectorSize;
423 }
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
int C74_EXPORT main(void)
Set up this class as a Max external the first time an object of this kind is instantiated.
Definition: j.unsig.cpp:67
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.
#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