Zrythm v2.0.0-alpha.1
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
transport.h
1// SPDX-FileCopyrightText: © 2018-2025 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include "dsp/itransport.h"
7#include "dsp/playhead_qml_adapter.h"
8#include "dsp/position.h"
9#include "dsp/tempo_map_qml_adapter.h"
10#include "utils/icloneable.h"
11#include "utils/views.h"
12
13#include <farbot/RealtimeObject.hpp>
14
15namespace zrythm::dsp
16{
17enum class PrerollCountBars
18{
19 PrerollNone,
20 PrerollOne,
21 PrerollTwo,
22 PrerollFour,
23};
24
25inline int
26preroll_count_bars_enum_to_int (PrerollCountBars bars)
27{
28 switch (bars)
29 {
30 case PrerollCountBars::PrerollNone:
31 return 0;
32 case PrerollCountBars::PrerollOne:
33 return 1;
34 case PrerollCountBars::PrerollTwo:
35 return 2;
36 case PrerollCountBars::PrerollFour:
37 return 4;
38 }
39 return -1;
40}
41
47class Transport : public QObject
48{
49 Q_OBJECT
50 QML_ELEMENT
51 Q_PROPERTY (
52 bool loopEnabled READ loopEnabled WRITE setLoopEnabled NOTIFY
53 loopEnabledChanged)
54 Q_PROPERTY (
55 bool recordEnabled READ recordEnabled WRITE setRecordEnabled NOTIFY
56 recordEnabledChanged)
57 Q_PROPERTY (
58 bool punchEnabled READ punchEnabled WRITE setPunchEnabled NOTIFY
59 punchEnabledChanged)
60 Q_PROPERTY (
61 zrythm::dsp::ITransport::PlayState playState READ getPlayState WRITE
62 setPlayState NOTIFY playStateChanged)
63 Q_PROPERTY (zrythm::dsp::PlayheadQmlWrapper * playhead READ playhead CONSTANT)
64 Q_PROPERTY (
65 zrythm::dsp::TimelinePosition * cuePosition READ cuePosition CONSTANT)
66 Q_PROPERTY (
67 zrythm::dsp::TimelinePosition * loopStartPosition READ loopStartPosition
68 CONSTANT)
69 Q_PROPERTY (
70 zrythm::dsp::TimelinePosition * loopEndPosition READ loopEndPosition CONSTANT)
71 Q_PROPERTY (
72 zrythm::dsp::TimelinePosition * punchInPosition READ punchInPosition CONSTANT)
73 Q_PROPERTY (
74 zrythm::dsp::TimelinePosition * punchOutPosition READ punchOutPosition
75 CONSTANT)
76 QML_UNCREATABLE ("")
78
80 static constexpr auto REPEATED_BACKWARD_MS = au::milli (units::seconds) (240);
81
82public:
83 using PlayState = dsp::ITransport::PlayState;
84 Q_ENUM (PlayState)
85
90 enum class Display
91 {
92 BBT,
93 Time,
94 };
95 Q_ENUM (Display)
96
97
103 enum class RecordingMode
104 {
115
125
128 * when looping or entering/leaving the punch range).
129 */
130 Takes,
131
137 };
138 Q_ENUM (RecordingMode)
140public:
141 struct ConfigProvider
142 {
146 std::function<bool ()> return_to_cue_on_pause_;
147
148
149 * @brief Number of bars to count-in when requesting playback with metronome
150 * enabled.
151 */
152 std::function<int ()> metronome_countin_bars_;
153
159 std::function<int ()> recording_preroll_bars_;
160 };
161
163 {
164 public:
166 std::pair<units::sample_t, units::sample_t> loop_range,
167 std::pair<units::sample_t, units::sample_t> punch_range,
168 units::sample_t playhead_position,
171 PlayState play_state,
172 bool loop_enabled,
173 bool punch_enabled,
175 : loop_range_ (loop_range), punch_range_ (punch_range),
176 playhead_position_ (playhead_position),
177 recording_preroll_frames_remaining_ (recording_preroll_frames_remaining),
178 metronome_countin_frames_remaining_ (metronome_countin_frames_remaining),
179 play_state_ (play_state), loop_enabled_ (loop_enabled),
180 punch_enabled_ (punch_enabled), recording_enabled_ (recording_enabled)
181 {
182 }
184 std::pair<units::sample_t, units::sample_t>
185 get_loop_range_positions () const noexcept override
186 {
187 return loop_range_;
188 }
189 std::pair<units::sample_t, units::sample_t>
190 get_punch_range_positions () const noexcept override
191 {
192 return punch_range_;
193 }
194 PlayState get_play_state () const noexcept override { return play_state_; }
195 units::sample_t
196 get_playhead_position_in_audio_thread () const noexcept override
197 {
198 return playhead_position_;
199 }
200 bool loop_enabled () const noexcept override { return loop_enabled_; }
201
202 bool punch_enabled () const noexcept override { return punch_enabled_; }
203 bool recording_enabled () const noexcept override
204 {
205 return recording_enabled_;
206 }
207 units::sample_t
208 recording_preroll_frames_remaining () const noexcept override
209 {
210 return recording_preroll_frames_remaining_;
211 }
212 units::sample_t
213 metronome_countin_frames_remaining () const noexcept override
214 {
215 return metronome_countin_frames_remaining_;
216 }
217
218 void set_play_state (dsp::ITransport::PlayState play_state)
219 {
220 play_state_ = play_state;
221 }
222 void set_position (units::sample_t position)
223 {
224 playhead_position_ = position;
225 }
226 void consume_metronome_countin_samples (units::sample_t samples)
227 {
228 metronome_countin_frames_remaining_ -= samples;
229 }
230 void consume_recording_preroll_samples (units::sample_t samples)
231 {
232 recording_preroll_frames_remaining_ -= samples;
233 }
234
235 private:
236 std::pair<units::sample_t, units::sample_t> loop_range_;
237 std::pair<units::sample_t, units::sample_t> punch_range_;
238 units::sample_t playhead_position_;
239 units::sample_t recording_preroll_frames_remaining_;
240 units::sample_t metronome_countin_frames_remaining_;
241 PlayState play_state_;
242 bool loop_enabled_;
243 bool punch_enabled_;
244 bool recording_enabled_;
245 };
246
247 Transport (
248 const dsp::TempoMapWrapper &tempo_map_wrapper,
249 ConfigProvider config_provider,
250 QObject * parent = nullptr);
251
252 // ==================================================================
253 // QML Interface
254 // ==================================================================
255
256 bool loopEnabled () const { return loop_; }
257 void setLoopEnabled (bool enabled);
258 Q_SIGNAL void loopEnabledChanged (bool enabled);
259
260 bool recordEnabled () const { return recording_; }
261 void setRecordEnabled (bool enabled);
262 Q_SIGNAL void recordEnabledChanged (bool enabled);
263
264 bool punchEnabled () const { return punch_mode_; }
265 void setPunchEnabled (bool enabled);
266 Q_SIGNAL void punchEnabledChanged (bool enabled);
267
268 PlayState getPlayState () const { return play_state_; }
269 void setPlayState (PlayState state);
270 Q_SIGNAL void playStateChanged (PlayState state);
271
272 dsp::PlayheadQmlWrapper * playhead () const
273 {
274 return playhead_adapter_.get ();
275 }
276 dsp::TimelinePosition * cuePosition () const { return cue_position_.get (); }
277 dsp::TimelinePosition * loopStartPosition () const
278 {
279 return loop_start_position_.get ();
280 }
281 dsp::TimelinePosition * loopEndPosition () const
282 {
283 return loop_end_position_.get ();
285 dsp::TimelinePosition * punchInPosition () const
286 {
287 return punch_in_position_.get ();
288 }
289 dsp::TimelinePosition * punchOutPosition () const
290 {
291 return punch_out_position_.get ();
292 }
293
297 Q_INVOKABLE void requestPause () [[clang::blocking]];
298
302 Q_INVOKABLE void requestRoll () [[clang::blocking]];
303
313 Q_INVOKABLE void movePlayhead (double ticks, bool setCuePoint);
314
315 // ==================================================================
316
317 // ==================================================================
318 // Audio-thread-safe accessors
319 // ==================================================================
320
321 units::sample_t
322 get_playhead_position_in_audio_thread () const noexcept [[clang::nonblocking]]
323 {
324 return playhead_.position_during_processing_rounded ();
325 }
326
327 // ==================================================================
328
329 Q_INVOKABLE bool isRolling () const
331 return play_state_ == PlayState::Rolling;
332 }
333
334 Q_INVOKABLE bool isPaused () const
335 {
336 return play_state_ == PlayState::Paused;
337 }
338
344 const dsp::Transport::TransportSnapshot &transport_snapshot,
345 units::sample_t nframes) noexcept [[clang::nonblocking]];
346
347 void set_play_state_rt_safe (PlayState state);
356 * Should not be used during exporting.
357 *
358 * @param target_ticks Position to set to.
359 * @param set_cue_point Also set the cue point at this position.
360 */
361 void move_playhead (units::precise_tick_t target_ticks, bool set_cue_point);
362
369 bool prev,
370 utils::RangeOf<units::precise_tick_t> auto &&extra_markers)
371 {
372 /* gather all markers */
373 std::vector<units::precise_tick_t> marker_ticks;
374 static_assert (__cpp_lib_containers_ranges >= 202202L);
375 marker_ticks.append_range (extra_markers);
376 marker_ticks.emplace_back (units::ticks (cue_position_->ticks ()));
377 marker_ticks.emplace_back (units::ticks (loop_start_position_->ticks ()));
378 marker_ticks.emplace_back (units::ticks (loop_end_position_->ticks ()));
379 marker_ticks.emplace_back ();
380 std::ranges::sort (marker_ticks);
381
382 if (prev)
383 {
384 for (
385 const auto &[index, marker_tick] :
386 marker_ticks | utils::views::enumerate | std::views::reverse)
387 {
388 if (marker_tick >= playhead_.position_ticks ())
389 continue;
390
391 if (
392 isRolling () && index > 0
393 && (playhead_.get_tempo_map ().tick_to_seconds (
394 TimelineTick{ playhead_.position_ticks () })
395 - playhead_.get_tempo_map ().tick_to_seconds (
396 TimelineTick{ marker_tick }))
397 < REPEATED_BACKWARD_MS)
398 {
399 continue;
400 }
401
402 move_playhead (marker_tick, true);
403 break;
404 }
405 }
406 else
407 {
408 for (const auto &marker : marker_ticks)
409 {
410 if (marker > playhead_.position_ticks ())
411 {
412 move_playhead (marker, true);
413 break;
414 }
415 }
416 }
417 }
419 bool position_is_inside_punch_range (units::sample_t pos) const;
420
421 auto playhead_ticks_before_pause () const [[clang::blocking]]
422 {
423 return playhead_before_pause_;
424 }
425
429 * @param samples Samples to consume.
430 */
431 void consume_metronome_countin_samples (units::sample_t samples)
432 {
433 assert (countin_frames_remaining_ >= samples);
434 countin_frames_remaining_ -= samples;
435 }
436
442 void consume_recording_preroll_samples (units::sample_t samples)
443 {
444 assert (recording_preroll_frames_remaining_ >= samples);
445 recording_preroll_frames_remaining_ -= samples;
446 }
447
448 auto get_snapshot () const noexcept [[clang::nonblocking]]
449 {
450 decltype (rt_markers_)::ScopedAccess<farbot::ThreadType::realtime> access{
451 rt_markers_
452 };
453 return TransportSnapshot{
454 { units::samples (access->loop_start_samples),
455 units::samples (access->loop_end_samples) },
456 { units::samples (access->punch_in_samples),
457 units::samples (access->punch_out_samples) },
458 get_playhead_position_in_audio_thread (),
459 recording_preroll_frames_remaining_,
460 countin_frames_remaining_,
461 play_state_,
462 loop_,
463 punch_mode_,
464 recording_
465 };
466 }
467
468 friend void init_from (
469 Transport &obj,
470 const Transport &other,
471 utils::ObjectCloneType clone_type);
472
473private:
474 static constexpr auto kPlayheadKey = "playheadPosition"sv;
475 static constexpr auto kCuePosKey = "cuePosition"sv;
476 static constexpr auto kLoopStartPosKey = "loopStartPosition"sv;
477 static constexpr auto kLoopEndPosKey = "loopEndPosition"sv;
478 static constexpr auto kPunchInPosKey = "punchInPosition"sv;
479 static constexpr auto kPunchOutPosKey = "punchOutPosition"sv;
480 friend void to_json (nlohmann::json &j, const Transport &transport);
481 friend void from_json (const nlohmann::json &j, Transport &transport);
482
487 bool can_user_move_playhead () const;
488
490 struct MarkerSnapshot
491 {
492 int64_t loop_start_samples{};
493 int64_t loop_end_samples{};
494 int64_t punch_in_samples{};
495 int64_t punch_out_samples{};
496 };
497
498 void update_rt_marker_snapshot ();
499
500private:
502 dsp::Playhead playhead_;
503 utils::QObjectUniquePtr<dsp::PlayheadQmlWrapper> playhead_adapter_;
504
506 utils::QObjectUniquePtr<dsp::TimelinePosition> cue_position_;
507
509 utils::QObjectUniquePtr<dsp::TimelinePosition> loop_start_position_;
510
512 utils::QObjectUniquePtr<dsp::TimelinePosition> loop_end_position_;
513
515 utils::QObjectUniquePtr<dsp::TimelinePosition> punch_in_position_;
516
518 utils::QObjectUniquePtr<dsp::TimelinePosition> punch_out_position_;
519
526 mutable farbot::RealtimeObject<
527 MarkerSnapshot,
528 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
529 rt_markers_;
530
532 bool loop_{ true };
533
535 bool punch_mode_{ false };
536
539 bool recording_{ false };
540
542 units::sample_t recording_preroll_frames_remaining_;
543
545 units::sample_t countin_frames_remaining_;
546
552 units::precise_tick_t playhead_before_pause_;
553
555 PlayState play_state_{ PlayState::Paused };
556
564 QTimer * property_notification_timer_ = nullptr;
565 std::atomic<bool> needs_property_notification_{ false };
566
567 ConfigProvider config_provider_;
568};
569}
Interface for transport.
Definition itransport.h:47
auto position_ticks() const
Get current playhead position in ticks (GUI thread only).
Definition playhead.h:176
A Position whose ticks are absolute timeline ticks.
Definition position.h:97
units::sample_t get_playhead_position_in_audio_thread() const noexcept override
Get the playhead position.
Definition transport.h:183
std::pair< units::sample_t, units::sample_t > get_punch_range_positions() const noexcept override
Returns the punch recording range positions in samples.
Definition transport.h:177
units::sample_t metronome_countin_frames_remaining() const noexcept override
Frames remaining for metronome countin.
Definition transport.h:200
std::pair< units::sample_t, units::sample_t > get_loop_range_positions() const noexcept override
Returns the loop range positions in samples.
Definition transport.h:172
units::sample_t recording_preroll_frames_remaining() const noexcept override
Frames remaining to preroll (playing back some time earlier before actually recording/rolling).
Definition transport.h:195
bool recording_enabled() const noexcept override
Returns whether recording is enabled.
Definition transport.h:190
Q_INVOKABLE void movePlayhead(double ticks, bool setCuePoint)
Moves the playhead to the given tick position.
Q_INVOKABLE void requestPause()
Request pause.
void consume_metronome_countin_samples(units::sample_t samples)
For engine use only.
Definition transport.h:418
void move_playhead(units::precise_tick_t target_ticks, bool set_cue_point)
Moves playhead to given pos.
RecordingMode
Recording mode for MIDI and audio.
Definition transport.h:91
@ MergeEvents
Merge events in existing clip.
Definition transport.h:111
@ Takes
Events get put in new lanes each time recording starts/resumes (eg, when looping or entering/leaving ...
Definition transport.h:117
@ OverwriteEvents
Overwrite events in first recorded clip.
Definition transport.h:101
@ TakesMuted
Same as RECORDING_MODE_TAKES, except previous takes (since recording started) are muted.
Definition transport.h:123
void add_to_playhead_in_audio_thread(const dsp::Transport::TransportSnapshot &transport_snapshot, units::sample_t nframes) noexcept
Moves the playhead by the time corresponding to given samples, taking into account the loop end point...
Q_INVOKABLE void requestRoll()
Request playback.
void consume_recording_preroll_samples(units::sample_t samples)
For engine use only.
Definition transport.h:429
Display
Corrseponts to "transport-display" in the gsettings.
Definition transport.h:78
void goto_prev_or_next_marker(bool prev, utils::RangeOf< units::precise_tick_t > auto &&extra_markers)
Moves the playhead to the previous or next marker.
Definition transport.h:355
std::function< bool()> return_to_cue_on_pause_
Whether to return to cue position on pause.
Definition transport.h:133