Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
playhead.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 <mutex>
8
9#include "dsp/tempo_map.h"
10
11namespace zrythm::dsp
12{
13using namespace std::string_view_literals;
14
65{
66 friend class PlayheadProcessingGuard;
67
68public:
73 Playhead (const TempoMap &tempo_map) : tempo_map_ (tempo_map) { }
74
75 // Audio threads ONLY ----------------------------------------------------
76
77private:
84 void prepare_for_processing () noexcept
85 {
86 // Copy current position before processing
87 const auto samples =
88 position_samples_.load (std::memory_order_acquire).in (units::samples);
89 position_samples_at_start_of_processing_ = samples;
90 position_samples_processing_.store (samples, std::memory_order_release);
91 }
92
93public:
101 void advance_processing (units::sample_t nframes) noexcept
102 {
103 if (nframes >= units::samples (0)) [[likely]]
104 {
105 position_samples_processing_.fetch_add (
106 nframes.in<double> (units::samples));
107 }
108 else
109 {
110 position_samples_processing_.fetch_sub (
111 -nframes.in<double> (units::samples));
112 }
113 }
114
122 {
123 return position_samples_processing_.load (std::memory_order_acquire);
124 }
125 units::sample_t position_during_processing_rounded () const noexcept
126 {
127 return units::samples (
128 static_cast<int64_t> (std::round (
129 position_samples_processing_.load (std::memory_order_acquire))));
130 }
131
132private:
139 void finalize_processing () noexcept
140 {
141 // Commit position only at end of block
142 const auto processing_samples =
143 position_samples_processing_.load (std::memory_order_acquire);
144
145 // Atomically compare and exchange - only update if current value matches
146 // start value (otherwise the position was changed by the user meanwhile and
147 // we should honor that change)
148 auto expected = units::samples (position_samples_at_start_of_processing_);
149 position_samples_.compare_exchange_strong (
150 expected, units::samples (processing_samples), std::memory_order_acq_rel,
151 std::memory_order_acquire);
152 }
153
154 // GUI thread ONLY ------------------------------------------------------
155
156public:
163 void set_position_ticks (units::precise_tick_t ticks) [[clang::blocking]]
164 {
165 std::lock_guard lock (position_mutex_);
166 position_ticks_ = ticks;
167 position_samples_.store (
168 tempo_map_.tick_to_samples (ticks), std::memory_order_release);
169 }
170
175 auto position_ticks () const [[clang::blocking]]
176 {
177 std::lock_guard lock (position_mutex_);
178 return position_ticks_;
179 }
180
181 // Any thread (non-RT) --------------------------------------------------
182
189 void update_ticks_from_samples () [[clang::blocking]]
190 {
191 std::lock_guard lock (position_mutex_);
192 position_ticks_ = tempo_map_.samples_to_tick (
193 position_samples_.load (std::memory_order_acquire));
194 }
195
196 const auto &get_tempo_map () const { return tempo_map_; }
197
202 auto position_samples_FOR_TESTING () const noexcept
203 {
204 return position_samples_.load (std::memory_order_acquire);
205 }
206
207private:
208 // These just mimic what AtomicPosition does so we don't invent another
209 // position type in the project schema
210 static constexpr auto kMode = "mode"sv;
211 static constexpr auto kValue = "value"sv;
212 friend void to_json (nlohmann::json &j, const Playhead &pos);
213 friend void from_json (const nlohmann::json &j, Playhead &pos);
214
215private:
216 const TempoMap &tempo_map_;
217
218 // Audio thread state
219 std::atomic<double> position_samples_processing_ = 0.0;
220 double position_samples_at_start_of_processing_{};
221
222 // Shared state (protected)
223 std::atomic<units::precise_sample_t> position_samples_;
224 units::precise_tick_t position_ticks_;
225 mutable std::mutex position_mutex_;
226
227 static_assert (decltype (position_samples_)::is_always_lock_free);
228};
229
238{
239public:
244 explicit PlayheadProcessingGuard (Playhead &playhead) noexcept
245 : playhead_ (playhead)
246 {
247 playhead_.prepare_for_processing ();
248 }
249
251 ~PlayheadProcessingGuard () noexcept { playhead_.finalize_processing (); }
252
253 // Prevent copying
255 PlayheadProcessingGuard &operator= (const PlayheadProcessingGuard &) = delete;
256
257private:
258 Playhead &playhead_;
259};
260
261} // namespace zrythm::dsp
RAII helper for Playhead audio processing block.
Definition playhead.h:238
PlayheadProcessingGuard(Playhead &playhead) noexcept
Constructor - calls playhead.prepare_for_processing().
Definition playhead.h:244
~PlayheadProcessingGuard() noexcept
Destructor - calls playhead.finalize_processing().
Definition playhead.h:251
Provides thread-safe playhead positioning with sample-accurate timing.
Definition playhead.h:65
auto position_during_processing_precise() const noexcept
Get current position during processing (audio thread safe).
Definition playhead.h:121
void advance_processing(units::sample_t nframes) noexcept
Advance processing position (audio thread safe).
Definition playhead.h:101
auto position_samples_FOR_TESTING() const noexcept
Get current playhead position in samples (non-RT).
Definition playhead.h:202
void update_ticks_from_samples()
Update tick position from sample position.
Definition playhead.h:189
Playhead(const TempoMap &tempo_map)
Construct a Playhead associated with a TempoMap.
Definition playhead.h:73
auto position_ticks() const
Get current playhead position in ticks (GUI thread only).
Definition playhead.h:175
void set_position_ticks(units::precise_tick_t ticks)
Set playhead position in musical ticks (GUI thread only).
Definition playhead.h:163