mirror of
https://github.com/holub/mame
synced 2025-06-03 11:26:56 +03:00
291 lines
9.3 KiB
C
291 lines
9.3 KiB
C
/* fast.c -- send many MIDI messages very fast.
|
|
*
|
|
* This is a stress test created to explore reports of
|
|
* pm_write() call blocking (forever) on Linux when
|
|
* sending very dense MIDI sequences.
|
|
*
|
|
* Modified 8 Aug 2017 with -n to send expired timestamps
|
|
* to test a theory about why Linux ALSA hangs in Audacity.
|
|
*
|
|
* Modified 9 Aug 2017 with -m, -p to test when timestamps are
|
|
* wrapping from negative to positive or positive to negative.
|
|
*
|
|
* Roger B. Dannenberg, Aug 2017
|
|
*/
|
|
|
|
#include "portmidi.h"
|
|
#include "porttime.h"
|
|
#include "stdlib.h"
|
|
#include "stdio.h"
|
|
#include "string.h"
|
|
#include "assert.h"
|
|
|
|
#define DEVICE_INFO NULL
|
|
#define DRIVER_INFO NULL
|
|
#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
|
|
|
|
#define STRING_MAX 80 /* used for console input */
|
|
// need to get declaration for Sleep()
|
|
#ifdef WIN32
|
|
#include "windows.h"
|
|
#else
|
|
#include <unistd.h>
|
|
#define Sleep(n) usleep(n * 1000)
|
|
#endif
|
|
|
|
|
|
int32_t latency = 0;
|
|
int32_t msgrate = 0;
|
|
int deviceno = -9999;
|
|
int duration = 0;
|
|
int expired_timestamps = FALSE;
|
|
int use_timeoffset = 0;
|
|
|
|
/* read a number from console */
|
|
/**/
|
|
int get_number(const char *prompt)
|
|
{
|
|
int n = 0, i;
|
|
fputs(prompt, stdout);
|
|
while (n != 1) {
|
|
n = scanf("%d", &i);
|
|
while (getchar() != '\n') ;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
|
|
/* get_time -- the time reference. Normally, this will be the default
|
|
* time, Pt_Time(), but if you use the -p or -m option, the time
|
|
* reference will start at an offset of -10s for -m, or
|
|
* maximum_time - 10s for -p, so that we can observe what happens
|
|
* with negative time or when time changes sign or wraps (by
|
|
* generating output for more than 10s).
|
|
*/
|
|
PmTimestamp get_time(void *info)
|
|
{
|
|
PmTimestamp now = (PmTimestamp) (Pt_Time() + use_timeoffset);
|
|
return now;
|
|
}
|
|
|
|
|
|
void fast_test()
|
|
{
|
|
PmStream *midi;
|
|
char line[STRING_MAX];
|
|
int pause = FALSE; /* pause if this is a virtual output port */
|
|
PmError err = pmNoError;
|
|
/* output buffer size should be a little more than
|
|
msgrate * latency / 1000. PortMidi will guarantee
|
|
a minimum of latency / 2 */
|
|
int buffer_size = msgrate * latency / 900;
|
|
PmTimestamp start, now;
|
|
int msgcnt = 0;
|
|
int polling_count = 0;
|
|
int pitch = 60;
|
|
int printtime = 1000;
|
|
|
|
/* It is recommended to start timer before PortMidi */
|
|
TIME_START;
|
|
|
|
/* open output device */
|
|
if (deviceno == Pm_CountDevices()) {
|
|
deviceno = Pm_CreateVirtualOutput("fast", NULL, DEVICE_INFO);
|
|
if (deviceno >= 0) {
|
|
err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size,
|
|
get_time, NULL, latency);
|
|
pause = TRUE;
|
|
}
|
|
} else if (err >= pmNoError) {
|
|
err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size,
|
|
get_time, NULL, latency);
|
|
}
|
|
if (err == pmHostError) {
|
|
Pm_GetHostErrorText(line, STRING_MAX);
|
|
printf("PortMidi found host error...\n %s\n", line);
|
|
goto done;
|
|
} else if (err < 0) {
|
|
printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
|
|
goto done;
|
|
}
|
|
printf("Midi Output opened with %ld ms latency.\n", (long) latency);
|
|
if (pause) {
|
|
printf("Pausing so you can connect a receiver to the newly created\n"
|
|
" \"fast\" port. Type ENTER to proceed: ");
|
|
while (getchar() != '\n') ;
|
|
}
|
|
/* wait a sec after printing previous line */
|
|
start = get_time(NULL) + 1000;
|
|
while (start > get_time(NULL)) {
|
|
Sleep(10);
|
|
}
|
|
printf("sending output...\n");
|
|
fflush(stdout); /* make sure message goes to console */
|
|
|
|
/* every 10ms send on/off pairs at timestamps set to current time */
|
|
now = get_time(NULL);
|
|
/* if expired_timestamps, we want to send timestamps that have
|
|
* expired. They should be sent immediately, but there's a suggestion
|
|
* that negative delay might cause problems in the ALSA implementation
|
|
* so this is something we can test using the -n flag.
|
|
*/
|
|
if (expired_timestamps) {
|
|
now = now - 2 * latency;
|
|
}
|
|
|
|
while (((PmTimestamp) (now - start)) < duration * 1000 || pitch != 60) {
|
|
/* how many messages do we send? Total should be
|
|
* (elapsed * rate) / 1000
|
|
*/
|
|
int send_total = (((PmTimestamp) ((now - start))) * msgrate) / 1000;
|
|
/* always send until pitch would be 60 so if we run again, the
|
|
next pitch (60) will be expected */
|
|
if (msgcnt < send_total) {
|
|
if ((msgcnt & 1) == 0) {
|
|
Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 100));
|
|
} else {
|
|
Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 0));
|
|
/* play 60, 61, 62, ... 71, then wrap back to 60, 61, ... */
|
|
pitch = (pitch - 59) % 12 + 60;
|
|
}
|
|
msgcnt += 1;
|
|
if (((PmTimestamp) (now - start)) >= printtime) {
|
|
printf("%d at %dms, polling count %d\n", msgcnt, now - start,
|
|
polling_count);
|
|
fflush(stdout); /* make sure message goes to console */
|
|
printtime += 1000; /* next msg in 1s */
|
|
}
|
|
}
|
|
now = get_time(NULL);
|
|
polling_count++;
|
|
}
|
|
/* close device (this not explicitly needed in most implementations) */
|
|
printf("ready to close and terminate... (type RETURN):");
|
|
while (getchar() != '\n') ;
|
|
|
|
Pm_Close(midi);
|
|
done:
|
|
Pm_Terminate();
|
|
printf("done closing and terminating...\n");
|
|
}
|
|
|
|
|
|
void show_usage()
|
|
{
|
|
printf("Usage: fast [-h] [-l latency] [-r rate] [-d device] [-s dur] "
|
|
"[-n] [-p] [-m]\n"
|
|
", where latency is in ms,\n"
|
|
" rate is messages per second,\n"
|
|
" device is the PortMidi device number,\n"
|
|
" dur is the length of the test in seconds,\n"
|
|
" -n means send timestamps in the past,\n"
|
|
" -p means use a large positive time offset,\n"
|
|
" -m means use a large negative time offset, and\n"
|
|
" -h means help.\n");
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int default_in;
|
|
int default_out;
|
|
char *deflt;
|
|
int i = 0;
|
|
int latency_valid = FALSE;
|
|
int rate_valid = FALSE;
|
|
int device_valid = FALSE;
|
|
int dur_valid = FALSE;
|
|
|
|
if (sizeof(void *) == 8)
|
|
printf("Apparently this is a 64-bit machine.\n");
|
|
else if (sizeof(void *) == 4)
|
|
printf ("Apparently this is a 32-bit machine.\n");
|
|
|
|
if (argc <= 1) {
|
|
show_usage();
|
|
} else {
|
|
for (i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "-h") == 0) {
|
|
show_usage();
|
|
} else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
|
|
i = i + 1;
|
|
latency = atoi(argv[i]);
|
|
printf("Latency will be %ld\n", (long) latency);
|
|
latency_valid = TRUE;
|
|
} else if (strcmp(argv[i], "-r") == 0) {
|
|
i = i + 1;
|
|
msgrate = atoi(argv[i]);
|
|
printf("Rate will be %d messages/second\n", msgrate);
|
|
rate_valid = TRUE;
|
|
} else if (strcmp(argv[i], "-d") == 0) {
|
|
i = i + 1;
|
|
deviceno = atoi(argv[i]);
|
|
printf("Device will be %d\n", deviceno);
|
|
} else if (strcmp(argv[i], "-s") == 0) {
|
|
i = i + 1;
|
|
duration = atoi(argv[i]);
|
|
printf("Duration will be %d seconds\n", duration);
|
|
dur_valid = TRUE;
|
|
} else if (strcmp(argv[i], "-n") == 0) {
|
|
printf("Sending expired timestamps (-n)\n");
|
|
expired_timestamps = TRUE;
|
|
} else if (strcmp(argv[i], "-p") == 0) {
|
|
printf("Time offset set to 2147473648 (-p)\n");
|
|
use_timeoffset = 2147473648;
|
|
} else if (strcmp(argv[i], "-m") == 0) {
|
|
printf("Time offset set to -10000 (-m)\n");
|
|
use_timeoffset = -10000;
|
|
} else {
|
|
show_usage();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!latency_valid) {
|
|
// coerce to known size
|
|
latency = (int32_t) get_number("Latency in ms: ");
|
|
}
|
|
|
|
if (!rate_valid) {
|
|
// coerce from "%d" to known size
|
|
msgrate = (int32_t) get_number("Rate in messages per second: ");
|
|
}
|
|
|
|
if (!dur_valid) {
|
|
duration = get_number("Duration in seconds: ");
|
|
}
|
|
|
|
/* list device information */
|
|
default_in = Pm_GetDefaultInputDeviceID();
|
|
default_out = Pm_GetDefaultOutputDeviceID();
|
|
for (i = 0; i < Pm_CountDevices(); i++) {
|
|
const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
|
|
if (info->output) {
|
|
printf("%d: %s, %s", i, info->interf, info->name);
|
|
if (i == deviceno) {
|
|
device_valid = TRUE;
|
|
deflt = "selected ";
|
|
} else if (i == default_out) {
|
|
deflt = "default ";
|
|
} else {
|
|
deflt = "";
|
|
}
|
|
printf(" (%soutput)\n", deflt);
|
|
}
|
|
}
|
|
printf("%d: Create virtual port named \"fast\"", i);
|
|
if (i == deviceno) {
|
|
device_valid = TRUE;
|
|
deflt = "selected ";
|
|
} else {
|
|
deflt = "";
|
|
}
|
|
printf(" (%soutput)\n", deflt);
|
|
|
|
if (!device_valid) {
|
|
deviceno = get_number("Output device number: ");
|
|
}
|
|
|
|
fast_test();
|
|
return 0;
|
|
}
|