diff options
Diffstat (limited to 'portmidi/pm_mac')
| -rwxr-xr-x | portmidi/pm_mac/Makefile.osx | 125 | ||||
| -rw-r--r-- | portmidi/pm_mac/README_MAC.txt | 65 | ||||
| -rwxr-xr-x | portmidi/pm_mac/pmmac.c | 44 | ||||
| -rwxr-xr-x | portmidi/pm_mac/pmmacosxcm.c | 1179 | ||||
| -rwxr-xr-x | portmidi/pm_mac/pmmacosxcm.h | 4 |
5 files changed, 1417 insertions, 0 deletions
diff --git a/portmidi/pm_mac/Makefile.osx b/portmidi/pm_mac/Makefile.osx new file mode 100755 index 0000000..2044381 --- /dev/null +++ b/portmidi/pm_mac/Makefile.osx | |||
| @@ -0,0 +1,125 @@ | |||
| 1 | # MAKEFILE FOR PORTMIDI | ||
| 2 | |||
| 3 | # Roger B. Dannenberg | ||
| 4 | # Sep 2009 | ||
| 5 | |||
| 6 | # NOTE: PortMidi is currently built and tested with CMake. | ||
| 7 | # This makefile is probably broken, but if you want to | ||
| 8 | # directly use make, start here, and please contribute any | ||
| 9 | # fixes. | ||
| 10 | |||
| 11 | # NOTE: you can use | ||
| 12 | # make -f pm_mac/Makefile.osx configuration=Release | ||
| 13 | # to override the default Debug configuration | ||
| 14 | configuration=Release | ||
| 15 | |||
| 16 | PF=/usr/local | ||
| 17 | |||
| 18 | # For debugging, define PM_CHECK_ERRORS | ||
| 19 | ifeq ($(configuration),Release) | ||
| 20 | CONFIG = Release | ||
| 21 | else | ||
| 22 | CONFIG = Debug | ||
| 23 | endif | ||
| 24 | |||
| 25 | current: all | ||
| 26 | |||
| 27 | all: $(CONFIG)/CMakeCache.txt | ||
| 28 | cd $(CONFIG); make | ||
| 29 | |||
| 30 | $(CONFIG)/CMakeCache.txt: | ||
| 31 | rm -f $(CONFIG)/CMakeCache.txt | ||
| 32 | mkdir -p $(CONFIG) | ||
| 33 | cd $(CONFIG); cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=$(CONFIG) | ||
| 34 | |||
| 35 | |||
| 36 | **** For instructions: make -f pm_mac/Makefile.osx help ****\n' | ||
| 37 | |||
| 38 | help: | ||
| 39 | echo $$'\n\n\ | ||
| 40 | This is help for portmidi/pm_mac/Makefile.osx\n\n\ | ||
| 41 | Installation path for dylib is $(PF)\n\ | ||
| 42 | To build Release version libraries and test applications,\n \ | ||
| 43 | make -f pm_mac/Makefile.osx\n\ | ||
| 44 | To build Debug version libraries and test applications,\n \ | ||
| 45 | make -f pm_mac/Makefile.osx configuration=Debug\n\ | ||
| 46 | To install universal dynamic library,\n \ | ||
| 47 | sudo make -f pm_mac/Makefile.osx install\n\ | ||
| 48 | To install universal dynamic library with xcode,\n \ | ||
| 49 | make -f pm_mac/Makefile.osx install-with-xcode\n\ | ||
| 50 | To make PmDefaults Java application,\n \ | ||
| 51 | make -f pm_mac/Makefile.osx pmdefaults\n\n \ | ||
| 52 | configuration = $(configuration)\n' | ||
| 53 | |||
| 54 | |||
| 55 | clean: | ||
| 56 | rm -f *.o *~ core* */*.o */*/*.o */*~ */core* pm_test/*/pm_dll.dll | ||
| 57 | rm -f *.opt *.ncb *.plg pm_win/Debug/pm_dll.lib pm_win/Release/pm_dll.lib | ||
| 58 | rm -f pm_test/*.opt pm_test/*.ncb | ||
| 59 | rm -f pm_java/pmjni/*.o pm_java/pmjni/*~ pm_java/*.h | ||
| 60 | rm -rf Release/CMakeFiles Debug/CMakeFiles | ||
| 61 | rm -rf pm_mac/pmdefaults/lib pm_mac/pmdefaults/src | ||
| 62 | |||
| 63 | cleaner: clean | ||
| 64 | rm -rf pm_mac/build | ||
| 65 | rm -rf pm_mac/Debug pm_mac/Release pm_test/Debug pm_test/Release | ||
| 66 | rm -f Debug/*.dylib Release/*.dylib | ||
| 67 | rm -f pm_java/pmjni/Debug/*.jnilib | ||
| 68 | rm -f pm_java/pmjni/Release/*.jnilib | ||
| 69 | |||
| 70 | cleanest: cleaner | ||
| 71 | rm -f Debug/CMakeCache.txt Release/CMakeCache.txt | ||
| 72 | rm -f CMakeCache.txt | ||
| 73 | rm -f Debug/libportmidi_s.a Release/libportmidi_s.a | ||
| 74 | rm -f pm_test/Debug/test pm_test/Debug/sysex pm_test/Debug/midithread | ||
| 75 | rm -f pm_test/Debug/latency pm_test/Debug/midithru | ||
| 76 | rm -f pm_test/Debug/qtest pm_test/Debug/mm | ||
| 77 | rm -f pm_test/Release/test pm_test/Release/sysex pm_test/Release/midithread | ||
| 78 | rm -f pm_test/Release/latency pm_test/Release/midithru | ||
| 79 | rm -f pm_test/Release/qtest pm_test/Release/mm | ||
| 80 | rm -f pm_java/*/*.class | ||
| 81 | rm -f pm_java/pmjni/jportmidi_JPortMidiApi_PortMidiStream.h | ||
| 82 | |||
| 83 | backup: cleanest | ||
| 84 | cd ..; zip -r portmidi.zip portmidi | ||
| 85 | |||
| 86 | install: porttime/porttime.h pm_common/portmidi.h \ | ||
| 87 | $(CONFIG)/libportmidi.dylib | ||
| 88 | install porttime/porttime.h $(PF)/include/ | ||
| 89 | install pm_common/portmidi.h $(PF)/include | ||
| 90 | install $(CONFIG)/libportmidi.dylib $(PF)/lib/ | ||
| 91 | |||
| 92 | # note - this uses xcode to build and install portmidi universal binaries | ||
| 93 | install-with-xcode: | ||
| 94 | sudo xcodebuild -project pm_mac/pm_mac.xcodeproj \ | ||
| 95 | -configuration Release install DSTROOT=/ | ||
| 96 | |||
| 97 | ##### build pmdefault ###### | ||
| 98 | |||
| 99 | pm_java/pmjni/jportmidi_JPortMidiApi.h: pm_java/jportmidi/JPortMidiApi.class | ||
| 100 | cd pm_java; javah jportmidi.JPortMidiApi | ||
| 101 | mv pm_java/jportmidi_JportMidiApi.h pm_java/pmjni | ||
| 102 | |||
| 103 | JAVASRC = pmdefaults/PmDefaultsFrame.java \ | ||
| 104 | pmdefaults/PmDefaults.java \ | ||
| 105 | jportmidi/JPortMidiApi.java jportmidi/JPortMidi.java \ | ||
| 106 | jportmidi/JPortMidiException.java | ||
| 107 | |||
| 108 | # this compiles ALL of the java code | ||
| 109 | pm_java/jportmidi/JPortMidiApi.class: $(JAVASRC:%=pm_java/%) | ||
| 110 | cd pm_java; javac $(JAVASRC) | ||
| 111 | |||
| 112 | $(CONFIG)/libpmjni.dylib: | ||
| 113 | mkdir -p $(CONFIG) | ||
| 114 | cd $(CONFIG); make -f ../pm_mac/$(MAKEFILE) | ||
| 115 | |||
| 116 | pmdefaults: $(CONFIG)/libpmjni.dylib pm_java/jportmidi/JPortMidiApi.class | ||
| 117 | ifeq ($(CONFIG),Debug) | ||
| 118 | echo "Error: you cannot build pmdefaults in a Debug configuration \n\ | ||
| 119 | You should use configuration=Release in the Makefile command line. " | ||
| 120 | @exit 2 | ||
| 121 | endif | ||
| 122 | xcodebuild -project pm_mac/pm_mac.xcodeproj \ | ||
| 123 | -configuration Release -target PmDefaults | ||
| 124 | echo "pmdefaults java application is made" | ||
| 125 | |||
diff --git a/portmidi/pm_mac/README_MAC.txt b/portmidi/pm_mac/README_MAC.txt new file mode 100644 index 0000000..41e8341 --- /dev/null +++ b/portmidi/pm_mac/README_MAC.txt | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | README_MAC.txt for PortMidi | ||
| 2 | Roger Dannenberg | ||
| 3 | 20 nov 2009 | ||
| 4 | |||
| 5 | revised Mar 2024 to remove pmdefaults references | ||
| 6 | revised Jan 2022 for the PortMidi/portmidi repo on github.com | ||
| 7 | revised 20 Sep 2010 for Xcode 4.3.2 and CMake 2.8.8 | ||
| 8 | |||
| 9 | This documents how I build PortMidi for macOS. It's not the only way, | ||
| 10 | and command-line/scripting enthusiasts will say it's not even a good | ||
| 11 | way. Feel free to contribute your approach if you are willing to | ||
| 12 | describe it carefully and test it. | ||
| 13 | |||
| 14 | Install Xcode and the CMake application, CMake.app. I use the GUI | ||
| 15 | version of CMake which makes it easy to see/edit variables and | ||
| 16 | options. | ||
| 17 | |||
| 18 | ==== USING CMAKE ==== | ||
| 19 | |||
| 20 | Run CMake.app and select your portmidi repo working directory as the | ||
| 21 | location for source and build. (Yes, I use so called "in-tree" | ||
| 22 | builds -- it doesn't hurt, but I don't think it is necessary.) | ||
| 23 | |||
| 24 | Default settings should all be fine, but select options under BUILD if | ||
| 25 | you wish: | ||
| 26 | |||
| 27 | BUILD_NATIVE_JAVA_INTERFACE to build a Java interface (JNI) library. | ||
| 28 | |||
| 29 | BUILD_PORTMIDI_TESTS to create some test programs. Of particular | ||
| 30 | interest are test/mm, a handy command-line MIDI Input Monitor, and | ||
| 31 | test/testio, a simple command-line program to send or receive some | ||
| 32 | MIDI notes in case you need a quick test: What devices do I have? Does | ||
| 33 | this input work? Does this output work? | ||
| 34 | |||
| 35 | I disable BUILD_SHARED_LIBS and always link statically: Static linking only | ||
| 36 | adds about 40KB to any application and then you don't have to worry | ||
| 37 | about versions, instally, copying or finding the dynamic link library, | ||
| 38 | etc. | ||
| 39 | |||
| 40 | To make sure you link statically, I rename the library to | ||
| 41 | libportmidi_static.a. To do this, set PM_STATIC_LIB_NAME (in CMake, | ||
| 42 | under the "PM" group) to "portmidi_static", and of course your | ||
| 43 | application will have to specify portmidi_static as the library to | ||
| 44 | link to. | ||
| 45 | |||
| 46 | If you are building simple command-line applications, you might want | ||
| 47 | to enable PM_CHECK_ERRORS. If you do, then calls into the PortMidi | ||
| 48 | library will print error messages and exit in the event of an error | ||
| 49 | (such as trying to open a device that does not exist). This saves you | ||
| 50 | from having to check for errors everytime you call a library function | ||
| 51 | or getting confused when errors are detected but not reported. For | ||
| 52 | high-quality applications, do NOT enable PM_CHECK_ERRORS -- any | ||
| 53 | failure could immediately abort your whole application, which is not | ||
| 54 | very friendly to users. | ||
| 55 | |||
| 56 | Click on Configure (maybe a couple of times). | ||
| 57 | |||
| 58 | Click on Generate and make an Xcode project. | ||
| 59 | |||
| 60 | Open portmidi/portmidi.xcodeproj with Xcode and build what you | ||
| 61 | need. The simplest thing is to build the ALL_BUILD target. Be careful | ||
| 62 | to specify a Debug or Release depending on what you want. "ALL_BUILD" | ||
| 63 | is a misnomer -- it only builds the version you select. | ||
| 64 | |||
| 65 | |||
diff --git a/portmidi/pm_mac/pmmac.c b/portmidi/pm_mac/pmmac.c new file mode 100755 index 0000000..48ac17a --- /dev/null +++ b/portmidi/pm_mac/pmmac.c | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | /* pmmac.c -- PortMidi os-dependent code */ | ||
| 2 | |||
| 3 | /* This file only needs to implement: | ||
| 4 | pm_init(), which calls various routines to register the | ||
| 5 | available midi devices, | ||
| 6 | Pm_GetDefaultInputDeviceID(), and | ||
| 7 | Pm_GetDefaultOutputDeviceID(). | ||
| 8 | It is seperate from pmmacosxcm because we might want to register | ||
| 9 | non-CoreMIDI devices. | ||
| 10 | */ | ||
| 11 | |||
| 12 | #include "stdlib.h" | ||
| 13 | #include "portmidi.h" | ||
| 14 | #include "pmutil.h" | ||
| 15 | #include "pminternal.h" | ||
| 16 | #include "pmmacosxcm.h" | ||
| 17 | |||
| 18 | void pm_init(void) | ||
| 19 | { | ||
| 20 | pm_macosxcm_init(); | ||
| 21 | } | ||
| 22 | |||
| 23 | |||
| 24 | void pm_term(void) | ||
| 25 | { | ||
| 26 | pm_macosxcm_term(); | ||
| 27 | } | ||
| 28 | |||
| 29 | PmDeviceID Pm_GetDefaultInputDeviceID(void) | ||
| 30 | { | ||
| 31 | Pm_Initialize(); | ||
| 32 | return pm_default_input_device_id; | ||
| 33 | } | ||
| 34 | |||
| 35 | PmDeviceID Pm_GetDefaultOutputDeviceID(void) { | ||
| 36 | Pm_Initialize(); | ||
| 37 | return pm_default_output_device_id; | ||
| 38 | } | ||
| 39 | |||
| 40 | void *pm_alloc(size_t s) { return malloc(s); } | ||
| 41 | |||
| 42 | void pm_free(void *ptr) { free(ptr); } | ||
| 43 | |||
| 44 | |||
diff --git a/portmidi/pm_mac/pmmacosxcm.c b/portmidi/pm_mac/pmmacosxcm.c new file mode 100755 index 0000000..e8b196c --- /dev/null +++ b/portmidi/pm_mac/pmmacosxcm.c | |||
| @@ -0,0 +1,1179 @@ | |||
| 1 | /* | ||
| 2 | * Platform interface to the MacOS X CoreMIDI framework | ||
| 3 | * | ||
| 4 | * Jon Parise <jparise at cmu.edu> | ||
| 5 | * and subsequent work by Andrew Zeldis and Zico Kolter | ||
| 6 | * and Roger B. Dannenberg | ||
| 7 | * | ||
| 8 | * $Id: pmmacosx.c,v 1.17 2002/01/27 02:40:40 jon Exp $ | ||
| 9 | */ | ||
| 10 | |||
| 11 | /* Notes: | ||
| 12 | |||
| 13 | Since the input and output streams are represented by | ||
| 14 | MIDIEndpointRef values and almost no other state, we store the | ||
| 15 | MIDIEndpointRef on pm_descriptors[midi->device_id].descriptor. | ||
| 16 | |||
| 17 | OS X does not seem to have an error-code-to-text function, so we | ||
| 18 | will just use text messages instead of error codes. | ||
| 19 | |||
| 20 | Virtual device input synchronization: Once we create a virtual | ||
| 21 | device, it is always "on" and receiving messages, but it must drop | ||
| 22 | messages unless the device has been opened with Pm_OpenInput. To | ||
| 23 | open, the main thread should create all the data structures, then | ||
| 24 | call OSMemoryBarrier so that writes are observed, then set | ||
| 25 | is_opened = TRUE. To close without locks, we need to get the | ||
| 26 | callback to set is_opened to FALSE before we free data structures; | ||
| 27 | otherwise, there's a race condition where closing could delete | ||
| 28 | structures in use by the virtual_read_callback function. We send | ||
| 29 | 8 MIDI resets (FF) in a single packet to our own port to signal | ||
| 30 | the virtual_read_callback to close it. Then, we wait for the | ||
| 31 | callback to recognize the "close" packet and reset is_opened. | ||
| 32 | |||
| 33 | Device scanning is done when you first open an application. | ||
| 34 | PortMIDI does not actively update the devices. Instead, you must | ||
| 35 | Pm_Terminate() and Pm_Initialize(), basically starting over. But | ||
| 36 | CoreMIDI does not have a way to shut down(!), and even | ||
| 37 | MIDIClientDispose() somehow retains state (and docs say do not | ||
| 38 | call it even if it worked). The solution, apparently, is to | ||
| 39 | call CFRunLoopRunInMode(), which somehow updates CoreMIDI | ||
| 40 | state. | ||
| 41 | |||
| 42 | But when do we call CFRunLoopRunInMode()? I tried calling it | ||
| 43 | in midi_in_poll() which is called when you call Pm_Read() since | ||
| 44 | that is called often. I observed that this caused the program | ||
| 45 | to block for as long as 50ms and fairly often for 2 or 3ms. | ||
| 46 | What was Apple thinking? Is it really OK to design systems that | ||
| 47 | can only function with a tricky multi-threaded, non-blocking | ||
| 48 | priority-based solution, and then not provide a proof of concept | ||
| 49 | or documentation? Or is Apple's design really flawed? If anyone | ||
| 50 | at Apple reads this, please let me know -- I'm curious. | ||
| 51 | |||
| 52 | But I digress... Here's the PortMidi approach: Since | ||
| 53 | CFRunLoopRunInMode() is potentially a non-realtime operation, | ||
| 54 | we only call it in Pm_Initialize(), where other calls to look | ||
| 55 | up devices and device names are quite slow to begin with. Again, | ||
| 56 | PortMidi does not actively scan for new or deleted devices, so | ||
| 57 | if devices change, you won't see it until the next Pm_Terminate | ||
| 58 | and Pm_Initialize. | ||
| 59 | |||
| 60 | Calling CFRunLoopRunInMode() once is probably not enough. There | ||
| 61 | might be better way, but it seems to work to just call it 100 | ||
| 62 | times and insert 20 1ms delays (in case some inter-process | ||
| 63 | communication or synchronization is going on). | ||
| 64 | This adds 20ms to the wall time of Pm_Initialize(), but it | ||
| 65 | typically runs 30ms to much more (~4s), so this has little impact. | ||
| 66 | */ | ||
| 67 | |||
| 68 | #include <stdlib.h> | ||
| 69 | |||
| 70 | /* turn on lots of debugging print statements */ | ||
| 71 | #define CM_DEBUG if (0) | ||
| 72 | /* #define CM_DEBUG if (1) */ | ||
| 73 | |||
| 74 | #include "portmidi.h" | ||
| 75 | #include "pmutil.h" | ||
| 76 | #include "pminternal.h" | ||
| 77 | #include "porttime.h" | ||
| 78 | #include "pmmacosxcm.h" | ||
| 79 | |||
| 80 | #include <stdio.h> | ||
| 81 | #include <string.h> | ||
| 82 | |||
| 83 | #include <CoreServices/CoreServices.h> | ||
| 84 | #include <CoreMIDI/MIDIServices.h> | ||
| 85 | #include <CoreAudio/HostTime.h> | ||
| 86 | #include <unistd.h> | ||
| 87 | #include <libkern/OSAtomic.h> | ||
| 88 | |||
| 89 | #define PACKET_BUFFER_SIZE 1024 | ||
| 90 | /* maximum overall data rate (OS X limits MIDI rate in case there | ||
| 91 | * is a cycle among IAC ports. | ||
| 92 | */ | ||
| 93 | |||
| 94 | #define MAX_BYTES_PER_S 5400 | ||
| 95 | |||
| 96 | /* Apple reports that packets are dropped when the MIDI bytes/sec | ||
| 97 | exceeds 15000. This is computed by "tracking the number of MIDI | ||
| 98 | bytes scheduled into 1-second buckets over the last six seconds and | ||
| 99 | averaging these counts." This was confirmed in measurements | ||
| 100 | (2021) with pm_test/fast.c and pm_test/fastrcv.c Now, in 2022, with | ||
| 101 | macOS 12, pm_test/fast{rcv}.c show problems begin at 6000 bytes/sec. | ||
| 102 | Previously, we set MAX_BYTES_PER_S to 14000. This is reduced to | ||
| 103 | 5400 based on testing (which shows 5700 is too high) to fix the | ||
| 104 | packet loss problem that showed up with macOS 12. | ||
| 105 | |||
| 106 | Experiments show this restriction applies to IAC bus MIDI, but not | ||
| 107 | to hardware interfaces. (I measured 0.5 Mbps each way over USB to a | ||
| 108 | Teensy 3.2 microcontroller implementing a USB MIDI loopback. Maybe | ||
| 109 | it would get 1 Mbps one-way, which would make the CoreMIDI | ||
| 110 | restriction 18x slower than USB. Maybe other USB MIDI | ||
| 111 | implementations are faster -- USB top speed for other protocols is | ||
| 112 | certainly higher than 1 Mbps!) | ||
| 113 | |||
| 114 | This is apparently based on timestamps, not on real time, so we | ||
| 115 | have to avoid constructing packets that schedule high speed output | ||
| 116 | regardless of when writes occur. The solution is to alter | ||
| 117 | timestamps to limit data rates. This adds a slight time | ||
| 118 | distortion, e.g. an 11 note chord with all notes on the same | ||
| 119 | timestamp will be altered so that the last message is delayed by | ||
| 120 | 11 messages x 3 bytes/message / 5400 bytes/second = 6.1 ms. | ||
| 121 | Note that this is about 2x MIDI speed, but at least 18x slower | ||
| 122 | than USB MIDI. | ||
| 123 | |||
| 124 | Altering timestamps creates another problem, which is that a sender | ||
| 125 | that exceeds the maximum rate can queue up an unbounded number of | ||
| 126 | messages. With non-USB MIDI devices, you could be writing 5x faster | ||
| 127 | to CoreMIDI than the hardware interface can send, causing an | ||
| 128 | unbounded backlog, not to mention that the output stream will be a | ||
| 129 | steady byte stream (e.g., one 3-byte MIDI message every 0.55 ms), | ||
| 130 | losing any original timing or rhythm. PortMidi does not guarantee | ||
| 131 | delivery if, over the long run, you write faster than the hardware | ||
| 132 | can send. | ||
| 133 | |||
| 134 | The LIMIT_RATE symbol, if defined (which is the default), enables | ||
| 135 | code to modify timestamps for output to an IAC device as follows: | ||
| 136 | |||
| 137 | Before a packet is formed, the message timestamp is set to the | ||
| 138 | maximum of the PortMidi timestamp (converted to CoreMIDI time) | ||
| 139 | and min_next_time. After each send, min_next_time is updated to | ||
| 140 | the packet time + packet length * delay_per_byte, which limits | ||
| 141 | the scheduled bytes-per-second. Also, after each packet list | ||
| 142 | flush, min_next_time is updated to the maximum of min_next_time | ||
| 143 | and the real time, which prevents many bytes to be scheduled in | ||
| 144 | the past. (We could more directly just say packets are never | ||
| 145 | scheduled in the past, but we prefer to get the current time -- a | ||
| 146 | system call -- only when we perform the more expensive operation | ||
| 147 | of flushing packets, so that's when we update min_next_time to | ||
| 148 | the current real time. If we are sending a lot, we have to flush | ||
| 149 | a lot, so the time will be updated frequently when it matters.) | ||
| 150 | |||
| 151 | This possible adjustment to timestamps can distort accurate | ||
| 152 | timestamps by up to 0.556 us per 3-byte MIDI message. | ||
| 153 | |||
| 154 | Nothing blocks the sender from queueing up an arbitrary number of | ||
| 155 | messages. Timestamps should be used for accurate timing by sending | ||
| 156 | timestamped messages a little ahead of real time, not for | ||
| 157 | scheduling an entire MIDI sequence at once! | ||
| 158 | */ | ||
| 159 | #define LIMIT_RATE 1 | ||
| 160 | |||
| 161 | #define SYSEX_BUFFER_SIZE 128 | ||
| 162 | /* What is the maximum PortMidi device number for an IAC device? A | ||
| 163 | * cleaner design would be to not use the endpoint as our device | ||
| 164 | * representation. Instead, we could have a private extensible struct | ||
| 165 | * to keep all device information, including whether the device is | ||
| 166 | * implemented with the AppleMIDIIACDriver, which we need because we | ||
| 167 | * have to limit the data rate to this particular driver to avoid | ||
| 168 | * dropping messages. Rather than rewrite a lot of code, I am just | ||
| 169 | * allocating 64 bytes to flag which devices are IAC ones. If an IAC | ||
| 170 | * device number is greater than 63, PortMidi will fail to limit | ||
| 171 | * writes to it, but will not complain and will not access memory | ||
| 172 | * outside the 64-element array of char. | ||
| 173 | */ | ||
| 174 | #define MAX_IAC_NUM 63 | ||
| 175 | |||
| 176 | #define VERBOSE_ON 1 | ||
| 177 | #define VERBOSE if (VERBOSE_ON) | ||
| 178 | |||
| 179 | #define MIDI_SYSEX 0xf0 | ||
| 180 | #define MIDI_EOX 0xf7 | ||
| 181 | #define MIDI_CLOCK 0xf8 | ||
| 182 | #define MIDI_STATUS_MASK 0x80 | ||
| 183 | |||
| 184 | // "Ref"s are pointers on 32-bit machines and ints on 64 bit machines | ||
| 185 | // NULL_REF is our representation of either 0 or NULL | ||
| 186 | #ifdef __LP64__ | ||
| 187 | #define NULL_REF 0 | ||
| 188 | #else | ||
| 189 | #define NULL_REF NULL | ||
| 190 | #endif | ||
| 191 | |||
| 192 | static MIDIClientRef client = NULL_REF; /* Client handle to the MIDI server */ | ||
| 193 | static MIDIPortRef portIn = NULL_REF; /* Input port handle */ | ||
| 194 | static MIDIPortRef portOut = NULL_REF; /* Output port handle */ | ||
| 195 | static char isIAC[MAX_IAC_NUM + 1]; /* is device an IAC device */ | ||
| 196 | |||
| 197 | extern pm_fns_node pm_macosx_in_dictionary; | ||
| 198 | extern pm_fns_node pm_macosx_out_dictionary; | ||
| 199 | |||
| 200 | typedef struct coremidi_info_struct { | ||
| 201 | int is_virtual; /* virtual device (TRUE) or actual device (FALSE)? */ | ||
| 202 | UInt64 delta; /* difference between stream time and real time in ns */ | ||
| 203 | int sysex_mode; /* middle of sending sysex */ | ||
| 204 | uint32_t sysex_word; /* accumulate data when receiving sysex */ | ||
| 205 | uint32_t sysex_byte_count; /* count how many received */ | ||
| 206 | char error[PM_HOST_ERROR_MSG_LEN]; | ||
| 207 | char callback_error[PM_HOST_ERROR_MSG_LEN]; | ||
| 208 | Byte packetBuffer[PACKET_BUFFER_SIZE]; | ||
| 209 | MIDIPacketList *packetList; /* a pointer to packetBuffer */ | ||
| 210 | MIDIPacket *packet; | ||
| 211 | Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */ | ||
| 212 | MIDITimeStamp sysex_timestamp; /* host timestamp to use with sysex data */ | ||
| 213 | /* allow for running status (is running status possible here? -rbd): -cpr */ | ||
| 214 | UInt64 min_next_time; /* when can the next send take place? (host time) */ | ||
| 215 | int isIACdevice; | ||
| 216 | Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */ | ||
| 217 | UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */ | ||
| 218 | } coremidi_info_node, *coremidi_info_type; | ||
| 219 | |||
| 220 | /* private function declarations */ | ||
| 221 | MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); // returns host time | ||
| 222 | PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); // returns ms | ||
| 223 | |||
| 224 | char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag); | ||
| 225 | |||
| 226 | static PmError check_hosterror(OSStatus err, const char *msg) | ||
| 227 | { | ||
| 228 | if (err != noErr) { | ||
| 229 | snprintf(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, "Host error %ld: %s", (long) err, msg); | ||
| 230 | pm_hosterror = TRUE; | ||
| 231 | return pmHostError; | ||
| 232 | } | ||
| 233 | return pmNoError; | ||
| 234 | } | ||
| 235 | |||
| 236 | |||
| 237 | static PmTimestamp midi_synchronize(PmInternal *midi) | ||
| 238 | { | ||
| 239 | coremidi_info_type info = (coremidi_info_type) midi->api_info; | ||
| 240 | UInt64 pm_stream_time_2 = // current time in ns | ||
| 241 | AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); | ||
| 242 | PmTimestamp real_time; // in ms | ||
| 243 | UInt64 pm_stream_time; // in ns | ||
| 244 | /* if latency is zero and this is an output, there is no | ||
| 245 | time reference and midi_synchronize should never be called */ | ||
| 246 | assert(midi->time_proc); | ||
| 247 | assert(midi->is_input || midi->latency != 0); | ||
| 248 | do { | ||
| 249 | /* read real_time between two reads of stream time */ | ||
| 250 | pm_stream_time = pm_stream_time_2; | ||
| 251 | real_time = (*midi->time_proc)(midi->time_info); | ||
| 252 | pm_stream_time_2 = AudioConvertHostTimeToNanos( | ||
| 253 | AudioGetCurrentHostTime()); | ||
| 254 | /* repeat if more than 0.5 ms has elapsed */ | ||
| 255 | } while (pm_stream_time_2 > pm_stream_time + 500000); | ||
| 256 | info->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000); | ||
| 257 | midi->sync_time = real_time; | ||
| 258 | return real_time; | ||
| 259 | } | ||
| 260 | |||
| 261 | |||
| 262 | /* called when MIDI packets are received */ | ||
| 263 | static void read_callback(const MIDIPacketList *newPackets, PmInternal *midi) | ||
| 264 | { | ||
| 265 | PmTimestamp timestamp; | ||
| 266 | MIDIPacket *packet; | ||
| 267 | unsigned int packetIndex; | ||
| 268 | uint32_t now; | ||
| 269 | /* Retrieve the context for this connection */ | ||
| 270 | coremidi_info_type info = (coremidi_info_type) midi->api_info; | ||
| 271 | assert(info); | ||
| 272 | |||
| 273 | CM_DEBUG printf("read_callback: numPackets %d: ", newPackets->numPackets); | ||
| 274 | |||
| 275 | /* synchronize time references every 100ms */ | ||
| 276 | now = (*midi->time_proc)(midi->time_info); | ||
| 277 | if (midi->first_message || midi->sync_time + 100 /*ms*/ < now) { | ||
| 278 | /* time to resync */ | ||
| 279 | now = midi_synchronize(midi); | ||
| 280 | midi->first_message = FALSE; | ||
| 281 | } | ||
| 282 | |||
| 283 | packet = (MIDIPacket *) &newPackets->packet[0]; | ||
| 284 | /* hardware devices get untimed messages and apply timestamps. We | ||
| 285 | * want to preserve them because they should be more accurate than | ||
| 286 | * applying the current time here. virtual devices just pass on the | ||
| 287 | * packet->timeStamp, which could be anything. PortMidi says the | ||
| 288 | * PortMidi timestamp is the time the message is received. We do not | ||
| 289 | * know if we are receiving from a device driver or a virtual device. | ||
| 290 | * PortMidi sends to virtual devices get a current timestamp, so we | ||
| 291 | * can treat them as the receive time. If the timestamp is zero, | ||
| 292 | * suggested by CoreMIDI as the value to use for immediate delivery, | ||
| 293 | * then we plug in `now` which is obtained above. If another | ||
| 294 | * application sends bogus non-zero timestamps, we will convert them | ||
| 295 | * to this port's reference time and pass them as event.timestamp. | ||
| 296 | * Receiver beware. | ||
| 297 | */ | ||
| 298 | CM_DEBUG printf("read_callback packet @ %lld ns (host %lld) " | ||
| 299 | "status %x length %d\n", | ||
| 300 | AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()), | ||
| 301 | AudioGetCurrentHostTime(), | ||
| 302 | packet->data[0], packet->length); | ||
| 303 | for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) { | ||
| 304 | /* Set the timestamp and dispatch this message */ | ||
| 305 | CM_DEBUG printf(" packet->timeStamp %lld ns %lld host\n", | ||
| 306 | packet->timeStamp, | ||
| 307 | AudioConvertHostTimeToNanos(packet->timeStamp)); | ||
| 308 | if (packet->timeStamp == 0) { | ||
| 309 | timestamp = now; | ||
| 310 | } else { | ||
| 311 | timestamp = (PmTimestamp) /* explicit conversion */ ( | ||
| 312 | (AudioConvertHostTimeToNanos(packet->timeStamp) - info->delta) / | ||
| 313 | (UInt64) 1000000); | ||
| 314 | } | ||
| 315 | pm_read_bytes(midi, packet->data, packet->length, timestamp); | ||
| 316 | packet = MIDIPacketNext(packet); | ||
| 317 | } | ||
| 318 | } | ||
| 319 | |||
| 320 | /* callback for real devices - redirects to read_callback */ | ||
| 321 | static void device_read_callback(const MIDIPacketList *newPackets, | ||
| 322 | void *refCon, void *connRefCon) | ||
| 323 | { | ||
| 324 | read_callback(newPackets, (PmInternal *) connRefCon); | ||
| 325 | } | ||
| 326 | |||
| 327 | |||
| 328 | /* callback for virtual devices - redirects to read_callback */ | ||
| 329 | static void virtual_read_callback(const MIDIPacketList *newPackets, | ||
| 330 | void *refCon, void *connRefCon) | ||
| 331 | { | ||
| 332 | /* this refCon is the device ID -- if there is a valid ID and | ||
| 333 | the pm_descriptors table has a non-null pointer to a PmInternal, | ||
| 334 | then then device is open and should receive this data */ | ||
| 335 | PmDeviceID id = (PmDeviceID) (size_t) refCon; | ||
| 336 | if (id >= 0 && id < pm_descriptor_len) { | ||
| 337 | if (pm_descriptors[id].pub.opened) { | ||
| 338 | /* check for close request (7 reset status bytes): */ | ||
| 339 | if (newPackets->numPackets == 1 && | ||
| 340 | newPackets->packet[0].length == 8 && | ||
| 341 | /* CoreMIDI declares packets with 4-byte alignment, so we | ||
| 342 | * should be safe to test for 8 0xFF's as 2 32-bit values: */ | ||
| 343 | *(SInt32 *) &newPackets->packet[0].data[0] == -1 && | ||
| 344 | *(SInt32 *) &newPackets->packet[0].data[4] == -1) { | ||
| 345 | CM_DEBUG printf("got close request packet\n"); | ||
| 346 | pm_descriptors[id].pub.opened = FALSE; | ||
| 347 | return; | ||
| 348 | } else { | ||
| 349 | read_callback(newPackets, pm_descriptors[id].pm_internal); | ||
| 350 | } | ||
| 351 | } | ||
| 352 | } | ||
| 353 | } | ||
| 354 | |||
| 355 | |||
| 356 | /* allocate and initialize our internal coremidi connection info */ | ||
| 357 | static coremidi_info_type create_macosxcm_info(int is_virtual, int is_input) | ||
| 358 | { | ||
| 359 | coremidi_info_type info = (coremidi_info_type) | ||
| 360 | pm_alloc(sizeof(coremidi_info_node)); | ||
| 361 | if (!info) { | ||
| 362 | return NULL; | ||
| 363 | } | ||
| 364 | info->is_virtual = is_virtual; | ||
| 365 | info->delta = 0; | ||
| 366 | info->sysex_mode = FALSE; | ||
| 367 | info->sysex_word = 0; | ||
| 368 | info->sysex_byte_count = 0; | ||
| 369 | info->packet = NULL; | ||
| 370 | info->min_next_time = 0; | ||
| 371 | info->isIACdevice = FALSE; | ||
| 372 | info->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency(); | ||
| 373 | info->host_ticks_per_byte = | ||
| 374 | (UInt64) (1000000.0 / (info->us_per_host_tick * MAX_BYTES_PER_S)); | ||
| 375 | info->packetList = (is_input ? NULL : | ||
| 376 | (MIDIPacketList *) info->packetBuffer); | ||
| 377 | return info; | ||
| 378 | } | ||
| 379 | |||
| 380 | |||
| 381 | static PmError midi_in_open(PmInternal *midi, void *driverInfo) | ||
| 382 | { | ||
| 383 | MIDIEndpointRef endpoint; | ||
| 384 | coremidi_info_type info; | ||
| 385 | OSStatus macHostError; | ||
| 386 | int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual; | ||
| 387 | |||
| 388 | /* if this is an external device, descriptor is a MIDIEndpointRef. | ||
| 389 | * if this is a virtual device for this application, descriptor is NULL. | ||
| 390 | */ | ||
| 391 | if (!is_virtual) { | ||
| 392 | endpoint = (MIDIEndpointRef) (intptr_t) | ||
| 393 | pm_descriptors[midi->device_id].descriptor; | ||
| 394 | if (endpoint == NULL_REF) { | ||
| 395 | return pmInvalidDeviceId; | ||
| 396 | } | ||
| 397 | } | ||
| 398 | |||
| 399 | info = create_macosxcm_info(is_virtual, TRUE); | ||
| 400 | midi->api_info = info; | ||
| 401 | if (!info) { | ||
| 402 | return pmInsufficientMemory; | ||
| 403 | } | ||
| 404 | if (!is_virtual) { | ||
| 405 | macHostError = MIDIPortConnectSource(portIn, endpoint, midi); | ||
| 406 | if (macHostError != noErr) { | ||
| 407 | midi->api_info = NULL; | ||
| 408 | pm_free(info); | ||
| 409 | return check_hosterror(macHostError, | ||
| 410 | "MIDIPortConnectSource() in midi_in_open()"); | ||
| 411 | } | ||
| 412 | } | ||
| 413 | return pmNoError; | ||
| 414 | } | ||
| 415 | |||
| 416 | static PmError midi_in_close(PmInternal *midi) | ||
| 417 | { | ||
| 418 | MIDIEndpointRef endpoint; | ||
| 419 | OSStatus macHostError; | ||
| 420 | PmError err = pmNoError; | ||
| 421 | |||
| 422 | coremidi_info_type info = (coremidi_info_type) midi->api_info; | ||
| 423 | |||
| 424 | if (!info) return pmBadPtr; | ||
| 425 | |||
| 426 | endpoint = (MIDIEndpointRef) (intptr_t) | ||
| 427 | pm_descriptors[midi->device_id].descriptor; | ||
| 428 | if (endpoint == NULL_REF) { | ||
| 429 | return pmBadPtr; | ||
| 430 | } | ||
| 431 | |||
| 432 | if (!info->is_virtual) { | ||
| 433 | /* shut off the incoming messages before freeing data structures */ | ||
| 434 | macHostError = MIDIPortDisconnectSource(portIn, endpoint); | ||
| 435 | /* If the source closes, you get paramErr == -50 here. It seems | ||
| 436 | * possible to monitor changes like sources closing by getting | ||
| 437 | * notifications ALL changes, but the CoreMIDI documentation is | ||
| 438 | * really terrible overall, and it seems easier to just ignore | ||
| 439 | * this host error. | ||
| 440 | */ | ||
| 441 | if (macHostError != noErr && macHostError != -50) { | ||
| 442 | pm_hosterror = TRUE; | ||
| 443 | err = check_hosterror(macHostError, | ||
| 444 | "MIDIPortDisconnectSource() in midi_in_close()"); | ||
| 445 | } | ||
| 446 | } else { | ||
| 447 | /* make "close virtual port" message */ | ||
| 448 | SInt64 close_port_bytes = 0xFFFFFFFFFFFFFFFF; | ||
| 449 | /* memory requirements: packet count (4), timestamp (8), length (2), | ||
| 450 | * data (8). Total: 22, but we allocate plenty more: | ||
| 451 | */ | ||
| 452 | Byte packetBuffer[64]; | ||
| 453 | MIDIPacketList *plist = (MIDIPacketList *) packetBuffer; | ||
| 454 | MIDIPacket *packet = MIDIPacketListInit(plist); | ||
| 455 | MIDIPacketListAdd(plist, 64, packet, 0, 8, | ||
| 456 | (const Byte *) &close_port_bytes); | ||
| 457 | macHostError = MIDISend(portOut, endpoint, plist); | ||
| 458 | if (macHostError != noErr) { | ||
| 459 | err = check_hosterror(macHostError, "MIDISend() (PortMidi close " | ||
| 460 | "port packet) in midi_in_close()"); | ||
| 461 | } | ||
| 462 | /* when packet is delivered, callback thread will clear opened; | ||
| 463 | * we must wait for that before removing the input queues etc. | ||
| 464 | * Maybe this could use signals of some kind, but if signals use | ||
| 465 | * locks, locks can cause priority inversion problems, so we will | ||
| 466 | * just sleep as needed. On the MIDI timescale, inserting a 0.5ms | ||
| 467 | * latency should be OK, as the application has no business | ||
| 468 | * opening/closing devices during time-critical moments. | ||
| 469 | * | ||
| 470 | * We expect the MIDI thread to close the device quickly (<0.5ms), | ||
| 471 | * but we wait up to 50ms in case something terrible happens like | ||
| 472 | * getting paged out in the middle of deliving packets to this | ||
| 473 | * virtual device. If there is still no response, we time out and | ||
| 474 | * force the close without the MIDI thread (even this will probably | ||
| 475 | * succeed - the problem would be: this thread proceeds to delete | ||
| 476 | * the input queues, and the freed memory is reallocated and | ||
| 477 | * overwritten so that queues are no longer usable. Meanwhile, | ||
| 478 | * the MIDI thread has already begun to deliver packets, so the | ||
| 479 | * check for opened == TRUE passed, but MIDI thread does not insert | ||
| 480 | * into queue until queue is freed, reallocated and overwritten. | ||
| 481 | */ | ||
| 482 | for (int i = 0; i < 100; i++) { /* up to 50ms delay */ | ||
| 483 | if (!pm_descriptors[midi->device_id].pub.opened) { | ||
| 484 | break; | ||
| 485 | } | ||
| 486 | usleep(500); /* 0.5ms */ | ||
| 487 | } | ||
| 488 | pm_descriptors[midi->device_id].pub.opened = FALSE; /* force it */ | ||
| 489 | } | ||
| 490 | midi->api_info = NULL; | ||
| 491 | pm_free(info); | ||
| 492 | return err; | ||
| 493 | } | ||
| 494 | |||
| 495 | |||
| 496 | static PmError midi_out_open(PmInternal *midi, void *driverInfo) | ||
| 497 | { | ||
| 498 | coremidi_info_type info; | ||
| 499 | int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual; | ||
| 500 | |||
| 501 | info = create_macosxcm_info(is_virtual, FALSE); | ||
| 502 | if (midi->device_id <= MAX_IAC_NUM) { | ||
| 503 | info->isIACdevice = isIAC[midi->device_id]; | ||
| 504 | CM_DEBUG printf("midi_out_open isIACdevice %d\n", info->isIACdevice); | ||
| 505 | } | ||
| 506 | midi->api_info = info; | ||
| 507 | if (!info) { | ||
| 508 | return pmInsufficientMemory; | ||
| 509 | } | ||
| 510 | return pmNoError; | ||
| 511 | } | ||
| 512 | |||
| 513 | |||
| 514 | static PmError midi_out_close(PmInternal *midi) | ||
| 515 | { | ||
| 516 | coremidi_info_type info = (coremidi_info_type) midi->api_info; | ||
| 517 | if (!info) return pmBadPtr; | ||
| 518 | midi->api_info = NULL; | ||
| 519 | pm_free(info); | ||
| 520 | return pmNoError; | ||
| 521 | } | ||
| 522 | |||
| 523 | |||
| 524 | /* MIDIDestinationCreate apparently cannot create a virtual device | ||
| 525 | * without a callback and a "refCon" parameter, but when we create | ||
| 526 | * a virtual device, we do not want a PortMidi stream yet -- that | ||
| 527 | * should wait for the user to open the stream. So, for the refCon, | ||
| 528 | * use the PortMidi device ID. The callback will check if the | ||
| 529 | * device is opened within PortMidi, and if so, use the pm_descriptors | ||
| 530 | * table to locate the corresponding PmStream. | ||
| 531 | */ | ||
| 532 | static PmError midi_create_virtual(int is_input, const char *name, | ||
| 533 | void *device_info) | ||
| 534 | { | ||
| 535 | OSStatus macHostError; | ||
| 536 | MIDIEndpointRef endpoint; | ||
| 537 | CFStringRef nameRef; | ||
| 538 | PmDeviceID id = pm_add_device("CoreMIDI", name, is_input, TRUE, NULL, | ||
| 539 | (is_input ? &pm_macosx_in_dictionary : | ||
| 540 | &pm_macosx_out_dictionary)); | ||
| 541 | if (id < 0) { /* error -- out of memory or name conflict? */ | ||
| 542 | return id; | ||
| 543 | } | ||
| 544 | |||
| 545 | nameRef = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8); | ||
| 546 | if (is_input) { | ||
| 547 | macHostError = MIDIDestinationCreate(client, nameRef, | ||
| 548 | virtual_read_callback, (void *) (intptr_t) id, &endpoint); | ||
| 549 | } else { | ||
| 550 | macHostError = MIDISourceCreate(client, nameRef, &endpoint); | ||
| 551 | } | ||
| 552 | CFRelease(nameRef); | ||
| 553 | |||
| 554 | if (macHostError != noErr) { | ||
| 555 | /* undo the device we just allocated */ | ||
| 556 | pm_undo_add_device(id); | ||
| 557 | return check_hosterror(macHostError, (is_input ? | ||
| 558 | "MIDIDestinationCreateWithProtocol() in midi_create_virtual()" : | ||
| 559 | "MIDISourceCreateWithProtocol() in midi_create_virtual()")); | ||
| 560 | } | ||
| 561 | |||
| 562 | /* Do we have a manufacturer name? If not, set to "PortMidi" */ | ||
| 563 | const char *mfr_name = "PortMidi"; | ||
| 564 | PmSysDepInfo *info = (PmSysDepInfo *) device_info; | ||
| 565 | /* the version where pmKeyCoreMidiManufacturer was introduced is 210 */ | ||
| 566 | if (info && info->structVersion >= 210) { | ||
| 567 | int i; | ||
| 568 | for (i = 0; i < info->length; i++) { /* search for key */ | ||
| 569 | if (info->properties[i].key == pmKeyCoreMidiManufacturer) { | ||
| 570 | mfr_name = info->properties[i].value; | ||
| 571 | break; | ||
| 572 | } /* no other keys are recognized; they are ignored */ | ||
| 573 | } | ||
| 574 | } | ||
| 575 | nameRef = CFStringCreateWithCString(NULL, mfr_name, kCFStringEncodingUTF8); | ||
| 576 | MIDIObjectSetStringProperty(endpoint, kMIDIPropertyManufacturer, nameRef); | ||
| 577 | CFRelease(nameRef); | ||
| 578 | |||
| 579 | pm_descriptors[id].descriptor = (void *) (intptr_t) endpoint; | ||
| 580 | return id; | ||
| 581 | } | ||
| 582 | |||
| 583 | |||
| 584 | static PmError midi_delete_virtual(PmDeviceID id) | ||
| 585 | { | ||
| 586 | MIDIEndpointRef endpoint; | ||
| 587 | OSStatus macHostError; | ||
| 588 | |||
| 589 | endpoint = (MIDIEndpointRef) (long) pm_descriptors[id].descriptor; | ||
| 590 | if (endpoint == NULL_REF) { | ||
| 591 | return pmBadPtr; | ||
| 592 | } | ||
| 593 | macHostError = MIDIEndpointDispose(endpoint); | ||
| 594 | return check_hosterror(macHostError, | ||
| 595 | "MIDIEndpointDispose() in midi_in_close()"); | ||
| 596 | } | ||
| 597 | |||
| 598 | |||
| 599 | static PmError midi_abort(PmInternal *midi) | ||
| 600 | { | ||
| 601 | OSStatus macHostError; | ||
| 602 | MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t) | ||
| 603 | pm_descriptors[midi->device_id].descriptor; | ||
| 604 | macHostError = MIDIFlushOutput(endpoint); | ||
| 605 | return check_hosterror(macHostError, | ||
| 606 | "MIDIFlushOutput() in midi_abort()"); | ||
| 607 | } | ||
| 608 | |||
| 609 | |||
| 610 | static PmError midi_write_flush(PmInternal *midi, PmTimestamp timestamp) | ||
| 611 | { | ||
| 612 | OSStatus macHostError = 0; | ||
| 613 | coremidi_info_type info = (coremidi_info_type) midi->api_info; | ||
| 614 | MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t) | ||
| 615 | pm_descriptors[midi->device_id].descriptor; | ||
| 616 | assert(info); | ||
| 617 | assert(endpoint); | ||
| 618 | if (info->packet != NULL) { | ||
| 619 | /* out of space, send the buffer and start refilling it */ | ||
| 620 | /* update min_next_time each flush to support rate limit */ | ||
| 621 | UInt64 host_now = AudioGetCurrentHostTime(); | ||
| 622 | if (host_now > info->min_next_time) | ||
| 623 | info->min_next_time = host_now; | ||
| 624 | if (info->is_virtual) { | ||
| 625 | macHostError = MIDIReceived(endpoint, info->packetList); | ||
| 626 | } else { | ||
| 627 | macHostError = MIDISend(portOut, endpoint, info->packetList); | ||
| 628 | } | ||
| 629 | info->packet = NULL; /* indicate no data in packetList now */ | ||
| 630 | } | ||
| 631 | return check_hosterror(macHostError, (info->is_virtual ? | ||
| 632 | "MIDIReceived() in midi_write()" : | ||
| 633 | "MIDISend() in midi_write()")); | ||
| 634 | } | ||
| 635 | |||
| 636 | |||
| 637 | static PmError send_packet(PmInternal *midi, Byte *message, | ||
| 638 | unsigned int messageLength, MIDITimeStamp timestamp) | ||
| 639 | { | ||
| 640 | PmError err; | ||
| 641 | coremidi_info_type info = (coremidi_info_type) midi->api_info; | ||
| 642 | assert(info); | ||
| 643 | |||
| 644 | CM_DEBUG printf("add %d to packet %p len %d timestamp %lld @ %lld ns " | ||
| 645 | "(host %lld)\n", | ||
| 646 | message[0], info->packet, messageLength, timestamp, | ||
| 647 | AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()), | ||
| 648 | AudioGetCurrentHostTime()); | ||
| 649 | info->packet = MIDIPacketListAdd(info->packetList, | ||
| 650 | sizeof(info->packetBuffer), info->packet, | ||
| 651 | timestamp, messageLength, message); | ||
| 652 | #if LIMIT_SEND_RATE | ||
| 653 | info->byte_count += messageLength; | ||
| 654 | #endif | ||
| 655 | if (info->packet == NULL) { | ||
| 656 | /* out of space, send the buffer and start refilling it */ | ||
| 657 | /* make midi->packet non-null to fool midi_write_flush into sending */ | ||
| 658 | info->packet = (MIDIPacket *) 4; | ||
| 659 | /* timestamp is 0 because midi_write_flush ignores timestamp since | ||
| 660 | * timestamps are already in packets. The timestamp parameter is here | ||
| 661 | * because other API's need it. midi_write_flush can be called | ||
| 662 | * from system-independent code that must be cross-API. | ||
| 663 | */ | ||
| 664 | if ((err = midi_write_flush(midi, 0)) != pmNoError) return err; | ||
| 665 | info->packet = MIDIPacketListInit(info->packetList); | ||
| 666 | assert(info->packet); /* if this fails, it's a programming error */ | ||
| 667 | info->packet = MIDIPacketListAdd(info->packetList, | ||
| 668 | sizeof(info->packetBuffer), info->packet, | ||
| 669 | timestamp, messageLength, message); | ||
| 670 | assert(info->packet); /* can't run out of space on first message */ | ||
| 671 | } | ||
| 672 | return pmNoError; | ||
| 673 | } | ||
| 674 | |||
| 675 | |||
| 676 | static PmError midi_write_short(PmInternal *midi, PmEvent *event) | ||
| 677 | { | ||
| 678 | PmTimestamp when = event->timestamp; | ||
| 679 | PmMessage what = event->message; | ||
| 680 | MIDITimeStamp timestamp; | ||
| 681 | coremidi_info_type info = (coremidi_info_type) midi->api_info; | ||
| 682 | Byte message[4]; | ||
| 683 | unsigned int messageLength; | ||
| 684 | |||
| 685 | if (info->packet == NULL) { | ||
| 686 | info->packet = MIDIPacketListInit(info->packetList); | ||
| 687 | /* this can never fail, right? failure would indicate something | ||
| 688 | unrecoverable */ | ||
| 689 | assert(info->packet); | ||
| 690 | } | ||
| 691 | |||
| 692 | /* PortMidi specifies that incoming timestamps are the receive | ||
| 693 | * time. Devices attach their receive times, but virtual devices | ||
| 694 | * do not. Instead, they pass along whatever timestamp was sent to | ||
| 695 | * them. We do not know if we are connected to real or virtual | ||
| 696 | * device. To avoid wild timestamps on the receiving end, we | ||
| 697 | * consider 2 cases: PortMidi timestamp is zero or latency is | ||
| 698 | * zero. Both mean send immediately, so we attach the current time | ||
| 699 | * which will go out immediately and arrive with a sensible | ||
| 700 | * timestamp (not zero and not zero mapped to the client's local | ||
| 701 | * time). Otherwise, we assume the timestamp is reasonable. It | ||
| 702 | * might be slighly in the past, but we pass it along after | ||
| 703 | * translation to MIDITimeStamp units. | ||
| 704 | * | ||
| 705 | * Compute timestamp: use current time if timestamp is zero or | ||
| 706 | * latency is zero. Both mean no timing and send immediately. | ||
| 707 | */ | ||
| 708 | if (when == 0 || midi->latency == 0) { | ||
| 709 | timestamp = AudioGetCurrentHostTime(); | ||
| 710 | } else { /* translate PortMidi time + latency to CoreMIDI time */ | ||
| 711 | timestamp = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + | ||
| 712 | info->delta; | ||
| 713 | timestamp = AudioConvertNanosToHostTime(timestamp); | ||
| 714 | } | ||
| 715 | |||
| 716 | message[0] = Pm_MessageStatus(what); | ||
| 717 | message[1] = Pm_MessageData1(what); | ||
| 718 | message[2] = Pm_MessageData2(what); | ||
| 719 | messageLength = pm_midi_length((int32_t) what); | ||
| 720 | |||
| 721 | #ifdef LIMIT_RATE | ||
| 722 | /* Make sure we go forward in time. */ | ||
| 723 | if (timestamp < info->min_next_time) { | ||
| 724 | timestamp = info->min_next_time; | ||
| 725 | } | ||
| 726 | /* Note that if application is way behind and slowly catching up, then | ||
| 727 | * timestamps could be increasing faster than real time, and since | ||
| 728 | * timestamps are used to estimate data rate, our estimate could be | ||
| 729 | * low, causing CoreMIDI to drop packets. This seems very unlikely. | ||
| 730 | */ | ||
| 731 | if (info->isIACdevice || info->is_virtual) { | ||
| 732 | info->min_next_time = timestamp + messageLength * | ||
| 733 | info->host_ticks_per_byte; | ||
| 734 | } | ||
| 735 | #endif | ||
| 736 | /* Add this message to the packet list */ | ||
| 737 | return send_packet(midi, message, messageLength, timestamp); | ||
| 738 | } | ||
| 739 | |||
| 740 | |||
| 741 | static PmError midi_begin_sysex(PmInternal *midi, PmTimestamp when) | ||
| 742 | { | ||
| 743 | UInt64 when_ns; | ||
| 744 | coremidi_info_type info = (coremidi_info_type) midi->api_info; | ||
| 745 | assert(info); | ||
| 746 | info->sysex_byte_count = 0; | ||
| 747 | |||
| 748 | /* compute timestamp */ | ||
| 749 | if (when == 0) when = midi->now; | ||
| 750 | /* if latency == 0, midi->now is not valid. We will just set it to zero */ | ||
| 751 | if (midi->latency == 0) when = 0; | ||
| 752 | when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + | ||
| 753 | info->delta; | ||
| 754 | info->sysex_timestamp = | ||
| 755 | (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns); | ||
| 756 | UInt64 now; /* only make system time call when writing a virtual port */ | ||
| 757 | if (info->is_virtual && info->sysex_timestamp < | ||
| 758 | (now = AudioGetCurrentHostTime())) { | ||
| 759 | info->sysex_timestamp = now; | ||
| 760 | } | ||
| 761 | |||
| 762 | if (info->packet == NULL) { | ||
| 763 | info->packet = MIDIPacketListInit(info->packetList); | ||
| 764 | /* this can never fail, right? failure would indicate something | ||
| 765 | unrecoverable */ | ||
| 766 | assert(info->packet); | ||
| 767 | } | ||
| 768 | return pmNoError; | ||
| 769 | } | ||
| 770 | |||
| 771 | |||
| 772 | static PmError midi_end_sysex(PmInternal *midi, PmTimestamp when) | ||
| 773 | { | ||
| 774 | PmError err; | ||
| 775 | coremidi_info_type info = (coremidi_info_type) midi->api_info; | ||
| 776 | assert(info); | ||
| 777 | |||
| 778 | #ifdef LIMIT_RATE | ||
| 779 | /* make sure we go foreward in time */ | ||
| 780 | if (info->sysex_timestamp < info->min_next_time) | ||
| 781 | info->sysex_timestamp = info->min_next_time; | ||
| 782 | |||
| 783 | if (info->isIACdevice) { | ||
| 784 | info->min_next_time = info->sysex_timestamp + info->sysex_byte_count * | ||
| 785 | info->host_ticks_per_byte; | ||
| 786 | } | ||
| 787 | #endif | ||
| 788 | |||
| 789 | /* now send what's in the buffer */ | ||
| 790 | err = send_packet(midi, info->sysex_buffer, info->sysex_byte_count, | ||
| 791 | info->sysex_timestamp); | ||
| 792 | info->sysex_byte_count = 0; | ||
| 793 | if (err != pmNoError) { | ||
| 794 | info->packet = NULL; /* flush everything in the packet list */ | ||
| 795 | } | ||
| 796 | return err; | ||
| 797 | } | ||
| 798 | |||
| 799 | |||
| 800 | static PmError midi_write_byte(PmInternal *midi, unsigned char byte, | ||
| 801 | PmTimestamp timestamp) | ||
| 802 | { | ||
| 803 | coremidi_info_type info = (coremidi_info_type) midi->api_info; | ||
| 804 | assert(info); | ||
| 805 | if (info->sysex_byte_count >= SYSEX_BUFFER_SIZE) { | ||
| 806 | PmError err = midi_end_sysex(midi, timestamp); | ||
| 807 | if (err != pmNoError) return err; | ||
| 808 | } | ||
| 809 | info->sysex_buffer[info->sysex_byte_count++] = byte; | ||
| 810 | return pmNoError; | ||
| 811 | } | ||
| 812 | |||
| 813 | |||
| 814 | static PmError midi_write_realtime(PmInternal *midi, PmEvent *event) | ||
| 815 | { | ||
| 816 | /* to send a realtime message during a sysex message, first | ||
| 817 | flush all pending sysex bytes into packet list */ | ||
| 818 | PmError err = midi_end_sysex(midi, 0); | ||
| 819 | if (err != pmNoError) return err; | ||
| 820 | /* then we can just do a normal midi_write_short */ | ||
| 821 | return midi_write_short(midi, event); | ||
| 822 | } | ||
| 823 | |||
| 824 | |||
| 825 | static unsigned int midi_check_host_error(PmInternal *midi) | ||
| 826 | { | ||
| 827 | return FALSE; | ||
| 828 | } | ||
| 829 | |||
| 830 | |||
| 831 | MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp) | ||
| 832 | { | ||
| 833 | UInt64 nanos; | ||
| 834 | if (timestamp <= 0) { | ||
| 835 | return (MIDITimeStamp)0; | ||
| 836 | } else { | ||
| 837 | nanos = (UInt64)timestamp * (UInt64)1000000; | ||
| 838 | return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos); | ||
| 839 | } | ||
| 840 | } | ||
| 841 | |||
| 842 | |||
| 843 | PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp) | ||
| 844 | { | ||
| 845 | UInt64 nanos; | ||
| 846 | nanos = AudioConvertHostTimeToNanos(timestamp); | ||
| 847 | return (PmTimestamp)(nanos / (UInt64)1000000); | ||
| 848 | } | ||
| 849 | |||
| 850 | |||
| 851 | // | ||
| 852 | // Code taken from http://developer.apple.com/qa/qa2004/qa1374.html | ||
| 853 | ////////////////////////////////////// | ||
| 854 | // Obtain the name of an endpoint without regard for whether it has connections. | ||
| 855 | // The result should be released by the caller. | ||
| 856 | CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal, | ||
| 857 | int *iac_flag) | ||
| 858 | { | ||
| 859 | CFMutableStringRef result = CFStringCreateMutable(NULL, 0); | ||
| 860 | CFStringRef str; | ||
| 861 | *iac_flag = FALSE; | ||
| 862 | |||
| 863 | // begin with the endpoint's name | ||
| 864 | str = NULL; | ||
| 865 | MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str); | ||
| 866 | if (str != NULL) { | ||
| 867 | CFStringAppend(result, str); | ||
| 868 | CFRelease(str); | ||
| 869 | } | ||
| 870 | MIDIEntityRef entity = NULL_REF; | ||
| 871 | MIDIEndpointGetEntity(endpoint, &entity); | ||
| 872 | if (entity == NULL_REF) { | ||
| 873 | // probably virtual | ||
| 874 | return result; | ||
| 875 | } | ||
| 876 | if (!isExternal) { /* detect IAC devices */ | ||
| 877 | //extern const CFStringRef kMIDIPropertyDriverOwner; | ||
| 878 | MIDIObjectGetStringProperty(entity, kMIDIPropertyDriverOwner, &str); | ||
| 879 | if (str != NULL) { | ||
| 880 | char s[32]; /* driver name may truncate, but that's OK */ | ||
| 881 | CFStringGetCString(str, s, 31, kCFStringEncodingUTF8); | ||
| 882 | s[31] = 0; /* make sure it is terminated just to be safe */ | ||
| 883 | CM_DEBUG printf("driver %s\n", s); | ||
| 884 | *iac_flag = (strcmp(s, "com.apple.AppleMIDIIACDriver") == 0); | ||
| 885 | } | ||
| 886 | } | ||
| 887 | |||
| 888 | if (CFStringGetLength(result) == 0) { | ||
| 889 | // endpoint name has zero length -- try the entity | ||
| 890 | str = NULL; | ||
| 891 | MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str); | ||
| 892 | if (str != NULL) { | ||
| 893 | CFStringAppend(result, str); | ||
| 894 | CFRelease(str); | ||
| 895 | } | ||
| 896 | } | ||
| 897 | // now consider the device's name | ||
| 898 | MIDIDeviceRef device = NULL_REF; | ||
| 899 | MIDIEntityGetDevice(entity, &device); | ||
| 900 | if (device == NULL_REF) | ||
| 901 | return result; | ||
| 902 | |||
| 903 | str = NULL; | ||
| 904 | MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str); | ||
| 905 | if (CFStringGetLength(result) == 0) { | ||
| 906 | CFRelease(result); | ||
| 907 | return str; | ||
| 908 | } | ||
| 909 | if (str != NULL) { | ||
| 910 | // if an external device has only one entity, throw away | ||
| 911 | // the endpoint name and just use the device name | ||
| 912 | if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) { | ||
| 913 | CFRelease(result); | ||
| 914 | return str; | ||
| 915 | } else { | ||
| 916 | if (CFStringGetLength(str) == 0) { | ||
| 917 | CFRelease(str); | ||
| 918 | return result; | ||
| 919 | } | ||
| 920 | // does the entity name already start with the device name? | ||
| 921 | // (some drivers do this though they shouldn't) | ||
| 922 | // if so, do not prepend | ||
| 923 | if (CFStringCompareWithOptions(result, /* endpoint name */ | ||
| 924 | str, /* device name */ | ||
| 925 | CFRangeMake(0, CFStringGetLength(str)), 0) != | ||
| 926 | kCFCompareEqualTo) { | ||
| 927 | // prepend the device name to the entity name | ||
| 928 | if (CFStringGetLength(result) > 0) | ||
| 929 | CFStringInsert(result, 0, CFSTR(" ")); | ||
| 930 | CFStringInsert(result, 0, str); | ||
| 931 | } | ||
| 932 | CFRelease(str); | ||
| 933 | } | ||
| 934 | } | ||
| 935 | return result; | ||
| 936 | } | ||
| 937 | |||
| 938 | |||
| 939 | // Obtain the name of an endpoint, following connections. | ||
| 940 | // The result should be released by the caller. | ||
| 941 | static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint, | ||
| 942 | int *iac_flag) | ||
| 943 | { | ||
| 944 | CFMutableStringRef result = CFStringCreateMutable(NULL, 0); | ||
| 945 | CFStringRef str; | ||
| 946 | OSStatus err; | ||
| 947 | long i; | ||
| 948 | |||
| 949 | // Does the endpoint have connections? | ||
| 950 | CFDataRef connections = NULL; | ||
| 951 | long nConnected = 0; | ||
| 952 | bool anyStrings = false; | ||
| 953 | err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, | ||
| 954 | &connections); | ||
| 955 | if (connections != NULL) { | ||
| 956 | // It has connections, follow them | ||
| 957 | // Concatenate the names of all connected devices | ||
| 958 | nConnected = CFDataGetLength(connections) / | ||
| 959 | (int32_t) sizeof(MIDIUniqueID); | ||
| 960 | if (nConnected) { | ||
| 961 | const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); | ||
| 962 | for (i = 0; i < nConnected; ++i, ++pid) { | ||
| 963 | MIDIUniqueID id = EndianS32_BtoN(*pid); | ||
| 964 | MIDIObjectRef connObject; | ||
| 965 | MIDIObjectType connObjectType; | ||
| 966 | err = MIDIObjectFindByUniqueID(id, &connObject, | ||
| 967 | &connObjectType); | ||
| 968 | if (err == noErr) { | ||
| 969 | if (connObjectType == kMIDIObjectType_ExternalSource || | ||
| 970 | connObjectType == kMIDIObjectType_ExternalDestination) { | ||
| 971 | // Connected to an external device's endpoint (>=10.3) | ||
| 972 | str = EndpointName((MIDIEndpointRef)(connObject), true, | ||
| 973 | iac_flag); | ||
| 974 | } else { | ||
| 975 | // Connected to an external device (10.2) | ||
| 976 | // (or something else, catch-all) | ||
| 977 | str = NULL; | ||
| 978 | MIDIObjectGetStringProperty(connObject, | ||
| 979 | kMIDIPropertyName, &str); | ||
| 980 | } | ||
| 981 | if (str != NULL) { | ||
| 982 | if (anyStrings) | ||
| 983 | CFStringAppend(result, CFSTR(", ")); | ||
| 984 | else anyStrings = true; | ||
| 985 | CFStringAppend(result, str); | ||
| 986 | CFRelease(str); | ||
| 987 | } | ||
| 988 | } | ||
| 989 | } | ||
| 990 | } | ||
| 991 | CFRelease(connections); | ||
| 992 | } | ||
| 993 | if (anyStrings) | ||
| 994 | return result; // caller should release result | ||
| 995 | |||
| 996 | CFRelease(result); | ||
| 997 | |||
| 998 | // Here, either the endpoint had no connections, or we failed to | ||
| 999 | // obtain names for any of them. | ||
| 1000 | return EndpointName(endpoint, false, iac_flag); | ||
| 1001 | } | ||
| 1002 | |||
| 1003 | |||
| 1004 | char *cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag) | ||
| 1005 | { | ||
| 1006 | /* Thanks to Dan Wilcox for fixes for Unicode handling */ | ||
| 1007 | CFStringRef fullName = ConnectedEndpointName(endpoint, iac_flag); | ||
| 1008 | CFIndex utf16_len = CFStringGetLength(fullName) + 1; | ||
| 1009 | CFIndex max_byte_len = CFStringGetMaximumSizeForEncoding( | ||
| 1010 | utf16_len, kCFStringEncodingUTF8) + 1; | ||
| 1011 | char* pmname = (char *) pm_alloc(CFStringGetLength(fullName) + 1); | ||
| 1012 | |||
| 1013 | /* copy the string into our buffer; note that there may be some wasted | ||
| 1014 | space, but the total waste is not large */ | ||
| 1015 | CFStringGetCString(fullName, pmname, max_byte_len, kCFStringEncodingUTF8); | ||
| 1016 | |||
| 1017 | /* clean up */ | ||
| 1018 | if (fullName) CFRelease(fullName); | ||
| 1019 | return pmname; | ||
| 1020 | } | ||
| 1021 | |||
| 1022 | |||
| 1023 | pm_fns_node pm_macosx_in_dictionary = { | ||
| 1024 | none_write_short, | ||
| 1025 | none_sysex, | ||
| 1026 | none_sysex, | ||
| 1027 | none_write_byte, | ||
| 1028 | none_write_short, | ||
| 1029 | none_write_flush, | ||
| 1030 | none_synchronize, | ||
| 1031 | midi_in_open, | ||
| 1032 | midi_abort, | ||
| 1033 | midi_in_close, | ||
| 1034 | success_poll, | ||
| 1035 | midi_check_host_error | ||
| 1036 | }; | ||
| 1037 | |||
| 1038 | pm_fns_node pm_macosx_out_dictionary = { | ||
| 1039 | midi_write_short, | ||
| 1040 | midi_begin_sysex, | ||
| 1041 | midi_end_sysex, | ||
| 1042 | midi_write_byte, | ||
| 1043 | midi_write_realtime, | ||
| 1044 | midi_write_flush, | ||
| 1045 | midi_synchronize, | ||
| 1046 | midi_out_open, | ||
| 1047 | midi_abort, | ||
| 1048 | midi_out_close, | ||
| 1049 | success_poll, | ||
| 1050 | midi_check_host_error | ||
| 1051 | }; | ||
| 1052 | |||
| 1053 | |||
| 1054 | /* We do nothing with callbacks, but generating the callbacks also | ||
| 1055 | * updates CoreMIDI state. Callback may not be essential, but calling | ||
| 1056 | * the CFRunLoopRunInMode is necessary. | ||
| 1057 | */ | ||
| 1058 | void cm_notify(const MIDINotification *msg, void *refCon) | ||
| 1059 | { | ||
| 1060 | /* for debugging, trace change notifications: | ||
| 1061 | const char *descr[] = { | ||
| 1062 | "undefined (0)", | ||
| 1063 | "kMIDIMsgSetupChanged", | ||
| 1064 | "kMIDIMsgObjectAdded", | ||
| 1065 | "kMIDIMsgObjectRemoved", | ||
| 1066 | "kMIDIMsgPropertyChanged", | ||
| 1067 | "kMIDIMsgThruConnectionsChanged", | ||
| 1068 | "kMIDIMsgSerialPortOwnerChanged", | ||
| 1069 | "kMIDIMsgIOError"}; | ||
| 1070 | |||
| 1071 | printf("MIDI Notify, messageID %d (%s)\n", (int) msg->messageID, | ||
| 1072 | descr[(int) msg->messageID]); | ||
| 1073 | */ | ||
| 1074 | return; | ||
| 1075 | } | ||
| 1076 | |||
| 1077 | |||
| 1078 | PmError pm_macosxcm_init(void) | ||
| 1079 | { | ||
| 1080 | ItemCount numInputs, numOutputs, numDevices; | ||
| 1081 | MIDIEndpointRef endpoint; | ||
| 1082 | OSStatus macHostError = noErr; | ||
| 1083 | char *error_text; | ||
| 1084 | |||
| 1085 | memset(isIAC, 0, sizeof(isIAC)); /* initialize all FALSE */ | ||
| 1086 | |||
| 1087 | /* Register interface CoreMIDI with create_virtual fn */ | ||
| 1088 | pm_add_interf("CoreMIDI", &midi_create_virtual, &midi_delete_virtual); | ||
| 1089 | /* no check for error return because this always succeeds */ | ||
| 1090 | |||
| 1091 | /* Determine the number of MIDI devices on the system */ | ||
| 1092 | numDevices = MIDIGetNumberOfDevices(); | ||
| 1093 | |||
| 1094 | /* Return prematurely if no devices exist on the system | ||
| 1095 | Note that this is not an error. There may be no devices. | ||
| 1096 | Pm_CountDevices() will return zero, which is correct and | ||
| 1097 | useful information | ||
| 1098 | */ | ||
| 1099 | if (numDevices <= 0) { | ||
| 1100 | return pmNoError; | ||
| 1101 | } | ||
| 1102 | |||
| 1103 | /* Initialize the client handle */ | ||
| 1104 | if (client == NULL_REF) { | ||
| 1105 | macHostError = MIDIClientCreate(CFSTR("PortMidi"), &cm_notify, NULL, | ||
| 1106 | &client); | ||
| 1107 | } else { /* see notes above on device scanning */ | ||
| 1108 | for (int i = 0; i < 100; i++) { | ||
| 1109 | // look for any changes before scanning for devices | ||
| 1110 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); | ||
| 1111 | if (i % 5 == 0) Pt_Sleep(1); /* insert 20 delays */ | ||
| 1112 | } | ||
| 1113 | } | ||
| 1114 | if (macHostError != noErr) { | ||
| 1115 | error_text = "MIDIClientCreate() in pm_macosxcm_init()"; | ||
| 1116 | goto error_return; | ||
| 1117 | } | ||
| 1118 | numInputs = MIDIGetNumberOfSources(); | ||
| 1119 | numOutputs = MIDIGetNumberOfDestinations(); | ||
| 1120 | |||
| 1121 | /* Create the input port */ | ||
| 1122 | macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), | ||
| 1123 | device_read_callback, NULL, &portIn); | ||
| 1124 | if (macHostError != noErr) { | ||
| 1125 | error_text = "MIDIInputPortCreate() in pm_macosxcm_init()"; | ||
| 1126 | goto error_return; | ||
| 1127 | } | ||
| 1128 | |||
| 1129 | /* Create the output port */ | ||
| 1130 | macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut); | ||
| 1131 | if (macHostError != noErr) { | ||
| 1132 | error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()"; | ||
| 1133 | goto error_return; | ||
| 1134 | } | ||
| 1135 | |||
| 1136 | /* Iterate over the MIDI input devices */ | ||
| 1137 | for (int i = 0; i < numInputs; i++) { | ||
| 1138 | int iac_flag; | ||
| 1139 | endpoint = MIDIGetSource(i); | ||
| 1140 | if (endpoint == NULL_REF) { | ||
| 1141 | continue; | ||
| 1142 | } | ||
| 1143 | /* Register this device with PortMidi */ | ||
| 1144 | pm_add_device("CoreMIDI", | ||
| 1145 | cm_get_full_endpoint_name(endpoint, &iac_flag), TRUE, FALSE, | ||
| 1146 | (void *) (intptr_t) endpoint, &pm_macosx_in_dictionary); | ||
| 1147 | } | ||
| 1148 | |||
| 1149 | /* Iterate over the MIDI output devices */ | ||
| 1150 | for (int i = 0; i < numOutputs; i++) { | ||
| 1151 | int iac_flag; | ||
| 1152 | PmDeviceID id; | ||
| 1153 | endpoint = MIDIGetDestination(i); | ||
| 1154 | if (endpoint == NULL_REF) { | ||
| 1155 | continue; | ||
| 1156 | } | ||
| 1157 | /* Register this device with PortMidi */ | ||
| 1158 | id = pm_add_device("CoreMIDI", | ||
| 1159 | cm_get_full_endpoint_name(endpoint, &iac_flag), FALSE, FALSE, | ||
| 1160 | (void *) (intptr_t) endpoint, &pm_macosx_out_dictionary); | ||
| 1161 | /* if this is an IAC device, tuck that info away for write functions */ | ||
| 1162 | if (iac_flag && id <= MAX_IAC_NUM) { | ||
| 1163 | isIAC[id] = TRUE; | ||
| 1164 | } | ||
| 1165 | } | ||
| 1166 | return pmNoError; | ||
| 1167 | |||
| 1168 | error_return: | ||
| 1169 | pm_macosxcm_term(); /* clear out any opened ports */ | ||
| 1170 | return check_hosterror(macHostError, error_text); | ||
| 1171 | } | ||
| 1172 | |||
| 1173 | void pm_macosxcm_term(void) | ||
| 1174 | { | ||
| 1175 | /* docs say do not explicitly dispose of client | ||
| 1176 | if (client != NULL_REF) MIDIClientDispose(client); */ | ||
| 1177 | if (portIn != NULL_REF) MIDIPortDispose(portIn); | ||
| 1178 | if (portOut != NULL_REF) MIDIPortDispose(portOut); | ||
| 1179 | } | ||
diff --git a/portmidi/pm_mac/pmmacosxcm.h b/portmidi/pm_mac/pmmacosxcm.h new file mode 100755 index 0000000..166a0b7 --- /dev/null +++ b/portmidi/pm_mac/pmmacosxcm.h | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | /* system-specific definitions */ | ||
| 2 | |||
| 3 | PmError pm_macosxcm_init(void); | ||
| 4 | void pm_macosxcm_term(void); | ||
