Path: blob/master/src/java.desktop/macosx/native/libjsound/PLATFORM_API_MacOSX_MidiUtils.c
41149 views
/*1* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26**27** Overview:28** Implementation of the functions used for both MIDI in and MIDI out.29**30** Java package com.sun.media.sound defines the AbstractMidiDevice class31** which encapsulates functionalities shared by both MidiInDevice and32** MidiOutDevice classes in the same package.33**34** The Java layer classes MidiInDevice and MidiOutDevice in turn map to35** the MIDIEndpointRef data type in the CoreMIDI framework, which36** represents a source or destination for a standard 16-channel MIDI data37** stream.38*/39/*****************************************************************************/4041//#define USE_ERROR42//#define USE_TRACE4344#if (USE_PLATFORM_MIDI_IN == TRUE) || (USE_PLATFORM_MIDI_OUT == TRUE)4546#include "PLATFORM_API_MacOSX_MidiUtils.h"47#include <pthread.h>48#include <assert.h>4950// Constant character string definitions of CoreMIDI's corresponding error codes.5152static const char* strMIDIInvalidClient =53"An invalid MIDIClientRef was passed.";54static const char* strMIDIInvalidPort =55"An invalid MIDIPortRef was passed.";56static const char* strMIDIWrongEndpointType =57"A source endpoint was passed to a function expecting a destination, or vice versa.";58static const char* strMIDINoConnection =59"Attempt to close a non-existant connection.";60static const char* strMIDIUnknownEndpoint =61"An invalid MIDIEndpointRef was passed.";62static const char* strMIDIUnknownProperty =63"Attempt to query a property not set on the object.";64static const char* strMIDIWrongPropertyType =65"Attempt to set a property with a value not of the correct type.";66static const char* strMIDINoCurrentSetup =67"Internal error; there is no current MIDI setup object.";68static const char* strMIDIMessageSendErr =69"Communication with MIDIServer failed.";70static const char* strMIDIServerStartErr =71"Unable to start MIDIServer.";72static const char* strMIDISetupFormatErr =73"Unable to read the saved state.";74static const char* strMIDIWrongThread =75"A driver is calling a non-I/O function in the server from a thread other than"76"the server's main thread.";77static const char* strMIDIObjectNotFound =78"The requested object does not exist.";79static const char* strMIDIIDNotUnique =80"Attempt to set a non-unique kMIDIPropertyUniqueID on an object.";8182static const char* midi_strerror(int err) {83/*84@enum Error Constants85@abstract The error constants unique to Core MIDI.86@discussion These are the error constants that are unique to Core MIDI. Note that Core MIDI87functions may return other codes that are not listed here.88*/89const char* strerr;9091switch (err) {92case kMIDIInvalidClient:93strerr = strMIDIInvalidClient;94break;95case kMIDIInvalidPort:96strerr = strMIDIInvalidPort;97break;98case kMIDIWrongEndpointType:99strerr = strMIDIWrongEndpointType;100break;101case kMIDINoConnection:102strerr = strMIDINoConnection;103break;104case kMIDIUnknownEndpoint:105strerr = strMIDIUnknownEndpoint;106break;107case kMIDIUnknownProperty:108strerr = strMIDIUnknownProperty;109break;110case kMIDIWrongPropertyType:111strerr = strMIDIWrongPropertyType;112break;113case kMIDINoCurrentSetup:114strerr = strMIDINoCurrentSetup;115break;116case kMIDIMessageSendErr:117strerr = strMIDIMessageSendErr;118break;119case kMIDIServerStartErr:120strerr = strMIDIServerStartErr;121break;122case kMIDISetupFormatErr:123strerr = strMIDISetupFormatErr;124break;125case kMIDIWrongThread:126strerr = strMIDIWrongThread;127break;128case kMIDIObjectNotFound:129strerr = strMIDIObjectNotFound;130break;131case kMIDIIDNotUnique:132strerr = strMIDIIDNotUnique;133break;134default:135strerr = "Unknown error.";136break;137}138return strerr;139}140141const char* MIDI_Utils_GetErrorMsg(int err) {142return midi_strerror(err);143}144145146void MIDI_Utils_PrintError(int err) {147#ifdef USE_ERROR148const char* s = MIDI_Utils_GetErrorMsg(err);149if (s != NULL) {150fprintf(stderr, "%s\n", s);151}152#endif153}154155156// Note direction is either MIDI_IN or MIDI_OUT.157INT32 MIDI_Utils_GetNumDevices(int direction) {158int num_endpoints;159if (direction == MIDI_IN) {160num_endpoints = MIDIGetNumberOfSources();161//fprintf(stdout, "MIDIGetNumberOfSources() returns %d\n", num_endpoints);162} else if (direction == MIDI_OUT) {163num_endpoints = MIDIGetNumberOfDestinations();164//printf(stdout, "MIDIGetNumberOfDestinations() returns %d\n", num_endpoints);165} else {166assert((direction == MIDI_IN || direction == MIDI_OUT));167num_endpoints = 0;168}169return (INT32) num_endpoints;170}171172// Wraps calls to CFStringGetCStringPtr and CFStringGetCString to make sure173// we extract the c characters into the buffer and null-terminate it.174static void CFStringExtractCString(CFStringRef cfs, char* buffer, UINT32 bufferSize, CFStringEncoding encoding) {175const char* ptr = CFStringGetCStringPtr(cfs, encoding);176if (ptr) {177strlcpy(buffer, ptr, bufferSize);178} else {179if (! CFStringGetCString(cfs, buffer, bufferSize, encoding)) {180// There's an error in conversion, make sure we null-terminate the buffer.181buffer[bufferSize - 1] = '\0';182}183}184}185186//187// @see com.sun.media.sound.AbstractMidiDeviceProvider.getDeviceInfo().188static int getEndpointProperty(int direction, INT32 deviceID, char *buffer, int bufferLength, CFStringRef propertyID) {189190if (deviceID < 0) {191return MIDI_INVALID_DEVICEID;192}193194MIDIEndpointRef endpoint;195196if (direction == MIDI_IN) {197endpoint = MIDIGetSource(deviceID);198} else if (direction == MIDI_OUT) {199endpoint = MIDIGetDestination(deviceID);200} else {201return MIDI_INVALID_ARGUMENT;202}203204if (!endpoint) {205return MIDI_INVALID_DEVICEID;206}207208int status = MIDI_SUCCESS;209if (propertyID == kMIDIPropertyDriverVersion) {210SInt32 driverVersion;211status = MIDIObjectGetIntegerProperty(endpoint, kMIDIPropertyDriverVersion, &driverVersion);212if (status != MIDI_SUCCESS) return status;213snprintf(buffer,214bufferLength,215"%d",216(int) driverVersion);217}218else {219CFStringRef pname;220status = MIDIObjectGetStringProperty(endpoint, propertyID, &pname);221if (status != MIDI_SUCCESS) return status;222CFStringExtractCString(pname, buffer, bufferLength, 0);223}224return MIDI_ERROR_NONE;225}226227// A simple utility which encapsulates CoreAudio's HostTime APIs.228// It returns the current host time in nanoseconds which when subtracted from229// a previous getCurrentTimeInNanos() result produces the delta in nanos.230static UInt64 getCurrentTimeInNanos() {231UInt64 hostTime = AudioGetCurrentHostTime();232UInt64 nanos = AudioConvertHostTimeToNanos(hostTime);233return nanos;234}235236237INT32 MIDI_Utils_GetDeviceName(int direction, INT32 deviceID, char *name, UINT32 bufferLength) {238return getEndpointProperty(direction, deviceID, name, bufferLength, kMIDIPropertyName);239}240241242INT32 MIDI_Utils_GetDeviceVendor(int direction, INT32 deviceID, char *name, UINT32 bufferLength) {243return getEndpointProperty(direction, deviceID, name, bufferLength, kMIDIPropertyManufacturer);244}245246247INT32 MIDI_Utils_GetDeviceDescription(int direction, INT32 deviceID, char *name, UINT32 bufferLength) {248return getEndpointProperty(direction, deviceID, name, bufferLength, kMIDIPropertyDisplayName);249}250251252INT32 MIDI_Utils_GetDeviceVersion(int direction, INT32 deviceID, char *name, UINT32 bufferLength) {253return getEndpointProperty(direction, deviceID, name, bufferLength, kMIDIPropertyDriverVersion);254}255256257static MIDIClientRef client = (MIDIClientRef) 0;258static MIDIPortRef inPort = (MIDIPortRef) 0;259static MIDIPortRef outPort = (MIDIPortRef) 0;260261// Each MIDIPacket can contain more than one midi messages.262// This function processes the packet and adds the messages to the specified message queue.263// @see also src/share/native/com/sun/media/sound/PlatformMidi.h.264static void processMessagesForPacket(const MIDIPacket* packet, MacMidiDeviceHandle* handle) {265const UInt8* data;266UInt16 length;267UInt8 byte;268UInt8 pendingMessageStatus;269UInt8 pendingData[2];270UInt16 pendingDataIndex, pendingDataLength;271UINT32 packedMsg;272MIDITimeStamp ts = packet->timeStamp;273274pendingMessageStatus = 0;275pendingDataIndex = pendingDataLength = 0;276277data = packet->data;278length = packet->length;279while (length--) {280bool byteIsInvalid = FALSE;281282byte = *data++;283packedMsg = byte;284285if (byte >= 0xF8) {286// Each RealTime Category message (ie, Status of 0xF8 to 0xFF) consists of only 1 byte, the Status.287// Except that 0xFD is an invalid status code.288//289// 0xF8 -> Midi clock290// 0xF9 -> Midi tick291// 0xFA -> Midi start292// 0xFB -> Midi continue293// 0xFC -> Midi stop294// 0xFE -> Active sense295// 0xFF -> Reset296if (byte == 0xFD) {297byteIsInvalid = TRUE;298} else {299pendingDataLength = 0;300}301} else {302if (byte < 0x80) {303// Not a status byte -- check our history.304if (handle->readingSysExData) {305CFDataAppendBytes(handle->readingSysExData, &byte, 1);306307} else if (pendingDataIndex < pendingDataLength) {308pendingData[pendingDataIndex] = byte;309pendingDataIndex++;310311if (pendingDataIndex == pendingDataLength) {312// This message is now done -- do the final processing.313if (pendingDataLength == 2) {314packedMsg = pendingMessageStatus | pendingData[0] << 8 | pendingData[1] << 16;315} else if (pendingDataLength == 1) {316packedMsg = pendingMessageStatus | pendingData[0] << 8;317} else {318fprintf(stderr, "%s: %d->internal error: pendingMessageStatus=0x%X, pendingDataLength=%d\n",319__FILE__, __LINE__, pendingMessageStatus, pendingDataLength);320byteIsInvalid = TRUE;321}322pendingDataLength = 0;323}324} else {325// Skip this byte -- it is invalid.326byteIsInvalid = TRUE;327}328} else {329if (handle->readingSysExData /* && (byte == 0xF7) */) {330// We have reached the end of system exclusive message -- send it finally.331const UInt8* bytes = CFDataGetBytePtr(handle->readingSysExData);332CFIndex size = CFDataGetLength(handle->readingSysExData);333MIDI_QueueAddLong(handle->h.queue,334(UBYTE*) bytes,335(UINT32) size,3360, // Don't care, windowish porting only.337(INT64) (AudioConvertHostTimeToNanos(ts) + 500) / 1000,338TRUE);339CFRelease(handle->readingSysExData);340handle->readingSysExData = NULL;341}342343pendingMessageStatus = byte;344pendingDataLength = 0;345pendingDataIndex = 0;346347switch (byte & 0xF0) {348case 0x80: // Note off349case 0x90: // Note on350case 0xA0: // Aftertouch351case 0xB0: // Controller352case 0xE0: // Pitch wheel353pendingDataLength = 2;354break;355356case 0xC0: // Program change357case 0xD0: // Channel pressure358pendingDataLength = 1;359break;360361case 0xF0: {362// System common message363switch (byte) {364case 0xF0:365// System exclusive366// Allocates a CFMutableData reference to accumulate the SysEx data until EOX (0xF7) is reached.367handle->readingSysExData = CFDataCreateMutable(NULL, 0);368break;369370case 0xF7:371// System exclusive ends--already handled above.372// But if this is showing up outside of sysex, it's invalid.373byteIsInvalid = TRUE;374break;375376case 0xF1: // MTC quarter frame message377case 0xF3: // Song select378pendingDataLength = 1;379break;380381case 0xF2: // Song position pointer382pendingDataLength = 2;383break;384385case 0xF6: // Tune request386pendingDataLength = 0;387break;388389default:390// Invalid message391byteIsInvalid = TRUE;392break;393}394break;395}396397default:398// This can't happen, but handle it anyway.399byteIsInvalid = TRUE;400break;401}402}403}404if (byteIsInvalid) continue;405406// If the byte is valid and pendingDataLength is 0, we are ready to send the message.407if (pendingDataLength == 0) {408MIDI_QueueAddShort(handle->h.queue, packedMsg, (INT64) (AudioConvertHostTimeToNanos(ts) + 500) / 1000, TRUE);409}410}411}412413static void midiReadProc(const MIDIPacketList* packetList, void* refCon, void* connRefCon) {414unsigned int i;415const MIDIPacket* packet;416MacMidiDeviceHandle* handle = (MacMidiDeviceHandle*) connRefCon;417418packet = packetList->packet;419for (i = 0; i < packetList->numPackets; ++i) {420processMessagesForPacket(packet, handle);421packet = MIDIPacketNext(packet);422}423424// Notify the waiting thread that there's data available.425if (handle) {426MIDI_SignalConditionVariable(handle->h.platformData);427}428}429430static void midiInit() {431if (client) {432return;433}434435OSStatus err = noErr;436437err = MIDIClientCreate(CFSTR("MIDI Client"), NULL, NULL, &client);438if (err != noErr) { goto Exit; }439440// This just creates an input port through which the client may receive441// incoming MIDI messages from any MIDI source.442err = MIDIInputPortCreate(client, CFSTR("MIDI Input Port"), midiReadProc, NULL, &inPort);443if (err != noErr) { goto Exit; }444445err = MIDIOutputPortCreate(client, CFSTR("MIDI Output Port"), &outPort);446if (err != noErr) { goto Exit; }447448Exit:449if (err != noErr) {450const char* s = MIDI_Utils_GetErrorMsg(err);451if (s != NULL) {452printf("%s\n", s);453}454}455}456457458INT32 MIDI_Utils_OpenDevice(int direction, INT32 deviceID, MacMidiDeviceHandle** handle,459int num_msgs, int num_long_msgs,460size_t lm_size)461{462midiInit();463464int err = MIDI_ERROR_NONE;465MIDIEndpointRef endpoint = (MIDIEndpointRef) 0;466467TRACE0("MIDI_Utils_OpenDevice\n");468469(*handle) = (MacMidiDeviceHandle*) malloc(sizeof(MacMidiDeviceHandle));470if (!(*handle)) {471ERROR0("ERROR: MIDI_Utils_OpenDevice: out of memory\n");472return MIDI_OUT_OF_MEMORY;473}474memset(*handle, 0, sizeof(MacMidiDeviceHandle));475476// Create the infrastructure for MIDI in/out, and after that,477// get the device's endpoint.478if (direction == MIDI_IN) {479// Create queue and the pthread condition variable.480(*handle)->h.queue = MIDI_CreateQueue(num_msgs);481(*handle)->h.platformData = MIDI_CreateConditionVariable();482if (!(*handle)->h.queue || !(*handle)->h.platformData) {483ERROR0("< ERROR: MIDI_IN_OpenDevice: could not create queue or condition variable\n");484free(*handle);485(*handle) = NULL;486return MIDI_OUT_OF_MEMORY;487}488endpoint = MIDIGetSource(deviceID);489(*handle)->port = inPort;490} else if (direction == MIDI_OUT) {491endpoint = MIDIGetDestination(deviceID);492(*handle)->port = outPort;493}494495if (!endpoint) {496// An error occurred.497free(*handle);498return MIDI_INVALID_DEVICEID;499}500(*handle)->h.deviceHandle = (void*) (intptr_t) endpoint;501(*handle)->h.startTime = getCurrentTimeInNanos();502(*handle)->direction = direction;503(*handle)->deviceID = deviceID;504505TRACE0("MIDI_Utils_OpenDevice: succeeded\n");506return err;507}508509510INT32 MIDI_Utils_CloseDevice(MacMidiDeviceHandle* handle) {511int err = MIDI_ERROR_NONE;512bool midiIn = (handle->direction == MIDI_IN);513514TRACE0("> MIDI_Utils_CloseDevice\n");515if (!handle) {516ERROR0("< ERROR: MIDI_Utils_CloseDevice: handle is NULL\n");517return MIDI_INVALID_HANDLE;518}519if (!handle->h.deviceHandle) {520ERROR0("< ERROR: MIDI_Utils_CloseDevice: native handle is NULL\n");521return MIDI_INVALID_HANDLE;522}523handle->isStarted = FALSE;524handle->h.deviceHandle = NULL;525526if (midiIn) {527if (handle->h.queue != NULL) {528MidiMessageQueue* queue = handle->h.queue;529handle->h.queue = NULL;530MIDI_DestroyQueue(queue);531}532if (handle->h.platformData) {533MIDI_DestroyConditionVariable(handle->h.platformData);534}535}536free(handle);537538TRACE0("< MIDI_Utils_CloseDevice: succeeded\n");539return err;540}541542543INT32 MIDI_Utils_StartDevice(MacMidiDeviceHandle* handle) {544OSStatus err = noErr;545546if (!handle || !handle->h.deviceHandle) {547ERROR0("ERROR: MIDI_Utils_StartDevice: handle or native is NULL\n");548return MIDI_INVALID_HANDLE;549}550551// Clears all the events from the queue.552MIDI_QueueClear(handle->h.queue);553554if (!handle->isStarted) {555/* set the flag that we can now receive messages */556handle->isStarted = TRUE;557558if (handle->direction == MIDI_IN) {559// The handle->h.platformData field contains the (pthread_cond_t*)560// associated with the source of the MIDI input stream, and is561// used in the CoreMIDI's callback to signal the arrival of new562// data.563//564// Similarly, handle->h.queue is used in the CoreMDID's callback565// to dispatch the incoming messages to the appropriate queue.566//567err = MIDIPortConnectSource(inPort, (MIDIEndpointRef) (intptr_t) (handle->h.deviceHandle), (void*) handle);568} else if (handle->direction == MIDI_OUT) {569// Unschedules previous-sent packets.570err = MIDIFlushOutput((MIDIEndpointRef) (intptr_t) handle->h.deviceHandle);571}572573MIDI_CHECK_ERROR;574}575return MIDI_SUCCESS; /* don't fail */576}577578579INT32 MIDI_Utils_StopDevice(MacMidiDeviceHandle* handle) {580OSStatus err = noErr;581582if (!handle || !handle->h.deviceHandle) {583ERROR0("ERROR: MIDI_Utils_StopDevice: handle or native handle is NULL\n");584return MIDI_INVALID_HANDLE;585}586587if (handle->isStarted) {588/* set the flag that we don't want to receive messages anymore */589handle->isStarted = FALSE;590591if (handle->direction == MIDI_IN) {592err = MIDIPortDisconnectSource(inPort, (MIDIEndpointRef) (intptr_t) (handle->h.deviceHandle));593} else if (handle->direction == MIDI_OUT) {594// Unschedules previously-sent packets.595err = MIDIFlushOutput((MIDIEndpointRef) (intptr_t) handle->h.deviceHandle);596}597598MIDI_CHECK_ERROR;599}600return MIDI_SUCCESS;601}602603604INT64 MIDI_Utils_GetTimeStamp(MacMidiDeviceHandle* handle) {605606if (!handle || !handle->h.deviceHandle) {607ERROR0("ERROR: MIDI_Utils_GetTimeStamp: handle or native handle is NULL\n");608return (INT64) -1; /* failure */609}610611UInt64 delta = getCurrentTimeInNanos() - handle->h.startTime;612return (INT64) ((delta + 500) / 1000);613}614615616/***************************************************************************/617/* Condition Variable Support for Mac OS X Port */618/* */619/* This works with the Native Locking Support defined below. We are using */620/* POSIX pthread_cond_t/pthread_mutex_t to do locking and synchronization. */621/* */622/* For MidiDeviceHandle* handle, the mutex reference is stored as handle-> */623/* queue->lock while the condition variabale reference is stored as handle */624/* ->platformData. */625/***************************************************************************/626627// Called from Midi_Utils_Opendevice(...) to create a condition variable628// used to synchronize between the receive thread created by the CoreMIDI629// and the Java-initiated MidiInDevice run loop.630void* MIDI_CreateConditionVariable() {631pthread_cond_t* cond = (pthread_cond_t*) malloc(sizeof(pthread_cond_t));632pthread_cond_init(cond, NULL);633return (void*) cond;634}635636void MIDI_DestroyConditionVariable(void* cond) {637while (pthread_cond_destroy((pthread_cond_t*) cond) == EBUSY) {638pthread_cond_broadcast((pthread_cond_t*) cond);639sched_yield();640}641return;642}643644// Called from MIDI_IN_GetMessage(...) to wait for MIDI messages to become645// available via delivery from the CoreMIDI receive thread646void MIDI_WaitOnConditionVariable(void* cond, void* lock) {647if (cond && lock) {648pthread_mutex_lock(lock);649pthread_cond_wait((pthread_cond_t*) cond, (pthread_mutex_t*) lock);650pthread_mutex_unlock(lock);651}652return;653}654655// Called from midiReadProc(...) to notify the waiting thread to unblock on656// the condition variable.657void MIDI_SignalConditionVariable(void* cond) {658if (cond) {659pthread_cond_signal((pthread_cond_t*) cond);660}661return;662}663664665/**************************************************************************/666/* Native Locking Support */667/* */668/* @see src/share/natve/com/sun/media/sound/PlatformMidi.c which contains */669/* utility functions for platform midi support where the section of code */670/* for MessageQueue implementation calls out to these functions. */671/**************************************************************************/672673void* MIDI_CreateLock() {674pthread_mutex_t* lock = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t));675pthread_mutex_init(lock, NULL);676TRACE0("MIDI_CreateLock\n");677return (void *)lock;678}679680void MIDI_DestroyLock(void* lock) {681if (lock) {682pthread_mutex_destroy((pthread_mutex_t*) lock);683free(lock);684TRACE0("MIDI_DestroyLock\n");685}686}687688void MIDI_Lock(void* lock) {689if (lock) {690pthread_mutex_lock((pthread_mutex_t*) lock);691}692}693694void MIDI_Unlock(void* lock) {695if (lock) {696pthread_mutex_unlock((pthread_mutex_t*) lock);697}698}699700701#endif // USE_PLATFORM_MIDI_IN || USE_PLATFORM_MIDI_OUT702703704