Skip to content
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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 39 additions & 40 deletions pm_mac/pmmacosxcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,15 @@
#include "pmutil.h"
#include "pminternal.h"
#include "porttime.h"
#include "ptmacosx.h"
#include "pmmacosxcm.h"

#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <CoreServices/CoreServices.h>
#include <CoreMIDI/MIDIServices.h>
#include <CoreAudio/HostTime.h>
#include <unistd.h>
#include <libkern/OSAtomic.h>

Copy link
Contributor

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hence #65 (comment):

Ideally, we should probably share these utility methods between porttime and pm_mac. The right place for that would likely be porttime, but we'd also create a leaky abstraction by exposing these implementation details, even if only to the (also platform-specific) pm_mac. Ideas welcome.

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).

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.

That would indeed be interesting to check, I haven't done so yet. I would be surprised though if they would differ.

Copy link
Contributor

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.

Copy link
Contributor

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.

Expand Down Expand Up @@ -199,7 +200,7 @@ extern pm_fns_node pm_macosx_out_dictionary;

typedef struct coremidi_info_struct {
int is_virtual; /* virtual device (TRUE) or actual device (FALSE)? */
UInt64 delta; /* difference between stream time and real time in ns */
uint64_t delta; /* difference between stream time and real time in ns */
int sysex_mode; /* middle of sending sysex */
uint32_t sysex_word; /* accumulate data when receiving sysex */
uint32_t sysex_byte_count; /* count how many received */
Expand All @@ -213,10 +214,10 @@ typedef struct coremidi_info_struct {
/* allow for running status (is running status possible here? -rbd): -cpr */
unsigned char last_command;
int32_t last_msg_length;
UInt64 min_next_time; /* when can the next send take place? (host time) */
uint64_t min_next_time; /* when can the next send take place? (host time) */
int isIACdevice;
Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */
UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */
uint64_t host_ticks_per_byte; /* host clock units per byte at maximum rate */
} coremidi_info_node, *coremidi_info_type;

/* private function declarations */
Expand Down Expand Up @@ -258,10 +259,10 @@ static int midi_length(int32_t msg)
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());
uint64_t pm_stream_time_2 = // current time in ns
Pt_HostTimeToNanos(Pt_CurrentHostTime());
PmTimestamp real_time; // in ms
UInt64 pm_stream_time; // in ns
uint64_t pm_stream_time; // in ns
/* if latency is zero and this is an output, there is no
time reference and midi_synchronize should never be called */
assert(midi->time_proc);
Expand All @@ -270,11 +271,10 @@ 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 = Pt_HostTimeToNanos(Pt_CurrentHostTime());
/* 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);
info->delta = pm_stream_time - ((uint64_t) real_time * (uint64_t) 1000000);
midi->sync_time = real_time;
return real_time;
}
Expand Down Expand Up @@ -415,20 +415,20 @@ 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(),
Pt_HostTimeToNanos(Pt_CurrentHostTime()),
Pt_CurrentHostTime(),
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));
Pt_HostTimeToNanos(packet->timeStamp));
if (packet->timeStamp == 0) {
event.timestamp = now;
} else {
event.timestamp = (PmTimestamp) /* explicit conversion */ (
(AudioConvertHostTimeToNanos(packet->timeStamp) - info->delta) /
(UInt64) 1000000);
(Pt_HostTimeToNanos(packet->timeStamp) - info->delta) /
(uint64_t) 1000000);
}
status = packet->data[0];
/* process packet as sysex data if it begins with MIDI_SYSEX, or
Expand Down Expand Up @@ -474,8 +474,8 @@ static void virtual_read_callback(const MIDIPacketList *newPackets,
newPackets->packet[0].length == 8 &&
/* CoreMIDI declares packets with 4-byte alignment, so we
* should be safe to test for 8 0xFF's as 2 32-bit values: */
*(SInt32 *) &newPackets->packet[0].data[0] == -1 &&
*(SInt32 *) &newPackets->packet[0].data[4] == -1) {
*(int32_t *) &newPackets->packet[0].data[0] == -1 &&
*(int32_t *) &newPackets->packet[0].data[4] == -1) {
CM_DEBUG printf("got close request packet\n");
pm_descriptors[id].pub.opened = FALSE;
return;
Expand Down Expand Up @@ -505,9 +505,9 @@ 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 = Pt_MicrosPerHostTick();
info->host_ticks_per_byte =
(UInt64) (1000000.0 / (info->us_per_host_tick * MAX_BYTES_PER_S));
(uint64_t) (1000000.0 / (info->us_per_host_tick * MAX_BYTES_PER_S));
info->packetList = (is_input ? NULL :
(MIDIPacketList *) info->packetBuffer);
return info;
Expand Down Expand Up @@ -581,7 +581,7 @@ static PmError midi_in_close(PmInternal *midi)
}
} else {
/* make "close virtual port" message */
SInt64 close_port_bytes = 0xFFFFFFFFFFFFFFFF;
int64_t close_port_bytes = 0xFFFFFFFFFFFFFFFF;
/* memory requirements: packet count (4), timestamp (8), length (2),
* data (8). Total: 22, but we allocate plenty more:
*/
Expand Down Expand Up @@ -754,7 +754,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_t host_now = Pt_CurrentHostTime();
if (host_now > info->min_next_time)
info->min_next_time = host_now;
if (info->is_virtual) {
Expand All @@ -780,8 +780,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());
Pt_HostTimeToNanos(Pt_CurrentHostTime()),
Pt_CurrentHostTime());
info->packet = MIDIPacketListAdd(info->packetList,
sizeof(info->packetBuffer), info->packet,
timestamp, messageLength, message);
Expand Down Expand Up @@ -842,11 +842,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 = Pt_CurrentHostTime();
} else { /* translate PortMidi time + latency to CoreMIDI time */
timestamp = ((UInt64) (when + midi->latency) * (UInt64) 1000000) +
timestamp = ((uint64_t) (when + midi->latency) * (uint64_t) 1000000) +
info->delta;
timestamp = AudioConvertNanosToHostTime(timestamp);
timestamp = Pt_NanosToHostTime(timestamp);
}

message[0] = Pm_MessageStatus(what);
Expand Down Expand Up @@ -876,7 +876,7 @@ static PmError midi_write_short(PmInternal *midi, PmEvent *event)

static PmError midi_begin_sysex(PmInternal *midi, PmTimestamp when)
{
UInt64 when_ns;
uint64_t when_ns;
coremidi_info_type info = (coremidi_info_type) midi->api_info;
assert(info);
info->sysex_byte_count = 0;
Expand All @@ -885,13 +885,13 @@ static PmError midi_begin_sysex(PmInternal *midi, PmTimestamp when)
if (when == 0) when = midi->now;
/* if latency == 0, midi->now is not valid. We will just set it to zero */
if (midi->latency == 0) when = 0;
when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) +
when_ns = ((uint64_t) (when + midi->latency) * (uint64_t) 1000000) +
info->delta;
info->sysex_timestamp =
(MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
UInt64 now; /* only make system time call when writing a virtual port */
(MIDITimeStamp) Pt_NanosToHostTime(when_ns);
uint64_t now; /* only make system time call when writing a virtual port */
if (info->is_virtual && info->sysex_timestamp <
(now = AudioGetCurrentHostTime())) {
(now = Pt_CurrentHostTime())) {
info->sysex_timestamp = now;
}

Expand Down Expand Up @@ -963,24 +963,23 @@ static unsigned int midi_check_host_error(PmInternal *midi)
return FALSE;
}


MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)
{
UInt64 nanos;
uint64_t nanos;
if (timestamp <= 0) {
return (MIDITimeStamp)0;
} else {
nanos = (UInt64)timestamp * (UInt64)1000000;
return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);
nanos = (uint64_t)timestamp * (uint64_t)1000000;
return (MIDITimeStamp)Pt_NanosToHostTime(nanos);
}
}


PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)
{
UInt64 nanos;
nanos = AudioConvertHostTimeToNanos(timestamp);
return (PmTimestamp)(nanos / (UInt64)1000000);
uint64_t nanos;
nanos = Pt_HostTimeToNanos(timestamp);
return (PmTimestamp)(nanos / (uint64_t)1000000);
}


Expand Down Expand Up @@ -1094,9 +1093,9 @@ static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint,
nConnected = CFDataGetLength(connections) /
(int32_t) sizeof(MIDIUniqueID);
if (nConnected) {
const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
const int32_t *pid = (const int32_t *)(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,
Expand Down
21 changes: 21 additions & 0 deletions porttime/ptmacosx.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/** @file ptmacosx.h portable timer implementation for mac os x. */

#include <stdint.h> // needed for uint64_t

#ifdef __cplusplus
extern "C" {
#endif

uint64_t Pt_CurrentHostTime(void);

uint64_t Pt_NanosToHostTime(uint64_t nanos);

uint64_t Pt_HostTimeToNanos(uint64_t host_time);

uint64_t Pt_MicrosPerHostTick(void);

/** @} */

#ifdef __cplusplus
}
#endif
68 changes: 59 additions & 9 deletions porttime/ptmacosx_mach.c
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
/* ptmacosx.c -- portable timer implementation for mac os x */

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <CoreAudio/HostTime.h>

#import <mach/mach.h>
#import <mach/mach_error.h>
#import <mach/mach_time.h>
#import <mach/clock.h>
#include <unistd.h>
#include <AvailabilityMacros.h>
#include <TargetConditionals.h>

#if TARGET_OS_OSX
#include <CoreAudio/HostTime.h>
#else
#include <mach/mach_time.h>
#endif

#include "porttime.h"
#include "ptmacosx.h"
#include "sys/time.h"
#include "pthread.h"

Expand All @@ -29,7 +37,7 @@
#endif

static int time_started_flag = FALSE;
static UInt64 start_time;
static uint64_t start_time;
static pthread_t pt_thread_pid;

/* note that this is static data -- we only need one copy */
Expand Down Expand Up @@ -115,12 +123,12 @@ static void *Pt_CallbackProc(void *p)
parameters->id); */
while (pt_callback_proc_id == parameters->id) {
/* wait for a multiple of resolution ms */
UInt64 wait_time;
uint64_t wait_time;
int delay = mytime++ * parameters->resolution - Pt_Time();
PtTimestamp timestamp;
if (delay < 0) delay = 0;
wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC);
wait_time += AudioGetCurrentHostTime();
wait_time = Pt_NanosToHostTime((uint64_t)delay * NSEC_PER_MSEC);
wait_time += Pt_CurrentHostTime();
mach_wait_until(wait_time);
timestamp = Pt_Time();
(*(parameters->callback))(timestamp, parameters->userData);
Expand All @@ -133,7 +141,7 @@ static void *Pt_CallbackProc(void *p)
PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
{
if (time_started_flag) return ptAlreadyStarted;
start_time = AudioGetCurrentHostTime();
start_time = Pt_CurrentHostTime();

if (callback) {
int res;
Expand Down Expand Up @@ -191,9 +199,9 @@ int Pt_Started(void)

PtTimestamp Pt_Time(void)
{
UInt64 clock_time, nsec_time;
clock_time = AudioGetCurrentHostTime() - start_time;
nsec_time = AudioConvertHostTimeToNanos(clock_time);
uint64_t clock_time, nsec_time;
clock_time = Pt_CurrentHostTime() - start_time;
nsec_time = Pt_HostTimeToNanos(clock_time);
return (PtTimestamp)(nsec_time / NSEC_PER_MSEC);
}

Expand All @@ -202,3 +210,45 @@ void Pt_Sleep(int32_t duration)
{
usleep(duration * 1000);
}

uint64_t Pt_CurrentHostTime(void)
{
#if TARGET_OS_OSX
return AudioGetCurrentHostTime();
#else
return mach_absolute_time();
#endif
}

uint64_t Pt_NanosToHostTime(uint64_t 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_t Pt_HostTimeToNanos(uint64_t 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_t Pt_MicrosPerHostTick(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
}