Zrythm v2.0.0-DEV
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/atomic_position_qml_adapter.h"
7#include "dsp/itransport.h"
8#include "dsp/playhead_qml_adapter.h"
9#include "utils/icloneable.h"
10#include "utils/views.h"
11
12namespace zrythm::dsp
13{
14enum class PrerollCountBars
15{
16 PrerollNone,
17 PrerollOne,
18 PrerollTwo,
19 PrerollFour,
20};
21
22inline int
23preroll_count_bars_enum_to_int (PrerollCountBars bars)
24{
25 switch (bars)
26 {
27 case PrerollCountBars::PrerollNone:
28 return 0;
29 case PrerollCountBars::PrerollOne:
30 return 1;
31 case PrerollCountBars::PrerollTwo:
32 return 2;
33 case PrerollCountBars::PrerollFour:
34 return 4;
35 }
36 return -1;
37}
38
44class Transport : public QObject, public dsp::ITransport
45{
46 Q_OBJECT
47 QML_ELEMENT
48 Q_PROPERTY (
49 bool loopEnabled READ loopEnabled WRITE setLoopEnabled NOTIFY
50 loopEnabledChanged)
51 Q_PROPERTY (
52 bool recordEnabled READ recordEnabled WRITE setRecordEnabled NOTIFY
53 recordEnabledChanged)
54 Q_PROPERTY (
55 bool punchEnabled READ punchEnabled WRITE setPunchEnabled NOTIFY
56 punchEnabledChanged)
57 Q_PROPERTY (
58 PlayState playState READ getPlayState WRITE setPlayState NOTIFY
59 playStateChanged)
60 Q_PROPERTY (zrythm::dsp::PlayheadQmlWrapper * playhead READ playhead CONSTANT)
61 Q_PROPERTY (
62 zrythm::dsp::AtomicPositionQmlAdapter * cuePosition READ cuePosition CONSTANT)
63 Q_PROPERTY (
64 zrythm::dsp::AtomicPositionQmlAdapter * loopStartPosition READ
65 loopStartPosition CONSTANT)
66 Q_PROPERTY (
67 zrythm::dsp::AtomicPositionQmlAdapter * loopEndPosition READ loopEndPosition
68 CONSTANT)
69 Q_PROPERTY (
70 zrythm::dsp::AtomicPositionQmlAdapter * punchInPosition READ punchInPosition
71 CONSTANT)
72 Q_PROPERTY (
74 punchOutPosition CONSTANT)
75 QML_UNCREATABLE ("")
76
77
78
79 static constexpr auto REPEATED_BACKWARD_MS = au::milli (units::seconds) (240);
80
81public:
82 Q_ENUM (PlayState)
83
86 * gsettings.
87 */
88 enum class Display
89 {
90 BBT,
91 Time,
92 };
93 Q_ENUM (Display)
94
95
101 enum class RecordingMode
102 {
121
123
124 /**
125 * Events get put in new lanes each time recording starts/resumes (eg,
126 * when looping or entering/leaving the punch range).
127 */
128 Takes,
135 };
136 Q_ENUM (RecordingMode)
137
138public:
139 struct ConfigProvider
140 {
144 std::function<bool ()> return_to_cue_on_pause_;
150 std::function<int ()> metronome_countin_bars_;
151
157 std::function<int ()> recording_preroll_bars_;
158 };
159
161 {
162 public:
164 std::pair<units::sample_t, units::sample_t> loop_range,
165 std::pair<units::sample_t, units::sample_t> punch_range,
166 units::sample_t playhead_position,
169 PlayState play_state,
170 bool loop_enabled,
171 bool punch_enabled,
173 : loop_range_ (loop_range), punch_range_ (punch_range),
174 playhead_position_ (playhead_position),
175 recording_preroll_frames_remaining_ (recording_preroll_frames_remaining),
176 metronome_countin_frames_remaining_ (metronome_countin_frames_remaining),
177 play_state_ (play_state), loop_enabled_ (loop_enabled),
178 punch_enabled_ (punch_enabled), recording_enabled_ (recording_enabled)
180 }
181
182 std::pair<units::sample_t, units::sample_t>
183 get_loop_range_positions () const noexcept override
184 {
185 return loop_range_;
187 std::pair<units::sample_t, units::sample_t>
188 get_punch_range_positions () const noexcept override
189 {
190 return punch_range_;
192 PlayState get_play_state () const noexcept override { return play_state_; }
193 units::sample_t
194 get_playhead_position_in_audio_thread () const noexcept override
195 {
196 return playhead_position_;
197 }
198 bool loop_enabled () const noexcept override { return loop_enabled_; }
199
200 bool punch_enabled () const noexcept override { return punch_enabled_; }
201 bool recording_enabled () const noexcept override
202 {
203 return recording_enabled_;
204 }
205 units::sample_t
206 recording_preroll_frames_remaining () const noexcept override
207 {
208 return recording_preroll_frames_remaining_;
209 }
210 units::sample_t
211 metronome_countin_frames_remaining () const noexcept override
212 {
213 return metronome_countin_frames_remaining_;
214 }
215
216 void set_play_state (dsp::ITransport::PlayState play_state)
217 {
218 play_state_ = play_state;
219 }
220 void set_position (units::sample_t position)
221 {
222 playhead_position_ = position;
223 }
224 void consume_metronome_countin_samples (units::sample_t samples)
225 {
226 metronome_countin_frames_remaining_ -= samples;
227 }
228 void consume_recording_preroll_samples (units::sample_t samples)
229 {
230 recording_preroll_frames_remaining_ -= samples;
231 }
232
233 private:
234 std::pair<units::sample_t, units::sample_t> loop_range_;
235 std::pair<units::sample_t, units::sample_t> punch_range_;
236 units::sample_t playhead_position_;
237 units::sample_t recording_preroll_frames_remaining_;
238 units::sample_t metronome_countin_frames_remaining_;
239 PlayState play_state_;
240 bool loop_enabled_;
241 bool punch_enabled_;
242 bool recording_enabled_;
243 };
244
245 Transport (
246 const dsp::TempoMap &tempo_map,
247 ConfigProvider config_provider,
248 QObject * parent = nullptr);
249
250 // ==================================================================
251 // QML Interface
252 // ==================================================================
253
254 bool loopEnabled () const { return loop_enabled (); }
255 void setLoopEnabled (bool enabled);
256 Q_SIGNAL void loopEnabledChanged (bool enabled);
257
258 bool recordEnabled () const { return recording_enabled (); }
259 void setRecordEnabled (bool enabled);
260 Q_SIGNAL void recordEnabledChanged (bool enabled);
261
262 bool punchEnabled () const { return punch_enabled (); }
263 void setPunchEnabled (bool enabled);
264 Q_SIGNAL void punchEnabledChanged (bool enabled);
265
266 PlayState getPlayState () const;
267 void setPlayState (PlayState state);
268 Q_SIGNAL void playStateChanged (PlayState state);
269
270 dsp::PlayheadQmlWrapper * playhead () const
271 {
272 return playhead_adapter_.get ();
273 }
274 dsp::AtomicPositionQmlAdapter * cuePosition () const
275 {
276 return cue_position_adapter_.get ();
277 }
278 dsp::AtomicPositionQmlAdapter * loopStartPosition () const
279 {
280 return loop_start_position_adapter_.get ();
281 }
282 dsp::AtomicPositionQmlAdapter * loopEndPosition () const
284 return loop_end_position_adapter_.get ();
285 }
286 dsp::AtomicPositionQmlAdapter * punchInPosition () const
287 {
288 return punch_in_position_adapter_.get ();
289 }
290 dsp::AtomicPositionQmlAdapter * punchOutPosition () const
291 {
292 return punch_out_position_adapter_.get ();
293 }
294
298 Q_INVOKABLE void requestPause () [[clang::blocking]];
303 Q_INVOKABLE void requestRoll () [[clang::blocking]];
304
308 * This is intended for user-initiated playhead moves (e.g., clicking on the
309 * ruler).
310 *
311 * @param ticks The target position in ticks.
312 * @param setCuePoint If true, also sets the cue position to this position.
313 */
314 Q_INVOKABLE void movePlayhead (double ticks, bool setCuePoint);
315
316 // ==================================================================
317
318 // ==================================================================
319 // ITransport Interface
320 // ==================================================================
322 units::sample_t
323 get_playhead_position_in_audio_thread () const noexcept override
324 {
325 return playhead_.position_during_processing_rounded ();
326 }
327
328 std::pair<units::sample_t, units::sample_t>
329 get_loop_range_positions () const noexcept override
330 {
331 return std::make_pair (
332 loop_start_position_.get_samples (), loop_end_position_.get_samples ());
333 }
334
335 std::pair<units::sample_t, units::sample_t>
336 get_punch_range_positions () const noexcept override
337 {
338 return std::make_pair (
339 punch_in_position_.get_samples (), punch_out_position_.get_samples ());
340 }
341
342 PlayState get_play_state () const noexcept override { return play_state_; }
343
344 bool loop_enabled () const noexcept override { return loop_; }
345 bool punch_enabled () const noexcept override { return punch_mode_; }
346 bool recording_enabled () const noexcept override { return recording_; }
347 units::sample_t recording_preroll_frames_remaining () const noexcept override
348 {
349 return recording_preroll_frames_remaining_;
350 }
351 units::sample_t metronome_countin_frames_remaining () const noexcept override
352 {
353 return countin_frames_remaining_;
354 }
355
356 // ==================================================================
358 Q_INVOKABLE bool isRolling () const
359 {
360 return play_state_ == PlayState::Rolling;
361 }
362
363 Q_INVOKABLE bool isPaused () const
364 {
365 return play_state_ == PlayState::Paused;
366 }
367
372 void add_to_playhead_in_audio_thread (units::sample_t nframes);
374 void set_play_state_rt_safe (PlayState state);
375
380 * playing. For example it should be used for moves when the user clicks on a
381 * position in the ruler.
382 *
383 * Should not be used during exporting.
384 *
385 * @param target_ticks Position to set to.
386 * @param set_cue_point Also set the cue point at this position.
387 */
388 void move_playhead (units::precise_tick_t target_ticks, bool set_cue_point);
389
396 bool prev,
397 RangeOf<units::precise_tick_t> auto &&extra_markers)
398 {
399 /* gather all markers */
400 std::vector<units::precise_tick_t> marker_ticks;
401 static_assert (__cpp_lib_containers_ranges >= 202202L);
402 marker_ticks.append_range (extra_markers);
403 marker_ticks.emplace_back (cue_position_.get_ticks ());
404 marker_ticks.emplace_back (loop_start_position_.get_ticks ());
405 marker_ticks.emplace_back (loop_end_position_.get_ticks ());
406 marker_ticks.emplace_back ();
407 std::ranges::sort (marker_ticks);
408
409 if (prev)
410 {
411 for (
412 const auto &[index, marker_tick] :
413 marker_ticks | utils::views::enumerate | std::views::reverse)
414 {
415 if (marker_tick >= playhead_.position_ticks ())
416 continue;
417
418 if (
419 isRolling () && index > 0
420 && (playhead_.get_tempo_map ().tick_to_seconds (
421 playhead_.position_ticks ())
422 - playhead_.get_tempo_map ().tick_to_seconds (marker_tick))
423 < REPEATED_BACKWARD_MS)
424 {
425 continue;
426 }
427
428 move_playhead (marker_tick, true);
429 break;
430 }
431 }
432 else
433 {
434 for (auto &marker : marker_ticks)
435 {
436 if (marker > playhead_.position_ticks ())
437 {
438 move_playhead (marker, true);
439 break;
440 }
441 }
443 }
444
445 bool position_is_inside_punch_range (units::sample_t pos);
446
447 auto playhead_ticks_before_pause () const [[clang::blocking]]
448 {
449 return playhead_before_pause_;
450 }
451
453 * @brief For engine use only.
454 *
455 * @param samples Samples to consume.
456 */
457 void consume_metronome_countin_samples (units::sample_t samples)
458 {
459 assert (countin_frames_remaining_ >= samples);
460 countin_frames_remaining_ -= samples;
461 }
462
468 void consume_recording_preroll_samples (units::sample_t samples)
469 {
470 assert (recording_preroll_frames_remaining_ >= samples);
471 recording_preroll_frames_remaining_ -= samples;
472 }
473
474 auto get_snapshot () const
475 {
476 return TransportSnapshot{
482 get_play_state (),
483 loop_enabled (),
484 punch_enabled (),
486 };
487 }
488
489 friend void init_from (
490 Transport &obj,
491 const Transport &other,
492 utils::ObjectCloneType clone_type);
493
494private:
495 static constexpr auto kPlayheadKey = "playheadPosition"sv;
496 static constexpr auto kCuePosKey = "cuePosition"sv;
497 static constexpr auto kLoopStartPosKey = "loopStartPosition"sv;
498 static constexpr auto kLoopEndPosKey = "loopEndPosition"sv;
499 static constexpr auto kPunchInPosKey = "punchInPosition"sv;
500 static constexpr auto kPunchOutPosKey = "punchOutPosition"sv;
501 friend void to_json (nlohmann::json &j, const Transport &transport);
502 friend void from_json (const nlohmann::json &j, Transport &transport);
503
508 bool can_user_move_playhead () const;
509
510private:
512 dsp::Playhead playhead_;
513 utils::QObjectUniquePtr<dsp::PlayheadQmlWrapper> playhead_adapter_;
514
515 std::unique_ptr<dsp::AtomicPosition::TimeConversionFunctions>
516 time_conversion_funcs_;
517
519 dsp::AtomicPosition cue_position_;
520 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter> cue_position_adapter_;
521
523 dsp::AtomicPosition loop_start_position_;
524 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter>
525 loop_start_position_adapter_;
526
528 dsp::AtomicPosition loop_end_position_;
529 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter>
530 loop_end_position_adapter_;
531
533 dsp::AtomicPosition punch_in_position_;
534 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter>
535 punch_in_position_adapter_;
536
538 dsp::AtomicPosition punch_out_position_;
539 utils::QObjectUniquePtr<dsp::AtomicPositionQmlAdapter>
540 punch_out_position_adapter_;
541
543 bool loop_{ true };
544
546 bool punch_mode_{ false };
547
550 bool recording_{ false };
551
553 units::sample_t recording_preroll_frames_remaining_;
554
556 units::sample_t countin_frames_remaining_;
557
563 units::precise_tick_t playhead_before_pause_;
564
566 PlayState play_state_{ PlayState::Paused };
567
575 QTimer * property_notification_timer_ = nullptr;
576 std::atomic<bool> needs_property_notification_{ false };
577
578 ConfigProvider config_provider_;
579};
580}
Interface for transport.
Definition itransport.h:17
auto position_ticks() const
Get current playhead position in ticks (GUI thread only).
Definition playhead.h:173
units::sample_t get_playhead_position_in_audio_thread() const noexcept override
Get the playhead position.
Definition transport.h:179
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:173
units::sample_t metronome_countin_frames_remaining() const noexcept override
Frames remaining for metronome countin.
Definition transport.h:196
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:168
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:191
bool recording_enabled() const noexcept override
Returns whether recording is enabled.
Definition transport.h:186
Q_INVOKABLE void movePlayhead(double ticks, bool setCuePoint)
Moves the playhead to the given tick position.
void goto_prev_or_next_marker(bool prev, RangeOf< units::precise_tick_t > auto &&extra_markers)
Moves the playhead to the previous or next marker.
Definition transport.h:380
Q_INVOKABLE void requestPause()
Request pause.
units::sample_t get_playhead_position_in_audio_thread() const noexcept override
Get the playhead position.
Definition transport.h:308
units::sample_t metronome_countin_frames_remaining() const noexcept override
Frames remaining for metronome countin.
Definition transport.h:336
void consume_metronome_countin_samples(units::sample_t samples)
For engine use only.
Definition transport.h:442
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:87
@ MergeEvents
Merge events in existing region.
Definition transport.h:107
@ Takes
Events get put in new lanes each time recording starts/resumes (eg, when looping or entering/leaving ...
Definition transport.h:113
@ OverwriteEvents
Overwrite events in first recorded region.
Definition transport.h:97
@ TakesMuted
Same as RECORDING_MODE_TAKES, except previous takes (since recording started) are muted.
Definition transport.h:119
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:321
void add_to_playhead_in_audio_thread(units::sample_t nframes)
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:453
bool recording_enabled() const noexcept override
Returns whether recording is enabled.
Definition transport.h:331
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:314
Display
Corrseponts to "transport-display" in the gsettings.
Definition transport.h:74
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:332
std::function< bool()> return_to_cue_on_pause_
Whether to return to cue position on pause.
Definition transport.h:129
std::function< int()> metronome_countin_bars_
Number of bars to count-in when requesting playback with metronome enabled.
Definition transport.h:135
std::function< int()> recording_preroll_bars_
Number of bars to pre-roll before recording.
Definition transport.h:142