Zrythm v2.0.0-DEV
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
timeline_data_provider.h
1// SPDX-FileCopyrightText: © 2025 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include "dsp/graph_node.h"
7#include "dsp/itransport.h"
8#include "dsp/timeline_data_cache.h"
9#include "structure/arrangement/region_renderer.h"
10#include "utils/expandable_tick_range.h"
11#include "utils/qt.h"
12
13#include <farbot/RealtimeObject.hpp>
14
15namespace zrythm::structure::arrangement
16{
17
25class TimelineDataProvider : public QObject
26{
27 Q_OBJECT
28
29public:
30 using IntervalType = std::pair<units::sample_t, units::sample_t>;
31
32 explicit TimelineDataProvider (QObject * parent = nullptr) : QObject (parent)
33 {
34 }
35
36 ~TimelineDataProvider () override;
37
50 template <RegionObject RegionType>
52 this auto &self,
53 const dsp::TempoMap &tempo_map,
54 RangeOf<const RegionType *> auto regions,
55 utils::ExpandableTickRange affected_range)
56 {
57 // Convert tick range to sample range
58 const auto sample_interval = [&affected_range, &tempo_map] ()
59 -> std::pair<units::sample_t, units::sample_t> {
60 if (!affected_range.is_full_content ())
61 {
62 const auto tick_range = affected_range.range ().value ();
63 return std::make_pair (
64 tempo_map.tick_to_samples_rounded (units::ticks (tick_range.first)),
65 tempo_map.tick_to_samples_rounded (units::ticks (tick_range.second)));
66 }
67 return std::make_pair (
68 units::samples (static_cast<int64_t> (0)),
69 units::samples (std::numeric_limits<int64_t>::max ()));
70 }();
71
72 // Remove existing caches at given interval (or all caches if no interval
73 // given)
74
75 if (affected_range.is_full_content ())
76 {
77 self.get_base_cache ()->clear ();
78 }
79 else
80 {
81 self.get_base_cache ()->remove_sequences_matching_interval (
82 sample_interval);
83 }
84
85 const auto regions_inside_interval_filter_func =
86 [&affected_range, sample_interval] (const auto &region) {
87 if (affected_range.is_full_content ())
88 return true;
89
90 return region->bounds ()->is_hit_by_range (sample_interval);
91 };
92
93 const auto cache_region = [&] (const auto * r) {
94 // Skip muted regions
95 if (r->mute ()->muted ())
96 return;
97
98 if constexpr (
99 std::is_same_v<RegionType, arrangement::MidiRegion>
100 || std::is_same_v<RegionType, arrangement::ChordRegion>)
101 {
102 self.cache_midi_region (*r, tempo_map);
103 }
104 else if constexpr (std::is_same_v<RegionType, arrangement::AudioRegion>)
105 {
106 self.cache_audio_region (*r);
107 }
108 else if constexpr (
109 std::is_same_v<RegionType, arrangement::AutomationRegion>)
110 {
111 self.cache_automation_region (*r, tempo_map);
112 }
113 };
114
115 // Go through each region and add a cache
116 std::ranges::for_each (
117 std::views::filter (regions, regions_inside_interval_filter_func),
118 cache_region);
119
120 // Finalize
121 self.get_base_cache ()->finalize_changes ();
122 }
123
124 virtual void clear_all_caches () = 0;
125 virtual void
126 remove_sequences_matching_interval_from_all_caches (IntervalType interval) = 0;
127
128 virtual const dsp::TimelineDataCache * get_base_cache () const = 0;
129
130protected:
131 virtual dsp::TimelineDataCache * get_base_cache () = 0;
133 dsp::ITransport::PlayState last_seen_transport_state_{
134 dsp::ITransport::PlayState::Paused
135 };
136
139};
140
147class MidiTimelineDataProvider : public TimelineDataProvider
148{
149 friend class TimelineDataProvider;
150
151public:
152 explicit MidiTimelineDataProvider (QObject * parent = nullptr);
153
154 const dsp::TimelineDataCache * get_base_cache () const override
155 {
156 return midi_cache_.get ();
157 }
158
163 const dsp::graph::ProcessBlockInfo &time_nfo,
164 dsp::ITransport::PlayState transport_state,
165 dsp::MidiEventVector &output_buffer) noexcept [[clang::nonblocking]];
166
167 void clear_all_caches () override;
168 void remove_sequences_matching_interval_from_all_caches (
169 IntervalType interval) override;
170
171 const juce::MidiMessageSequence &get_midi_events () const;
172
184 const dsp::TempoMap &tempo_map,
186 utils::ExpandableTickRange affected_range)
187 {
189 tempo_map, midi_regions, affected_range);
190 set_midi_events (midi_cache_->get_midi_events ());
191 }
192
204 const dsp::TempoMap &tempo_map,
206 utils::ExpandableTickRange affected_range)
207 {
209 tempo_map, chord_regions, affected_range);
210 set_midi_events (midi_cache_->get_midi_events ());
211 }
212
213protected:
214 dsp::TimelineDataCache * get_base_cache () override
215 {
216 return midi_cache_.get ();
217 }
218
219private:
223 template <typename MidiRegionType>
224 void
225 cache_midi_region (const MidiRegionType &region, const dsp::TempoMap &tempo_map)
226 {
227 // MIDI region processing
228 juce::MidiMessageSequence region_seq;
229
230 // Serialize region (timings in timeline ticks)
232 region_seq.addTimeToMessages (region.position ()->ticks ());
233
234 // Convert timings to samples
235 for (auto &event : region_seq)
236 {
237 event->message.setTimeStamp (
238 static_cast<double> (
239 tempo_map
240 .tick_to_samples_rounded (
241 units::ticks (event->message.getTimeStamp ()))
242 .in (units::samples)));
243 }
244
245 // Add to cache
246 midi_cache_->add_midi_sequence (
247 std::make_pair (
248 units::samples (region.position ()->samples ()),
249 region.bounds ()->get_end_position_samples (true)),
250 region_seq);
251 }
252
258 void set_midi_events (const juce::MidiMessageSequence &events);
259
260 utils::QObjectUniquePtr<dsp::MidiTimelineDataCache> midi_cache_;
261
262 farbot::RealtimeObject<
263 juce::MidiMessageSequence,
264 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
265 active_midi_playback_sequence_;
266};
267
274class AudioTimelineDataProvider : public TimelineDataProvider
275{
276 friend class TimelineDataProvider;
277
278public:
279 explicit AudioTimelineDataProvider (QObject * parent = nullptr);
280
281 const dsp::TimelineDataCache * get_base_cache () const override
282 {
283 return audio_cache_.get ();
284 }
285
290 const dsp::graph::ProcessBlockInfo &time_nfo,
291 dsp::ITransport::PlayState transport_state,
292 std::span<float> output_left,
293 std::span<float> output_right) noexcept [[clang::nonblocking]];
294
295 void clear_all_caches () override;
296 void remove_sequences_matching_interval_from_all_caches (
297 IntervalType interval) override;
298
299 const std::vector<dsp::AudioTimelineDataCache::AudioRegionEntry> &
300 get_audio_regions () const;
301
313 const dsp::TempoMap &tempo_map,
315 utils::ExpandableTickRange affected_range)
316 {
318 tempo_map, audio_regions, affected_range);
319 set_audio_regions (audio_cache_->get_audio_regions ());
320 }
321
322protected:
323 dsp::TimelineDataCache * get_base_cache () override
324 {
325 return audio_cache_.get ();
326 }
327
328private:
332 void cache_audio_region (const arrangement::AudioRegion &region);
333
339 void set_audio_regions (
340 const std::vector<dsp::AudioTimelineDataCache::AudioRegionEntry> &regions);
341
343
344 farbot::RealtimeObject<
345 std::vector<dsp::AudioTimelineDataCache::AudioRegionEntry>,
346 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
347 active_audio_regions_;
348};
349
356class AutomationTimelineDataProvider : public TimelineDataProvider
357{
358 friend class TimelineDataProvider;
359
360public:
361 explicit AutomationTimelineDataProvider (QObject * parent = nullptr);
362
363 const dsp::TimelineDataCache * get_base_cache () const override
364 {
365 return automation_cache_.get ();
366 }
367
372 const dsp::graph::ProcessBlockInfo &time_nfo,
373 dsp::ITransport::PlayState transport_state,
374 std::span<float> output_values) noexcept [[clang::nonblocking]];
375
382 std::optional<float>
383 get_automation_value_rt (units::sample_t sample_position) noexcept
384 [[clang::nonblocking]];
385
386 void clear_all_caches () override;
387 void remove_sequences_matching_interval_from_all_caches (
388 IntervalType interval) override;
389
390 const std::vector<dsp::AutomationTimelineDataCache::AutomationCacheEntry> &
391 get_automation_sequences () const;
392
404 const dsp::TempoMap &tempo_map,
406 utils::ExpandableTickRange affected_range)
407 {
409 tempo_map, automation_regions, affected_range);
410 set_automation_sequences (automation_cache_->get_automation_sequences ());
411 }
412
413protected:
414 dsp::TimelineDataCache * get_base_cache () override
415 {
416 return automation_cache_.get ();
417 }
418
419private:
423 void cache_automation_region (
424 const arrangement::AutomationRegion &region,
425 const dsp::TempoMap &tempo_map);
426
432 void set_automation_sequences (
433 const std::vector<dsp::AutomationTimelineDataCache::AutomationCacheEntry>
434 &sequences);
435
437
438 farbot::RealtimeObject<
439 std::vector<dsp::AutomationTimelineDataCache::AutomationCacheEntry>,
440 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
441 active_automation_sequences_;
442};
443
444} // namespace zrythm::structure::arrangement
A lock-free thread-safe vector of MidiEvents.
Definition midi_event.h:74
Base class for timeline data caches.
A region for playing back audio samples.
void process_audio_events(const dsp::graph::ProcessBlockInfo &time_nfo, dsp::ITransport::PlayState transport_state, std::span< float > output_left, std::span< float > output_right) noexcept
Process audio events for the given time range.
void generate_audio_events(const dsp::TempoMap &tempo_map, RangeOf< const arrangement::AudioRegion * > auto audio_regions, utils::ExpandableTickRange affected_range)
Generate the audio event sequence to be used during realtime processing.
Represents an automation region, which contains a collection of automation points.
void generate_automation_events(const dsp::TempoMap &tempo_map, RangeOf< const arrangement::AutomationRegion * > auto automation_regions, utils::ExpandableTickRange affected_range)
Generate the automation event sequence to be used during realtime processing.
std::optional< float > get_automation_value_rt(units::sample_t sample_position) noexcept
Get the automation value for a specific sample position.
void process_automation_events(const dsp::graph::ProcessBlockInfo &time_nfo, dsp::ITransport::PlayState transport_state, std::span< float > output_values) noexcept
Process automation events for the given time range.
void process_midi_events(const dsp::graph::ProcessBlockInfo &time_nfo, dsp::ITransport::PlayState transport_state, dsp::MidiEventVector &output_buffer) noexcept
Process MIDI events for the given time range.
void generate_midi_events(const dsp::TempoMap &tempo_map, RangeOf< const arrangement::ChordRegion * > auto chord_regions, utils::ExpandableTickRange affected_range)
Generate the MIDI event sequence to be used during realtime processing.
void generate_midi_events(const dsp::TempoMap &tempo_map, RangeOf< const arrangement::MidiRegion * > auto midi_regions, utils::ExpandableTickRange affected_range)
Generate the MIDI event sequence to be used during realtime processing.
static void serialize_to_sequence(const MidiRegion &region, juce::MidiMessageSequence &events, std::optional< std::pair< double, double > > timeline_range_ticks=std::nullopt)
Serializes a MIDI region to a MIDI message sequence.
void generate_events(this auto &self, const dsp::TempoMap &tempo_map, RangeOf< const RegionType * > auto regions, utils::ExpandableTickRange affected_range)
Generate the event sequence to be used during realtime processing.
dsp::ITransport::PlayState last_seen_transport_state_
Last transport state we've seen.
units::sample_t next_expected_transport_position_
Next expected transport position (for detecting jumps).
bool is_full_content() const
Returns whether the range is the full content (ie, there is no range).
auto range() const -> std::optional< std::pair< double, double > >
Returns the range, or nullopt if the full content is covered.
A unique pointer for QObject objects that also works with QObject-based ownership.
Definition qt.h:36
Common struct to pass around during processing to avoid repeating the data in function arguments.
Definition graph_node.h:51