| @@ -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); | ||||