mirror of
https://github.com/mamedev/mame.git
synced 2024-11-16 07:48:32 +01:00
1010 lines
32 KiB
C
1010 lines
32 KiB
C
/*
|
|
* Platform interface to the MacOS X CoreMIDI framework
|
|
*
|
|
* Jon Parise <jparise at cmu.edu>
|
|
* and subsequent work by Andrew Zeldis and Zico Kolter
|
|
* and Roger B. Dannenberg
|
|
*
|
|
* $Id: pmmacosx.c,v 1.17 2002/01/27 02:40:40 jon Exp $
|
|
*/
|
|
|
|
/* Notes:
|
|
since the input and output streams are represented by MIDIEndpointRef
|
|
values and almost no other state, we store the MIDIEndpointRef on
|
|
descriptors[midi->device_id].descriptor. The only other state we need
|
|
is for errors: we need to know if there is an error and if so, what is
|
|
the error text. We use a structure with two kinds of
|
|
host error: "error" and "callback_error". That way, asynchronous callbacks
|
|
do not interfere with other error information.
|
|
|
|
OS X does not seem to have an error-code-to-text function, so we will
|
|
just use text messages instead of error codes.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
|
|
//#define CM_DEBUG 1
|
|
|
|
#include "portmidi.h"
|
|
#include "pmutil.h"
|
|
#include "pminternal.h"
|
|
#include "porttime.h"
|
|
#include "pmmac.h"
|
|
#include "pmmacosxcm.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <CoreServices/CoreServices.h>
|
|
#include <CoreMIDI/MIDIServices.h>
|
|
#include <CoreAudio/HostTime.h>
|
|
#include <unistd.h>
|
|
|
|
#define PACKET_BUFFER_SIZE 1024
|
|
/* maximum overall data rate (OS X limit is 15000 bytes/second) */
|
|
#define MAX_BYTES_PER_S 14000
|
|
|
|
/* Apple reports that packets are dropped when the MIDI bytes/sec
|
|
exceeds 15000. This is computed by "tracking the number of MIDI
|
|
bytes scheduled into 1-second buckets over the last six seconds
|
|
and averaging these counts."
|
|
|
|
This is apparently based on timestamps, not on real time, so
|
|
we have to avoid constructing packets that schedule high speed
|
|
output even if the actual writes are delayed (which was my first
|
|
solution).
|
|
|
|
The LIMIT_RATE symbol, if defined, enables code to modify
|
|
timestamps as follows:
|
|
After each packet is formed, the next allowable timestamp is
|
|
computed as this_packet_time + this_packet_len * delay_per_byte
|
|
|
|
This is the minimum timestamp allowed in the next packet.
|
|
|
|
Note that this distorts accurate timestamps somewhat.
|
|
*/
|
|
#define LIMIT_RATE 1
|
|
|
|
#define SYSEX_BUFFER_SIZE 128
|
|
|
|
#define VERBOSE_ON 1
|
|
#define VERBOSE if (VERBOSE_ON)
|
|
|
|
#define MIDI_SYSEX 0xf0
|
|
#define MIDI_EOX 0xf7
|
|
#define MIDI_STATUS_MASK 0x80
|
|
|
|
// "Ref"s are pointers on 32-bit machines and ints on 64 bit machines
|
|
// NULL_REF is our representation of either 0 or NULL
|
|
#ifdef __LP64__
|
|
#define NULL_REF 0
|
|
#else
|
|
#define NULL_REF NULL
|
|
#endif
|
|
|
|
static MIDIClientRef client = NULL_REF; /* Client handle to the MIDI server */
|
|
static MIDIPortRef portIn = NULL_REF; /* Input port handle */
|
|
static MIDIPortRef portOut = NULL_REF; /* Output port handle */
|
|
|
|
extern pm_fns_node pm_macosx_in_dictionary;
|
|
extern pm_fns_node pm_macosx_out_dictionary;
|
|
|
|
typedef struct midi_macosxcm_struct {
|
|
PmTimestamp sync_time; /* when did we last determine delta? */
|
|
UInt64 delta; /* difference between stream time and real time in ns */
|
|
UInt64 last_time; /* last output time in host units*/
|
|
int first_message; /* tells midi_write to sychronize timestamps */
|
|
int sysex_mode; /* middle of sending sysex */
|
|
uint32_t sysex_word; /* accumulate data when receiving sysex */
|
|
uint32_t sysex_byte_count; /* count how many received */
|
|
char error[PM_HOST_ERROR_MSG_LEN];
|
|
char callback_error[PM_HOST_ERROR_MSG_LEN];
|
|
Byte packetBuffer[PACKET_BUFFER_SIZE];
|
|
MIDIPacketList *packetList; /* a pointer to packetBuffer */
|
|
MIDIPacket *packet;
|
|
Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */
|
|
MIDITimeStamp sysex_timestamp; /* timestamp to use with sysex data */
|
|
/* allow for running status (is running status possible here? -rbd): -cpr */
|
|
unsigned char last_command;
|
|
int32_t last_msg_length;
|
|
/* limit midi data rate (a CoreMidi requirement): */
|
|
UInt64 min_next_time; /* when can the next send take place? */
|
|
int byte_count; /* how many bytes in the next packet list? */
|
|
Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */
|
|
UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */
|
|
} midi_macosxcm_node, *midi_macosxcm_type;
|
|
|
|
/* private function declarations */
|
|
MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp);
|
|
PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp);
|
|
|
|
char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint);
|
|
|
|
|
|
static int
|
|
midi_length(int32_t msg)
|
|
{
|
|
int status, high, low;
|
|
static int high_lengths[] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 through 0x70 */
|
|
3, 3, 3, 3, 2, 2, 3, 1 /* 0x80 through 0xf0 */
|
|
};
|
|
static int low_lengths[] = {
|
|
1, 2, 3, 2, 1, 1, 1, 1, /* 0xf0 through 0xf8 */
|
|
1, 1, 1, 1, 1, 1, 1, 1 /* 0xf9 through 0xff */
|
|
};
|
|
|
|
status = msg & 0xFF;
|
|
high = status >> 4;
|
|
low = status & 15;
|
|
|
|
return (high != 0xF) ? high_lengths[high] : low_lengths[low];
|
|
}
|
|
|
|
static PmTimestamp midi_synchronize(PmInternal *midi)
|
|
{
|
|
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
|
|
UInt64 pm_stream_time_2 =
|
|
AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
|
|
PmTimestamp real_time;
|
|
UInt64 pm_stream_time;
|
|
/* if latency is zero and this is an output, there is no
|
|
time reference and midi_synchronize should never be called */
|
|
assert(midi->time_proc);
|
|
assert(!(midi->write_flag && midi->latency == 0));
|
|
do {
|
|
/* read real_time between two reads of stream time */
|
|
pm_stream_time = pm_stream_time_2;
|
|
real_time = (*midi->time_proc)(midi->time_info);
|
|
pm_stream_time_2 = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
|
|
/* repeat if more than 0.5 ms has elapsed */
|
|
} while (pm_stream_time_2 > pm_stream_time + 500000);
|
|
m->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);
|
|
m->sync_time = real_time;
|
|
return real_time;
|
|
}
|
|
|
|
|
|
static void
|
|
process_packet(MIDIPacket *packet, PmEvent *event,
|
|
PmInternal *midi, midi_macosxcm_type m)
|
|
{
|
|
/* handle a packet of MIDI messages from CoreMIDI */
|
|
/* there may be multiple short messages in one packet (!) */
|
|
unsigned int remaining_length = packet->length;
|
|
unsigned char *cur_packet_data = packet->data;
|
|
while (remaining_length > 0) {
|
|
if (cur_packet_data[0] == MIDI_SYSEX ||
|
|
/* are we in the middle of a sysex message? */
|
|
(m->last_command == 0 &&
|
|
!(cur_packet_data[0] & MIDI_STATUS_MASK))) {
|
|
m->last_command = 0; /* no running status */
|
|
unsigned int amt = pm_read_bytes(midi, cur_packet_data,
|
|
remaining_length,
|
|
event->timestamp);
|
|
remaining_length -= amt;
|
|
cur_packet_data += amt;
|
|
} else if (cur_packet_data[0] == MIDI_EOX) {
|
|
/* this should never happen, because pm_read_bytes should
|
|
* get and read all EOX bytes*/
|
|
midi->sysex_in_progress = FALSE;
|
|
m->last_command = 0;
|
|
} else if (cur_packet_data[0] & MIDI_STATUS_MASK) {
|
|
/* compute the length of the next (short) msg in packet */
|
|
unsigned int cur_message_length = midi_length(cur_packet_data[0]);
|
|
if (cur_message_length > remaining_length) {
|
|
#ifdef DEBUG
|
|
printf("PortMidi debug msg: not enough data");
|
|
#endif
|
|
/* since there's no more data, we're done */
|
|
return;
|
|
}
|
|
m->last_msg_length = cur_message_length;
|
|
m->last_command = cur_packet_data[0];
|
|
switch (cur_message_length) {
|
|
case 1:
|
|
event->message = Pm_Message(cur_packet_data[0], 0, 0);
|
|
break;
|
|
case 2:
|
|
event->message = Pm_Message(cur_packet_data[0],
|
|
cur_packet_data[1], 0);
|
|
break;
|
|
case 3:
|
|
event->message = Pm_Message(cur_packet_data[0],
|
|
cur_packet_data[1],
|
|
cur_packet_data[2]);
|
|
break;
|
|
default:
|
|
/* PortMIDI internal error; should never happen */
|
|
assert(cur_message_length == 1);
|
|
return; /* give up on packet if continued after assert */
|
|
}
|
|
pm_read_short(midi, event);
|
|
remaining_length -= m->last_msg_length;
|
|
cur_packet_data += m->last_msg_length;
|
|
} else if (m->last_msg_length > remaining_length + 1) {
|
|
/* we have running status, but not enough data */
|
|
#ifdef DEBUG
|
|
printf("PortMidi debug msg: not enough data in CoreMIDI packet");
|
|
#endif
|
|
/* since there's no more data, we're done */
|
|
return;
|
|
} else { /* output message using running status */
|
|
switch (m->last_msg_length) {
|
|
case 1:
|
|
event->message = Pm_Message(m->last_command, 0, 0);
|
|
break;
|
|
case 2:
|
|
event->message = Pm_Message(m->last_command,
|
|
cur_packet_data[0], 0);
|
|
break;
|
|
case 3:
|
|
event->message = Pm_Message(m->last_command,
|
|
cur_packet_data[0],
|
|
cur_packet_data[1]);
|
|
break;
|
|
default:
|
|
/* last_msg_length is invalid -- internal PortMIDI error */
|
|
assert(m->last_msg_length == 1);
|
|
}
|
|
pm_read_short(midi, event);
|
|
remaining_length -= (m->last_msg_length - 1);
|
|
cur_packet_data += (m->last_msg_length - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* called when MIDI packets are received */
|
|
static void
|
|
readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon)
|
|
{
|
|
PmInternal *midi;
|
|
midi_macosxcm_type m;
|
|
PmEvent event;
|
|
MIDIPacket *packet;
|
|
unsigned int packetIndex;
|
|
uint32_t now;
|
|
unsigned int status;
|
|
|
|
#ifdef CM_DEBUG
|
|
printf("readProc: numPackets %d: ", newPackets->numPackets);
|
|
#endif
|
|
|
|
/* Retrieve the context for this connection */
|
|
midi = (PmInternal *) connRefCon;
|
|
m = (midi_macosxcm_type) midi->descriptor;
|
|
assert(m);
|
|
|
|
/* synchronize time references every 100ms */
|
|
now = (*midi->time_proc)(midi->time_info);
|
|
if (m->first_message || m->sync_time + 100 /*ms*/ < now) {
|
|
/* time to resync */
|
|
now = midi_synchronize(midi);
|
|
m->first_message = FALSE;
|
|
}
|
|
|
|
packet = (MIDIPacket *) &newPackets->packet[0];
|
|
/* printf("readproc packet status %x length %d\n", packet->data[0],
|
|
packet->length); */
|
|
for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {
|
|
/* Set the timestamp and dispatch this message */
|
|
event.timestamp = (PmTimestamp) /* explicit conversion */ (
|
|
(AudioConvertHostTimeToNanos(packet->timeStamp) - m->delta) /
|
|
(UInt64) 1000000);
|
|
status = packet->data[0];
|
|
/* process packet as sysex data if it begins with MIDI_SYSEX, or
|
|
MIDI_EOX or non-status byte with no running status */
|
|
#ifdef CM_DEBUG
|
|
printf(" %d", packet->length);
|
|
#endif
|
|
if (status == MIDI_SYSEX || status == MIDI_EOX ||
|
|
((!(status & MIDI_STATUS_MASK)) && !m->last_command)) {
|
|
/* previously was: !(status & MIDI_STATUS_MASK)) {
|
|
* but this could mistake running status for sysex data
|
|
*/
|
|
/* reset running status data -cpr */
|
|
m->last_command = 0;
|
|
m->last_msg_length = 0;
|
|
/* printf("sysex packet length: %d\n", packet->length); */
|
|
pm_read_bytes(midi, packet->data, packet->length, event.timestamp);
|
|
} else {
|
|
process_packet(packet, &event, midi, m);
|
|
}
|
|
packet = MIDIPacketNext(packet);
|
|
}
|
|
#ifdef CM_DEBUG
|
|
printf("\n");
|
|
#endif
|
|
}
|
|
|
|
static PmError
|
|
midi_in_open(PmInternal *midi, void *driverInfo)
|
|
{
|
|
MIDIEndpointRef endpoint;
|
|
midi_macosxcm_type m;
|
|
OSStatus macHostError;
|
|
|
|
/* insure that we have a time_proc for timing */
|
|
if (midi->time_proc == NULL) {
|
|
if (!Pt_Started())
|
|
Pt_Start(1, 0, 0);
|
|
/* time_get does not take a parameter, so coerce */
|
|
midi->time_proc = (PmTimeProcPtr) Pt_Time;
|
|
}
|
|
endpoint = (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor;
|
|
if (endpoint == NULL_REF) {
|
|
return pmInvalidDeviceId;
|
|
}
|
|
|
|
m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
|
|
midi->descriptor = m;
|
|
if (!m) {
|
|
return pmInsufficientMemory;
|
|
}
|
|
m->error[0] = 0;
|
|
m->callback_error[0] = 0;
|
|
m->sync_time = 0;
|
|
m->delta = 0;
|
|
m->last_time = 0;
|
|
m->first_message = TRUE;
|
|
m->sysex_mode = FALSE;
|
|
m->sysex_word = 0;
|
|
m->sysex_byte_count = 0;
|
|
m->packetList = NULL;
|
|
m->packet = NULL;
|
|
m->last_command = 0;
|
|
m->last_msg_length = 0;
|
|
|
|
macHostError = MIDIPortConnectSource(portIn, endpoint, midi);
|
|
if (macHostError != noErr) {
|
|
pm_hosterror = macHostError;
|
|
sprintf(pm_hosterror_text,
|
|
"Host error %ld: MIDIPortConnectSource() in midi_in_open()",
|
|
(long) macHostError);
|
|
midi->descriptor = NULL;
|
|
pm_free(m);
|
|
return pmHostError;
|
|
}
|
|
|
|
return pmNoError;
|
|
}
|
|
|
|
static PmError
|
|
midi_in_close(PmInternal *midi)
|
|
{
|
|
MIDIEndpointRef endpoint;
|
|
OSStatus macHostError;
|
|
PmError err = pmNoError;
|
|
|
|
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
|
|
|
|
if (!m) return pmBadPtr;
|
|
|
|
endpoint = (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor;
|
|
if (endpoint == NULL_REF) {
|
|
pm_hosterror = pmBadPtr;
|
|
}
|
|
|
|
/* shut off the incoming messages before freeing data structures */
|
|
macHostError = MIDIPortDisconnectSource(portIn, endpoint);
|
|
if (macHostError != noErr) {
|
|
pm_hosterror = macHostError;
|
|
sprintf(pm_hosterror_text,
|
|
"Host error %ld: MIDIPortDisconnectSource() in midi_in_close()",
|
|
(long) macHostError);
|
|
err = pmHostError;
|
|
}
|
|
|
|
midi->descriptor = NULL;
|
|
pm_free(midi->descriptor);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static PmError
|
|
midi_out_open(PmInternal *midi, void *driverInfo)
|
|
{
|
|
midi_macosxcm_type m;
|
|
|
|
m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
|
|
midi->descriptor = m;
|
|
if (!m) {
|
|
return pmInsufficientMemory;
|
|
}
|
|
m->error[0] = 0;
|
|
m->callback_error[0] = 0;
|
|
m->sync_time = 0;
|
|
m->delta = 0;
|
|
m->last_time = 0;
|
|
m->first_message = TRUE;
|
|
m->sysex_mode = FALSE;
|
|
m->sysex_word = 0;
|
|
m->sysex_byte_count = 0;
|
|
m->packetList = (MIDIPacketList *) m->packetBuffer;
|
|
m->packet = NULL;
|
|
m->last_command = 0;
|
|
m->last_msg_length = 0;
|
|
m->min_next_time = 0;
|
|
m->byte_count = 0;
|
|
m->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency();
|
|
m->host_ticks_per_byte = (UInt64) (1000000.0 /
|
|
(m->us_per_host_tick * MAX_BYTES_PER_S));
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
static PmError
|
|
midi_out_close(PmInternal *midi)
|
|
{
|
|
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
|
|
if (!m) return pmBadPtr;
|
|
|
|
midi->descriptor = NULL;
|
|
pm_free(midi->descriptor);
|
|
|
|
return pmNoError;
|
|
}
|
|
|
|
static PmError
|
|
midi_abort(PmInternal *midi)
|
|
{
|
|
PmError err = pmNoError;
|
|
OSStatus macHostError;
|
|
MIDIEndpointRef endpoint =
|
|
(MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor;
|
|
macHostError = MIDIFlushOutput(endpoint);
|
|
if (macHostError != noErr) {
|
|
pm_hosterror = macHostError;
|
|
sprintf(pm_hosterror_text,
|
|
"Host error %ld: MIDIFlushOutput()", (long) macHostError);
|
|
err = pmHostError;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
static PmError
|
|
midi_write_flush(PmInternal *midi, PmTimestamp timestamp)
|
|
{
|
|
OSStatus macHostError;
|
|
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
|
|
MIDIEndpointRef endpoint =
|
|
(MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor;
|
|
assert(m);
|
|
assert(endpoint);
|
|
if (m->packet != NULL) {
|
|
/* out of space, send the buffer and start refilling it */
|
|
/* before we can send, maybe delay to limit data rate. OS X allows
|
|
* 15KB/s. */
|
|
UInt64 now = AudioGetCurrentHostTime();
|
|
if (now < m->min_next_time) {
|
|
usleep((useconds_t)
|
|
((m->min_next_time - now) * m->us_per_host_tick));
|
|
}
|
|
macHostError = MIDISend(portOut, endpoint, m->packetList);
|
|
m->packet = NULL; /* indicate no data in packetList now */
|
|
m->min_next_time = now + m->byte_count * m->host_ticks_per_byte;
|
|
m->byte_count = 0;
|
|
if (macHostError != noErr) goto send_packet_error;
|
|
}
|
|
return pmNoError;
|
|
|
|
send_packet_error:
|
|
pm_hosterror = macHostError;
|
|
sprintf(pm_hosterror_text,
|
|
"Host error %ld: MIDISend() in midi_write()",
|
|
(long) macHostError);
|
|
return pmHostError;
|
|
|
|
}
|
|
|
|
|
|
static PmError
|
|
send_packet(PmInternal *midi, Byte *message, unsigned int messageLength,
|
|
MIDITimeStamp timestamp)
|
|
{
|
|
PmError err;
|
|
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
|
|
assert(m);
|
|
|
|
/* printf("add %d to packet %p len %d\n", message[0], m->packet, messageLength); */
|
|
m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer),
|
|
m->packet, timestamp, messageLength,
|
|
message);
|
|
m->byte_count += messageLength;
|
|
if (m->packet == NULL) {
|
|
/* out of space, send the buffer and start refilling it */
|
|
/* make midi->packet non-null to fool midi_write_flush into sending */
|
|
m->packet = (MIDIPacket *) 4;
|
|
/* timestamp is 0 because midi_write_flush ignores timestamp since
|
|
* timestamps are already in packets. The timestamp parameter is here
|
|
* because other API's need it. midi_write_flush can be called
|
|
* from system-independent code that must be cross-API.
|
|
*/
|
|
if ((err = midi_write_flush(midi, 0)) != pmNoError) return err;
|
|
m->packet = MIDIPacketListInit(m->packetList);
|
|
assert(m->packet); /* if this fails, it's a programming error */
|
|
m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer),
|
|
m->packet, timestamp, messageLength,
|
|
message);
|
|
assert(m->packet); /* can't run out of space on first message */
|
|
}
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
static PmError
|
|
midi_write_short(PmInternal *midi, PmEvent *event)
|
|
{
|
|
PmTimestamp when = event->timestamp;
|
|
PmMessage what = event->message;
|
|
MIDITimeStamp timestamp;
|
|
UInt64 when_ns;
|
|
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
|
|
Byte message[4];
|
|
unsigned int messageLength;
|
|
|
|
if (m->packet == NULL) {
|
|
m->packet = MIDIPacketListInit(m->packetList);
|
|
/* this can never fail, right? failure would indicate something
|
|
unrecoverable */
|
|
assert(m->packet);
|
|
}
|
|
|
|
/* compute timestamp */
|
|
if (when == 0) when = midi->now;
|
|
/* if latency == 0, midi->now is not valid. We will just set it to zero */
|
|
if (midi->latency == 0) when = 0;
|
|
when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;
|
|
timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
|
|
|
|
message[0] = Pm_MessageStatus(what);
|
|
message[1] = Pm_MessageData1(what);
|
|
message[2] = Pm_MessageData2(what);
|
|
messageLength = midi_length(what);
|
|
|
|
/* make sure we go foreward in time */
|
|
if (timestamp < m->min_next_time) timestamp = m->min_next_time;
|
|
|
|
#ifdef LIMIT_RATE
|
|
if (timestamp < m->last_time)
|
|
timestamp = m->last_time;
|
|
m->last_time = timestamp + messageLength * m->host_ticks_per_byte;
|
|
#endif
|
|
|
|
/* Add this message to the packet list */
|
|
return send_packet(midi, message, messageLength, timestamp);
|
|
}
|
|
|
|
|
|
static PmError
|
|
midi_begin_sysex(PmInternal *midi, PmTimestamp when)
|
|
{
|
|
UInt64 when_ns;
|
|
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
|
|
assert(m);
|
|
m->sysex_byte_count = 0;
|
|
|
|
/* compute timestamp */
|
|
if (when == 0) when = midi->now;
|
|
/* if latency == 0, midi->now is not valid. We will just set it to zero */
|
|
if (midi->latency == 0) when = 0;
|
|
when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;
|
|
m->sysex_timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
|
|
|
|
if (m->packet == NULL) {
|
|
m->packet = MIDIPacketListInit(m->packetList);
|
|
/* this can never fail, right? failure would indicate something
|
|
unrecoverable */
|
|
assert(m->packet);
|
|
}
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
static PmError
|
|
midi_end_sysex(PmInternal *midi, PmTimestamp when)
|
|
{
|
|
PmError err;
|
|
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
|
|
assert(m);
|
|
|
|
/* make sure we go foreward in time */
|
|
if (m->sysex_timestamp < m->min_next_time)
|
|
m->sysex_timestamp = m->min_next_time;
|
|
|
|
#ifdef LIMIT_RATE
|
|
if (m->sysex_timestamp < m->last_time)
|
|
m->sysex_timestamp = m->last_time;
|
|
m->last_time = m->sysex_timestamp + m->sysex_byte_count *
|
|
m->host_ticks_per_byte;
|
|
#endif
|
|
|
|
/* now send what's in the buffer */
|
|
err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count,
|
|
m->sysex_timestamp);
|
|
m->sysex_byte_count = 0;
|
|
if (err != pmNoError) {
|
|
m->packet = NULL; /* flush everything in the packet list */
|
|
return err;
|
|
}
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
static PmError
|
|
midi_write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
|
|
{
|
|
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
|
|
assert(m);
|
|
if (m->sysex_byte_count >= SYSEX_BUFFER_SIZE) {
|
|
PmError err = midi_end_sysex(midi, timestamp);
|
|
if (err != pmNoError) return err;
|
|
}
|
|
m->sysex_buffer[m->sysex_byte_count++] = byte;
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
static PmError
|
|
midi_write_realtime(PmInternal *midi, PmEvent *event)
|
|
{
|
|
/* to send a realtime message during a sysex message, first
|
|
flush all pending sysex bytes into packet list */
|
|
PmError err = midi_end_sysex(midi, 0);
|
|
if (err != pmNoError) return err;
|
|
/* then we can just do a normal midi_write_short */
|
|
return midi_write_short(midi, event);
|
|
}
|
|
|
|
static unsigned int midi_has_host_error(PmInternal *midi)
|
|
{
|
|
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
|
|
return (m->callback_error[0] != 0) || (m->error[0] != 0);
|
|
}
|
|
|
|
|
|
static void midi_get_host_error(PmInternal *midi, char *msg, unsigned int len)
|
|
{
|
|
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
|
|
msg[0] = 0; /* initialize to empty string */
|
|
if (m) { /* make sure there is an open device to examine */
|
|
if (m->error[0]) {
|
|
strncpy(msg, m->error, len);
|
|
m->error[0] = 0; /* clear the error */
|
|
} else if (m->callback_error[0]) {
|
|
strncpy(msg, m->callback_error, len);
|
|
m->callback_error[0] = 0; /* clear the error */
|
|
}
|
|
msg[len - 1] = 0; /* make sure string is terminated */
|
|
}
|
|
}
|
|
|
|
|
|
MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)
|
|
{
|
|
UInt64 nanos;
|
|
if (timestamp <= 0) {
|
|
return (MIDITimeStamp)0;
|
|
} else {
|
|
nanos = (UInt64)timestamp * (UInt64)1000000;
|
|
return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);
|
|
}
|
|
}
|
|
|
|
PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)
|
|
{
|
|
UInt64 nanos;
|
|
nanos = AudioConvertHostTimeToNanos(timestamp);
|
|
return (PmTimestamp)(nanos / (UInt64)1000000);
|
|
}
|
|
|
|
|
|
//
|
|
// Code taken from http://developer.apple.com/qa/qa2004/qa1374.html
|
|
//////////////////////////////////////
|
|
// Obtain the name of an endpoint without regard for whether it has connections.
|
|
// The result should be released by the caller.
|
|
CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
|
|
{
|
|
CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
|
|
CFStringRef str;
|
|
|
|
// begin with the endpoint's name
|
|
str = NULL;
|
|
MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);
|
|
if (str != NULL) {
|
|
CFStringAppend(result, str);
|
|
CFRelease(str);
|
|
}
|
|
|
|
MIDIEntityRef entity = NULL_REF;
|
|
MIDIEndpointGetEntity(endpoint, &entity);
|
|
if (entity == NULL_REF)
|
|
// probably virtual
|
|
return result;
|
|
|
|
if (CFStringGetLength(result) == 0) {
|
|
// endpoint name has zero length -- try the entity
|
|
str = NULL;
|
|
MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);
|
|
if (str != NULL) {
|
|
CFStringAppend(result, str);
|
|
CFRelease(str);
|
|
}
|
|
}
|
|
// now consider the device's name
|
|
MIDIDeviceRef device = NULL_REF;
|
|
MIDIEntityGetDevice(entity, &device);
|
|
if (device == NULL_REF)
|
|
return result;
|
|
|
|
str = NULL;
|
|
MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);
|
|
if (CFStringGetLength(result) == 0) {
|
|
CFRelease(result);
|
|
return str;
|
|
}
|
|
if (str != NULL) {
|
|
// if an external device has only one entity, throw away
|
|
// the endpoint name and just use the device name
|
|
if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) {
|
|
CFRelease(result);
|
|
return str;
|
|
} else {
|
|
if (CFStringGetLength(str) == 0) {
|
|
CFRelease(str);
|
|
return result;
|
|
}
|
|
// does the entity name already start with the device name?
|
|
// (some drivers do this though they shouldn't)
|
|
// if so, do not prepend
|
|
if (CFStringCompareWithOptions( result, /* endpoint name */
|
|
str /* device name */,
|
|
CFRangeMake(0, CFStringGetLength(str)), 0) != kCFCompareEqualTo) {
|
|
// prepend the device name to the entity name
|
|
if (CFStringGetLength(result) > 0)
|
|
CFStringInsert(result, 0, CFSTR(" "));
|
|
CFStringInsert(result, 0, str);
|
|
}
|
|
CFRelease(str);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
// Obtain the name of an endpoint, following connections.
|
|
// The result should be released by the caller.
|
|
static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint)
|
|
{
|
|
CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
|
|
CFStringRef str;
|
|
OSStatus err;
|
|
long i;
|
|
|
|
// Does the endpoint have connections?
|
|
CFDataRef connections = NULL;
|
|
long nConnected = 0;
|
|
bool anyStrings = false;
|
|
err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections);
|
|
if (connections != NULL) {
|
|
// It has connections, follow them
|
|
// Concatenate the names of all connected devices
|
|
nConnected = CFDataGetLength(connections) / (int32_t) sizeof(MIDIUniqueID);
|
|
if (nConnected) {
|
|
const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
|
|
for (i = 0; i < nConnected; ++i, ++pid) {
|
|
MIDIUniqueID id = EndianS32_BtoN(*pid);
|
|
MIDIObjectRef connObject;
|
|
MIDIObjectType connObjectType;
|
|
err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType);
|
|
if (err == noErr) {
|
|
if (connObjectType == kMIDIObjectType_ExternalSource ||
|
|
connObjectType == kMIDIObjectType_ExternalDestination) {
|
|
// Connected to an external device's endpoint (10.3 and later).
|
|
str = EndpointName((MIDIEndpointRef)(connObject), true);
|
|
} else {
|
|
// Connected to an external device (10.2) (or something else, catch-all)
|
|
str = NULL;
|
|
MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str);
|
|
}
|
|
if (str != NULL) {
|
|
if (anyStrings)
|
|
CFStringAppend(result, CFSTR(", "));
|
|
else anyStrings = true;
|
|
CFStringAppend(result, str);
|
|
CFRelease(str);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CFRelease(connections);
|
|
}
|
|
if (anyStrings)
|
|
return result;
|
|
|
|
// Here, either the endpoint had no connections, or we failed to obtain names for any of them.
|
|
return EndpointName(endpoint, false);
|
|
}
|
|
|
|
|
|
char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint)
|
|
{
|
|
#ifdef OLDCODE
|
|
MIDIEntityRef entity;
|
|
MIDIDeviceRef device;
|
|
|
|
CFStringRef endpointName = NULL;
|
|
CFStringRef deviceName = NULL;
|
|
#endif
|
|
CFStringRef fullName = NULL;
|
|
CFStringEncoding defaultEncoding;
|
|
char* newName;
|
|
|
|
/* get the default string encoding */
|
|
defaultEncoding = CFStringGetSystemEncoding();
|
|
|
|
fullName = ConnectedEndpointName(endpoint);
|
|
|
|
#ifdef OLDCODE
|
|
/* get the entity and device info */
|
|
MIDIEndpointGetEntity(endpoint, &entity);
|
|
MIDIEntityGetDevice(entity, &device);
|
|
|
|
/* create the nicely formated name */
|
|
MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &endpointName);
|
|
MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName);
|
|
if (deviceName != NULL) {
|
|
fullName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@: %@"),
|
|
deviceName, endpointName);
|
|
} else {
|
|
fullName = endpointName;
|
|
}
|
|
#endif
|
|
/* copy the string into our buffer */
|
|
newName = (char *) malloc(CFStringGetLength(fullName) + 1);
|
|
CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1,
|
|
defaultEncoding);
|
|
|
|
/* clean up */
|
|
#ifdef OLDCODE
|
|
if (endpointName) CFRelease(endpointName);
|
|
if (deviceName) CFRelease(deviceName);
|
|
#endif
|
|
if (fullName) CFRelease(fullName);
|
|
|
|
return newName;
|
|
}
|
|
|
|
|
|
|
|
pm_fns_node pm_macosx_in_dictionary = {
|
|
none_write_short,
|
|
none_sysex,
|
|
none_sysex,
|
|
none_write_byte,
|
|
none_write_short,
|
|
none_write_flush,
|
|
none_synchronize,
|
|
midi_in_open,
|
|
midi_abort,
|
|
midi_in_close,
|
|
success_poll,
|
|
midi_has_host_error,
|
|
midi_get_host_error,
|
|
};
|
|
|
|
pm_fns_node pm_macosx_out_dictionary = {
|
|
midi_write_short,
|
|
midi_begin_sysex,
|
|
midi_end_sysex,
|
|
midi_write_byte,
|
|
midi_write_realtime,
|
|
midi_write_flush,
|
|
midi_synchronize,
|
|
midi_out_open,
|
|
midi_abort,
|
|
midi_out_close,
|
|
success_poll,
|
|
midi_has_host_error,
|
|
midi_get_host_error,
|
|
};
|
|
|
|
|
|
PmError pm_macosxcm_init(void)
|
|
{
|
|
ItemCount numInputs, numOutputs, numDevices;
|
|
MIDIEndpointRef endpoint;
|
|
int i;
|
|
OSStatus macHostError;
|
|
char *error_text;
|
|
|
|
/* Determine the number of MIDI devices on the system */
|
|
numDevices = MIDIGetNumberOfDevices();
|
|
numInputs = MIDIGetNumberOfSources();
|
|
numOutputs = MIDIGetNumberOfDestinations();
|
|
|
|
/* Return prematurely if no devices exist on the system
|
|
Note that this is not an error. There may be no devices.
|
|
Pm_CountDevices() will return zero, which is correct and
|
|
useful information
|
|
*/
|
|
if (numDevices <= 0) {
|
|
return pmNoError;
|
|
}
|
|
|
|
|
|
/* Initialize the client handle */
|
|
macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client);
|
|
if (macHostError != noErr) {
|
|
error_text = (char *)"MIDIClientCreate() in pm_macosxcm_init()";
|
|
goto error_return;
|
|
}
|
|
|
|
/* Create the input port */
|
|
macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), readProc,
|
|
NULL, &portIn);
|
|
if (macHostError != noErr) {
|
|
error_text = (char *)"MIDIInputPortCreate() in pm_macosxcm_init()";
|
|
goto error_return;
|
|
}
|
|
|
|
/* Create the output port */
|
|
macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);
|
|
if (macHostError != noErr) {
|
|
error_text = (char *)"MIDIOutputPortCreate() in pm_macosxcm_init()";
|
|
goto error_return;
|
|
}
|
|
|
|
/* Iterate over the MIDI input devices */
|
|
for (i = 0; i < numInputs; i++) {
|
|
endpoint = MIDIGetSource(i);
|
|
if (endpoint == NULL_REF) {
|
|
continue;
|
|
}
|
|
|
|
/* set the first input we see to the default */
|
|
if (pm_default_input_device_id == -1)
|
|
pm_default_input_device_id = pm_descriptor_index;
|
|
|
|
/* Register this device with PortMidi */
|
|
pm_add_device((char *)"CoreMIDI", cm_get_full_endpoint_name(endpoint),
|
|
TRUE, (void *) (long) endpoint, &pm_macosx_in_dictionary);
|
|
}
|
|
|
|
/* Iterate over the MIDI output devices */
|
|
for (i = 0; i < numOutputs; i++) {
|
|
endpoint = MIDIGetDestination(i);
|
|
if (endpoint == NULL_REF) {
|
|
continue;
|
|
}
|
|
|
|
/* set the first output we see to the default */
|
|
if (pm_default_output_device_id == -1)
|
|
pm_default_output_device_id = pm_descriptor_index;
|
|
|
|
/* Register this device with PortMidi */
|
|
pm_add_device((char *)"CoreMIDI", cm_get_full_endpoint_name(endpoint),
|
|
FALSE, (void *) (long) endpoint,
|
|
&pm_macosx_out_dictionary);
|
|
}
|
|
return pmNoError;
|
|
|
|
error_return:
|
|
pm_hosterror = macHostError;
|
|
sprintf(pm_hosterror_text, "Host error %ld: %s\n", (long) macHostError,
|
|
error_text);
|
|
pm_macosxcm_term(); /* clear out any opened ports */
|
|
return pmHostError;
|
|
}
|
|
|
|
void pm_macosxcm_term(void)
|
|
{
|
|
if (client != NULL_REF) MIDIClientDispose(client);
|
|
if (portIn != NULL_REF) MIDIPortDispose(portIn);
|
|
if (portOut != NULL_REF) MIDIPortDispose(portOut);
|
|
}
|