From 988f5d2b5343850e19ad1512cefe6c53953aa02e Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Mon, 7 Oct 2024 06:50:04 +0200 Subject: Added bunch of examples --- portmidi/porttime/porttime.c | 3 + portmidi/porttime/porttime.h | 103 +++++++++++++++++++ portmidi/porttime/pthaiku.cpp | 88 ++++++++++++++++ portmidi/porttime/ptlinux.c | 139 ++++++++++++++++++++++++++ portmidi/porttime/ptmacosx_cf.c | 140 ++++++++++++++++++++++++++ portmidi/porttime/ptmacosx_mach.c | 204 ++++++++++++++++++++++++++++++++++++++ portmidi/porttime/ptwinmm.c | 70 +++++++++++++ 7 files changed, 747 insertions(+) create mode 100755 portmidi/porttime/porttime.c create mode 100755 portmidi/porttime/porttime.h create mode 100644 portmidi/porttime/pthaiku.cpp create mode 100755 portmidi/porttime/ptlinux.c create mode 100755 portmidi/porttime/ptmacosx_cf.c create mode 100755 portmidi/porttime/ptmacosx_mach.c create mode 100755 portmidi/porttime/ptwinmm.c (limited to 'portmidi/porttime') diff --git a/portmidi/porttime/porttime.c b/portmidi/porttime/porttime.c new file mode 100755 index 0000000..71b06f4 --- /dev/null +++ b/portmidi/porttime/porttime.c @@ -0,0 +1,3 @@ +/* porttime.c -- portable API for millisecond timer */ + +/* There is no machine-independent implementation code to put here */ diff --git a/portmidi/porttime/porttime.h b/portmidi/porttime/porttime.h new file mode 100755 index 0000000..0a61c5c --- /dev/null +++ b/portmidi/porttime/porttime.h @@ -0,0 +1,103 @@ +/** @file porttime.h portable interface to millisecond timer. */ + +/* CHANGE LOG FOR PORTTIME + 10-Jun-03 Mark Nelson & RBD + boost priority of timer thread in ptlinux.c implementation + */ + +#ifndef PORTMIDI_PORTTIME_H +#define PORTMIDI_PORTTIME_H + +/* Should there be a way to choose the source of time here? */ + +#ifdef WIN32 +#ifndef INT32_DEFINED +// rather than having users install a special .h file for windows, +// just put the required definitions inline here. portmidi.h uses +// these too, so the definitions are (unfortunately) duplicated there +typedef int int32_t; +typedef unsigned int uint32_t; +#define INT32_DEFINED +#endif +#else +#include // needed for int32_t +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef PMEXPORT +#ifdef _WINDLL +#define PMEXPORT __declspec(dllexport) +#else +#define PMEXPORT +#endif +#endif + +/** @defgroup grp_porttime PortTime: Millisecond Timer + @{ +*/ + +typedef enum { + ptNoError = 0, /* success */ + ptHostError = -10000, /* a system-specific error occurred */ + ptAlreadyStarted, /* cannot start timer because it is already started */ + ptAlreadyStopped, /* cannot stop timer because it is already stopped */ + ptInsufficientMemory /* memory could not be allocated */ +} PtError; /**< @brief @enum PtError PortTime error code; a common return type. + * No error is indicated by zero; errors are indicated by < 0. + */ + +/** real time or time offset in milliseconds. */ +typedef int32_t PtTimestamp; + +/** a function that gets a current time */ +typedef void (PtCallback)(PtTimestamp timestamp, void *userData); + +/** start a real-time clock service. + + @param resolution the timer resolution in ms. The time will advance every + \p resolution ms. + + @param callback a function pointer to be called every resolution ms. + + @param userData is passed to \p callback as a parameter. + + @return #ptNoError on success. See #PtError for other values. +*/ +PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData); + +/** stop the timer. + + @return #ptNoError on success. See #PtError for other values. +*/ +PMEXPORT PtError Pt_Stop(void); + +/** test if the timer is running. + + @return TRUE or FALSE +*/ +PMEXPORT int Pt_Started(void); + +/** get the current time in ms. + + @return the current time +*/ +PMEXPORT PtTimestamp Pt_Time(void); + +/** pauses the current thread, allowing other threads to run. + + @param duration the length of the pause in ms. The true duration + of the pause may be rounded to the nearest or next clock tick + as determined by resolution in #Pt_Start(). +*/ +PMEXPORT void Pt_Sleep(int32_t duration); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif // PORTMIDI_PORTTIME_H diff --git a/portmidi/porttime/pthaiku.cpp b/portmidi/porttime/pthaiku.cpp new file mode 100644 index 0000000..9d8de14 --- /dev/null +++ b/portmidi/porttime/pthaiku.cpp @@ -0,0 +1,88 @@ +// pthaiku.cpp - portable timer implementation for Haiku + +#include "porttime.h" +#include +#include +#include + +namespace { + const uint32 timerMessage = 'PTTM'; + + struct TimerLooper : BLooper { + TimerLooper() : BLooper() { + } + + + virtual void MessageReceived(BMessage *message) + { + PtCallback *callback; + void *userData; + if (message->what == timerMessage && message->FindPointer("callback", (void**)&callback) == B_OK && message->FindPointer("userData", &userData) == B_OK) { + (*callback)(Pt_Time(), userData); + } + BLooper::MessageReceived(message); + } + }; + + bool time_started_flag = false; + bigtime_t time_offset; + TimerLooper *timerLooper; + BMessageRunner *timerRunner; +} + +extern "C" { + PtError Pt_Start(int resolution, PtCallback *callback, void *userData) + { + if (time_started_flag) return ptAlreadyStarted; + time_offset = system_time(); + if (callback) { + timerLooper = new TimerLooper; + timerLooper->Run(); + BMessenger target(timerLooper); + BMessage message(timerMessage); + message.AddPointer("callback", (void*)callback); + message.AddPointer("userData", userData); + bigtime_t interval = resolution * 1000; + timerRunner = new BMessageRunner(target, &message, interval); + if(timerRunner->InitCheck() != B_OK) { + delete timerRunner; + timerRunner = NULL; + timerLooper->PostMessage(B_QUIT_REQUESTED); + timerLooper = NULL; + return ptHostError; + } + } + time_started_flag = true; + return ptNoError; + } + + + PtError Pt_Stop() + { + if (!time_started_flag) return ptAlreadyStopped; + time_started_flag = false; + delete timerRunner; + timerRunner = NULL; + timerLooper->PostMessage(B_QUIT_REQUESTED); + timerLooper = NULL; + return ptNoError; + } + + + int Pt_Started() + { + return time_started_flag; + } + + + PtTimestamp Pt_Time() + { + return (system_time() - time_offset) / 1000; + } + + + void Pt_Sleep(int32_t duration) + { + snooze(duration * 1000); + } +} diff --git a/portmidi/porttime/ptlinux.c b/portmidi/porttime/ptlinux.c new file mode 100755 index 0000000..c4af5c1 --- /dev/null +++ b/portmidi/porttime/ptlinux.c @@ -0,0 +1,139 @@ +/* ptlinux.c -- portable timer implementation for linux */ + + +/* IMPLEMENTATION NOTES (by Mark Nelson): + +Unlike Windows, Linux has no system call to request a periodic callback, +so if Pt_Start() receives a callback parameter, it must create a thread +that wakes up periodically and calls the provided callback function. +If running as superuser, use setpriority() to renice thread to -20. +One could also set the timer thread to a real-time priority (SCHED_FIFO +and SCHED_RR), but this is dangerous for This is necessary because +if the callback hangs it'll never return. A more serious reason +is that the current scheduler implementation busy-waits instead +of sleeping when realtime threads request a sleep of <=2ms (as a way +to get around the 10ms granularity), which means the thread would never +let anyone else on the CPU. + +CHANGE LOG + +18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer + thread. Simplified implementation notes. + +*/ +/* stdlib, stdio, unistd, and sys/types were added because they appeared + * in a Gentoo patch, but I'm not sure why they are needed. -RBD + */ +#include +#include +#include +#include +#include "porttime.h" +#include "time.h" +#include "sys/resource.h" +#include "pthread.h" + +#define TRUE 1 +#define FALSE 0 + +#ifndef CLOCK_MONOTONIC_RAW +#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC +#endif + +static int time_started_flag = FALSE; +static struct timespec time_offset = {0, 0}; +static pthread_t pt_thread_pid; +static int pt_thread_created = FALSE; + +/* note that this is static data -- we only need one copy */ +typedef struct { + int id; + int resolution; + PtCallback *callback; + void *userData; +} pt_callback_parameters; + +static int pt_callback_proc_id = 0; + +static void *Pt_CallbackProc(void *p) +{ + pt_callback_parameters *parameters = (pt_callback_parameters *) p; + int mytime = 1; + /* to kill a process, just increment the pt_callback_proc_id */ + /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, + parameters->id); */ + if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20); + while (pt_callback_proc_id == parameters->id) { + /* wait for a multiple of resolution ms */ + struct timeval timeout; + int delay = mytime++ * parameters->resolution - Pt_Time(); + if (delay < 0) delay = 0; + timeout.tv_sec = 0; + timeout.tv_usec = delay * 1000; + select(0, NULL, NULL, NULL, &timeout); + (*(parameters->callback))(Pt_Time(), parameters->userData); + } + /* printf("Pt_CallbackProc exiting\n"); */ + // free(parameters); + return NULL; +} + + +PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +{ + if (time_started_flag) return ptNoError; + /* need this set before process runs: */ + clock_gettime(CLOCK_MONOTONIC_RAW, &time_offset); + if (callback) { + int res; + pt_callback_parameters *parms = (pt_callback_parameters *) + malloc(sizeof(pt_callback_parameters)); + if (!parms) return ptInsufficientMemory; + parms->id = pt_callback_proc_id; + parms->resolution = resolution; + parms->callback = callback; + parms->userData = userData; + res = pthread_create(&pt_thread_pid, NULL, + Pt_CallbackProc, parms); + if (res != 0) return ptHostError; + pt_thread_created = TRUE; + } + time_started_flag = TRUE; + return ptNoError; +} + + +PtError Pt_Stop() +{ + /* printf("Pt_Stop called\n"); */ + pt_callback_proc_id++; + if (pt_thread_created) { + pthread_join(pt_thread_pid, NULL); + pt_thread_created = FALSE; + } + time_started_flag = FALSE; + return ptNoError; +} + + +int Pt_Started() +{ + return time_started_flag; +} + + +PtTimestamp Pt_Time() +{ + long seconds, ms; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC_RAW, &now); + seconds = now.tv_sec - time_offset.tv_sec; + ms = (now.tv_nsec - time_offset.tv_nsec) / 1000000; /* round down */ + return seconds * 1000 + ms; +} + + +void Pt_Sleep(int32_t duration) +{ + usleep(duration * 1000); +} diff --git a/portmidi/porttime/ptmacosx_cf.c b/portmidi/porttime/ptmacosx_cf.c new file mode 100755 index 0000000..a174c86 --- /dev/null +++ b/portmidi/porttime/ptmacosx_cf.c @@ -0,0 +1,140 @@ +/* ptmacosx.c -- portable timer implementation for mac os x */ + +#include +#include +#include +#include + +#import +#import +#import +#import + +#include "porttime.h" + +#define THREAD_IMPORTANCE 30 +#define LONG_TIME 1000000000.0 + +static int time_started_flag = FALSE; +static CFAbsoluteTime startTime = 0.0; +static CFRunLoopRef timerRunLoop; + +typedef struct { + int resolution; + PtCallback *callback; + void *userData; +} PtThreadParams; + + +void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info) +{ + PtThreadParams *params = (PtThreadParams*)info; + (*params->callback)(Pt_Time(), params->userData); +} + +static void* Pt_Thread(void *p) +{ + CFTimeInterval timerInterval; + CFRunLoopTimerContext timerContext; + CFRunLoopTimerRef timer; + PtThreadParams *params = (PtThreadParams*)p; + //CFTimeInterval timeout; + + /* raise the thread's priority */ + kern_return_t error; + thread_extended_policy_data_t extendedPolicy; + thread_precedence_policy_data_t precedencePolicy; + + extendedPolicy.timeshare = 0; + error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, + (thread_policy_t)&extendedPolicy, + THREAD_EXTENDED_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread timeshare policy", error); + } + + precedencePolicy.importance = THREAD_IMPORTANCE; + error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, + (thread_policy_t)&precedencePolicy, + THREAD_PRECEDENCE_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread precedence policy", error); + } + + /* set up the timer context */ + timerContext.version = 0; + timerContext.info = params; + timerContext.retain = NULL; + timerContext.release = NULL; + timerContext.copyDescription = NULL; + + /* create a new timer */ + timerInterval = (double)params->resolution / 1000.0; + timer = CFRunLoopTimerCreate(NULL, startTime+timerInterval, timerInterval, + 0, 0, Pt_CFTimerCallback, &timerContext); + + timerRunLoop = CFRunLoopGetCurrent(); + CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode")); + + /* run until we're told to stop by Pt_Stop() */ + CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false); + + CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode")); + CFRelease(timer); + free(params); + + return NULL; +} + +PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +{ + PtThreadParams *params = (PtThreadParams*)malloc(sizeof(PtThreadParams)); + pthread_t pthread_id; + + printf("Pt_Start() called\n"); + + // /* make sure we're not already playing */ + if (time_started_flag) return ptAlreadyStarted; + startTime = CFAbsoluteTimeGetCurrent(); + + if (callback) { + + params->resolution = resolution; + params->callback = callback; + params->userData = userData; + + pthread_create(&pthread_id, NULL, Pt_Thread, params); + } + + time_started_flag = TRUE; + return ptNoError; +} + + +PtError Pt_Stop() +{ + printf("Pt_Stop called\n"); + + CFRunLoopStop(timerRunLoop); + time_started_flag = FALSE; + return ptNoError; +} + + +int Pt_Started() +{ + return time_started_flag; +} + + +PtTimestamp Pt_Time() +{ + CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); + return (PtTimestamp) ((now - startTime) * 1000.0); +} + + +void Pt_Sleep(int32_t duration) +{ + usleep(duration * 1000); +} diff --git a/portmidi/porttime/ptmacosx_mach.c b/portmidi/porttime/ptmacosx_mach.c new file mode 100755 index 0000000..1390af8 --- /dev/null +++ b/portmidi/porttime/ptmacosx_mach.c @@ -0,0 +1,204 @@ +/* ptmacosx.c -- portable timer implementation for mac os x */ + +#include +#include +#include + +#import +#import +#import +#import +#include +#include + +#include "porttime.h" +#include "sys/time.h" +#include "pthread.h" + +#ifndef NSEC_PER_MSEC +#define NSEC_PER_MSEC 1000000 +#endif +#define THREAD_IMPORTANCE 63 + +// QOS headers are available as of macOS 10.10 +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 +#include "sys/qos.h" +#define HAVE_APPLE_QOS 1 +#else +#undef HAVE_APPLE_QOS +#endif + +static int time_started_flag = FALSE; +static UInt64 start_time; +static pthread_t pt_thread_pid; + +/* note that this is static data -- we only need one copy */ +typedef struct { + int id; + int resolution; + PtCallback *callback; + void *userData; +} pt_callback_parameters; + +static int pt_callback_proc_id = 0; + +static void *Pt_CallbackProc(void *p) +{ + pt_callback_parameters *parameters = (pt_callback_parameters *) p; + int mytime = 1; + + kern_return_t error; + thread_extended_policy_data_t extendedPolicy; + thread_precedence_policy_data_t precedencePolicy; + + extendedPolicy.timeshare = 0; + error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, + (thread_policy_t)&extendedPolicy, + THREAD_EXTENDED_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread timeshare policy", error); + } + + precedencePolicy.importance = THREAD_IMPORTANCE; + error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, + (thread_policy_t)&precedencePolicy, + THREAD_PRECEDENCE_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread precedence policy", error); + } + + // Most important, set real-time constraints. + + // Define the guaranteed and max fraction of time for the audio thread. + // These "duty cycle" values can range from 0 to 1. A value of 0.5 + // means the scheduler would give half the time to the thread. + // These values have empirically been found to yield good behavior. + // Good means that audio performance is high and other threads won't starve. + const double kGuaranteedAudioDutyCycle = 0.75; + const double kMaxAudioDutyCycle = 0.85; + + // Define constants determining how much time the audio thread can + // use in a given time quantum. All times are in milliseconds. + + // About 128 frames @44.1KHz + const double kTimeQuantum = 2.9; + + // Time guaranteed each quantum. + const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum; + + // Maximum time each quantum. + const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum; + + // Get the conversion factor from milliseconds to absolute time + // which is what the time-constraints call needs. + mach_timebase_info_data_t tb_info; + mach_timebase_info(&tb_info); + double ms_to_abs_time = + ((double)tb_info.denom / (double)tb_info.numer) * 1000000; + + thread_time_constraint_policy_data_t time_constraints; + time_constraints.period = (uint32_t)(kTimeQuantum * ms_to_abs_time); + time_constraints.computation = (uint32_t)(kAudioTimeNeeded * ms_to_abs_time); + time_constraints.constraint = (uint32_t)(kMaxTimeAllowed * ms_to_abs_time); + time_constraints.preemptible = 0; + + error = thread_policy_set(mach_thread_self(), + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t)&time_constraints, + THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread precedence policy", error); + } + + /* to kill a process, just increment the pt_callback_proc_id */ + /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, + parameters->id); */ + while (pt_callback_proc_id == parameters->id) { + /* wait for a multiple of resolution ms */ + UInt64 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(); + mach_wait_until(wait_time); + timestamp = Pt_Time(); + (*(parameters->callback))(timestamp, parameters->userData); + } + free(parameters); + return NULL; +} + + +PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +{ + if (time_started_flag) return ptAlreadyStarted; + start_time = AudioGetCurrentHostTime(); + + if (callback) { + int res; + pt_callback_parameters *parms; + + parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters)); + if (!parms) return ptInsufficientMemory; + parms->id = pt_callback_proc_id; + parms->resolution = resolution; + parms->callback = callback; + parms->userData = userData; + +#ifdef HAVE_APPLE_QOS + pthread_attr_t qosAttribute; + pthread_attr_init(&qosAttribute); + pthread_attr_set_qos_class_np(&qosAttribute, + QOS_CLASS_USER_INTERACTIVE, 0); + + res = pthread_create(&pt_thread_pid, &qosAttribute, Pt_CallbackProc, + parms); +#else + res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms); +#endif + + struct sched_param sp; + memset(&sp, 0, sizeof(struct sched_param)); + sp.sched_priority = sched_get_priority_max(SCHED_RR); + if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp) == -1) { + return ptHostError; + } + + if (res != 0) return ptHostError; + } + + time_started_flag = TRUE; + return ptNoError; +} + + +PtError Pt_Stop(void) +{ + /* printf("Pt_Stop called\n"); */ + pt_callback_proc_id++; + pthread_join(pt_thread_pid, NULL); + time_started_flag = FALSE; + return ptNoError; +} + + +int Pt_Started(void) +{ + return time_started_flag; +} + + +PtTimestamp Pt_Time(void) +{ + UInt64 clock_time, nsec_time; + clock_time = AudioGetCurrentHostTime() - start_time; + nsec_time = AudioConvertHostTimeToNanos(clock_time); + return (PtTimestamp)(nsec_time / NSEC_PER_MSEC); +} + + +void Pt_Sleep(int32_t duration) +{ + usleep(duration * 1000); +} diff --git a/portmidi/porttime/ptwinmm.c b/portmidi/porttime/ptwinmm.c new file mode 100755 index 0000000..5204659 --- /dev/null +++ b/portmidi/porttime/ptwinmm.c @@ -0,0 +1,70 @@ +/* ptwinmm.c -- portable timer implementation for win32 */ + + +#include "porttime.h" +#include "windows.h" +#include "time.h" + + +TIMECAPS caps; + +static long time_offset = 0; +static int time_started_flag = FALSE; +static long time_resolution; +static MMRESULT timer_id; +static PtCallback *time_callback; + +void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, + DWORD_PTR dw1, DWORD_PTR dw2) +{ + (*time_callback)(Pt_Time(), (void *) dwUser); +} + + +PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +{ + if (time_started_flag) return ptAlreadyStarted; + timeBeginPeriod(resolution); + time_resolution = resolution; + time_offset = timeGetTime(); + time_started_flag = TRUE; + time_callback = callback; + if (callback) { + timer_id = timeSetEvent(resolution, 1, winmm_time_callback, + (DWORD_PTR) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION); + if (!timer_id) return ptHostError; + } + return ptNoError; +} + + +PMEXPORT PtError Pt_Stop() +{ + if (!time_started_flag) return ptAlreadyStopped; + if (time_callback && timer_id) { + timeKillEvent(timer_id); + time_callback = NULL; + timer_id = 0; + } + time_started_flag = FALSE; + timeEndPeriod(time_resolution); + return ptNoError; +} + + +PMEXPORT int Pt_Started() +{ + return time_started_flag; +} + + +PMEXPORT PtTimestamp Pt_Time() +{ + return timeGetTime() - time_offset; +} + + +PMEXPORT void Pt_Sleep(int32_t duration) +{ + Sleep(duration); +} -- cgit v1.2.3