Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
playhead.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
8#include "dsp/tempo_map.h"
9
10namespace zrythm::dsp
11{
12
63{
64 friend class PlayheadProcessingGuard;
65
66public:
71 Playhead (const TempoMap &tempo_map) : tempo_map_ (tempo_map) { }
72
73 // Audio threads ONLY ----------------------------------------------------
74
75private:
82 void prepare_for_processing () noexcept
83 {
84 // Copy current position before processing
85 const auto samples =
86 position_samples_.load (std::memory_order_acquire).in (units::samples);
87 position_samples_at_start_of_processing_ = samples;
88 position_samples_processing_.store (samples, std::memory_order_release);
89 }
90
91public:
99 void advance_processing (units::sample_t nframes) noexcept
100 {
101 if (nframes >= units::samples (0)) [[likely]]
102 {
103 position_samples_processing_.fetch_add (
104 nframes.in<double> (units::samples));
105 }
106 else
107 {
108 position_samples_processing_.fetch_sub (
109 -nframes.in<double> (units::samples));
110 }
111 }
112
120 {
121 return position_samples_processing_.load (std::memory_order_acquire);
122 }
123 units::sample_t position_during_processing_rounded () const noexcept
124 {
125 return units::samples (
126 static_cast<int64_t> (std::round (
127 position_samples_processing_.load (std::memory_order_acquire))));
128 }
129
130private:
137 void finalize_processing () noexcept
138 {
139 // Commit position only at end of block
140 const auto processing_samples =
141 position_samples_processing_.load (std::memory_order_acquire);
142
143 // Atomically compare and exchange - only update if current value matches
144 // start value (otherwise the position was changed by the user meanwhile and
145 // we should honor that change)
146 auto expected = units::samples (position_samples_at_start_of_processing_);
147 position_samples_.compare_exchange_strong (
148 expected, units::samples (processing_samples), std::memory_order_acq_rel,
149 std::memory_order_acquire);
150 }
151
152 // GUI thread ONLY ------------------------------------------------------
153
154public:
161 void set_position_ticks (units::precise_tick_t ticks) [[clang::blocking]]
162 {
163 std::lock_guard lock (position_mutex_);
164 position_ticks_ = ticks;
165 position_samples_.store (
166 tempo_map_.tick_to_samples (ticks), std::memory_order_release);
167 }
168
173 auto position_ticks () const [[clang::blocking]]
174 {
175 std::lock_guard lock (position_mutex_);
176 return position_ticks_;
177 }
178
179 // Any thread (non-RT) --------------------------------------------------
180
187 void update_ticks_from_samples () [[clang::blocking]]
188 {
189 std::lock_guard lock (position_mutex_);
190 position_ticks_ = tempo_map_.samples_to_tick (
191 position_samples_.load (std::memory_order_acquire));
192 }
193
194 const auto &get_tempo_map () const { return tempo_map_; }
195
200 auto position_samples_FOR_TESTING () const noexcept
201 {
202 return position_samples_.load (std::memory_order_acquire);
203 }
204
205private:
206 static constexpr auto kTicks = "ticks"sv;
207 friend void to_json (nlohmann::json &j, const Playhead &pos)
208 {
209 j[kTicks] = pos.position_ticks ().in (units::ticks);
210 }
211 friend void from_json (const nlohmann::json &j, Playhead &pos)
212 {
213 double ticks{};
214 j.at (kTicks).get_to (ticks);
215 pos.set_position_ticks (units::ticks (ticks));
216 }
217
218private:
219 const TempoMap &tempo_map_;
220
221 // Audio thread state
222 std::atomic<double> position_samples_processing_ = 0.0;
223 double position_samples_at_start_of_processing_{};
224
225 // Shared state (protected)
226 std::atomic<units::precise_sample_t> position_samples_;
227 units::precise_tick_t position_ticks_;
228 mutable std::mutex position_mutex_;
229
230 static_assert (decltype (position_samples_)::is_always_lock_free);
231};
232
241{
242public:
247 explicit PlayheadProcessingGuard (Playhead &playhead) noexcept
248 : playhead_ (playhead)
249 {
250 playhead_.prepare_for_processing ();
251 }
252
254 ~PlayheadProcessingGuard () noexcept { playhead_.finalize_processing (); }
255
256 // Prevent copying
258 PlayheadProcessingGuard &operator= (const PlayheadProcessingGuard &) = delete;
259
260private:
261 Playhead &playhead_;
262};
263
264} // namespace zrythm::dsp
RAII helper for Playhead audio processing block.
Definition playhead.h:241
PlayheadProcessingGuard(Playhead &playhead) noexcept
Constructor - calls playhead.prepare_for_processing().
Definition playhead.h:247
~PlayheadProcessingGuard() noexcept
Destructor - calls playhead.finalize_processing().
Definition playhead.h:254
Provides thread-safe playhead positioning with sample-accurate timing.
Definition playhead.h:63
auto position_during_processing_precise() const noexcept
Get current position during processing (audio thread safe).
Definition playhead.h:119
void advance_processing(units::sample_t nframes) noexcept
Advance processing position (audio thread safe).
Definition playhead.h:99
auto position_samples_FOR_TESTING() const noexcept
Get current playhead position in samples (non-RT).
Definition playhead.h:200
void update_ticks_from_samples()
Update tick position from sample position.
Definition playhead.h:187
Playhead(const TempoMap &tempo_map)
Construct a Playhead associated with a TempoMap.
Definition playhead.h:71
auto position_ticks() const
Get current playhead position in ticks (GUI thread only).
Definition playhead.h:173
void set_position_ticks(units::precise_tick_t ticks)
Set playhead position in musical ticks (GUI thread only).
Definition playhead.h:161