@@ -50,20 +50,20 @@ struct Engine { | |||||
/** Returns the estimated timestamp corresponding to the current frame, based on the timestamp of when step() was last called. | /** Returns the estimated timestamp corresponding to the current frame, based on the timestamp of when step() was last called. | ||||
Calculated by `stepTime + framesSinceStep / sampleRate`. | Calculated by `stepTime + framesSinceStep / sampleRate`. | ||||
*/ | */ | ||||
int64_t getFrameTime(); | |||||
double getFrameTime(); | |||||
/** Returns the frame when step() was last called. | /** Returns the frame when step() was last called. | ||||
*/ | */ | ||||
int64_t getStepFrame(); | int64_t getStepFrame(); | ||||
/** Returns the timestamp in nanoseconds when step() was last called. | |||||
/** Returns the timestamp in seconds when step() was last called. | |||||
*/ | */ | ||||
int64_t getStepTime(); | |||||
double getStepTime(); | |||||
/** Returns the total number of frames in the current step() call. | /** Returns the total number of frames in the current step() call. | ||||
*/ | */ | ||||
int getStepFrames(); | int getStepFrames(); | ||||
/** Returns the total time that step() is advancing, in nanoseconds. | |||||
/** Returns the total time that step() is advancing, in seconds. | |||||
Calculated by `stepFrames / sampleRate`. | Calculated by `stepFrames / sampleRate`. | ||||
*/ | */ | ||||
int64_t getStepDuration(); | |||||
double getStepDuration(); | |||||
// Modules | // Modules | ||||
size_t getNumModules(); | size_t getNumModules(); | ||||
@@ -20,8 +20,8 @@ namespace midi { | |||||
struct Message { | struct Message { | ||||
/** Initialized to 3 empty bytes. */ | /** Initialized to 3 empty bytes. */ | ||||
std::vector<uint8_t> bytes; | std::vector<uint8_t> bytes; | ||||
/** Timestamp of MIDI message in nanoseconds. Negative if not set. */ | |||||
int64_t timestamp = -1; | |||||
/** Timestamp of MIDI message in nanoseconds. NAN if not set. */ | |||||
double timestamp = NAN; | |||||
Message() : bytes(3) {} | Message() : bytes(3) {} | ||||
@@ -131,11 +131,10 @@ void setThreadName(const std::string& name); | |||||
/** Returns the caller's human-readable stack trace with "\n"-separated lines. */ | /** Returns the caller's human-readable stack trace with "\n"-separated lines. */ | ||||
std::string getStackTrace(); | std::string getStackTrace(); | ||||
/** Returns the current number of nanoseconds since the epoch. | |||||
The goal of this function is to give the most precise (fine-grained) time available on the OS for benchmarking purposes, while being fast to compute. | |||||
The epoch is undefined. Do not use this function to get absolute time, as it is different on each OS. | |||||
/** Returns the number of seconds since application launch. | |||||
The goal of this function is to give the most precise (fine-grained) time differences available on the OS for benchmarking purposes, while being fast to compute. | |||||
*/ | */ | ||||
int64_t getNanoseconds(); | |||||
double getTime(); | |||||
std::string getOperatingSystemInfo(); | std::string getOperatingSystemInfo(); | ||||
// Applications | // Applications | ||||
@@ -152,6 +151,8 @@ The launched process will continue running if the current process is closed. | |||||
*/ | */ | ||||
void runProcessDetached(const std::string& path); | void runProcessDetached(const std::string& path); | ||||
void init(); | |||||
} // namespace system | } // namespace system | ||||
} // namespace rack | } // namespace rack |
@@ -7,7 +7,7 @@ namespace core { | |||||
struct CCMidiOutput : midi::Output { | struct CCMidiOutput : midi::Output { | ||||
int lastValues[128]; | int lastValues[128]; | ||||
int64_t timestamp = -1; | |||||
double timestamp = 0.0; | |||||
CCMidiOutput() { | CCMidiOutput() { | ||||
reset(); | reset(); | ||||
@@ -33,7 +33,7 @@ struct CCMidiOutput : midi::Output { | |||||
sendMessage(m); | sendMessage(m); | ||||
} | } | ||||
void setTimestamp(int64_t timestamp) { | |||||
void setTimestamp(double timestamp) { | |||||
this->timestamp = timestamp; | this->timestamp = timestamp; | ||||
} | } | ||||
}; | }; | ||||
@@ -8,7 +8,7 @@ namespace core { | |||||
struct GateMidiOutput : midi::Output { | struct GateMidiOutput : midi::Output { | ||||
int vels[128]; | int vels[128]; | ||||
bool lastGates[128]; | bool lastGates[128]; | ||||
int64_t timestamp = -1; | |||||
double timestamp = 0.0; | |||||
GateMidiOutput() { | GateMidiOutput() { | ||||
reset(); | reset(); | ||||
@@ -62,7 +62,7 @@ struct GateMidiOutput : midi::Output { | |||||
lastGates[note] = gate; | lastGates[note] = gate; | ||||
} | } | ||||
void setTimestamp(int64_t timestamp) { | |||||
void setTimestamp(double timestamp) { | |||||
this->timestamp = timestamp; | this->timestamp = timestamp; | ||||
} | } | ||||
}; | }; | ||||
@@ -198,7 +198,7 @@ struct Engine::Internal { | |||||
float sampleTime = 0.f; | float sampleTime = 0.f; | ||||
int64_t frame = 0; | int64_t frame = 0; | ||||
int64_t stepFrame = 0; | int64_t stepFrame = 0; | ||||
int64_t stepTime = 0; | |||||
double stepTime = 0.0; | |||||
int stepFrames = 0; | int stepFrames = 0; | ||||
Module* primaryModule = NULL; | Module* primaryModule = NULL; | ||||
@@ -338,9 +338,9 @@ static void Engine_stepModulesWorker(Engine* that, int threadId) { | |||||
Module* module = internal->modules[i]; | Module* module = internal->modules[i]; | ||||
// Start CPU timer | // Start CPU timer | ||||
int64_t startTime; | |||||
double startTime; | |||||
if (cpuMeter) { | if (cpuMeter) { | ||||
startTime = system::getNanoseconds(); | |||||
startTime = system::getTime(); | |||||
} | } | ||||
// Step module | // Step module | ||||
@@ -351,8 +351,8 @@ static void Engine_stepModulesWorker(Engine* that, int threadId) { | |||||
// Stop CPU timer | // Stop CPU timer | ||||
if (cpuMeter) { | if (cpuMeter) { | ||||
int64_t endTime = system::getNanoseconds(); | |||||
float duration = (endTime - startTime) * 1e-9f; | |||||
double endTime = system::getTime(); | |||||
float duration = (endTime - startTime) / 1e9; | |||||
// Smooth CPU time | // Smooth CPU time | ||||
const float cpuTau = 2.f /* seconds */; | const float cpuTau = 2.f /* seconds */; | ||||
@@ -564,7 +564,7 @@ void Engine::step(int frames) { | |||||
random::init(); | random::init(); | ||||
internal->stepFrame = internal->frame; | internal->stepFrame = internal->frame; | ||||
internal->stepTime = system::getNanoseconds(); | |||||
internal->stepTime = system::getTime(); | |||||
internal->stepFrames = frames; | internal->stepFrames = frames; | ||||
// Set sample rate | // Set sample rate | ||||
@@ -635,9 +635,9 @@ int64_t Engine::getFrame() { | |||||
} | } | ||||
int64_t Engine::getFrameTime() { | |||||
double Engine::getFrameTime() { | |||||
double timeSinceStep = (internal->frame - internal->stepFrame) * internal->sampleTime; | double timeSinceStep = (internal->frame - internal->stepFrame) * internal->sampleTime; | ||||
return internal->stepTime + int64_t(timeSinceStep * 1e9); | |||||
return internal->stepTime + timeSinceStep; | |||||
} | } | ||||
@@ -646,7 +646,7 @@ int64_t Engine::getStepFrame() { | |||||
} | } | ||||
int64_t Engine::getStepTime() { | |||||
double Engine::getStepTime() { | |||||
return internal->stepTime; | return internal->stepTime; | ||||
} | } | ||||
@@ -656,9 +656,8 @@ int Engine::getStepFrames() { | |||||
} | } | ||||
int64_t Engine::getStepDuration() { | |||||
double duration = internal->stepFrames * internal->sampleTime; | |||||
return int64_t(duration * 1e9); | |||||
double Engine::getStepDuration() { | |||||
return internal->stepFrames * internal->sampleTime; | |||||
} | } | ||||
@@ -12,12 +12,12 @@ namespace logger { | |||||
static FILE* outputFile = NULL; | static FILE* outputFile = NULL; | ||||
static int64_t startTime = 0; | |||||
static double startTime = 0.0; | |||||
static std::mutex logMutex; | static std::mutex logMutex; | ||||
void init() { | void init() { | ||||
startTime = system::getNanoseconds(); | |||||
startTime = system::getTime(); | |||||
// Don't open a file in development mode. | // Don't open a file in development mode. | ||||
if (settings::devMode) { | if (settings::devMode) { | ||||
outputFile = stderr; | outputFile = stderr; | ||||
@@ -64,8 +64,8 @@ static void logVa(Level level, const char* filename, int line, const char* func, | |||||
if (!outputFile) | if (!outputFile) | ||||
return; | return; | ||||
int64_t nowTime = system::getNanoseconds(); | |||||
double duration = (nowTime - startTime) / 1e9; | |||||
double nowTime = system::getTime(); | |||||
double duration = nowTime - startTime; | |||||
if (outputFile == stderr) | if (outputFile == stderr) | ||||
std::fprintf(outputFile, "\x1B[%dm", levelColors[level]); | std::fprintf(outputFile, "\x1B[%dm", levelColors[level]); | ||||
std::fprintf(outputFile, "[%.03f %s %s:%d %s] ", duration, levelLabels[level], filename, line, func); | std::fprintf(outputFile, "[%.03f %s %s:%d %s] ", duration, levelLabels[level], filename, line, func); | ||||
@@ -30,8 +30,8 @@ void InputDevice::unsubscribe(Input* input) { | |||||
void InputDevice::onMessage(const Message &message) { | void InputDevice::onMessage(const Message &message) { | ||||
// Set timestamp to now if unset | // Set timestamp to now if unset | ||||
Message msg = message; | Message msg = message; | ||||
if (msg.timestamp < 0) | |||||
msg.timestamp = system::getNanoseconds(); | |||||
if (msg.timestamp == 0.0) | |||||
msg.timestamp = system::getTime(); | |||||
for (Input* input : subscribed) { | for (Input* input : subscribed) { | ||||
// We're probably in the MIDI driver's thread, so set the Rack context. | // We're probably in the MIDI driver's thread, so set the Rack context. | ||||
@@ -69,11 +69,11 @@ void PatchManager::save(std::string path) { | |||||
// Take screenshot (disabled because there is currently no way to quickly view them on any OS or website.) | // Take screenshot (disabled because there is currently no way to quickly view them on any OS or website.) | ||||
// APP->window->screenshot(system::join(asset::autosavePath, "screenshot.png")); | // APP->window->screenshot(system::join(asset::autosavePath, "screenshot.png")); | ||||
uint64_t startTime = system::getNanoseconds(); | |||||
double startTime = system::getTime(); | |||||
// Set compression level to 1 so that a 500MB/s SSD is almost bottlenecked | // Set compression level to 1 so that a 500MB/s SSD is almost bottlenecked | ||||
system::archiveFolder(path, asset::autosavePath, 1); | system::archiveFolder(path, asset::autosavePath, 1); | ||||
uint64_t endTime = system::getNanoseconds(); | |||||
INFO("Archived patch in %lf seconds", (endTime - startTime) / 1e9); | |||||
double endTime = system::getTime(); | |||||
INFO("Archived patch in %lf seconds", (endTime - startTime)); | |||||
} | } | ||||
@@ -222,10 +222,10 @@ void PatchManager::load(std::string path) { | |||||
} | } | ||||
else { | else { | ||||
// Extract the .vcv file as a .tar.zst archive. | // Extract the .vcv file as a .tar.zst archive. | ||||
uint64_t startTime = system::getNanoseconds(); | |||||
double startTime = system::getTime(); | |||||
system::unarchiveToFolder(path, asset::autosavePath); | system::unarchiveToFolder(path, asset::autosavePath); | ||||
uint64_t endTime = system::getNanoseconds(); | |||||
INFO("Unarchived patch in %lf seconds", (endTime - startTime) / 1e9); | |||||
double endTime = system::getTime(); | |||||
INFO("Unarchived patch in %lf seconds", (endTime - startTime)); | |||||
} | } | ||||
loadAutosave(); | loadAutosave(); | ||||
@@ -148,12 +148,14 @@ struct RtMidiOutputDevice : midi::OutputDevice { | |||||
else { | else { | ||||
// Get earliest message | // Get earliest message | ||||
const midi::Message& message = messageQueue.top(); | const midi::Message& message = messageQueue.top(); | ||||
int64_t duration = message.timestamp - system::getNanoseconds(); | |||||
double duration = message.timestamp - system::getTime(); | |||||
// If we need to wait, release the lock and wait for the timeout, or if the CV is notified. | // If we need to wait, release the lock and wait for the timeout, or if the CV is notified. | ||||
// This correctly handles MIDI messages with no timestamp, because duration will be negative. | |||||
if ((duration > 0) && cv.wait_for(lock, std::chrono::nanoseconds(duration)) != std::cv_status::timeout) | |||||
continue; | |||||
// This correctly handles MIDI messages with no timestamp, because duration will be NAN. | |||||
if (duration > 0) { | |||||
if (cv.wait_for(lock, std::chrono::duration<double>(duration)) != std::cv_status::timeout) | |||||
continue; | |||||
} | |||||
// Send and remove from queue | // Send and remove from queue | ||||
sendMessageNow(message); | sendMessageNow(message); | ||||
@@ -18,6 +18,8 @@ | |||||
#if defined ARCH_MAC | #if defined ARCH_MAC | ||||
#include <mach/mach_init.h> | #include <mach/mach_init.h> | ||||
#include <mach/thread_act.h> | #include <mach/thread_act.h> | ||||
#include <mach/clock.h> | |||||
#include <mach/mach.h> | |||||
#endif | #endif | ||||
#if defined ARCH_WIN | #if defined ARCH_WIN | ||||
@@ -526,30 +528,62 @@ std::string getStackTrace() { | |||||
} | } | ||||
int64_t getNanoseconds() { | |||||
#if defined ARCH_WIN | #if defined ARCH_WIN | ||||
static int64_t startCounter = 0; | |||||
static double counterTime = 0.0; | |||||
#endif | |||||
#if defined ARCH_LIN || defined ARCH_MAC | |||||
static int64_t startTime = 0; | |||||
#endif | |||||
static void initTime() { | |||||
#if defined ARCH_WIN | |||||
assert(startCounter == 0); | |||||
LARGE_INTEGER counter; | LARGE_INTEGER counter; | ||||
QueryPerformanceCounter(&counter); | QueryPerformanceCounter(&counter); | ||||
startCounter = counter.QuadPart; | |||||
LARGE_INTEGER frequency; | LARGE_INTEGER frequency; | ||||
QueryPerformanceFrequency(&frequency); | QueryPerformanceFrequency(&frequency); | ||||
// TODO Check if this is always an integer factor on all CPUs | |||||
int64_t nsPerTick = 1000000000LL / frequency.QuadPart; | |||||
int64_t time = counter.QuadPart * nsPerTick; | |||||
return time; | |||||
counterTime = 1.0 / frequency.QuadPart; | |||||
#endif | |||||
#if defined ARCH_LIN | |||||
assert(startTime == 0); | |||||
struct timespec ts; | |||||
clock_gettime(CLOCK_MONOTONIC_RAW, &ts); | |||||
startTime = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec; | |||||
#endif | |||||
#if defined ARCH_MAC | |||||
assert(startTime == 0); | |||||
clock_serv_t cclock; | |||||
mach_timespec_t mts; | |||||
host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); | |||||
clock_get_time(cclock, &mts); | |||||
mach_port_deallocate(mach_task_self(), cclock); | |||||
startTime = int64_t(mts.tv_sec) * 1000000000LL + mts.tv_nsec; | |||||
#endif | |||||
} | |||||
double getTime() { | |||||
#if defined ARCH_WIN | |||||
LARGE_INTEGER counter; | |||||
QueryPerformanceCounter(&counter); | |||||
return (counter.QuadPart - startCounter) * counterTime; | |||||
#endif | #endif | ||||
#if defined ARCH_LIN | #if defined ARCH_LIN | ||||
struct timespec ts; | struct timespec ts; | ||||
clock_gettime(CLOCK_MONOTONIC_RAW, &ts); | clock_gettime(CLOCK_MONOTONIC_RAW, &ts); | ||||
int64_t time = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec; | int64_t time = int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec; | ||||
return time; | |||||
return (time - startTime) / 1e9; | |||||
#endif | #endif | ||||
#if defined ARCH_MAC | #if defined ARCH_MAC | ||||
using clock = std::chrono::high_resolution_clock; | |||||
using time_point = std::chrono::time_point<clock>; | |||||
time_point now = clock::now(); | |||||
using duration = std::chrono::duration<int64_t, std::nano>; | |||||
duration d = now.time_since_epoch(); | |||||
return d.count(); | |||||
clock_serv_t cclock; | |||||
mach_timespec_t mts; | |||||
host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); | |||||
clock_get_time(cclock, &mts); | |||||
mach_port_deallocate(mach_task_self(), cclock); | |||||
int64_t time = int64_t(mts.tv_sec) * 1000000000LL + mts.tv_nsec; | |||||
return (time - startTime) / 1e9; | |||||
#endif | #endif | ||||
} | } | ||||
@@ -621,5 +655,10 @@ void runProcessDetached(const std::string& path) { | |||||
} | } | ||||
void init() { | |||||
initTime(); | |||||
} | |||||
} // namespace system | } // namespace system | ||||
} // namespace rack | } // namespace rack |
@@ -108,10 +108,15 @@ int main(int argc, char* argv[]) { | |||||
} | } | ||||
// Initialize environment | // Initialize environment | ||||
system::init(); | |||||
asset::init(); | asset::init(); | ||||
bool loggerWasTruncated = logger::isTruncated(); | bool loggerWasTruncated = logger::isTruncated(); | ||||
logger::init(); | logger::init(); | ||||
for (int i = 0; i < 100; i++) | |||||
DEBUG("%lf", system::getTime() * 1e9); | |||||
exit(0); | |||||
// We can now install a signal handler and log the output | // We can now install a signal handler and log the output | ||||
if (!settings::devMode) { | if (!settings::devMode) { | ||||
signal(SIGABRT, fatalSignalHandler); | signal(SIGABRT, fatalSignalHandler); | ||||