-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for targeting iOS #65
base: master
Are you sure you want to change the base?
Changes from 3 commits
a96f40b
ba325d4
b14c729
15ddc24
8772481
d1219cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -82,9 +82,15 @@ | |||||||||||||||||
|
||||||||||||||||||
#include <CoreServices/CoreServices.h> | ||||||||||||||||||
#include <CoreMIDI/MIDIServices.h> | ||||||||||||||||||
#include <CoreAudio/HostTime.h> | ||||||||||||||||||
#include <unistd.h> | ||||||||||||||||||
#include <libkern/OSAtomic.h> | ||||||||||||||||||
#include <TargetConditionals.h> | ||||||||||||||||||
|
||||||||||||||||||
#if TARGET_OS_OSX | ||||||||||||||||||
#include <CoreAudio/HostTime.h> | ||||||||||||||||||
#else | ||||||||||||||||||
#include <mach/mach_time.h> | ||||||||||||||||||
#endif | ||||||||||||||||||
|
||||||||||||||||||
#define PACKET_BUFFER_SIZE 1024 | ||||||||||||||||||
/* maximum overall data rate (OS X limits MIDI rate in case there | ||||||||||||||||||
|
@@ -222,6 +228,10 @@ typedef struct coremidi_info_struct { | |||||||||||||||||
/* private function declarations */ | ||||||||||||||||||
MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); // returns host time | ||||||||||||||||||
PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); // returns ms | ||||||||||||||||||
static UInt64 current_host_time(void); | ||||||||||||||||||
static UInt64 nanos_to_host_time(UInt64 nanos); | ||||||||||||||||||
static UInt64 host_time_to_nanos(UInt64 host_time); | ||||||||||||||||||
static UInt64 micros_per_host_tick(void); | ||||||||||||||||||
|
||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. portmidi may be overoptimized to use host_time when possible rather than always using nanos (which would give a simpler, smaller set of time functions), but OK, let's extend porttime. These additional functions can exist for apple only (since they are not called from other platforms). Names should be, e.g. Pt_Current_Host_Time -- now I kind of hate this style, but it was originally supposed to be consistent with PortAudio, and better to be consistent that not (or to break everything by changing naming conventions now). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Line 184 in 928520f
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've moved these functions to a new header, ptmacosx.h, since they are Apple-specific and so they don't leak into the porttime interface on other platforms: Lines 9 to 15 in 15ddc24
|
||||||||||||||||||
char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag); | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -259,7 +269,7 @@ static PmTimestamp midi_synchronize(PmInternal *midi) | |||||||||||||||||
{ | ||||||||||||||||||
coremidi_info_type info = (coremidi_info_type) midi->api_info; | ||||||||||||||||||
UInt64 pm_stream_time_2 = // current time in ns | ||||||||||||||||||
AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); | ||||||||||||||||||
host_time_to_nanos(current_host_time()); | ||||||||||||||||||
PmTimestamp real_time; // in ms | ||||||||||||||||||
UInt64 pm_stream_time; // in ns | ||||||||||||||||||
/* if latency is zero and this is an output, there is no | ||||||||||||||||||
|
@@ -270,8 +280,7 @@ static PmTimestamp midi_synchronize(PmInternal *midi) | |||||||||||||||||
/* read real_time between two reads of stream time */ | ||||||||||||||||||
pm_stream_time = pm_stream_time_2; | ||||||||||||||||||
real_time = (*midi->time_proc)(midi->time_info); | ||||||||||||||||||
pm_stream_time_2 = AudioConvertHostTimeToNanos( | ||||||||||||||||||
AudioGetCurrentHostTime()); | ||||||||||||||||||
pm_stream_time_2 = host_time_to_nanos(current_host_time()); | ||||||||||||||||||
/* repeat if more than 0.5 ms has elapsed */ | ||||||||||||||||||
} while (pm_stream_time_2 > pm_stream_time + 500000); | ||||||||||||||||||
info->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000); | ||||||||||||||||||
|
@@ -415,19 +424,19 @@ static void read_callback(const MIDIPacketList *newPackets, PmInternal *midi) | |||||||||||||||||
*/ | ||||||||||||||||||
CM_DEBUG printf("read_callback packet @ %lld ns (host %lld) " | ||||||||||||||||||
"status %x length %d\n", | ||||||||||||||||||
AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()), | ||||||||||||||||||
AudioGetCurrentHostTime(), | ||||||||||||||||||
host_time_to_nanos(current_host_time()), | ||||||||||||||||||
current_host_time(), | ||||||||||||||||||
packet->data[0], packet->length); | ||||||||||||||||||
for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) { | ||||||||||||||||||
/* Set the timestamp and dispatch this message */ | ||||||||||||||||||
CM_DEBUG printf(" packet->timeStamp %lld ns %lld host\n", | ||||||||||||||||||
packet->timeStamp, | ||||||||||||||||||
AudioConvertHostTimeToNanos(packet->timeStamp)); | ||||||||||||||||||
host_time_to_nanos(packet->timeStamp)); | ||||||||||||||||||
if (packet->timeStamp == 0) { | ||||||||||||||||||
event.timestamp = now; | ||||||||||||||||||
} else { | ||||||||||||||||||
event.timestamp = (PmTimestamp) /* explicit conversion */ ( | ||||||||||||||||||
(AudioConvertHostTimeToNanos(packet->timeStamp) - info->delta) / | ||||||||||||||||||
(host_time_to_nanos(packet->timeStamp) - info->delta) / | ||||||||||||||||||
(UInt64) 1000000); | ||||||||||||||||||
} | ||||||||||||||||||
status = packet->data[0]; | ||||||||||||||||||
|
@@ -505,7 +514,7 @@ static coremidi_info_type create_macosxcm_info(int is_virtual, int is_input) | |||||||||||||||||
info->last_msg_length = 0; | ||||||||||||||||||
info->min_next_time = 0; | ||||||||||||||||||
info->isIACdevice = FALSE; | ||||||||||||||||||
info->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency(); | ||||||||||||||||||
info->us_per_host_tick = micros_per_host_tick(); | ||||||||||||||||||
info->host_ticks_per_byte = | ||||||||||||||||||
(UInt64) (1000000.0 / (info->us_per_host_tick * MAX_BYTES_PER_S)); | ||||||||||||||||||
info->packetList = (is_input ? NULL : | ||||||||||||||||||
|
@@ -754,7 +763,7 @@ static PmError midi_write_flush(PmInternal *midi, PmTimestamp timestamp) | |||||||||||||||||
if (info->packet != NULL) { | ||||||||||||||||||
/* out of space, send the buffer and start refilling it */ | ||||||||||||||||||
/* update min_next_time each flush to support rate limit */ | ||||||||||||||||||
UInt64 host_now = AudioGetCurrentHostTime(); | ||||||||||||||||||
UInt64 host_now = current_host_time(); | ||||||||||||||||||
if (host_now > info->min_next_time) | ||||||||||||||||||
info->min_next_time = host_now; | ||||||||||||||||||
if (info->is_virtual) { | ||||||||||||||||||
|
@@ -780,8 +789,8 @@ static PmError send_packet(PmInternal *midi, Byte *message, | |||||||||||||||||
CM_DEBUG printf("add %d to packet %p len %d timestamp %lld @ %lld ns " | ||||||||||||||||||
"(host %lld)\n", | ||||||||||||||||||
message[0], info->packet, messageLength, timestamp, | ||||||||||||||||||
AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()), | ||||||||||||||||||
AudioGetCurrentHostTime()); | ||||||||||||||||||
host_time_to_nanos(current_host_time()), | ||||||||||||||||||
current_host_time()); | ||||||||||||||||||
info->packet = MIDIPacketListAdd(info->packetList, | ||||||||||||||||||
sizeof(info->packetBuffer), info->packet, | ||||||||||||||||||
timestamp, messageLength, message); | ||||||||||||||||||
|
@@ -842,11 +851,11 @@ static PmError midi_write_short(PmInternal *midi, PmEvent *event) | |||||||||||||||||
* latency is zero. Both mean no timing and send immediately. | ||||||||||||||||||
*/ | ||||||||||||||||||
if (when == 0 || midi->latency == 0) { | ||||||||||||||||||
timestamp = AudioGetCurrentHostTime(); | ||||||||||||||||||
timestamp = current_host_time(); | ||||||||||||||||||
} else { /* translate PortMidi time + latency to CoreMIDI time */ | ||||||||||||||||||
timestamp = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + | ||||||||||||||||||
info->delta; | ||||||||||||||||||
timestamp = AudioConvertNanosToHostTime(timestamp); | ||||||||||||||||||
timestamp = nanos_to_host_time(timestamp); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
message[0] = Pm_MessageStatus(what); | ||||||||||||||||||
|
@@ -888,10 +897,10 @@ static PmError midi_begin_sysex(PmInternal *midi, PmTimestamp when) | |||||||||||||||||
when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + | ||||||||||||||||||
info->delta; | ||||||||||||||||||
info->sysex_timestamp = | ||||||||||||||||||
(MIDITimeStamp) AudioConvertNanosToHostTime(when_ns); | ||||||||||||||||||
(MIDITimeStamp) nanos_to_host_time(when_ns); | ||||||||||||||||||
UInt64 now; /* only make system time call when writing a virtual port */ | ||||||||||||||||||
if (info->is_virtual && info->sysex_timestamp < | ||||||||||||||||||
(now = AudioGetCurrentHostTime())) { | ||||||||||||||||||
(now = current_host_time())) { | ||||||||||||||||||
info->sysex_timestamp = now; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -963,6 +972,47 @@ static unsigned int midi_check_host_error(PmInternal *midi) | |||||||||||||||||
return FALSE; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
UInt64 current_host_time(void) | ||||||||||||||||||
{ | ||||||||||||||||||
#if TARGET_OS_OSX | ||||||||||||||||||
return AudioGetCurrentHostTime(); | ||||||||||||||||||
#else | ||||||||||||||||||
return mach_absolute_time(); | ||||||||||||||||||
#endif | ||||||||||||||||||
} | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The mach methods are available on macOS too, so we could probably drop the macOS-specific There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It seems to be the officially recommended drop-in replacement: https://developer.apple.com/library/archive/qa/qa1643/_index.html There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So it's probably a question of style which one to choose? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting, because documentation for mach_absolute_time says use clock_gettime_nsec_np(CLOCK_UPTIME_RAW) (!). But OK, if Apple can't decide, I'll go with your choice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the link, I must have missed that note. Interesting that they mention fingerprinting. But regardless, |
||||||||||||||||||
|
||||||||||||||||||
UInt64 nanos_to_host_time(UInt64 nanos) | ||||||||||||||||||
{ | ||||||||||||||||||
#if TARGET_OS_OSX | ||||||||||||||||||
return AudioConvertNanosToHostTime(nanos); | ||||||||||||||||||
#else | ||||||||||||||||||
mach_timebase_info_data_t clock_timebase; | ||||||||||||||||||
mach_timebase_info(&clock_timebase); | ||||||||||||||||||
return (nanos * clock_timebase.denom) / clock_timebase.numer; | ||||||||||||||||||
#endif | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
UInt64 host_time_to_nanos(UInt64 host_time) | ||||||||||||||||||
{ | ||||||||||||||||||
#if TARGET_OS_OSX | ||||||||||||||||||
return AudioConvertHostTimeToNanos(host_time); | ||||||||||||||||||
#else | ||||||||||||||||||
mach_timebase_info_data_t clock_timebase; | ||||||||||||||||||
mach_timebase_info(&clock_timebase); | ||||||||||||||||||
return (host_time * clock_timebase.numer) / clock_timebase.denom; | ||||||||||||||||||
#endif | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
UInt64 micros_per_host_tick(void) | ||||||||||||||||||
{ | ||||||||||||||||||
#if TARGET_OS_OSX | ||||||||||||||||||
return 1000000.0 / AudioGetHostClockFrequency(); | ||||||||||||||||||
#else | ||||||||||||||||||
mach_timebase_info_data_t clock_timebase; | ||||||||||||||||||
mach_timebase_info(&clock_timebase); | ||||||||||||||||||
return clock_timebase.numer / (clock_timebase.denom * 1000.0); | ||||||||||||||||||
#endif | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp) | ||||||||||||||||||
{ | ||||||||||||||||||
|
@@ -971,15 +1021,15 @@ MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp) | |||||||||||||||||
return (MIDITimeStamp)0; | ||||||||||||||||||
} else { | ||||||||||||||||||
nanos = (UInt64)timestamp * (UInt64)1000000; | ||||||||||||||||||
return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos); | ||||||||||||||||||
return (MIDITimeStamp)nanos_to_host_time(nanos); | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
|
||||||||||||||||||
PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp) | ||||||||||||||||||
{ | ||||||||||||||||||
UInt64 nanos; | ||||||||||||||||||
nanos = AudioConvertHostTimeToNanos(timestamp); | ||||||||||||||||||
nanos = host_time_to_nanos(timestamp); | ||||||||||||||||||
return (PmTimestamp)(nanos / (UInt64)1000000); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -1096,7 +1146,7 @@ static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint, | |||||||||||||||||
if (nConnected) { | ||||||||||||||||||
const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); | ||||||||||||||||||
for (i = 0; i < nConnected; ++i, ++pid) { | ||||||||||||||||||
MIDIUniqueID id = EndianS32_BtoN(*pid); | ||||||||||||||||||
MIDIUniqueID id = CFSwapInt32BigToHost(*pid); | ||||||||||||||||||
MIDIObjectRef connObject; | ||||||||||||||||||
MIDIObjectType connObjectType; | ||||||||||||||||||
err = MIDIObjectFindByUniqueID(id, &connObject, | ||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be doing what PortTime was intended to do: provide a system-independent way to access time functions.
There are actually 2 porttime implementations for Mac: ptmacosx_cf.c based on core foundation calls and ptmacosx_mach.c based on Mach calls, but it looks like ptmacosx_mach.c still uses core foundation calls for time functions.
I'd much prefer to add system-independent functions in ptmacosx_mach.c and porttime.h than here for getting and translating nanos to portmidi (milliseconds).
A critical question though, is have you established that the nanos you get from Mach calls are the same nanos you get from CoreAudio? It's important because CoreMidi uses CoreAudio timestamps (undocumented but they match in tests) and it won't work to use just any nanosecond timer. I have no idea if mach gets the same nano values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hence #65 (comment):
The question would be how
pmmacosxcm.c
would consume those functions (or whether there's a way to model these conversions in a fully platform-agnostic way, so they can be part of the main porttime interface. Haven't dug deep enough into porttime to understand the architectural decisions there though).That would indeed be interesting to check, I haven't done so yet. I would be surprised though if they would differ.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your additional functions to get host time and convert host time to micros and nanos is fine. At this point, only OSX and IOS would use them, and I think it's fine to make them Apple-only functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm working to integrate a lot of changes into PortMidi. Returning to this, the whole reason for incorporating CoreAudio time into PortMidi was that the absolute time values are important because they are used as Midi timestamps by MacOS (I don't think this is documented). I believe many PortMidi tests would work fine with bad timestamps, so it's important to look at some MIDI packets in iOS and compare their timestamps to time sources to see what time source is being used (or find it in documentation if it is stated anywhere). Similarly, output timing should be tested to see if absolute timestamps based on mach_absolute_time() or whatever can be used to produce correctly timed output. If you can verify the time source is correct (at least usable for timing and seems correct), then this would be a nice addition to PortMidi.