xref: /haiku/src/servers/midi/MidiServerApp.cpp (revision 81f5654c124bf46fba0fd251f208e2d88d81e1ce)
1 /*
2  * Copyright (c) 2002-2004 Matthijs Hollemans
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20  * DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include <Alert.h>
24 
25 #include "debug.h"
26 #include "MidiServerApp.h"
27 #include "PortDrivers.h"
28 #include "ServerDefs.h"
29 #include "protocol.h"
30 
31 //------------------------------------------------------------------------------
32 
33 MidiServerApp::MidiServerApp()
34 	: BApplication(MIDI_SERVER_SIGNATURE)
35 {
36 	TRACE(("Running OpenBeOS MIDI server"))
37 
38 	nextId = 1;
39 	devWatcher.Start();
40 }
41 
42 //------------------------------------------------------------------------------
43 
44 MidiServerApp::~MidiServerApp()
45 {
46 	for (int32 t = 0; t < CountApps(); ++t)
47 	{
48 		delete AppAt(t);
49 	}
50 
51 	for (int32 t = 0; t < CountEndpoints(); ++t)
52 	{
53 		delete EndpointAt(t);
54 	}
55 }
56 
57 //------------------------------------------------------------------------------
58 
59 void MidiServerApp::AboutRequested()
60 {
61 	(new BAlert(0,
62 		"OpenBeOS midi_server\n\n"
63 		"notes disguised as bytes\n"
64 		"propagating to endpoints,\n"
65 		"an aural delight",
66 		"Okay", 0, 0, B_WIDTH_AS_USUAL,
67 		B_INFO_ALERT))->Go();
68 }
69 
70 //------------------------------------------------------------------------------
71 
72 void MidiServerApp::MessageReceived(BMessage* msg)
73 {
74 	#ifdef DEBUG
75 	printf("IN "); msg->PrintToStream();
76 	#endif
77 
78 	// About thread safety inside the midi_server: even though
79 	// multiple apps may be sending requests to the server at
80 	// the same time, the BLooper's thread handles passes them
81 	// to our MessageReceived() one after the other, so there
82 	// is no need to synchronize anything.
83 
84 	switch (msg->what)
85 	{
86 		case MSG_REGISTER_APP:         OnRegisterApp(msg);       break;
87 		case MSG_CREATE_ENDPOINT:      OnCreateEndpoint(msg);    break;
88 		case MSG_DELETE_ENDPOINT:      OnDeleteEndpoint(msg);    break;
89 		case MSG_PURGE_ENDPOINT:       OnPurgeEndpoint(msg);     break;
90 		case MSG_CHANGE_ENDPOINT:      OnChangeEndpoint(msg);    break;
91 		case MSG_CONNECT_ENDPOINTS:    OnConnectDisconnect(msg); break;
92 		case MSG_DISCONNECT_ENDPOINTS: OnConnectDisconnect(msg); break;
93 
94 		default: super::MessageReceived(msg); break;
95 	}
96 }
97 
98 //------------------------------------------------------------------------------
99 
100 void MidiServerApp::OnRegisterApp(BMessage* msg)
101 {
102 	TRACE(("MidiServerApp::OnRegisterApp"))
103 
104 	// We only send the "app registered" message upon success,
105 	// so if anything goes wrong here, we do not let the app
106 	// know about it, and we consider it unregistered. (Most
107 	// likely, the app is dead. If not, it freezes forever
108 	// in anticipation of a message that will never arrive.)
109 
110 	app_t* app = new app_t;
111 
112 	if (msg->FindMessenger("midi:messenger", &app->messenger) == B_OK)
113 	{
114 		if (SendAllEndpoints(app))
115 		{
116 			if (SendAllConnections(app))
117 			{
118 				BMessage reply;
119 				reply.what = MSG_APP_REGISTERED;
120 
121 				if (SendNotification(app, &reply))
122 				{
123 					apps.AddItem(app);
124 
125 					#ifdef DEBUG
126 					DumpApps();
127 					#endif
128 
129 					return;
130 				}
131 			}
132 		}
133 	}
134 
135 	delete app;
136 }
137 
138 //------------------------------------------------------------------------------
139 
140 void MidiServerApp::OnCreateEndpoint(BMessage* msg)
141 {
142 	TRACE(("MidiServerApp::OnCreateEndpoint"))
143 
144 	status_t err;
145 
146 	endpoint_t* endp = new endpoint_t;
147 
148 	endp->app = WhichApp(msg);
149 	if (endp->app == NULL)
150 	{
151 		err = B_ERROR;
152 	}
153 	else
154 	{
155 		err = B_BAD_VALUE;
156 
157 		if ((msg->FindBool("midi:consumer", &endp->consumer) == B_OK)
158 		&&  (msg->FindBool("midi:registered", &endp->registered) == B_OK)
159 		&&  (msg->FindString("midi:name", &endp->name) == B_OK)
160 		&&  (msg->FindMessage("midi:properties", &endp->properties) == B_OK))
161 		{
162 			if (endp->consumer)
163 			{
164 				if ((msg->FindInt32("midi:port", &endp->port) == B_OK)
165 				&&  (msg->FindInt64("midi:latency", &endp->latency) == B_OK))
166 				{
167 					err = B_OK;
168 				}
169 			}
170 			else
171 			{
172 				err = B_OK;
173 			}
174 		}
175 	}
176 
177 	BMessage reply;
178 
179 	if (err == B_OK)
180 	{
181 		endp->id = nextId++;
182 		reply.AddInt32("midi:id", endp->id);
183 	}
184 
185 	reply.AddInt32("midi:result", err);
186 
187 	if (SendReply(endp->app, msg, &reply) && (err == B_OK))
188 	{
189 		AddEndpoint(msg, endp);
190 	}
191 	else
192 	{
193 		delete endp;
194 	}
195 }
196 
197 //------------------------------------------------------------------------------
198 
199 void MidiServerApp::OnDeleteEndpoint(BMessage* msg)
200 {
201 	TRACE(("MidiServerApp::OnDeleteEndpoint"))
202 
203 	// Clients send the "delete endpoint" message from
204 	// the BMidiEndpoint destructor, so there is no point
205 	// sending a reply, because the endpoint object will
206 	// be destroyed no matter what.
207 
208 	app_t* app = WhichApp(msg);
209 	if (app != NULL)
210 	{
211 		endpoint_t* endp = WhichEndpoint(msg, app);
212 		if (endp != NULL)
213 		{
214 			RemoveEndpoint(app, endp);
215 		}
216 	}
217 }
218 
219 //------------------------------------------------------------------------------
220 
221 void MidiServerApp::OnPurgeEndpoint(BMessage* msg)
222 {
223 	TRACE(("MidiServerApp::OnPurgeEndpoint"))
224 
225 	// This performs the same task as OnDeleteEndpoint(),
226 	// except that this message was send by the midi_server
227 	// itself, so we don't check that the app that made the
228 	// request really is the owner of the endpoint. (But we
229 	// _do_ check that the message came from the server.)
230 
231 	if (!msg->IsSourceRemote())
232 	{
233 		int32 id;
234 		if (msg->FindInt32("midi:id", &id) == B_OK)
235 		{
236 			endpoint_t* endp = FindEndpoint(id);
237 			if (endp != NULL)
238 			{
239 				RemoveEndpoint(NULL, endp);
240 			}
241 		}
242 	}
243 }
244 
245 //------------------------------------------------------------------------------
246 
247 void MidiServerApp::OnChangeEndpoint(BMessage* msg)
248 {
249 	TRACE(("MidiServerApp::OnChangeEndpoint"))
250 
251 	endpoint_t* endp = NULL;
252 	status_t err;
253 
254 	app_t* app = WhichApp(msg);
255 	if (app == NULL)
256 	{
257 		err = B_ERROR;
258 	}
259 	else
260 	{
261 		endp = WhichEndpoint(msg, app);
262 		if (endp == NULL)
263 		{
264 			err = B_BAD_VALUE;
265 		}
266 		else
267 		{
268 			err = B_OK;
269 		}
270 	}
271 
272 	BMessage reply;
273 	reply.AddInt32("midi:result", err);
274 
275 	if (SendReply(app, msg, &reply) && (err == B_OK))
276 	{
277 		TRACE(("Endpoint %ld (%p) changed", endp->id, endp))
278 
279 		BMessage notify;
280 		notify.what = MSG_ENDPOINT_CHANGED;
281 		notify.AddInt32("midi:id", endp->id);
282 
283 		bool registered;
284 		if (msg->FindBool("midi:registered", &registered) == B_OK)
285 		{
286 			notify.AddBool("midi:registered", registered);
287 			endp->registered = registered;
288 		}
289 
290 		BString name;
291 		if (msg->FindString("midi:name", &name) == B_OK)
292 		{
293 			notify.AddString("midi:name", name);
294 			endp->name = name;
295 		}
296 
297 		BMessage properties;
298 		if (msg->FindMessage("midi:properties", &properties) == B_OK)
299 		{
300 			notify.AddMessage("midi:properties", &properties);
301 			endp->properties = properties;
302 		}
303 
304 		bigtime_t latency;
305 		if (msg->FindInt64("midi:latency", &latency) == B_OK)
306 		{
307 			notify.AddInt64("midi:latency", latency);
308 			endp->latency = latency;
309 		}
310 
311 		NotifyAll(&notify, app);
312 
313 		#ifdef DEBUG
314 		DumpEndpoints();
315 		#endif
316 	}
317 }
318 
319 //------------------------------------------------------------------------------
320 
321 void MidiServerApp::OnConnectDisconnect(BMessage* msg)
322 {
323 	TRACE(("MidiServerApp::OnConnectDisconnect"))
324 
325 	bool mustConnect = (msg->what == MSG_CONNECT_ENDPOINTS);
326 
327 	status_t err;
328 	endpoint_t* prod = NULL;
329 	endpoint_t* cons = NULL;
330 
331 	app_t* app = WhichApp(msg);
332 	if (app == NULL)
333 	{
334 		err = B_ERROR;
335 	}
336 	else
337 	{
338 		err = B_BAD_VALUE;
339 
340 		int32 prodId, consId;
341 		if ((msg->FindInt32("midi:producer", &prodId) == B_OK)
342 		&&  (msg->FindInt32("midi:consumer", &consId) == B_OK))
343 		{
344 			prod = FindEndpoint(prodId);
345 			cons = FindEndpoint(consId);
346 
347 			if ((prod != NULL) && !prod->consumer)
348 			{
349 				if ((cons != NULL) && cons->consumer)
350 				{
351 					// It is an error to connect two endpoints that
352 					// are already connected, or to disconnect two
353 					// endpoints that are not connected at all.
354 
355 					if (mustConnect == prod->connections.HasItem(cons))
356 					{
357 						err = B_ERROR;
358 					}
359 					else
360 					{
361 						err = B_OK;
362 					}
363 				}
364 			}
365 		}
366 	}
367 
368 	BMessage reply;
369 	reply.AddInt32("midi:result", err);
370 
371 	if (SendReply(app, msg, &reply) && (err == B_OK))
372 	{
373 		if (mustConnect)
374 		{
375 			TRACE(("Connection made: %ld ---> %ld", prod->id, cons->id))
376 
377 			prod->connections.AddItem(cons);
378 		}
379 		else
380 		{
381 			TRACE(("Connection broken: %ld -X-> %ld", prod->id, cons->id))
382 
383 			prod->connections.RemoveItem(cons);
384 		}
385 
386 		BMessage notify;
387 		MakeConnectedNotification(&notify, prod, cons, mustConnect);
388 		NotifyAll(&notify, app);
389 
390 		#ifdef DEBUG
391 		DumpEndpoints();
392 		#endif
393 	}
394 }
395 
396 //------------------------------------------------------------------------------
397 
398 bool MidiServerApp::SendAllEndpoints(app_t* app)
399 {
400 	ASSERT(app != NULL)
401 
402 	BMessage notify;
403 
404 	for (int32 t = 0; t < CountEndpoints(); ++t)
405 	{
406 		endpoint_t* endp = EndpointAt(t);
407 
408 		MakeCreatedNotification(&notify, endp);
409 
410 		if (!SendNotification(app, &notify)) { return false; }
411 	}
412 
413 	return true;
414 }
415 
416 //------------------------------------------------------------------------------
417 
418 bool MidiServerApp::SendAllConnections(app_t* app)
419 {
420 	ASSERT(app != NULL)
421 
422 	BMessage notify;
423 
424 	for (int32 t = 0; t < CountEndpoints(); ++t)
425 	{
426 		endpoint_t* prod = EndpointAt(t);
427 		if (!prod->consumer)
428 		{
429 			for (int32 k = 0; k < CountConnections(prod); ++k)
430 			{
431 				endpoint_t* cons = ConnectionAt(prod, k);
432 
433 				MakeConnectedNotification(&notify, prod, cons, true);
434 
435 				if (!SendNotification(app, &notify)) { return false; }
436 			}
437 		}
438 	}
439 
440 	return true;
441 }
442 
443 //------------------------------------------------------------------------------
444 
445 void MidiServerApp::AddEndpoint(BMessage* msg, endpoint_t* endp)
446 {
447 	ASSERT(msg != NULL)
448 	ASSERT(endp != NULL)
449 	ASSERT(!endpoints.HasItem(endp))
450 
451 	TRACE(("Endpoint %ld (%p) added", endp->id, endp))
452 
453 	endpoints.AddItem(endp);
454 
455 	BMessage notify;
456 	MakeCreatedNotification(&notify, endp);
457 	NotifyAll(&notify, endp->app);
458 
459 	#ifdef DEBUG
460 	DumpEndpoints();
461 	#endif
462 }
463 
464 //------------------------------------------------------------------------------
465 
466 void MidiServerApp::RemoveEndpoint(app_t* app, endpoint_t* endp)
467 {
468 	ASSERT(endp != NULL)
469 	ASSERT(endpoints.HasItem(endp))
470 
471 	TRACE(("Endpoint %ld (%p) removed", endp->id, endp))
472 
473 	endpoints.RemoveItem(endp);
474 
475 	if (endp->consumer)
476 	{
477 		DisconnectDeadConsumer(endp);
478 	}
479 
480 	BMessage notify;
481 	notify.what = MSG_ENDPOINT_DELETED;
482 	notify.AddInt32("midi:id", endp->id);
483 	NotifyAll(&notify, app);
484 
485 	delete endp;
486 
487 	#ifdef DEBUG
488 	DumpEndpoints();
489 	#endif
490 }
491 
492 //------------------------------------------------------------------------------
493 
494 void MidiServerApp::DisconnectDeadConsumer(endpoint_t* cons)
495 {
496 	ASSERT(cons != NULL)
497 	ASSERT(cons->consumer)
498 
499 	for (int32 t = 0; t < CountEndpoints(); ++t)
500 	{
501 		endpoint_t* prod = EndpointAt(t);
502 		if (!prod->consumer)
503 		{
504 			prod->connections.RemoveItem(cons);
505 		}
506 	}
507 }
508 
509 //------------------------------------------------------------------------------
510 
511 void MidiServerApp::MakeCreatedNotification(BMessage* msg, endpoint_t* endp)
512 {
513 	ASSERT(msg != NULL)
514 	ASSERT(endp != NULL)
515 
516 	msg->MakeEmpty();
517 	msg->what = MSG_ENDPOINT_CREATED;
518 	msg->AddInt32("midi:id", endp->id);
519 	msg->AddBool("midi:consumer", endp->consumer);
520 	msg->AddBool("midi:registered", endp->registered);
521 	msg->AddString("midi:name", endp->name);
522 	msg->AddMessage("midi:properties", &endp->properties);
523 
524 	if (endp->consumer)
525 	{
526 		msg->AddInt32("midi:port", endp->port);
527 		msg->AddInt64("midi:latency", endp->latency);
528 	}
529 }
530 
531 //------------------------------------------------------------------------------
532 
533 void MidiServerApp::MakeConnectedNotification(
534 	BMessage* msg, endpoint_t* prod, endpoint_t* cons, bool mustConnect)
535 {
536 	ASSERT(msg != NULL)
537 	ASSERT(prod != NULL)
538 	ASSERT(cons != NULL)
539 	ASSERT(!prod->consumer)
540 	ASSERT(cons->consumer)
541 
542 	msg->MakeEmpty();
543 
544 	if (mustConnect)
545 	{
546 		msg->what = MSG_ENDPOINTS_CONNECTED;
547 	}
548 	else
549 	{
550 		msg->what = MSG_ENDPOINTS_DISCONNECTED;
551 	}
552 
553 	msg->AddInt32("midi:producer", prod->id);
554 	msg->AddInt32("midi:consumer", cons->id);
555 }
556 
557 //------------------------------------------------------------------------------
558 
559 app_t* MidiServerApp::WhichApp(BMessage* msg)
560 {
561 	ASSERT(msg != NULL)
562 
563 	BMessenger retadr = msg->ReturnAddress();
564 
565 	for (int32 t = 0; t < CountApps(); ++t)
566 	{
567 		app_t* app = AppAt(t);
568 		if (app->messenger.Team() == retadr.Team())
569 		{
570 			return app;
571 		}
572 	}
573 
574 	TRACE(("Application %ld is not registered", retadr.Team()))
575 
576 	return NULL;
577 }
578 
579 //------------------------------------------------------------------------------
580 
581 endpoint_t* MidiServerApp::WhichEndpoint(BMessage* msg, app_t* app)
582 {
583 	ASSERT(msg != NULL)
584 	ASSERT(app != NULL)
585 
586 	int32 id;
587 	if (msg->FindInt32("midi:id", &id) == B_OK)
588 	{
589 		endpoint_t* endp = FindEndpoint(id);
590 		if ((endp != NULL) && (endp->app == app))
591 		{
592 			return endp;
593 		}
594 	}
595 
596 	TRACE(("Endpoint not found or wrong app"))
597 
598 	return NULL;
599 }
600 
601 //------------------------------------------------------------------------------
602 
603 endpoint_t* MidiServerApp::FindEndpoint(int32 id)
604 {
605 	if (id > 0)
606 	{
607 		for (int32 t = 0; t < CountEndpoints(); ++t)
608 		{
609 			endpoint_t* endp = EndpointAt(t);
610 			if (endp->id == id)
611 			{
612 				return endp;
613 			}
614 		}
615 	}
616 
617 	TRACE(("Endpoint %ld not found", id))
618 
619 	return NULL;
620 }
621 
622 //------------------------------------------------------------------------------
623 
624 void MidiServerApp::NotifyAll(BMessage* msg, app_t* except)
625 {
626 	ASSERT(msg != NULL)
627 
628 	for (int32 t = CountApps() - 1; t >= 0; --t)
629 	{
630 		app_t* app = AppAt(t);
631 		if (app != except)
632 		{
633 			if (!SendNotification(app, msg))
634 			{
635 				delete (app_t*) apps.RemoveItem(t);
636 
637 				#ifdef DEBUG
638 				DumpApps();
639 				#endif
640 			}
641 		}
642 	}
643 }
644 
645 //------------------------------------------------------------------------------
646 
647 bool MidiServerApp::SendNotification(app_t* app, BMessage* msg)
648 {
649 	ASSERT(app != NULL)
650 	ASSERT(msg != NULL)
651 
652 	status_t err = app->messenger.SendMessage(msg, (BHandler*) NULL, TIMEOUT);
653 
654 	if (err != B_OK)
655 	{
656 		DeliveryError(app);
657 	}
658 
659 	return (err == B_OK);
660 }
661 
662 //------------------------------------------------------------------------------
663 
664 bool MidiServerApp::SendReply(app_t* app, BMessage* msg, BMessage* reply)
665 {
666 	ASSERT(msg != NULL)
667 	ASSERT(reply != NULL)
668 
669 	status_t err = msg->SendReply(reply, (BHandler*) NULL, TIMEOUT);
670 
671 	if ((err != B_OK) && (app != NULL))
672 	{
673 		DeliveryError(app);
674 		apps.RemoveItem(app);
675 		delete app;
676 
677 		#ifdef DEBUG
678 		DumpApps();
679 		#endif
680 	}
681 
682 	return (err == B_OK);
683 }
684 
685 //------------------------------------------------------------------------------
686 
687 void MidiServerApp::DeliveryError(app_t* app)
688 {
689 	ASSERT(app != NULL)
690 
691 	// We cannot communicate with the app, so we assume it's
692 	// dead. We need to remove its endpoints from the roster,
693 	// but we cannot do that right away; removing endpoints
694 	// triggers a bunch of new notifications and we don't want
695 	// those to get in the way of the notifications we are
696 	// currently sending out. Instead, we consider the death
697 	// of an app as a separate event, and pretend that the
698 	// now-dead app sent us delete requests for its endpoints.
699 
700 	TRACE(("Delivery error; unregistering app (%p)", app))
701 
702 	BMessage msg;
703 
704 	for (int32 t = 0; t < CountEndpoints(); ++t)
705 	{
706 		endpoint_t* endp = EndpointAt(t);
707 		if (endp->app == app)
708 		{
709 			msg.MakeEmpty();
710 			msg.what = MSG_PURGE_ENDPOINT;
711 			msg.AddInt32("midi:id", endp->id);
712 
713 			// It is not safe to post a message to your own
714 			// looper's message queue, because you risk a
715 			// deadlock if the queue is full. The chance of
716 			// that happening is fairly small, but just in
717 			// case, we catch it with a timeout. Because this
718 			// situation is so unlikely, I decided to simply
719 			// forget about the whole "purge" message then.
720 
721 			if (be_app_messenger.SendMessage(
722 					&msg, (BHandler*) NULL, TIMEOUT) != B_OK)
723 			{
724 				WARN("Could not deliver purge message")
725 			}
726 		}
727 	}
728 }
729 
730 //------------------------------------------------------------------------------
731 
732 int32 MidiServerApp::CountApps()
733 {
734 	return apps.CountItems();
735 }
736 
737 //------------------------------------------------------------------------------
738 
739 app_t* MidiServerApp::AppAt(int32 index)
740 {
741 	ASSERT(index >= 0 && index < CountApps())
742 
743 	return (app_t*) apps.ItemAt(index);
744 }
745 
746 //------------------------------------------------------------------------------
747 
748 int32 MidiServerApp::CountEndpoints()
749 {
750 	return endpoints.CountItems();
751 }
752 
753 //------------------------------------------------------------------------------
754 
755 endpoint_t* MidiServerApp::EndpointAt(int32 index)
756 {
757 	ASSERT(index >= 0 && index < CountEndpoints())
758 
759 	return (endpoint_t*) endpoints.ItemAt(index);
760 }
761 
762 //------------------------------------------------------------------------------
763 
764 int32 MidiServerApp::CountConnections(endpoint_t* prod)
765 {
766 	ASSERT(prod != NULL)
767 	ASSERT(!prod->consumer)
768 
769 	return prod->connections.CountItems();
770 }
771 
772 //------------------------------------------------------------------------------
773 
774 endpoint_t* MidiServerApp::ConnectionAt(endpoint_t* prod, int32 index)
775 {
776 	ASSERT(prod != NULL)
777 	ASSERT(!prod->consumer)
778 	ASSERT(index >= 0 && index < CountConnections(prod))
779 
780 	return (endpoint_t*) prod->connections.ItemAt(index);
781 }
782 
783 //------------------------------------------------------------------------------
784 
785 #ifdef DEBUG
786 void MidiServerApp::DumpApps()
787 {
788 	printf("*** START DumpApps\n");
789 
790 	for (int32 t = 0; t < CountApps(); ++t)
791 	{
792 		app_t* app = AppAt(t);
793 
794 		printf("\tapp %ld (%p): team %ld\n", t, app, app->messenger.Team());
795 	}
796 
797 	printf("*** END DumpApps\n");
798 }
799 #endif
800 
801 //------------------------------------------------------------------------------
802 
803 #ifdef DEBUG
804 void MidiServerApp::DumpEndpoints()
805 {
806 	printf("*** START DumpEndpoints\n");
807 
808 	for (int32 t = 0; t < CountEndpoints(); ++t)
809 	{
810 		endpoint_t* endp = EndpointAt(t);
811 
812 		printf("\tendpoint %ld (%p):\n", t, endp);
813 
814 		printf("\t\tid %ld, name '%s', %s, %s, app %p\n",
815 			endp->id, endp->name.String(),
816 			endp->consumer ? "consumer" : "producer",
817 			endp->registered ? "registered" : "unregistered",
818 			endp->app);
819 
820 		printf("\t\tproperties: "); endp->properties.PrintToStream();
821 
822 		if (endp->consumer)
823 		{
824 			printf("\t\tport %ld, latency %Ld\n", endp->port, endp->latency);
825 		}
826 		else
827 		{
828 			printf("\t\tconnections:\n");
829 			for (int32 k = 0; k < CountConnections(endp); ++k)
830 			{
831 				endpoint_t* cons = ConnectionAt(endp, k);
832 				printf("\t\t\tid %ld (%p)\n", cons->id, cons);
833 			}
834 		}
835 	}
836 
837 	printf("*** END DumpEndpoints\n");
838 }
839 #endif
840 
841 //------------------------------------------------------------------------------
842 
843 int main()
844 {
845 	MidiServerApp app;
846 	app.Run();
847 	return 0;
848 }
849 
850 //------------------------------------------------------------------------------
851