frequency: frequency/frequency.hpp Source File

1#pragma once

2

8#include <cmath>

9#include <compare>

10#include <concepts>

11#include <cstdint>

12#include <limits>

13#include <ratio>

14#include <string>

15#ifndef CONFIG_FREQUENCY_STD_FORMAT

16#if __has_include(<format>) && defined(__cpp_lib_format)

17#define CONFIG_FREQUENCY_STD_FORMAT 1

18#else

19#define CONFIG_FREQUENCY_STD_FORMAT 0

20#endif

21#endif

22

23#if CONFIG_FREQUENCY_STD_FORMAT

24#include <format>

25#endif

26#include <assert.h>

27

89

90template<typename Rep, typename Precision = std::ratio<1>>

92

97template<typename T>

99

100template<typename Rep, typename Precision>

102

103template<typename T>

105

107template<typename T, template<typename...> class Template>

108struct _is_specialization_of : std::false_type {};

109

110template<template<typename...> class Template, typename... Args>

111struct _is_specialization_of<Template<Args...>, Template> : std::true_type {};

112

113template<typename T, template<typename...> class Template>

114inline constexpr bool _is_specialization_of_v = _is_specialization_of<T, Template>::value;

115

116template<typename Rep>

117concept not_frequency = !_is_specialization_of_v<Rep, frequency>;

118

127template<typename T>

128concept duration_like = requires(T t) {

129 { t.count() };

130 typename T::period;

131 typename T::rep;

132};

133

143template<typename T>

144concept distance_like = requires(T t) {

145 { t.count() };

146 typename T::period;

147 typename T::rep;

148};

149

154template<typename Rep>

155struct frequency_values {

157 static constexpr Rep zero() noexcept { return Rep(0); }

158

160 static constexpr Rep max() noexcept { return std::numeric_limits<Rep>::max(); }

161

163 static constexpr Rep min() noexcept { return std::numeric_limits<Rep>::lowest(); }

164};

165

166template<typename T>

167struct _is_ratio : std::false_type {};

168

169template<std::intmax_t Num, std::intmax_t Denom>

170struct _is_ratio<std::ratio<Num, Denom>> : std::true_type {};

171

193template<typename T>

194struct treat_as_inexact : std::bool_constant<std::floating_point<T>> {};

195

196template<typename T>

197inline constexpr bool treat_as_inexact_v = treat_as_inexact<T>::value;

198

199consteval intmax_t _gcd(intmax_t m, intmax_t n) noexcept {

200 while (n != 0) {

201 intmax_t rem = m % n;

202 m = n;

203 n = rem;

204 }

205 return m;

206}

207

208

209template<typename T>

210constexpr T _runtime_gcd(T m, T n) noexcept {

211 if (m < 0) {

212 m = -m;

213 }

214 if (n < 0) {

215 n = -n;

216 }

217 while (n != 0) {

218 T rem = m % n;

219 m = n;

220 n = rem;

221 }

222 return m;

223}

224

225template<typename R1, typename R2>

226inline constexpr intmax_t _safe_ratio_divide_den = [] {

227 constexpr intmax_t g1 = _gcd(R1::num, R2::num);

228 constexpr intmax_t g2 = _gcd(R1::den, R2::den);

229 return (R1::den / g2) * (R2::num / g1);

230}();

231

232template<typename From, typename To>

233concept _harmonic_precision = _safe_ratio_divide_den<From, To> == 1;

280template<typename Rep, typename Precision>

283 static_assert(_is_ratio<Precision>::value, "precision must be a specialization of std::ratio");

284 static_assert(Precision::num > 0, "precision must be positive");

285

286public:

291

295

305 template<typename Rep2>

306 requires std::convertible_to<const Rep2&, rep> && (treat_as_inexact_v<rep> || !treat_as_inexact_v<Rep2>)

308 : _r(static_cast<rep>(r)) {}

309

320 template<typename Rep2, typename Precision2>

321 requires std::convertible_to<const Rep2&, rep> &&

322 (treat_as_inexact_v<rep> || (_harmonic_precision<Precision2, precision> && !treat_as_inexact_v<Rep2>))

325

333 template<typename Rep2, typename Precision2>

334 requires(!std::is_same_v<frequency, frequency<Rep2, Precision2>>) && (!treat_as_inexact_v<rep>) &&

335 (!_harmonic_precision<Precision2, precision>)

338

341

343 constexpr rep count() const { return _r; }

344

348

352

354 ++_r;

355 return *this;

356 }

357

359

361 --_r;

362 return *this;

363 }

364

366

368 _r += f.count();

369 return *this;

370 }

371

373 _r -= f.count();

374 return *this;

375 }

376

378 _r *= r;

379 return *this;

380 }

381

383 _r /= r;

384 return *this;

385 }

386

389 {

390 _r %= r;

391 return *this;

392 }

393

396 {

397 _r %= f.count();

398 return *this;

399 }

400

403

406

409

441 template<duration_like Duration>

443

444

447 using cf = std::ratio_divide<period_ratio, duration_period>;

449

450 if (_r == rep(0)) {

451 return Duration::max();

452 }

453

454

455 if constexpr (std::is_integral_v<rep> && std::is_integral_v<duration_rep>) {

456#ifdef __SIZEOF_INT128__

457 using cr = std::common_type_t<duration_rep, rep, intmax_t>;

459#else

461#endif

462

464

465 if constexpr (cf::den == 1 && cf::num == 1) {

466

468 } else if constexpr (cf::den == 1) {

469

470

476 } else if constexpr (cf::num == 1) {

477

480 } else {

481

482

489 }

490 } else {

491

492 using cr = std::common_type_t<duration_rep, double>;

493

494 if constexpr (cf::den == 1 && cf::num == 1) {

496 } else if constexpr (cf::den == 1) {

497 return Duration(static_cast<duration_rep>(static_cast<cr>(cf::num) / static_cast<double>(_r)));

498 } else if constexpr (cf::num == 1) {

499 return Duration(static_cast<duration_rep>(1.0 / (static_cast<double>(_r) * static_cast<cr>(cf::den))));

500 } else {

503 static_cast<cr>(cf::num) / (static_cast<double>(_r) * static_cast<cr>(cf::den))

504 )

505 );

506 }

507 }

508 }

509

523

537 assert(n > 0 && "subharmonic divisor must be positive");

538 return *this / n;

539 }

540

564 template<typename T = double>

567 if constexpr (std::is_integral_v<rep>) {

568 return frequency(static_cast<rep>(std::round(static_cast<double>(_r) * multiplier)));

569 } else {

571 }

572 }

573

598 template<typename T = double>

600 double multiplier = std::pow(2.0, static_cast<double>(semitones) / 12.0);

601 if constexpr (std::is_integral_v<rep>) {

602 return frequency(static_cast<rep>(std::round(static_cast<double>(_r) * multiplier)));

603 } else {

605 }

606 }

607

619 template<typename T = double>

621 return static_cast<T>(std::log2(static_cast<double>(_r) / static_cast<double>(other._r)));

622 }

623

635 template<typename T = double>

637 return static_cast<T>(12.0 * std::log2(static_cast<double>(_r) / static_cast<double>(other._r)));

638 }

639

657 template<distance_like Distance, duration_like Duration>

659

662

663 if (_r == rep(0)) {

664 return Distance::max();

665 }

666

667

669

670

672 using time_cf = std::ratio_divide<period_ratio, duration_period>;

673 using time_cr = std::common_type_t<period_rep, typename Duration::rep, double>;

674

675

677 if constexpr (time_cf::den == 1 && time_cf::num == 1) {

679 } else if constexpr (time_cf::den == 1) {

681 } else if constexpr (time_cf::num == 1) {

683 } else {

685 static_cast<time_cr>(time_cf::den);

686 }

687

688

690

692 }

693

709 template<distance_like Distance>

711 if (_r == rep(0)) {

712 return Distance::max();

713 }

714

715

717 static_cast<double>(_r) * static_cast<double>(precision::num) / static_cast<double>(precision::den);

718

719

721

722

723

726 wavelength_meters * static_cast<double>(distance_period::den) / static_cast<double>(distance_period::num);

727

729 }

730

731private:

732 rep _r{};

733};

734

748template<typename ToFreq, typename Rep, typename Precision>

750 if constexpr (std::is_same_v<ToFreq, frequency<Rep, Precision>>) {

751 return f;

752 } else {

753 using to_rep = typename ToFreq::rep;

754 using to_precision = typename ToFreq::precision;

755 using cf = std::ratio_divide<Precision, to_precision>;

756

757

758 if constexpr (std::is_integral_v<Rep> && std::is_integral_v<to_rep>) {

759#ifdef __SIZEOF_INT128__

760 using cr = std::common_type_t<to_rep, Rep, intmax_t>;

762#else

764#endif

765

766 if constexpr (cf::den == 1 && cf::num == 1) {

768 } else if constexpr (cf::den == 1) {

771 } else if constexpr (cf::num == 1) {

774 } else {

775

776

784 }

785 } else {

786

787 using cr = std::common_type_t<to_rep, Rep, intmax_t>;

788 if constexpr (cf::den == 1 && cf::num == 1) {

790 } else if constexpr (cf::den == 1) {

791 return ToFreq(static_cast<to_rep>(static_cast<cr>(f.count()) * static_cast<cr>(cf::num)));

792 } else if constexpr (cf::num == 1) {

793 return ToFreq(static_cast<to_rep>(static_cast<cr>(f.count()) / static_cast<cr>(cf::den)));

794 } else {

797 static_cast<cr>(f.count()) * static_cast<cr>(cf::num) / static_cast<cr>(cf::den)

798 )

799 );

800 }

801 }

802 }

803}

804

824template<typename ToFreq, typename Rep, typename Precision>

826 using to_rep = typename ToFreq::rep;

828

829 if constexpr (std::is_integral_v<Rep> && std::is_integral_v<to_rep>) {

832 }

833 }

834

836}

837

857template<typename ToFreq, typename Rep, typename Precision>

859 using to_rep = typename ToFreq::rep;

861

862 if constexpr (std::is_integral_v<Rep> && std::is_integral_v<to_rep>) {

865 }

866 }

867

869}

870

893template<typename ToFreq, typename Rep, typename Precision>

895 using to_rep = typename ToFreq::rep;

896

897 if constexpr (std::is_integral_v<Rep> && std::is_integral_v<to_rep>) {

901

904

909 } else {

910

912 }

913 } else {

915 }

916}

917

953template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>

959

975template<typename Rep, typename Precision>

979

981template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>

987

989template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>

995

997template<typename Rep1, typename Precision, typename Rep2>

998 requires not_frequency<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>>

1002 return cf(cf(f).count() * r);

1003}

1004

1006template<typename Rep1, typename Rep2, typename Precision>

1007 requires not_frequency<Rep1> && std::convertible_to<const Rep1&, std::common_type_t<Rep1, Rep2>>

1012

1014template<typename Rep1, typename Precision, typename Rep2>

1015 requires not_frequency<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>>

1019 return cf(cf(f).count() / s);

1020}

1021

1023template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>

1025 -> std::common_type_t<Rep1, Rep2> {

1028}

1029

1031template<typename Rep1, typename Precision, typename Rep2>

1032 requires not_frequency<Rep2> && std::convertible_to<const Rep2&, std::common_type_t<Rep1, Rep2>> &&

1037 return cf(cf(f).count() % s);

1038}

1039

1041template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>

1048

1049template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>

1054

1055template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>

1056 requires std::three_way_comparable<std::common_type_t<Rep1, Rep2>>

1061

1099

1101

1103 return std::to_string(f.count()) + "mHz";

1104}

1105

1107 return std::to_string(f.count()) + "Hz";

1108}

1109

1111 return std::to_string(f.count()) + "kHz";

1112}

1113

1115 return std::to_string(f.count()) + "MHz";

1116}

1117

1119 return std::to_string(f.count()) + "GHz";

1120}

1121

1123 return std::to_string(f.count()) + "THz";

1124}

1125

1126}

1127

1129

1130template<typename Rep1, typename Precision1, typename Rep2, typename Precision2>

1131struct common_type<freq::frequency<Rep1, Precision1>, freq::frequency<Rep2, Precision2>> {

1132private:

1133 using common_precision = std::ratio<

1134 freq::_gcd(Precision1::num, Precision2::num),

1135 (Precision1::den / freq::_gcd(Precision1::den, Precision2::den)) * Precision2::den>;

1136

1137public:

1139};

1140

1141#if CONFIG_FREQUENCY_STD_FORMAT

1142template<>

1143struct formatter<freq::millihertz> {

1144 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }

1145

1146 auto format(freq::millihertz f, format_context& ctx) const { return std::format_to(ctx.out(), "{}mHz", f.count()); }

1147};

1148

1149template<>

1150struct formatter<freq::hertz> {

1151 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }

1152

1153 auto format(freq::hertz f, format_context& ctx) const { return std::format_to(ctx.out(), "{}Hz", f.count()); }

1154};

1155

1156template<>

1157struct formatter<freq::kilohertz> {

1158 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }

1159

1160 auto format(freq::kilohertz f, format_context& ctx) const { return std::format_to(ctx.out(), "{}kHz", f.count()); }

1161};

1162

1163template<>

1164struct formatter<freq::megahertz> {

1165 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }

1166

1167 auto format(freq::megahertz f, format_context& ctx) const { return std::format_to(ctx.out(), "{}MHz", f.count()); }

1168};

1169

1170template<>

1171struct formatter<freq::gigahertz> {

1172 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }

1173

1174 auto format(freq::gigahertz f, format_context& ctx) const { return std::format_to(ctx.out(), "{}GHz", f.count()); }

1175};

1176

1177template<>

1178struct formatter<freq::terahertz> {

1179 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }

1180

1181 auto format(freq::terahertz f, format_context& ctx) const { return std::format_to(ctx.out(), "{}THz", f.count()); }

1182};

1183#endif

1184

1185}

1186

1192namespace detail {

1193

1194template<unsigned long long Value, unsigned long long Power>

1195struct pow10 {

1196 static constexpr unsigned long long value = 10 * pow10<Value, Power - 1>::value;

1197};

1198

1199template<unsigned long long Value>

1200struct pow10<Value, 0> {

1201 static constexpr unsigned long long value = Value;

1202};

1203

1204template<char... Digits>

1205struct parse_int;

1206

1207template<char D, char... Rest>

1208struct parse_int<D, Rest...> {

1209 static_assert(D >= '0' && D <= '9', "invalid digit");

1210 static constexpr unsigned long long value = pow10<D - '0', sizeof...(Rest)>::value + parse_int<Rest...>::value;

1211};

1212

1213template<char D>

1214struct parse_int<D> {

1215 static_assert(D >= '0' && D <= '9', "invalid digit");

1216 static constexpr unsigned long long value = D - '0';

1217};

1218

1219template<typename Freq, char... Digits>

1220constexpr Freq check_overflow() {

1221 using parsed = parse_int<Digits...>;

1222 constexpr typename Freq::rep repval = parsed::value;

1223 static_assert(

1224 repval >= 0 && static_cast<unsigned long long>(repval) == parsed::value,

1225 "literal value cannot be represented by frequency type"

1226 );

1227 return Freq(repval);

1228}

1229

1230}

1234template<char... Digits>

1238

1240template<char... Digits>

1242 return detail::check_overflow<freq::hertz, Digits...>();

1243}

1244

1246template<char... Digits>

1250

1252template<char... Digits>

1256

1258template<char... Digits>

1262

1264template<char... Digits>

1268

1269}

A frequency value with a representation and precision.

constexpr Duration period() const

Returns the period of this frequency as a duration.

frequency semitone_shift(T semitones) const

Returns this frequency shifted by a number of semitones.

constexpr frequency & operator++()

static constexpr frequency max() noexcept

Returns the maximum representable frequency.

constexpr frequency & operator%=(const rep &r)

constexpr frequency(const Rep2 &r)

Constructs from a tick count.

constexpr frequency< typename std::common_type< rep >::type, precision > operator-() const

constexpr frequency(const frequency< Rep2, Precision2 > &f)

Explicit constructor for lossy precision conversions.

constexpr frequency & operator-=(const frequency &f)

Rep rep

The representation type.

T octaves_from(const frequency &other) const

Calculates the interval in octaves between this frequency and another.

constexpr Distance wavelength(const Duration &time_per_unit_distance) const

Calculates the wavelength for this frequency.

constexpr frequency & operator+=(const frequency &f)

static constexpr frequency min() noexcept

Returns the minimum representable frequency.

constexpr frequency()=default

Constructs a zero frequency.

static constexpr frequency zero() noexcept

Returns a zero frequency.

constexpr frequency subharmonic(unsigned int n) const

Returns the nth subharmonic of this frequency.

frequency(const frequency &)=default

T semitones_from(const frequency &other) const

Calculates the interval in semitones between this frequency and another.

frequency & operator=(const frequency &)=default

constexpr frequency & operator/=(const rep &r)

constexpr frequency< typename std::common_type< rep >::type, precision > operator+() const

typename Precision::type precision

The precision as a std::ratio.

frequency octave_shift(T octaves) const

Returns this frequency shifted by a number of octaves.

constexpr frequency operator++(int)

constexpr frequency operator--(int)

constexpr rep count() const

Returns the tick count.

constexpr frequency & operator--()

constexpr frequency & operator%=(const frequency &f)

constexpr frequency harmonic(unsigned int n) const

Returns the nth harmonic of this frequency.

constexpr frequency & operator*=(const rep &r)

constexpr Distance wavelength(double velocity=299792458.0) const

Calculates the wavelength for this frequency given a propagation velocity.

frequency< int64_t, std::tera > terahertz

Frequency with 1,000,000,000,000 Hz (terahertz) precision.

frequency< int64_t, std::mega > megahertz

Frequency with 1,000,000 Hz (megahertz) precision.

frequency< int64_t, std::kilo > kilohertz

Frequency with 1000 Hz (kilohertz) precision.

frequency< int64_t, std::giga > gigahertz

Frequency with 1,000,000,000 Hz (gigahertz) precision.

frequency< int64_t, std::milli > millihertz

Frequency with 0.001 Hz (millihertz) precision.

frequency< int64_t > hertz

Frequency with 1 Hz precision.

Frequency types and utilities.

constexpr bool operator==(const frequency< Rep1, Precision1 > &lhs, const frequency< Rep2, Precision2 > &rhs)

constexpr auto operator<=>(const frequency< Rep1, Precision1 > &lhs, const frequency< Rep2, Precision2 > &rhs)

constexpr auto operator/(const frequency< Rep1, Precision > &f, const Rep2 &s) -> frequency< std::common_type_t< Rep1, Rep2 >, Precision >

Divides a frequency by a scalar.

constexpr ToFreq ceil(const frequency< Rep, Precision > &f)

Converts a frequency to the target type, rounding toward positive infinity.

constexpr ToFreq frequency_cast(const frequency< Rep, Precision > &f)

Converts a frequency to a different precision or representation.

constexpr auto beat(const frequency< Rep1, Precision1 > &f1, const frequency< Rep2, Precision2 > &f2) -> std::common_type_t< frequency< Rep1, Precision1 >, frequency< Rep2, Precision2 > >

Calculates the beat frequency between two frequencies.

constexpr auto operator-(const frequency< Rep1, Precision1 > &lhs, const frequency< Rep2, Precision2 > &rhs) -> std::common_type_t< frequency< Rep1, Precision1 >, frequency< Rep2, Precision2 > >

Returns the difference of two frequencies.

constexpr auto operator*(const frequency< Rep1, Precision > &f, const Rep2 &r) -> frequency< std::common_type_t< Rep1, Rep2 >, Precision >

Multiplies a frequency by a scalar.

std::string to_string(millihertz f)

constexpr ToFreq round(const frequency< Rep, Precision > &f)

Converts a frequency to the target type, rounding to nearest (ties to even).

constexpr bool is_frequency_v

constexpr ToFreq floor(const frequency< Rep, Precision > &f)

Converts a frequency to the target type, rounding toward negative infinity.

constexpr auto operator+(const frequency< Rep1, Precision1 > &lhs, const frequency< Rep2, Precision2 > &rhs) -> std::common_type_t< frequency< Rep1, Precision1 >, frequency< Rep2, Precision2 > >

Returns the sum of two frequencies.

constexpr frequency< Rep, Precision > abs(const frequency< Rep, Precision > &f)

Returns the absolute value of a frequency.

User-defined literals for frequency types.

Trait to detect frequency specializations.