Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
atomic_position.h
1// SPDX-FileCopyrightText: © 2025 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include <atomic>
7#include <cstdint>
8#include <functional>
9#include <limits>
10
11#include "dsp/tempo_map.h"
12#include "utils/format.h"
13#include "utils/types.h"
14#include "utils/units.h"
15
16#include <fmt/format.h>
17#include <nlohmann/json_fwd.hpp>
18
19namespace zrythm::dsp
20{
21
22namespace internal
23{
24static_assert (
25 std::numeric_limits<double>::is_iec559,
26 "Requires IEEE-754 doubles");
27static_assert (
28 std::atomic<uint64_t>::is_always_lock_free,
29 "64-bit atomics not lock-free");
30
34class AtomicDoubleWithBool
35{
36 std::atomic<uint64_t> packed_;
37
38 // Mask to clear LSB (where we store the bool)
39 static constexpr uint64_t kValueMask = ~1ULL;
40
41public:
42 AtomicDoubleWithBool () noexcept = default;
43 AtomicDoubleWithBool (double d, bool b) noexcept { store (d, b); }
44
45 void store (double d, bool b) noexcept
46 {
47 assert (std::isfinite (d) && "Only finite doubles supported");
48 const auto bits = std::bit_cast<uint64_t> (d);
49 packed_.store ((bits & kValueMask) | (b ? 1 : 0), std::memory_order_release);
50 }
51
52 std::pair<double, bool> load () const noexcept
53 {
54 const uint64_t bits = packed_.load (std::memory_order_acquire);
55 return {
56 std::bit_cast<double> (bits & kValueMask), // Original value ≈ d
57 static_cast<bool> (bits & 1) // Stored bool
58 };
59 }
60};
61} // namespace internal
62
77{
78public:
80 {
81 std::function<units::precise_second_t (units::precise_tick_t)>
82 tick_to_seconds;
83 std::function<units::precise_tick_t (units::precise_second_t)>
84 seconds_to_tick;
85 std::function<units::precise_sample_t (units::precise_tick_t)>
86 tick_to_samples;
87 std::function<units::precise_tick_t (units::precise_sample_t)>
88 samples_to_tick;
89
90 static std::unique_ptr<TimeConversionFunctions>
91 from_tempo_map (const dsp::TempoMap &tempo_map)
92 {
93 return std::make_unique<
94 dsp::AtomicPosition::
95 TimeConversionFunctions> (dsp::AtomicPosition::TimeConversionFunctions{
96 .tick_to_seconds =
97 [&] (units::precise_tick_t ticks) {
98 return tempo_map.tick_to_seconds (ticks);
99 },
100 .seconds_to_tick =
101 [&] (units::precise_second_t seconds) {
102 return tempo_map.seconds_to_tick (seconds);
103 },
104 .tick_to_samples =
105 [&] (units::precise_tick_t ticks) {
106 return tempo_map.tick_to_samples (ticks);
107 },
108 .samples_to_tick =
109 [&] (units::precise_sample_t samples) {
110 return tempo_map.samples_to_tick (samples);
111 },
112 });
113 }
114 };
115
120 AtomicPosition (const TimeConversionFunctions &conversion_funcs) noexcept
121 : conversion_funcs_ (conversion_funcs),
122 value_ (0, format_to_bool (TimeFormat::Musical))
123 {
124 }
125
127 auto get_current_mode () const
128 {
129 return bool_to_format (value_.load ().second);
130 }
131
144 void set_mode (TimeFormat format)
145 {
146 const auto cur_mode = get_current_mode ();
147 if (cur_mode == TimeFormat::Absolute && format == TimeFormat::Musical)
148 {
149 value_.store (
150 get_ticks ().in (units::ticks), format_to_bool (TimeFormat::Musical));
151 }
152 else if (cur_mode == TimeFormat::Musical && format == TimeFormat::Absolute)
153 {
154 value_.store (
155 get_seconds ().in (units::seconds),
156 format_to_bool (TimeFormat::Absolute));
157 }
158 }
159
165 void set_ticks (units::precise_tick_t ticks)
166 {
167 const auto cur_mode = get_current_mode ();
168 if (cur_mode == TimeFormat::Musical)
169 {
170 value_.store (
171 ticks.in (units::ticks), format_to_bool (TimeFormat::Musical));
172 }
173 else if (cur_mode == TimeFormat::Absolute)
174 {
175 set_seconds (conversion_funcs_.tick_to_seconds (ticks));
176 }
177 }
178
184 void set_seconds (units::precise_second_t seconds)
185 {
186 const auto cur_mode = get_current_mode ();
187 if (cur_mode == TimeFormat::Absolute)
188 {
189 value_.store (
190 seconds.in (units::seconds), format_to_bool (TimeFormat::Absolute));
191 }
192 else if (cur_mode == TimeFormat::Musical)
193 {
194 set_ticks (conversion_funcs_.seconds_to_tick (seconds));
195 }
196 }
197
199 units::precise_tick_t get_ticks () const
200 {
201 const auto &[d, b] = value_.load ();
202 if (bool_to_format (b) == TimeFormat::Musical)
203 {
204 return units::ticks (d);
205 }
206 return conversion_funcs_.seconds_to_tick (units::seconds (d));
207 }
208
210 units::precise_second_t get_seconds () const
211 {
212 const auto &[d, b] = value_.load ();
213 if (bool_to_format (b) == TimeFormat::Absolute)
214 {
215 return units::seconds (d);
216 }
217 return conversion_funcs_.tick_to_seconds (units::ticks (d));
218 }
219
221 units::sample_t get_samples () const
222 {
223 const auto &[d, b] = value_.load ();
224 auto tick =
225 bool_to_format (b) == TimeFormat::Musical
226 ? units::ticks (d)
227 : conversion_funcs_.seconds_to_tick (units::seconds (d));
228 return au::round_as<std::int64_t> (
229 units::samples, conversion_funcs_.tick_to_samples (tick));
230 }
231
232 void set_samples (units::precise_sample_t samples)
233 {
234 set_ticks (conversion_funcs_.samples_to_tick (samples));
235 }
236
237 const auto &time_conversion_functions () const { return conversion_funcs_; }
238
239private:
240 static constexpr bool format_to_bool (TimeFormat format) noexcept
241 {
242 return format == TimeFormat::Absolute;
243 }
244 static constexpr TimeFormat bool_to_format (bool b) noexcept
245 {
247 }
248
249 static constexpr auto kMode = "mode"sv;
250 static constexpr auto kValue = "value"sv;
251 friend void to_json (nlohmann::json &j, const AtomicPosition &pos);
252 friend void from_json (const nlohmann::json &j, AtomicPosition &pos);
253
254private:
255 const TimeConversionFunctions &conversion_funcs_;
256 internal::AtomicDoubleWithBool value_;
257};
258
259} // namespace zrythm::dsp
260
261// Formatter for AtomicPosition
262template <>
263struct fmt::formatter<zrythm::dsp::AtomicPosition>
264 : fmt::formatter<std::string_view>
265{
266 template <typename FormatContext>
267 auto format (const zrythm::dsp::AtomicPosition &pos, FormatContext &ctx) const
268 {
269 return fmt::formatter<std::string_view>{}.format (
270 fmt::format (
271 "Ticks: {:.2f} | Seconds: {:.3f} | Samples: {} | [Mode: {}]",
272 pos.get_ticks (), pos.get_seconds (), pos.get_samples (),
273 pos.get_current_mode ()),
274 ctx);
275 }
276};
Thread-safe position storage with automatic musical/absolute time conversion.
void set_seconds(units::precise_second_t seconds)
Set position in absolute seconds.
void set_mode(TimeFormat format)
Change storage format with automatic value conversion.
units::precise_second_t get_seconds() const
Get position in absolute seconds (converts if necessary).
auto get_current_mode() const
Get current storage format (musical ticks or absolute seconds).
void set_ticks(units::precise_tick_t ticks)
Set position in musical ticks.
AtomicPosition(const TimeConversionFunctions &conversion_funcs) noexcept
Construct a new AtomicPosition object.
units::precise_tick_t get_ticks() const
Get position in musical ticks (converts if necessary).
units::sample_t get_samples() const
Helper method to get the position as samples.
auto tick_to_seconds(units::precise_tick_t tick) const -> units::precise_second_t
Convert fractional ticks to seconds.
Definition tempo_map.h:245
units::precise_tick_t samples_to_tick(units::precise_sample_t samples) const
Convert samples to fractional ticks.
Definition tempo_map.h:393
units::precise_tick_t seconds_to_tick(units::precise_second_t seconds) const
Convert seconds to fractional ticks.
Definition tempo_map.h:326
units::precise_sample_t tick_to_samples(units::precise_tick_t tick) const
Convert fractional ticks to samples.
Definition tempo_map.h:315
TimeFormat
Definition types.h:173
@ Musical
Musical time (ticks).
Definition types.h:175
@ Absolute
Absolute time (seconds).
Definition types.h:180