xref: /haiku/src/apps/cortex/RouteApp/RouteAppNodeManager.cpp (revision 17889a8c70dbb3d59c1412f6431968753c767bab)
1 /*
2  * Copyright (c) 1999-2000, Eric Moon.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions, and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions, and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. The name of the author may not be used to endorse or promote products
17  *    derived from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 
32 // RouteAppNodeManager.cpp
33 
34 #include "RouteAppNodeManager.h"
35 
36 #include "MediaIcon.h"
37 #include "NodeGroup.h"
38 #include "NodeRef.h"
39 #include "Connection.h"
40 
41 #include "route_app_io.h"
42 #include "ConnectionIO.h"
43 #include "DormantNodeIO.h"
44 #include "LiveNodeIO.h"
45 #include "MediaFormatIO.h"
46 #include "MessageIO.h"
47 #include "NodeSetIOContext.h"
48 #include "StringContent.h"
49 #include "MediaString.h"
50 
51 #include <Autolock.h>
52 #include <Debug.h>
53 #include <Entry.h>
54 #include <Path.h>
55 
56 #include <TimeSource.h>
57 
58 #include <cstring>
59 #include <cstdlib>
60 #include <cstdio>
61 #include <typeinfo>
62 
63 #include "set_tools.h"
64 
65 // Locale Kit
66 #include <Catalog.h>
67 
68 #undef B_TRANSLATION_CONTEXT
69 #define B_TRANSLATION_CONTEXT "CortexRouteApp"
70 
71 using namespace std;
72 
73 __USE_CORTEX_NAMESPACE
74 
75 #define D_METHOD(x) //PRINT (x)
76 #define D_HOOK(x) //PRINT (x)
77 #define D_SETTINGS(x) //PRINT (x)
78 
79 // -------------------------------------------------------- //
80 // *** ctor/dtor
81 // -------------------------------------------------------- //
82 
83 RouteAppNodeManager::~RouteAppNodeManager() {
84 
85 	_freeIcons();
86 }
87 
88 RouteAppNodeManager::RouteAppNodeManager(
89 	bool													useAddOnHost) :
90 	NodeManager(useAddOnHost),
91 	m_nextGroupNumber(1) {
92 
93 	// pre-cache icons? +++++
94 
95 }
96 
97 // -------------------------------------------------------- //
98 // *** group management
99 // -------------------------------------------------------- //
100 
101 // -------------------------------------------------------- //
102 // *** icon management
103 // -------------------------------------------------------- //
104 
105 // fetch cached icon for the given live node; the MediaIcon
106 // instance is guaranteed to last as long as this object.
107 // Returns 0 if the node doesn't exist.
108 
109 const MediaIcon* RouteAppNodeManager::mediaIconFor(
110 	media_node_id									nodeID,
111 	icon_size											iconSize) {
112 
113 	BAutolock _l(this);
114 
115 	uint64 key = _makeIconKey(nodeID, iconSize);
116 
117 	icon_map::const_iterator it = m_iconMap.find(key);
118 	if(it != m_iconMap.end()) {
119 		// already cached
120 		return (*it).second;
121 	}
122 
123 	// look up live_node_info
124 	NodeRef* ref;
125 	status_t err = getNodeRef(nodeID, &ref);
126 	if(err < B_OK)
127 		return 0;
128 
129 	return mediaIconFor(ref->nodeInfo(), iconSize);
130 }
131 
132 const MediaIcon* RouteAppNodeManager::mediaIconFor(
133 	live_node_info								nodeInfo,
134 	icon_size											iconSize) {
135 
136 	uint64 key = _makeIconKey(nodeInfo.node.node, iconSize);
137 
138 	icon_map::const_iterator it = m_iconMap.find(key);
139 	if(it != m_iconMap.end()) {
140 		// already cached
141 		return (*it).second;
142 	}
143 
144 	// create & cache icon
145 	MediaIcon* icon = new MediaIcon(
146 		nodeInfo, iconSize);
147 
148 	m_iconMap.insert(
149 		icon_map::value_type(key, icon));
150 
151 	return icon;
152 }
153 
154 // -------------------------------------------------------- //
155 // *** error handling
156 // -------------------------------------------------------- //
157 
158 status_t RouteAppNodeManager::setLogTarget(
159 	const BMessenger&							target) {
160 
161 	BAutolock _l(this);
162 
163 	if(!target.IsValid())
164 		return B_BAD_VALUE;
165 
166 	m_logTarget = target;
167 	return B_OK;
168 }
169 
170 // -------------------------------------------------------- //
171 // NodeManager hook implementations
172 // -------------------------------------------------------- //
173 
174 void RouteAppNodeManager::nodeCreated(
175 	NodeRef*											ref) {
176 
177 	// prepare the log message
178 	BMessage logMsg(M_LOG);
179 	BString title = B_TRANSLATE("Node '%name%' created");
180 	title.ReplaceFirst("%name%", ref->name());
181 	logMsg.AddString("title", title);
182 
183 	// create a default group for the node
184 	// [em 8feb00]
185 	NodeGroup* g = createGroup(ref->name());
186 
187 	if(ref->kind() & B_TIME_SOURCE) {
188 		// notify observers
189 		BMessage m(M_TIME_SOURCE_CREATED);
190 		m.AddInt32("nodeID", ref->id());
191 		notify(&m);
192 	}
193 
194 	// adopt node's time source if it's not the system clock (the default)
195 	// [em 20mar00]
196 	media_node systemClock;
197 	status_t err = roster->GetSystemTimeSource(&systemClock);
198 	if(err == B_OK)
199 	{
200 		BTimeSource* ts = roster->MakeTimeSourceFor(ref->node());
201 		if (ts == NULL)
202 			return;
203 		if(ts->Node() != systemClock)
204 		{
205 			g->setTimeSource(ts->Node());
206 			logMsg.AddString("line", "Synced to system clock");
207 		}
208 		ts->Release();
209 	}
210 
211 	g->addNode(ref);
212 
213 	m_logTarget.SendMessage(&logMsg);
214 }
215 
216 void RouteAppNodeManager::nodeDeleted(
217 	const NodeRef*								ref) {
218 
219 	// prepare the log message
220 	BMessage logMsg(M_LOG);
221 	BString title = B_TRANSLATE("Node '%name%' released");
222 	title.ReplaceFirst("%name%", ref->name());
223 	logMsg.AddString("title", title);
224 
225 	if(ref->kind() & B_TIME_SOURCE) {
226 		// notify observers
227 		BMessage m(M_TIME_SOURCE_DELETED);
228 		m.AddInt32("nodeID", ref->id());
229 		notify(&m);
230 	}
231 
232 	m_logTarget.SendMessage(&logMsg);
233 }
234 
235 void RouteAppNodeManager::connectionMade(
236 	Connection*										connection) {
237 
238 	D_HOOK((
239 		"@ RouteAppNodeManager::connectionMade()\n"));
240 
241 	status_t err;
242 
243 	// prepare the log message
244 	BMessage logMsg(M_LOG);
245 	BString title = B_TRANSLATE("Connection made");
246 	if (strcmp(connection->outputName(), connection->inputName()) == 0) {
247 		title = B_TRANSLATE("Connection '%name%' made");
248 		title.ReplaceFirst("%name%", connection->outputName());
249 	}
250 	logMsg.AddString("title", title);
251 
252 	if(!(connection->flags() & Connection::INTERNAL))
253 		// don't react to connection Cortex didn't make
254 		return;
255 
256 	// create or merge groups
257 	NodeRef *producer, *consumer;
258 	err = getNodeRef(connection->sourceNode(), &producer);
259 	if(err < B_OK) {
260 		D_HOOK((
261 			"!!! RouteAppNodeManager::connectionMade():\n"
262 			"    sourceNode (%ld) not found\n",
263 			connection->sourceNode()));
264 		return;
265 	}
266 	err = getNodeRef(connection->destinationNode(), &consumer);
267 	if(err < B_OK) {
268 		D_HOOK((
269 			"!!! RouteAppNodeManager::connectionMade():\n"
270 			"    destinationNode (%ld) not found\n",
271 			connection->destinationNode()));
272 		return;
273 	}
274 
275 	// add node names to log messages
276 	BString line = B_TRANSLATE("Between:");
277 	logMsg.AddString("line", line);
278 	line = "    ";
279 	line += B_TRANSLATE("%producer% and %consumer%");
280 	line.ReplaceFirst("%producer%", producer->name());
281 	line.ReplaceFirst("%consumer%", consumer->name());
282 	logMsg.AddString("line", line);
283 
284 	// add format to log message
285 	line = B_TRANSLATE("Negotiated format:");
286 	logMsg.AddString("line", line);
287 	line = "    ";
288 	line << MediaString::getStringFor(connection->format(), false);
289 	logMsg.AddString("line", line);
290 
291 	NodeGroup *group = 0;
292 	BString groupName = B_TRANSLATE("Untitled group");
293 	groupName += " ";
294 	if(_canGroup(producer) && _canGroup(consumer))
295 	{
296 		if (producer->group() && consumer->group() &&
297 			!(producer->group()->groupFlags() & NodeGroup::GROUP_LOCKED) &&
298 			!(consumer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
299 		{
300 			// merge into consumers group
301 			group = consumer->group();
302 			mergeGroups(producer->group(), group);
303 		}
304 		else if (producer->group() &&
305 			!(producer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
306 		{ // add consumer to producers group
307 			group = producer->group();
308 			group->addNode(consumer);
309 		}
310 		else if (consumer->group() &&
311 			!(consumer->group()->groupFlags() & NodeGroup::GROUP_LOCKED))
312 		{ // add producer to consumers group
313 			group = consumer->group();
314 			group->addNode(producer);
315 		}
316 		else
317 		{ // make new group for both
318 			groupName << m_nextGroupNumber++;
319 			group = createGroup(groupName.String());
320 			group->addNode(producer);
321 			group->addNode(consumer);
322 		}
323 	}
324 	else if(_canGroup(producer) && !producer->group())
325 	{ // make new group for producer
326 		groupName << m_nextGroupNumber++;
327 		group = createGroup(groupName.String());
328 		group->addNode(producer);
329 	}
330 	else if(_canGroup(consumer) && !consumer->group())
331 	{ // make new group for consumer
332 		groupName << m_nextGroupNumber++;
333 		group = createGroup(groupName.String());
334 		group->addNode(consumer);
335 	}
336 
337 	m_logTarget.SendMessage(&logMsg);
338 }
339 
340 void RouteAppNodeManager::connectionBroken(
341 	const Connection*									connection) {
342 
343 	D_HOOK((
344 		"@ RouteAppNodeManager::connectionBroken()\n"));
345 
346 	// prepare the log message
347 	BMessage logMsg(M_LOG);
348 	BString title = B_TRANSLATE("Connection broken");
349 	if (strcmp(connection->outputName(), connection->inputName()) == 0) {
350 		title = B_TRANSLATE("Connection '%name%' broken");
351 		title.ReplaceFirst("%name%", connection->outputName());
352 	}
353 	logMsg.AddString("title", title);
354 
355 	if(!(connection->flags() & Connection::INTERNAL))
356 		// don't react to connection Cortex didn't make
357 		return;
358 
359 	status_t err;
360 
361 	// if the source and destination nodes belong to the same group,
362 	// and if no direct or indirect connection remains between the
363 	// source and destination nodes, split groups
364 
365 	NodeRef *producer, *consumer;
366 	err = getNodeRef(connection->sourceNode(), &producer);
367 	if(err < B_OK) {
368 		D_HOOK((
369 			"!!! RouteAppNodeManager::connectionMade():\n"
370 			"    sourceNode (%ld) not found\n",
371 			connection->sourceNode()));
372 		return;
373 	}
374 	err = getNodeRef(connection->destinationNode(), &consumer);
375 	if(err < B_OK) {
376 		D_HOOK((
377 			"!!! RouteAppNodeManager::connectionMade():\n"
378 			"    destinationNode (%ld) not found\n",
379 			connection->destinationNode()));
380 		return;
381 	}
382 
383 	// add node names to log messages
384 	BString line = B_TRANSLATE("Between:");
385 	logMsg.AddString("line", line);
386 	line = "    ";
387 	line += B_TRANSLATE("%producer% and %consumer%");
388 	line.ReplaceFirst("%producer%", producer->name());
389 	line.ReplaceFirst("%consumer%", consumer->name());
390 	logMsg.AddString("line", line);
391 
392 	if(
393 		producer->group() &&
394 		producer->group() == consumer->group() &&
395 		!findRoute(producer->id(), consumer->id())) {
396 
397 		NodeGroup *newGroup;
398 		splitGroup(producer, consumer, &newGroup);
399 	}
400 
401 	m_logTarget.SendMessage(&logMsg);
402 }
403 
404 void RouteAppNodeManager::connectionFailed(
405 	const media_output &							output,
406 	const media_input &								input,
407 	const media_format &							format,
408 	status_t										error) {
409 	D_HOOK((
410 		"@ RouteAppNodeManager::connectionFailed()\n"));
411 
412 	status_t err;
413 
414 	// prepare the log message
415 	BMessage logMsg(M_LOG);
416 	BString title = B_TRANSLATE("Connection failed");
417 	logMsg.AddString("title", title);
418 	logMsg.AddInt32("error", error);
419 
420 	NodeRef *producer, *consumer;
421 	err = getNodeRef(output.node.node, &producer);
422 	if(err < B_OK) {
423 		return;
424 	}
425 	err = getNodeRef(input.node.node, &consumer);
426 	if(err < B_OK) {
427 		return;
428 	}
429 
430 	// add node names to log messages
431 	BString line = B_TRANSLATE("Between:");
432 	logMsg.AddString("line", line);
433 	line = "    ";
434 	line += B_TRANSLATE("%producer% and %consumer%");
435 	line.ReplaceFirst("%producer%", producer->name());
436 	line.ReplaceFirst("%consumer%", consumer->name());
437 	logMsg.AddString("line", line);
438 
439 	// add format to log message
440 	line = B_TRANSLATE("Tried format:");
441 	logMsg.AddString("line", line);
442 	line = "    ";
443 	line << MediaString::getStringFor(format, true);
444 	logMsg.AddString("line", line);
445 
446 	// and send it
447 	m_logTarget.SendMessage(&logMsg);
448 }
449 
450 // -------------------------------------------------------- //
451 // *** IPersistent
452 // -------------------------------------------------------- //
453 
454 void RouteAppNodeManager::xmlExportBegin(
455 	ExportContext&								context) const {
456 
457 	status_t err;
458 
459 	try {
460 		NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
461 		context.beginElement(_NODE_SET_ELEMENT);
462 
463 		// validate the node set
464 		for(int n = set.countNodes()-1; n >= 0; --n) {
465 			media_node_id id = set.nodeAt(n);
466 			ASSERT(id != media_node::null.node);
467 
468 			// fetch node
469 			NodeRef* ref;
470 			err = getNodeRef(id, &ref);
471 			if(err < B_OK) {
472 				D_SETTINGS((
473 					"! RVNM::xmlExportBegin(): node %ld doesn't exist\n", id));
474 
475 				set.removeNodeAt(n);
476 				continue;
477 			}
478 			// skip unless internal
479 			if(!ref->isInternal()) {
480 				D_SETTINGS((
481 					"! RVNM::xmlExportBegin(): node %ld not internal; skipping.\n", id));
482 
483 				set.removeNodeAt(n);
484 				continue;
485 			}
486 		}
487 	}
488 	catch(bad_cast& e) {
489 		context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
490 	}
491 }
492 
493 void RouteAppNodeManager::xmlExportAttributes(
494 	ExportContext&								context) const {}
495 
496 void RouteAppNodeManager::xmlExportContent(
497 	ExportContext&								context) const {
498 
499 	status_t err;
500 
501 	try {
502 		NodeSetIOContext& nodeSet = dynamic_cast<NodeSetIOContext&>(context);
503 		context.beginContent();
504 
505 		// write nodes; enumerate connections
506 		typedef map<uint32,Connection> connection_map;
507 		connection_map connections;
508 		int count = nodeSet.countNodes();
509 		for(int n = 0; n < count; ++n) {
510 			media_node_id id = nodeSet.nodeAt(n);
511 			ASSERT(id != media_node::null.node);
512 
513 			// fetch node
514 			NodeRef* ref;
515 			err = getNodeRef(id, &ref);
516 			if(err < B_OK) {
517 				D_SETTINGS((
518 					"! RouteAppNodeManager::xmlExportContent():\n"
519 					"  getNodeRef(%ld) failed: '%s'\n",
520 					id, strerror(err)));
521 				continue;
522 			}
523 
524 			// fetch connections
525 			vector<Connection> conSet;
526 			ref->getInputConnections(conSet);
527 			ref->getOutputConnections(conSet);
528 			for(uint32 c = 0; c < conSet.size(); ++c)
529 				// non-unique connections will be rejected:
530 				connections.insert(
531 					connection_map::value_type(conSet[c].id(), conSet[c]));
532 
533 			// create an IO object for the node & write it
534 			DormantNodeIO io(ref, nodeSet.keyAt(n));
535 			if(context.writeObject(&io) < B_OK)
536 				// abort
537 				return;
538 		}
539 
540 		// write connections
541 		for(connection_map::const_iterator it = connections.begin();
542 			it != connections.end(); ++it) {
543 
544 			ConnectionIO io(
545 				&(*it).second,
546 				this,
547 				&nodeSet);
548 			if(context.writeObject(&io) < B_OK)
549 				// abort
550 				return;
551 
552 		}
553 
554 		// +++++ write groups
555 
556 		// write UI state
557 		{
558 			BMessage m;
559 			nodeSet.exportUIState(&m);
560 			context.beginElement(_UI_STATE_ELEMENT);
561 			context.beginContent();
562 			MessageIO io(&m);
563 			context.writeObject(&io);
564 			context.endElement();
565 		}
566 	}
567 	catch(bad_cast& e) {
568 		context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
569 	}
570 }
571 
572 void RouteAppNodeManager::xmlExportEnd(
573 	ExportContext&								context) const {
574 
575 	context.endElement();
576 }
577 
578 // -------------------------------------------------------- //
579 // IMPORT
580 // -------------------------------------------------------- //
581 
582 void RouteAppNodeManager::xmlImportBegin(
583 	ImportContext&								context) {
584 
585 }
586 
587 void RouteAppNodeManager::xmlImportAttribute(
588 	const char*										key,
589 	const char*										value,
590 	ImportContext&								context) {}
591 
592 void RouteAppNodeManager::xmlImportContent(
593 	const char*										data,
594 	uint32												length,
595 	ImportContext&								context) {}
596 
597 void RouteAppNodeManager::xmlImportChild(
598 	IPersistent*									child,
599 	ImportContext&								context) {
600 
601 	status_t err;
602 
603 	if(!strcmp(context.element(), _DORMANT_NODE_ELEMENT)) {
604 		DormantNodeIO* io = dynamic_cast<DormantNodeIO*>(child);
605 		ASSERT(io);
606 
607 		NodeRef* newRef;
608 		err = io->instantiate(this, &newRef);
609 		if(err == B_OK) {
610 			// created node; add an entry to the set stored in the
611 			// ImportContext for later reference
612 			try {
613 				NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
614 				set.addNode(newRef->id(), io->nodeKey());
615 			}
616 			catch(bad_cast& e) {
617 				context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
618 			}
619 		}
620 		else {
621 			D_SETTINGS((
622 				"!!! RouteAppNodeManager::xmlImportChild():\n"
623 				"    DormantNodeIO::instantiate() failed:\n"
624 				"    '%s'\n",
625 				strerror(err)));
626 		}
627 	}
628 	else if(!strcmp(context.element(), _CONNECTION_ELEMENT)) {
629 		ConnectionIO* io = dynamic_cast<ConnectionIO*>(child);
630 		ASSERT(io);
631 
632 		// instantiate the connection
633 		Connection con;
634 		err = io->instantiate(
635 			this,
636 			dynamic_cast<NodeSetIOContext*>(&context),
637 			&con);
638 		if(err < B_OK) {
639 			D_SETTINGS((
640 				"!!! ConnectionIO::instantiate() failed:\n"
641 				"    '%s'\n", strerror(err)));
642 		}
643 
644 		// +++++ group magic?
645 
646 	}
647 	else if(!strcmp(context.element(), _NODE_GROUP_ELEMENT)) {
648 		// +++++
649 	}
650 	else if(
651 		context.parentElement() &&
652 		!strcmp(context.parentElement(), _UI_STATE_ELEMENT)) {
653 
654 		// expect a nested message
655 		MessageIO* io = dynamic_cast<MessageIO*>(child);
656 		if(!io) {
657 			BString err;
658 			err <<
659 				"RouteAppNodeManager: unexpected child '" <<
660 				context.element() << "'\n";
661 			context.reportError(err.String());
662 		}
663 
664 		// hand it off via the extended context object
665 		try {
666 			NodeSetIOContext& set = dynamic_cast<NodeSetIOContext&>(context);
667 			set.importUIState(io->message());
668 		}
669 		catch(bad_cast& e) {
670 			context.reportError("RouteAppNodeManager: expected a NodeSetIOContext\n");
671 		}
672 	}
673 
674 }
675 
676 void RouteAppNodeManager::xmlImportComplete(
677 	ImportContext&								context) {
678 
679 }
680 
681 // -------------------------------------------------------- //
682 
683 /*static*/
684 void RouteAppNodeManager::AddTo(
685 	XML::DocumentType*				docType) {
686 
687 	// set up the document type
688 
689 	MessageIO::AddTo(docType);
690 	MediaFormatIO::AddTo(docType);
691 	ConnectionIO::AddTo(docType);
692 	DormantNodeIO::AddTo(docType);
693 	LiveNodeIO::AddTo(docType);
694 	_add_string_elements(docType);
695 }
696 
697 // -------------------------------------------------------- //
698 // implementation
699 // -------------------------------------------------------- //
700 
701 uint64 RouteAppNodeManager::_makeIconKey(
702 	media_node_id nodeID, icon_size iconSize) {
703 
704 	return ((uint64)nodeID) << 32 | iconSize;
705 }
706 
707 void RouteAppNodeManager::_readIconKey(
708 	uint64 key, media_node_id& nodeID, icon_size& iconSize) {
709 
710 	nodeID = key >> 32;
711 	iconSize = icon_size(key & 0xffffffff);
712 }
713 
714 void RouteAppNodeManager::_freeIcons() {
715 
716 	ptr_map_delete(
717 		m_iconMap.begin(),
718 		m_iconMap.end());
719 }
720 
721 bool RouteAppNodeManager::_canGroup(NodeRef* ref) const {
722 
723 	// sanity check & easy cases
724 	ASSERT(ref);
725 	if(ref->isInternal())
726 		return true;
727 
728 	// bar 'touchy' system nodes
729 	if(ref == audioMixerNode() || ref == audioOutputNode())
730 		return false;
731 
732 	return true;
733 }
734 
735 // END -- RouteAppNodeManager.cpp --
736