blob: 4491c14b9c02f8cb63dd30447363f309982423c1 [file] [log] [blame]
/*
* Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
**
** Overview:
** Implementation of the functions used for both MIDI in and MIDI out.
**
** Java package com.sun.media.sound defines the AbstractMidiDevice class
** which encapsulates functionalities shared by both MidiInDevice and
** MidiOutDevice classes in the same package.
**
** The Java layer classes MidiInDevice and MidiOutDevice in turn map to
** the MIDIEndpointRef data type in the CoreMIDI framework, which
** represents a source or destination for a standard 16-channel MIDI data
** stream.
*/
/*****************************************************************************/
//#define USE_ERROR
//#define USE_TRACE
/* Use THIS_FILE when it is available. */
#ifndef THIS_FILE
#define THIS_FILE __FILE__
#endif
#if (USE_PLATFORM_MIDI_IN == TRUE) || (USE_PLATFORM_MIDI_OUT == TRUE)
#include "PLATFORM_API_MacOSX_MidiUtils.h"
#include <pthread.h>
#include <assert.h>
// Constant character string definitions of CoreMIDI's corresponding error codes.
static const char* strMIDIInvalidClient =
"An invalid MIDIClientRef was passed.";
static const char* strMIDIInvalidPort =
"An invalid MIDIPortRef was passed.";
static const char* strMIDIWrongEndpointType =
"A source endpoint was passed to a function expecting a destination, or vice versa.";
static const char* strMIDINoConnection =
"Attempt to close a non-existant connection.";
static const char* strMIDIUnknownEndpoint =
"An invalid MIDIEndpointRef was passed.";
static const char* strMIDIUnknownProperty =
"Attempt to query a property not set on the object.";
static const char* strMIDIWrongPropertyType =
"Attempt to set a property with a value not of the correct type.";
static const char* strMIDINoCurrentSetup =
"Internal error; there is no current MIDI setup object.";
static const char* strMIDIMessageSendErr =
"Communication with MIDIServer failed.";
static const char* strMIDIServerStartErr =
"Unable to start MIDIServer.";
static const char* strMIDISetupFormatErr =
"Unable to read the saved state.";
static const char* strMIDIWrongThread =
"A driver is calling a non-I/O function in the server from a thread other than"
"the server's main thread.";
static const char* strMIDIObjectNotFound =
"The requested object does not exist.";
static const char* strMIDIIDNotUnique =
"Attempt to set a non-unique kMIDIPropertyUniqueID on an object.";
static const char* midi_strerror(int err) {
/*
@enum Error Constants
@abstract The error constants unique to Core MIDI.
@discussion These are the error constants that are unique to Core MIDI. Note that Core MIDI
functions may return other codes that are not listed here.
*/
const char* strerr;
switch (err) {
case kMIDIInvalidClient:
strerr = strMIDIInvalidClient;
break;
case kMIDIInvalidPort:
strerr = strMIDIInvalidPort;
break;
case kMIDIWrongEndpointType:
strerr = strMIDIWrongEndpointType;
break;
case kMIDINoConnection:
strerr = strMIDINoConnection;
break;
case kMIDIUnknownEndpoint:
strerr = strMIDIUnknownEndpoint;
break;
case kMIDIUnknownProperty:
strerr = strMIDIUnknownProperty;
break;
case kMIDIWrongPropertyType:
strerr = strMIDIWrongPropertyType;
break;
case kMIDINoCurrentSetup:
strerr = strMIDINoCurrentSetup;
break;
case kMIDIMessageSendErr:
strerr = strMIDIMessageSendErr;
break;
case kMIDIServerStartErr:
strerr = strMIDIServerStartErr;
break;
case kMIDISetupFormatErr:
strerr = strMIDISetupFormatErr;
break;
case kMIDIWrongThread:
strerr = strMIDIWrongThread;
break;
case kMIDIObjectNotFound:
strerr = strMIDIObjectNotFound;
break;
case kMIDIIDNotUnique:
strerr = strMIDIIDNotUnique;
break;
default:
strerr = "Unknown error.";
break;
}
return strerr;
}
const char* MIDI_Utils_GetErrorMsg(int err) {
return midi_strerror(err);
}
void MIDI_Utils_PrintError(int err) {
#ifdef USE_ERROR
const char* s = MIDI_Utils_GetErrorMsg(err);
if (s != NULL) {
fprintf(stderr, "%s\n", s);
}
#endif
}
// Note direction is either MIDI_IN or MIDI_OUT.
INT32 MIDI_Utils_GetNumDevices(int direction) {
int num_endpoints;
if (direction == MIDI_IN) {
num_endpoints = MIDIGetNumberOfSources();
//fprintf(stdout, "MIDIGetNumberOfSources() returns %d\n", num_endpoints);
} else if (direction == MIDI_OUT) {
num_endpoints = MIDIGetNumberOfDestinations();
//printf(stdout, "MIDIGetNumberOfDestinations() returns %d\n", num_endpoints);
} else {
assert((direction == MIDI_IN || direction == MIDI_OUT));
num_endpoints = 0;
}
return (INT32) num_endpoints;
}
// Wraps calls to CFStringGetCStringPtr and CFStringGetCString to make sure
// we extract the c characters into the buffer and null-terminate it.
static void CFStringExtractCString(CFStringRef cfs, char* buffer, UINT32 bufferSize, CFStringEncoding encoding) {
const char* ptr = CFStringGetCStringPtr(cfs, encoding);
if (ptr) {
strlcpy(buffer, ptr, bufferSize);
} else {
if (! CFStringGetCString(cfs, buffer, bufferSize, encoding)) {
// There's an error in conversion, make sure we null-terminate the buffer.
buffer[bufferSize - 1] = '\0';
}
}
}
//
// @see com.sun.media.sound.AbstractMidiDeviceProvider.getDeviceInfo().
static int getEndpointProperty(int direction, INT32 deviceID, char *buffer, int bufferLength, CFStringRef propertyID) {
if (deviceID < 0) {
return MIDI_INVALID_DEVICEID;
}
MIDIEndpointRef endpoint;
if (direction == MIDI_IN) {
endpoint = MIDIGetSource(deviceID);
} else if (direction == MIDI_OUT) {
endpoint = MIDIGetDestination(deviceID);
} else {
return MIDI_INVALID_ARGUMENT;
}
if (!endpoint) {
return MIDI_INVALID_DEVICEID;
}
int status = MIDI_SUCCESS;
if (propertyID == kMIDIPropertyDriverVersion) {
SInt32 driverVersion;
status = MIDIObjectGetIntegerProperty(endpoint, kMIDIPropertyDriverVersion, &driverVersion);
if (status != MIDI_SUCCESS) return status;
snprintf(buffer,
bufferLength,
"%d",
(int) driverVersion);
}
else {
CFStringRef pname;
status = MIDIObjectGetStringProperty(endpoint, propertyID, &pname);
if (status != MIDI_SUCCESS) return status;
CFStringExtractCString(pname, buffer, bufferLength, 0);
}
return MIDI_ERROR_NONE;
}
// A simple utility which encapsulates CoreAudio's HostTime APIs.
// It returns the current host time in nanoseconds which when subtracted from
// a previous getCurrentTimeInNanos() result produces the delta in nanos.
static UInt64 getCurrentTimeInNanos() {
UInt64 hostTime = AudioGetCurrentHostTime();
UInt64 nanos = AudioConvertHostTimeToNanos(hostTime);
return nanos;
}
INT32 MIDI_Utils_GetDeviceName(int direction, INT32 deviceID, char *name, UINT32 bufferLength) {
return getEndpointProperty(direction, deviceID, name, bufferLength, kMIDIPropertyName);
}
INT32 MIDI_Utils_GetDeviceVendor(int direction, INT32 deviceID, char *name, UINT32 bufferLength) {
return getEndpointProperty(direction, deviceID, name, bufferLength, kMIDIPropertyManufacturer);
}
INT32 MIDI_Utils_GetDeviceDescription(int direction, INT32 deviceID, char *name, UINT32 bufferLength) {
return getEndpointProperty(direction, deviceID, name, bufferLength, kMIDIPropertyDisplayName);
}
INT32 MIDI_Utils_GetDeviceVersion(int direction, INT32 deviceID, char *name, UINT32 bufferLength) {
return getEndpointProperty(direction, deviceID, name, bufferLength, kMIDIPropertyDriverVersion);
}
static MIDIClientRef client = (MIDIClientRef) NULL;
static MIDIPortRef inPort = (MIDIPortRef) NULL;
static MIDIPortRef outPort = (MIDIPortRef) NULL;
// Each MIDIPacket can contain more than one midi messages.
// This function processes the packet and adds the messages to the specified message queue.
// @see also src/share/native/com/sun/media/sound/PlatformMidi.h.
static void processMessagesForPacket(const MIDIPacket* packet, MacMidiDeviceHandle* handle) {
const UInt8* data;
UInt16 length;
UInt8 byte;
UInt8 pendingMessageStatus;
UInt8 pendingData[2];
UInt16 pendingDataIndex, pendingDataLength;
UINT32 packedMsg;
MIDITimeStamp ts = packet->timeStamp;
pendingMessageStatus = 0;
pendingDataIndex = pendingDataLength = 0;
data = packet->data;
length = packet->length;
while (length--) {
bool byteIsInvalid = FALSE;
byte = *data++;
packedMsg = byte;
if (byte >= 0xF8) {
// Each RealTime Category message (ie, Status of 0xF8 to 0xFF) consists of only 1 byte, the Status.
// Except that 0xFD is an invalid status code.
//
// 0xF8 -> Midi clock
// 0xF9 -> Midi tick
// 0xFA -> Midi start
// 0xFB -> Midi continue
// 0xFC -> Midi stop
// 0xFE -> Active sense
// 0xFF -> Reset
if (byte == 0xFD) {
byteIsInvalid = TRUE;
} else {
pendingDataLength = 0;
}
} else {
if (byte < 0x80) {
// Not a status byte -- check our history.
if (handle->readingSysExData) {
CFDataAppendBytes(handle->readingSysExData, &byte, 1);
} else if (pendingDataIndex < pendingDataLength) {
pendingData[pendingDataIndex] = byte;
pendingDataIndex++;
if (pendingDataIndex == pendingDataLength) {
// This message is now done -- do the final processing.
if (pendingDataLength == 2) {
packedMsg = pendingMessageStatus | pendingData[0] << 8 | pendingData[1] << 16;
} else if (pendingDataLength == 1) {
packedMsg = pendingMessageStatus | pendingData[0] << 8;
} else {
fprintf(stderr, "%s: %d->internal error: pendingMessageStatus=0x%X, pendingDataLength=%d\n",
THIS_FILE, __LINE__, pendingMessageStatus, pendingDataLength);
byteIsInvalid = TRUE;
}
pendingDataLength = 0;
}
} else {
// Skip this byte -- it is invalid.
byteIsInvalid = TRUE;
}
} else {
if (handle->readingSysExData /* && (byte == 0xF7) */) {
// We have reached the end of system exclusive message -- send it finally.
const UInt8* bytes = CFDataGetBytePtr(handle->readingSysExData);
CFIndex size = CFDataGetLength(handle->readingSysExData);
MIDI_QueueAddLong(handle->h.queue,
(UBYTE*) bytes,
(UINT32) size,
0, // Don't care, windowish porting only.
(INT64) (AudioConvertHostTimeToNanos(ts) + 500) / 1000,
TRUE);
CFRelease(handle->readingSysExData);
handle->readingSysExData = NULL;
}
pendingMessageStatus = byte;
pendingDataLength = 0;
pendingDataIndex = 0;
switch (byte & 0xF0) {
case 0x80: // Note off
case 0x90: // Note on
case 0xA0: // Aftertouch
case 0xB0: // Controller
case 0xE0: // Pitch wheel
pendingDataLength = 2;
break;
case 0xC0: // Program change
case 0xD0: // Channel pressure
pendingDataLength = 1;
break;
case 0xF0: {
// System common message
switch (byte) {
case 0xF0:
// System exclusive
// Allocates a CFMutableData reference to accumulate the SysEx data until EOX (0xF7) is reached.
handle->readingSysExData = CFDataCreateMutable(NULL, 0);
break;
case 0xF7:
// System exclusive ends--already handled above.
// But if this is showing up outside of sysex, it's invalid.
byteIsInvalid = TRUE;
break;
case 0xF1: // MTC quarter frame message
case 0xF3: // Song select
pendingDataLength = 1;
break;
case 0xF2: // Song position pointer
pendingDataLength = 2;
break;
case 0xF6: // Tune request
pendingDataLength = 0;
break;
default:
// Invalid message
byteIsInvalid = TRUE;
break;
}
break;
}
default:
// This can't happen, but handle it anyway.
byteIsInvalid = TRUE;
break;
}
}
}
if (byteIsInvalid) continue;
// If the byte is valid and pendingDataLength is 0, we are ready to send the message.
if (pendingDataLength == 0) {
MIDI_QueueAddShort(handle->h.queue, packedMsg, (INT64) (AudioConvertHostTimeToNanos(ts) + 500) / 1000, TRUE);
}
}
}
static void midiReadProc(const MIDIPacketList* packetList, void* refCon, void* connRefCon) {
unsigned int i;
const MIDIPacket* packet;
MacMidiDeviceHandle* handle = (MacMidiDeviceHandle*) connRefCon;
packet = packetList->packet;
for (i = 0; i < packetList->numPackets; ++i) {
processMessagesForPacket(packet, handle);
packet = MIDIPacketNext(packet);
}
// Notify the waiting thread that there's data available.
if (handle) {
MIDI_SignalConditionVariable(handle->h.platformData);
}
}
static void midiInit() {
if (client) {
return;
}
OSStatus err = noErr;
err = MIDIClientCreate(CFSTR("MIDI Client"), NULL, NULL, &client);
if (err != noErr) { goto Exit; }
// This just creates an input port through which the client may receive
// incoming MIDI messages from any MIDI source.
err = MIDIInputPortCreate(client, CFSTR("MIDI Input Port"), midiReadProc, NULL, &inPort);
if (err != noErr) { goto Exit; }
err = MIDIOutputPortCreate(client, CFSTR("MIDI Output Port"), &outPort);
if (err != noErr) { goto Exit; }
Exit:
if (err != noErr) {
const char* s = MIDI_Utils_GetErrorMsg(err);
if (s != NULL) {
printf("%s\n", s);
}
}
}
INT32 MIDI_Utils_OpenDevice(int direction, INT32 deviceID, MacMidiDeviceHandle** handle,
int num_msgs, int num_long_msgs,
size_t lm_size)
{
midiInit();
int err = MIDI_ERROR_NONE;
MIDIEndpointRef endpoint = (MIDIEndpointRef) NULL;
TRACE0("MIDI_Utils_OpenDevice\n");
(*handle) = (MacMidiDeviceHandle*) malloc(sizeof(MacMidiDeviceHandle));
if (!(*handle)) {
ERROR0("ERROR: MIDI_Utils_OpenDevice: out of memory\n");
return MIDI_OUT_OF_MEMORY;
}
memset(*handle, 0, sizeof(MacMidiDeviceHandle));
// Create the infrastructure for MIDI in/out, and after that,
// get the device's endpoint.
if (direction == MIDI_IN) {
// Create queue and the pthread condition variable.
(*handle)->h.queue = MIDI_CreateQueue(num_msgs);
(*handle)->h.platformData = MIDI_CreateConditionVariable();
if (!(*handle)->h.queue || !(*handle)->h.platformData) {
ERROR0("< ERROR: MIDI_IN_OpenDevice: could not create queue or condition variable\n");
free(*handle);
(*handle) = NULL;
return MIDI_OUT_OF_MEMORY;
}
endpoint = MIDIGetSource(deviceID);
(*handle)->port = inPort;
} else if (direction == MIDI_OUT) {
endpoint = MIDIGetDestination(deviceID);
(*handle)->port = outPort;
}
if (!endpoint) {
// An error occurred.
free(*handle);
return MIDI_INVALID_DEVICEID;
}
(*handle)->h.deviceHandle = (void*) (intptr_t) endpoint;
(*handle)->h.startTime = getCurrentTimeInNanos();
(*handle)->direction = direction;
(*handle)->deviceID = deviceID;
TRACE0("MIDI_Utils_OpenDevice: succeeded\n");
return err;
}
INT32 MIDI_Utils_CloseDevice(MacMidiDeviceHandle* handle) {
int err = MIDI_ERROR_NONE;
bool midiIn = (handle->direction == MIDI_IN);
TRACE0("> MIDI_Utils_CloseDevice\n");
if (!handle) {
ERROR0("< ERROR: MIDI_Utils_CloseDevice: handle is NULL\n");
return MIDI_INVALID_HANDLE;
}
if (!handle->h.deviceHandle) {
ERROR0("< ERROR: MIDI_Utils_CloseDevice: native handle is NULL\n");
return MIDI_INVALID_HANDLE;
}
handle->isStarted = FALSE;
handle->h.deviceHandle = NULL;
if (midiIn) {
if (handle->h.queue != NULL) {
MidiMessageQueue* queue = handle->h.queue;
handle->h.queue = NULL;
MIDI_DestroyQueue(queue);
}
if (handle->h.platformData) {
MIDI_DestroyConditionVariable(handle->h.platformData);
}
}
free(handle);
TRACE0("< MIDI_Utils_CloseDevice: succeeded\n");
return err;
}
INT32 MIDI_Utils_StartDevice(MacMidiDeviceHandle* handle) {
OSStatus err = noErr;
if (!handle || !handle->h.deviceHandle) {
ERROR0("ERROR: MIDI_Utils_StartDevice: handle or native is NULL\n");
return MIDI_INVALID_HANDLE;
}
// Clears all the events from the queue.
MIDI_QueueClear(handle->h.queue);
if (!handle->isStarted) {
/* set the flag that we can now receive messages */
handle->isStarted = TRUE;
if (handle->direction == MIDI_IN) {
// The handle->h.platformData field contains the (pthread_cond_t*)
// associated with the source of the MIDI input stream, and is
// used in the CoreMIDI's callback to signal the arrival of new
// data.
//
// Similarly, handle->h.queue is used in the CoreMDID's callback
// to dispatch the incoming messages to the appropriate queue.
//
err = MIDIPortConnectSource(inPort, (MIDIEndpointRef) (intptr_t) (handle->h.deviceHandle), (void*) handle);
} else if (handle->direction == MIDI_OUT) {
// Unschedules previous-sent packets.
err = MIDIFlushOutput((MIDIEndpointRef) (intptr_t) handle->h.deviceHandle);
}
MIDI_CHECK_ERROR;
}
return MIDI_SUCCESS; /* don't fail */
}
INT32 MIDI_Utils_StopDevice(MacMidiDeviceHandle* handle) {
OSStatus err = noErr;
if (!handle || !handle->h.deviceHandle) {
ERROR0("ERROR: MIDI_Utils_StopDevice: handle or native handle is NULL\n");
return MIDI_INVALID_HANDLE;
}
if (handle->isStarted) {
/* set the flag that we don't want to receive messages anymore */
handle->isStarted = FALSE;
if (handle->direction == MIDI_IN) {
err = MIDIPortDisconnectSource(inPort, (MIDIEndpointRef) (intptr_t) (handle->h.deviceHandle));
} else if (handle->direction == MIDI_OUT) {
// Unschedules previously-sent packets.
err = MIDIFlushOutput((MIDIEndpointRef) (intptr_t) handle->h.deviceHandle);
}
MIDI_CHECK_ERROR;
}
return MIDI_SUCCESS;
}
INT64 MIDI_Utils_GetTimeStamp(MacMidiDeviceHandle* handle) {
if (!handle || !handle->h.deviceHandle) {
ERROR0("ERROR: MIDI_Utils_GetTimeStamp: handle or native handle is NULL\n");
return (INT64) -1; /* failure */
}
UInt64 delta = getCurrentTimeInNanos() - handle->h.startTime;
return (INT64) ((delta + 500) / 1000);
}
/***************************************************************************/
/* Condition Variable Support for Mac OS X Port */
/* */
/* This works with the Native Locking Support defined below. We are using */
/* POSIX pthread_cond_t/pthread_mutex_t to do locking and synchronization. */
/* */
/* For MidiDeviceHandle* handle, the mutex reference is stored as handle-> */
/* queue->lock while the condition variabale reference is stored as handle */
/* ->platformData. */
/***************************************************************************/
// Called from Midi_Utils_Opendevice(...) to create a condition variable
// used to synchronize between the receive thread created by the CoreMIDI
// and the Java-initiated MidiInDevice run loop.
void* MIDI_CreateConditionVariable() {
pthread_cond_t* cond = (pthread_cond_t*) malloc(sizeof(pthread_cond_t));
pthread_cond_init(cond, NULL);
return (void*) cond;
}
void MIDI_DestroyConditionVariable(void* cond) {
while (pthread_cond_destroy((pthread_cond_t*) cond) == EBUSY) {
pthread_cond_broadcast((pthread_cond_t*) cond);
sched_yield();
}
return;
}
// Called from MIDI_IN_GetMessage(...) to wait for MIDI messages to become
// available via delivery from the CoreMIDI receive thread
void MIDI_WaitOnConditionVariable(void* cond, void* lock) {
if (cond && lock) {
pthread_mutex_lock(lock);
pthread_cond_wait((pthread_cond_t*) cond, (pthread_mutex_t*) lock);
pthread_mutex_unlock(lock);
}
return;
}
// Called from midiReadProc(...) to notify the waiting thread to unblock on
// the condition variable.
void MIDI_SignalConditionVariable(void* cond) {
if (cond) {
pthread_cond_signal((pthread_cond_t*) cond);
}
return;
}
/**************************************************************************/
/* Native Locking Support */
/* */
/* @see src/share/natve/com/sun/media/sound/PlatformMidi.c which contains */
/* utility functions for platform midi support where the section of code */
/* for MessageQueue implementation calls out to these functions. */
/**************************************************************************/
void* MIDI_CreateLock() {
pthread_mutex_t* lock = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(lock, NULL);
TRACE0("MIDI_CreateLock\n");
return (void *)lock;
}
void MIDI_DestroyLock(void* lock) {
if (lock) {
pthread_mutex_destroy((pthread_mutex_t*) lock);
free(lock);
TRACE0("MIDI_DestroyLock\n");
}
}
void MIDI_Lock(void* lock) {
if (lock) {
pthread_mutex_lock((pthread_mutex_t*) lock);
}
}
void MIDI_Unlock(void* lock) {
if (lock) {
pthread_mutex_unlock((pthread_mutex_t*) lock);
}
}
#endif // USE_PLATFORM_MIDI_IN || USE_PLATFORM_MIDI_OUT