Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
atomic_position.h
1// SPDX-FileCopyrightText: © 2025-2026 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/enum_utils.h"
13#include "utils/units.h"
14
15#include <fmt/format.h>
16#include <nlohmann/json_fwd.hpp>
17
18namespace zrythm::dsp
19{
20
21namespace internal
22{
23static_assert (
24 std::numeric_limits<double>::is_iec559,
25 "Requires IEEE-754 doubles");
26static_assert (
27 std::atomic<uint64_t>::is_always_lock_free,
28 "64-bit atomics not lock-free");
29
38class AtomicDoubleWithBool
39{
40 std::atomic<uint64_t> packed_;
41
42 // Mask to clear LSB (where we store the bool)
43 static constexpr uint64_t kValueMask = ~1ULL;
44
45public:
46 AtomicDoubleWithBool () noexcept = default;
47 AtomicDoubleWithBool (double d, bool b) noexcept { store (d, b); }
48
49 void store (double d, bool b) noexcept
50 {
51 assert (std::isfinite (d) && "Only finite doubles supported");
52 const auto bits = std::bit_cast<uint64_t> (d);
53 packed_.store ((bits & kValueMask) | (b ? 1 : 0), std::memory_order_release);
54 }
55
56 std::pair<double, bool> load () const noexcept
57 {
58 const uint64_t bits = packed_.load (std::memory_order_acquire);
59 return {
60 std::bit_cast<double> (bits & kValueMask), // Original value ≈ d
61 static_cast<bool> (bits & 1) // Stored bool
62 };
63 }
64};
65} // namespace internal
66
81{
82public:
83 enum class TimeFormat : std::uint8_t
84 {
92 };
93
95 {
96 std::function<units::precise_second_t (units::precise_tick_t)>
97 tick_to_seconds;
98 std::function<units::precise_tick_t (units::precise_second_t)>
99 seconds_to_tick;
100 std::function<units::precise_sample_t (units::precise_tick_t)>
101 tick_to_samples;
102 std::function<units::precise_tick_t (units::precise_sample_t)>
103 samples_to_tick;
104
105 static std::unique_ptr<TimeConversionFunctions>
106 from_tempo_map (const dsp::TempoMap &tempo_map)
107 {
108 return std::make_unique<
109 dsp::AtomicPosition::
110 TimeConversionFunctions> (dsp::AtomicPosition::TimeConversionFunctions{
111 .tick_to_seconds =
112 [&] (units::precise_tick_t ticks) {
113 return tempo_map.tick_to_seconds (ticks);
114 },
115 .seconds_to_tick =
116 [&] (units::precise_second_t seconds) {
117 return tempo_map.seconds_to_tick (seconds);
118 },
119 .tick_to_samples =
120 [&] (units::precise_tick_t ticks) {
121 return tempo_map.tick_to_samples (ticks);
122 },
123 .samples_to_tick =
124 [&] (units::precise_sample_t samples) {
125 return tempo_map.samples_to_tick (samples);
126 },
127 });
128 }
129 };
130
135 AtomicPosition (const TimeConversionFunctions &conversion_funcs) noexcept
136 : conversion_funcs_ (conversion_funcs),
137 value_ (0, format_to_bool (TimeFormat::Musical))
138 {
139 }
140
142 auto get_current_mode () const
143 {
144 return bool_to_format (value_.load ().second);
145 }
146
159 void set_mode (TimeFormat format)
160 {
161 const auto cur_mode = get_current_mode ();
162 if (cur_mode == TimeFormat::Absolute && format == TimeFormat::Musical)
163 {
164 value_.store (
165 get_ticks ().in (units::ticks), format_to_bool (TimeFormat::Musical));
166 }
167 else if (cur_mode == TimeFormat::Musical && format == TimeFormat::Absolute)
168 {
169 value_.store (
170 get_seconds ().in (units::seconds),
171 format_to_bool (TimeFormat::Absolute));
172 }
173 }
174
180 void set_ticks (units::precise_tick_t ticks)
181 {
182 const auto cur_mode = get_current_mode ();
183 if (cur_mode == TimeFormat::Musical)
184 {
185 value_.store (
186 ticks.in (units::ticks), format_to_bool (TimeFormat::Musical));
187 }
188 else if (cur_mode == TimeFormat::Absolute)
189 {
190 set_seconds (conversion_funcs_.tick_to_seconds (ticks));
191 }
192 }
193
199 void set_seconds (units::precise_second_t seconds)
200 {
201 const auto cur_mode = get_current_mode ();
202 if (cur_mode == TimeFormat::Absolute)
203 {
204 value_.store (
205 seconds.in (units::seconds), format_to_bool (TimeFormat::Absolute));
206 }
207 else if (cur_mode == TimeFormat::Musical)
208 {
209 set_ticks (conversion_funcs_.seconds_to_tick (seconds));
210 }
211 }
212
214 units::precise_tick_t get_ticks () const
215 {
216 const auto &[d, b] = value_.load ();
217 if (bool_to_format (b) == TimeFormat::Musical)
218 {
219 return units::ticks (d);
220 }
221 return conversion_funcs_.seconds_to_tick (units::seconds (d));
222 }
223
225 units::precise_second_t get_seconds () const
226 {
227 const auto &[d, b] = value_.load ();
228 if (bool_to_format (b) == TimeFormat::Absolute)
229 {
230 return units::seconds (d);
231 }
232 return conversion_funcs_.tick_to_seconds (units::ticks (d));
233 }
234
236 units::sample_t get_samples () const
237 {
238 const auto &[d, b] = value_.load ();
239 auto tick =
240 bool_to_format (b) == TimeFormat::Musical
241 ? units::ticks (d)
242 : conversion_funcs_.seconds_to_tick (units::seconds (d));
243 return au::round_as<std::int64_t> (
244 units::samples, conversion_funcs_.tick_to_samples (tick));
245 }
246
247 void set_samples (units::precise_sample_t samples)
248 {
249 set_ticks (conversion_funcs_.samples_to_tick (samples));
250 }
251
252 const auto &time_conversion_functions () const { return conversion_funcs_; }
253
254private:
255 static constexpr bool format_to_bool (TimeFormat format) noexcept
256 {
257 return format == TimeFormat::Absolute;
258 }
259 static constexpr TimeFormat bool_to_format (bool b) noexcept
260 {
262 }
263
264 static constexpr std::string_view kMode = "mode";
265 static constexpr std::string_view kValue = "value";
266 friend void to_json (nlohmann::json &j, const AtomicPosition &pos);
267 friend void from_json (const nlohmann::json &j, AtomicPosition &pos);
268
269private:
270 const TimeConversionFunctions &conversion_funcs_;
271 internal::AtomicDoubleWithBool value_;
272};
273
274} // namespace zrythm::dsp
275
276// Formatter for AtomicPosition
277template <>
278struct fmt::formatter<zrythm::dsp::AtomicPosition>
279 : fmt::formatter<std::string_view>
280{
281 template <typename FormatContext>
282 auto format (const zrythm::dsp::AtomicPosition &pos, FormatContext &ctx) const
283 {
284 return fmt::formatter<std::string_view>{}.format (
285 fmt::format (
286 "Ticks: {:.2f} | Seconds: {:.3f} | Samples: {} | [Mode: {}]",
287 pos.get_ticks (), pos.get_seconds (), pos.get_samples (),
288 pos.get_current_mode ()),
289 ctx);
290 }
291};
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:240
units::precise_tick_t samples_to_tick(units::precise_sample_t samples) const
Convert samples to fractional ticks.
Definition tempo_map.h:388
units::precise_tick_t seconds_to_tick(units::precise_second_t seconds) const
Convert seconds to fractional ticks.
Definition tempo_map.h:321
units::precise_sample_t tick_to_samples(units::precise_tick_t tick) const
Convert fractional ticks to samples.
Definition tempo_map.h:310