#define _POSIX_C_SOURCE 200809L /* Offline workload timing, not hardware or hard realtime qualification. */ #include "xtc/xtc_midi.h" #include "xtc/xtc_mixer.h" #include #include #include #include #include #include #define REQUIRE(c) \ do { \ if (!(c)) { \ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #c); \ goto cleanup; \ } \ } while (0) static double now(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return (double)t.tv_sec + (double)t.tv_nsec / 1e9; } static double cpu_now(void) { struct timespec t; if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t)) abort(); return (double)t.tv_sec + (double)t.tv_nsec / 1e9; } static int compare(const void *left, const void *right) { double a = *(const double *)left, b = *(const double *)right; return a < b ? -1 : a > b; } int main(int argc, char **argv) { xtc_project *project = NULL; xtc_graph *graph = NULL; xtc_plan *plan = NULL; xtc_midi_plan *midi = NULL; long blocks = 2000; char *end; size_t i, j, source, previous, node, tracks = 16, frames = 128, misses = 0; size_t point_count, note_count; uint64_t position = 0, wraps = 0; struct rusage usage; uint64_t track, clip, note, future; float left[1024], right[1024], out_left[1024], out_right[1024]; double *times = NULL, total = 0, begin, prepare_ms, cpu_begin, cpu; int result = EXIT_FAILURE; if (argc != 1 && argc != 2 && argc != 4) return EXIT_FAILURE; if (argc >= 2) { blocks = strtol(argv[1], &end, 10); if (!*argv[1] || *end || blocks < 1 || blocks > 10000000) return EXIT_FAILURE; } if (argc == 4) { long value = strtol(argv[2], &end, 10); if (!*argv[2] || *end || value < 1 || value > 64) return EXIT_FAILURE; tracks = (size_t)value; value = strtol(argv[3], &end, 10); if (!*argv[3] || *end || value < 1 || value > 1024) return EXIT_FAILURE; frames = (size_t)value; } times = calloc((size_t)blocks, sizeof(*times)); REQUIRE(times); begin = now(); REQUIRE(xtc_project_create(&project) == XTC_OK); for (i = 0; i < tracks; ++i) { xtc_automation_point point; REQUIRE(xtc_project_add_track(project, "Performance", &track) == XTC_OK); REQUIRE(xtc_project_add_midi_clip(project, track, 0, 384000, &clip) == XTC_OK); REQUIRE(xtc_project_set_mix(project, track, 0.1f, i % 2 ? 0.5f : -0.5f, 0, 0) == XTC_OK); for (j = 0; j < 2; ++j) REQUIRE(xtc_project_add_note(project, clip, i < 16 ? 0 : 380000 + i * 2, i < 16 ? 370000 : 1, 48 + (unsigned int)i, 100, 0, ¬e) == XTC_OK); point = (xtc_automation_point){track, 0, XTC_AUTOMATION_GAIN, 0.1f,0}; REQUIRE(xtc_project_set_automation(project, &point) == XTC_OK); for (j = 1; j < 16; ++j) { point.tick = j * 25600; point.value = j % 2 ? 0.2f : 0.1f; REQUIRE(xtc_project_set_automation(project, &point) == XTC_OK); } } REQUIRE(xtc_project_add_midi_clip(project, track, 1000000, 10000, &future) == XTC_OK); for (i = tracks * 2; i < tracks * 64; ++i) REQUIRE(xtc_project_add_note(project, future, (i - tracks * 2) * 2, 1, 60, 100, 0, ¬e) == XTC_OK); REQUIRE(xtc_graph_create(64, &graph) == XTC_OK && xtc_graph_add_instrument(graph, &source) == XTC_OK); previous = source; for (i = 0; i < 61; ++i) { REQUIRE(xtc_graph_add_gain(graph, 0.99f, &node) == XTC_OK && xtc_graph_connect(graph, previous, node) == XTC_OK); previous = node; } REQUIRE(xtc_graph_add_meter(graph, &node) == XTC_OK && xtc_graph_connect(graph, previous, node) == XTC_OK); previous = node; REQUIRE(xtc_graph_add_output(graph, &node) == XTC_OK && xtc_graph_connect(graph, previous, node) == XTC_OK); REQUIRE(xtc_graph_compile(graph, 48000, frames, &plan) == XTC_OK && xtc_midi_plan_create(project, 48000, &midi) == XTC_OK); prepare_ms = (now() - begin) * 1000; REQUIRE(xtc_project_note_count(project, ¬e_count) == XTC_OK); REQUIRE(xtc_project_automation_count(project, &point_count) == XTC_OK); cpu_begin = cpu_now(); for (i = 0; i < (size_t)blocks; ++i) { begin = now(); REQUIRE(xtc_midi_plan_render_stereo(midi, position, NULL, 0, left, right, frames) == XTC_OK); REQUIRE(xtc_plan_process_stereo(plan, NULL, NULL, NULL, left, right, out_left, out_right, frames) == XTC_OK); times[i] = now() - begin; total += times[i]; if (times[i] > (double)frames / 48000) ++misses; position += frames; /* Revisit the active arrangement in long offline runs. Explicit sample * positions exercise discontinuous seek/wrap handling in the MIDI plan. */ if (position >= 96000) { position = 0; ++wraps; } for (j = 0; j < frames; ++j) REQUIRE(isfinite(out_left[j]) && isfinite(out_right[j])); } cpu = cpu_now() - cpu_begin; REQUIRE(getrusage(RUSAGE_SELF, &usage) == 0); qsort(times, (size_t)blocks, sizeof(*times), compare); printf("{\"tracks\":%zu,\"notes\":%zu,\"points\":%zu,\"nodes\":64," "\"rate\":48000,\"frames\":%zu,\"blocks\":%ld,\"wraps\":%" PRIu64 "," "\"prepare_ms\":%.6f,\"mean_us\":%.6f,\"p99_us\":%.6f,\"max_us\":%.6f," "\"deadline_us\":%.6f,\"deadline_misses\":%zu,\"process_cpu_ms\":%.6f," "\"cpu_audio_ratio\":%.6f,\"peak_rss_kib\":%ld}\n", tracks, note_count, point_count, frames, blocks, wraps, prepare_ms, total / (double)blocks * 1e6, times[(size_t)blocks * 99 / 100] * 1e6, times[blocks - 1] * 1e6, (double)frames / 48000 * 1e6, misses, cpu * 1e3, cpu / ((double)blocks * (double)frames / 48000), usage.ru_maxrss); result = EXIT_SUCCESS; cleanup: free(times); xtc_midi_plan_destroy(midi); xtc_plan_destroy(plan); xtc_graph_destroy(graph); xtc_project_destroy(project); return result; }