// tempo — a validated project tempo and every beats <-> seconds <-> ms conversion a // beat-denominated capture value resolves through. // // A BEAT IS A QUARTER NOTE — REAPER states project tempo in quarter notes per minute // regardless of time signature, so a division resolves without one. #pragma once #include #include namespace reasampler::instrument::note { inline constexpr double kMsPerSecond = 1000.0; constexpr double msToSeconds(double ms) { return ms / kMsPerSecond; } constexpr double secondsToMs(double seconds) { return seconds * kMsPerSecond; } // The largest magnitude, in beats or in milliseconds, the conversions below are required to // keep finite. `fromBpm` validates against it and every caller caps its own domain to it, so // the two halves of the totality claim meet at one number. Astronomically above anything // musical — a billion milliseconds is eleven days — so nothing real is excluded. inline constexpr double kMaxConvertibleMagnitude = 1e9; class Tempo { public: // Rejects rather than clamps, alone among this module's doors: an unusable BPM has no // nearest usable one to fall to. See this directory's CLAUDE.md for the rule. static std::optional fromBpm(double beatsPerMinute); double bpm() const { return bpm_; } double secondsPerBeat() const; double beatsToSeconds(double beats) const; double secondsToBeats(double seconds) const; double beatsToMs(double beats) const; double msToBeats(double ms) const; private: explicit Tempo(double beatsPerMinute) : bpm_(beatsPerMinute) {} double bpm_; }; static_assert(!std::is_default_constructible_v, "Tempo must not be constructible without a validated BPM"); static_assert(!std::is_constructible_v, "fromBpm must be the only way to give a Tempo a value"); } // namespace reasampler::instrument::note