Jamoma API  0.6.0.a19
j.midi.in.cpp
Go to the documentation of this file.
1 /** @file
2  *
3  * @ingroup implementationMaxExternalsGraph
4  *
5  * @brief j.midi.in# - Jamoma Graph external object for Max
6  *
7  * @details
8  *
9  * @authors Tim Place, Trond Lossius
10  *
11  * @copyright Copyright © 2011 by Timothy 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 "maxGraph.h"
18 
19 
20 // Data Structure for this object
21 struct MidiIn {
22  t_object obj;
23  TTGraphObjectBasePtr graphObject; // this _must_ be second
24  TTPtr graphOutlets[2]; // this _must_ be third (for the setup call) : null-terminated array
25  TTDictionaryPtr graphDictionary;
26  t_object* patcher; // the patcher -- cached for iterating to make connections
27  t_object* patcherview; // first view of the top-level patcher (for dirty notifications)
28  TTPtr qelem; // for clumping patcher dirty notifications
29 };
30 typedef MidiIn* MidiInPtr;
31 
32 
33 // Prototypes for methods
34 MidiInPtr MidiInNew (t_symbol* msg, long argc, t_atom* argv);
35 void MidiInFree (MidiInPtr self);
36 void PackStartTracking (MidiInPtr self);
37 t_max_err PackNotify (MidiInPtr self, t_symbol* s, t_symbol* msg, t_object* sender, TTPtr data);
38 void PackQFn (MidiInPtr self);
39 void MidiInAssist (MidiInPtr self, void* b, long msg, long arg, char* dst);
40 void MidiInGetDeviceNames (MidiInPtr self);
41 // Prototypes for attribute accessors
42 t_max_err MidiInSetDevice (MidiInPtr self, void* attr, long argc, t_atom* argv);
43 t_max_err MidiInGetDevice (MidiInPtr self, void* attr, long* argc, t_atom** argv);
44 
45 
46 // Globals
47 static t_class* sMidiInClass;
48 
49 
50 /************************************************************************************/
51 // Main() Function
52 
53 int C74_EXPORT main(void)
54 {
55  t_class* c;
56 
57  TTGraphInit();
58  common_symbols_init();
59 
60  c = class_new("j.midi.in-", (method)MidiInNew, (method)MidiInFree, sizeof(MidiIn), (method)0L, A_GIMME, 0);
61 
62  class_addmethod(c, (method)MidiInGetDeviceNames, "getAvailableDeviceNames", 0);
63  class_addmethod(c, (method)MaxGraphReset, "graph.reset", A_CANT, 0);
64  class_addmethod(c, (method)MaxGraphSetup, "graph.setup", A_CANT, 0);
65  class_addmethod(c, (method)MaxGraphDrop, "graph.drop", A_CANT, 0);
66  class_addmethod(c, (method)MaxGraphObject, "graph.object", A_CANT, 0);
67  class_addmethod(c, (method)PackNotify, "notify", A_CANT, 0);
68  class_addmethod(c, (method)MidiInAssist, "assist", A_CANT, 0);
69  class_addmethod(c, (method)object_obex_dumpout, "dumpout", A_CANT, 0);
70 
71  CLASS_ATTR_SYM(c, "device", 0, MidiIn, obj);
72  CLASS_ATTR_ACCESSORS(c, "device", MidiInGetDevice, MidiInSetDevice);
73 
74  class_register(_sym_box, c);
75  sMidiInClass = c;
76  return 0;
77 }
78 
79 
80 /************************************************************************************/
81 // Life Cycle
82 
83 MidiInPtr MidiInNew(t_symbol* msg, long argc, t_atom* argv)
84 {
85  MidiInPtr self = MidiInPtr(object_alloc(sMidiInClass));
86  TTValue v;
87  TTErr err;
88 
89  if (self) {
90  object_obex_store((void*)self, _sym_dumpout, (t_object*)outlet_new(self, NULL));
91  self->graphOutlets[0] = outlet_new(self, "graph.connect");
92 
93  v.resize(2);
94  v[0] = "midi.in";
95  v[1] = 1;
96  err = TTObjectBaseInstantiate(TT("graph.object"), (TTObjectBasePtr*)&self->graphObject, v);
97  self->graphObject->mKernel.set(TT("owner"), TTPtr(self->graphObject));
98 
99  if (!self->graphObject->mKernel.valid()) {
100  object_error(SELF, "cannot load Jamoma object");
101  return NULL;
102  }
103 
104  self->graphDictionary = new TTDictionary;
105  self->graphDictionary->setSchema(TT("none"));
106  self->graphDictionary->append(TT("outlet"), 0);
107 
108  attr_args_process(self, argc, argv);
109 
110  self->qelem = qelem_new(self, (method)PackQFn);
111  defer_low(self, (method)PackStartTracking, NULL, 0, NULL);
112  }
113  return self;
114 }
115 
116 
117 void MidiInFree(MidiInPtr self)
118 {
119  TTObjectBaseRelease((TTObjectBasePtr*)&self->graphObject);
120  qelem_free(self->qelem);
121 
122  // TODO: do we need to detach from the patcher view? Or is that automatic when we free?
123 }
124 
125 
126 /************************************************************************************/
127 
128 // TODO: This section has lots of ugly duplication from the pack# object because this object is the source for a graph
129 
130 t_max_err PackNotify(MidiInPtr self, t_symbol* s, t_symbol* msg, t_object* sender, TTPtr data)
131 {
132  if (sender == self->patcherview) {
133  if (msg == _sym_attr_modified) {
134  t_symbol* name = (t_symbol*)object_method((t_object*)data, _sym_getname);
135  if (name == _sym_dirty) {
136  qelem_set(self->qelem);
137  }
138  }
139  else if (msg == _sym_free)
140  self->patcherview = NULL;
141  }
142  else {
143  if (msg == _sym_free) {
144  t_object* sourceBox;
145  t_object* sourceObject;
146  long sourceOutlet;
147  t_object* destBox;
148  t_object* destObject;
149  long destInlet;
150 
151 #ifdef DEBUG_NOTIFICATIONS
152  object_post(SELF, "patch line deleted");
153 #endif // DEBUG_NOTIFICATIONS
154 
155  // get boxes and inlets
156  sourceBox = jpatchline_get_box1(sender);
157  if (!sourceBox)
158  goto out;
159 
160  sourceObject = jbox_get_object(sourceBox);
161  sourceOutlet = jpatchline_get_outletnum(sender);
162  destBox = jpatchline_get_box2(sender);
163  if (!destBox)
164  goto out;
165  destObject = jbox_get_object(destBox);
166  destInlet = jpatchline_get_inletnum(sender);
167 
168  // if both boxes are graph objects
169  if ( zgetfn(sourceObject, gensym("graph.object")) && zgetfn(destObject, gensym("graph.object")) ) {
170 #ifdef DEBUG_NOTIFICATIONS
171  object_post(SELF, "deleting graph patchline!");
172 #endif // DEBUG_NOTIFICATIONS
173 
174  object_method(destObject, gensym("graph.drop"), destInlet, sourceObject, sourceOutlet);
175  }
176  out:
177  ;
178  }
179  }
180  return MAX_ERR_NONE;
181 }
182 
183 
184 void PackIterateResetCallback(MidiInPtr self, t_object* obj)
185 {
186  t_max_err err = MAX_ERR_NONE;
187  method graphResetMethod = zgetfn(obj, gensym("graph.reset"));
188 
189  if (graphResetMethod)
190  err = (t_max_err)graphResetMethod(obj);
191 }
192 
193 
194 void PackIterateSetupCallback(MidiInPtr self, t_object* obj)
195 {
196  t_max_err err = MAX_ERR_NONE;
197  method graphSetupMethod = zgetfn(obj, gensym("graph.setup"));
198 
199  if (graphSetupMethod)
200  err = (t_max_err)graphSetupMethod(obj);
201 }
202 
203 
204 void PackAttachToPatchlinesForPatcher(MidiInPtr self, t_object* patcher)
205 {
206  t_object* patchline = object_attr_getobj(patcher, _sym_firstline);
207  t_object* box = jpatcher_get_firstobject(patcher);
208 
209  while (patchline) {
210  object_attach_byptr_register(self, patchline, _sym_nobox);
211  patchline = object_attr_getobj(patchline, _sym_nextline);
212  }
213 
214  while (box) {
215  t_symbol* classname = jbox_get_maxclass(box);
216 
217  if (classname == _sym_jpatcher) {
218  t_object* subpatcher = jbox_get_object(box);
219 
220  PackAttachToPatchlinesForPatcher(self, subpatcher);
221  }
222  box = jbox_get_nextobject(box);
223  }
224 }
225 
226 
227 // Qelem function, which clumps together dirty notifications before making the new connections
228 void PackQFn(MidiInPtr self)
229 {
230  t_atom result;
231 
232 #ifdef DEBUG_NOTIFICATIONS
233  object_post(SELF, "patcher dirtied");
234 #endif // DEBUG_NOTIFICATIONS
235 
236  object_method(self->patcher, gensym("iterate"), (method)PackIterateSetupCallback, self, PI_DEEP, &result);
237 
238  // attach to all of the patch cords so we will know if one is deleted
239  // we are not trying to detach first -- hopefully this is okay and multiple attachments will be filtered (?)
240  PackAttachToPatchlinesForPatcher(self, self->patcher);
241 }
242 
243 
244 // Start keeping track of edits and connections in the patcher
245 void PackStartTracking(MidiInPtr self)
246 {
247  t_object* patcher = NULL;
248  t_object* parent = NULL;
249  t_object* patcherview = NULL;
250  t_max_err err;
251  t_atom result;
252 
253  // first find the top-level patcher
254  err = object_obex_lookup(self, gensym("#P"), &patcher);
255  parent = patcher;
256  while (parent) {
257  patcher = parent;
258  parent = object_attr_getobj(patcher, _sym_parentpatcher);
259  }
260 
261  // now iterate recursively from the top-level patcher down through all of the subpatchers
262  object_method(patcher, gensym("iterate"), (method)PackIterateResetCallback, self, PI_DEEP, &result);
263  object_method(patcher, gensym("iterate"), (method)PackIterateSetupCallback, self, PI_DEEP, &result);
264 
265 
266  // now let's attach to the patcherview to get notifications about any further changes to the patch cords
267  // the patcher 'dirty' attribute is not modified for each change, but the patcherview 'dirty' attribute is
268  if (!self->patcherview) {
269  patcherview = jpatcher_get_firstview(patcher);
270  self->patcherview = patcherview;
271  self->patcher = patcher;
272  object_attach_byptr_register(self, patcherview, _sym_nobox);
273  }
274 
275  // now we want to go a step further and attach to all of the patch cords
276  // this is how we will know if one is deleted
277  PackAttachToPatchlinesForPatcher(self, self->patcher);
278 }
279 
280 
281 /************************************************************************************/
282 // Methods bound to input/inlets
283 
284 void MidiInAssist(MidiInPtr self, void* b, long msg, long arg, char* dst)
285 {
286  if (msg==1) // Inlets
287  strcpy(dst, "multichannel audio connection and control messages");
288  else if (msg==2) { // Outlets
289  if (arg == 0)
290  strcpy(dst, "stats on DSP graph");
291  else
292  strcpy(dst, "dumpout");
293 
294  }
295 }
296 
297 
298 void MidiInGetDeviceNames(MidiInPtr self)
299 {
300  TTValue v, none;
301  TTErr err;
302  long ac;
303  t_atom* ap;
304  TTSymbol name;
305 
306  err = self->graphObject->mKernel.send(TT("getAvailableDeviceNames"), none, v);
307  if (!err) {
308  ac = v.size();
309  ap = new t_atom[ac];
310 
311  for (long i=0; i<ac; i++) {
312  name = v[i];
313  atom_setsym(ap+i, gensym((char*)name.c_str()));
314  }
315  object_obex_dumpout(self, gensym("getAvailableDeviceNames"), ac, ap);
316  delete ap;
317  }
318 }
319 
320 
321 t_max_err MidiInSetDevice(MidiInPtr self, void* attr, long argc, t_atom* argv)
322 {
323  if (argc) {
324  t_symbol* s = atom_getsym(argv);
325  self->graphObject->mKernel.set(TT("device"), TT(s->s_name));
326  }
327  return MAX_ERR_NONE;
328 }
329 
330 
331 t_max_err MidiInGetDevice(MidiInPtr self, void* attr, long* argc, t_atom** argv)
332 {
333  TTValue v;
334  TTSymbol s;
335 
336  self->graphObject->mKernel.get(TT("device"), v);
337  s = v[0];
338  if (!s)
339  return MAX_ERR_GENERIC;
340 
341  *argc = 1;
342  if (!(*argv)) // otherwise use memory passed in
343  *argv = (t_atom *)sysmem_newptr(sizeof(t_atom));
344  atom_setsym(*argv, gensym((char*)s.c_str()));
345  return MAX_ERR_NONE;
346 }
The TTGraphObjectBase wraps a TTDSP object such that it is possible to build a dynamic graph of audio...
TTErr TTObjectBaseRelease(TTObjectBasePtr *anObject)
DEPRECATED.
size_type size() const noexcept
Return the number of elements.
Base class for all first-class Jamoma objects.
Definition: TTObjectBase.h:109
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.midi.in.cpp:53
#define TT
This macro is defined as a shortcut for doing a lookup in the symbol table.
Definition: TTSymbol.h:155
A type that represents the key as a C-String and the value as a pointer to the matching TTSymbol obje...
Definition: TTDictionary.h:47
void * TTPtr
A generic pointer.
Definition: TTBase.h:248
The TTSymbol class is used to represent a string and efficiently pass and compare that string...
Definition: TTSymbol.h:26
TTErr TTObjectBaseInstantiate(const TTSymbol className, TTObjectBasePtr *returnedObjectPtr, const TTValue arguments)
DEPRECATED.
const char * c_str() const
Return a pointer to the internal string as a C-string.
Definition: TTSymbol.h:77
TTErr setSchema(const TTSymbol aSchemaName)
TODO: Add documentation schemaName TODO: Add documentation.
Definition: TTDictionary.h:172
TTErr
Jamoma Error Codes Enumeration of error codes that might be returned by any of the TTBlue functions a...
Definition: TTBase.h:342
void resize(size_type n)
Change the number of elements.
[doxygenAppendixC_copyExample]
Definition: TTValue.h:34