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