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/itransport.h"
7#include "dsp/timeline_data_cache.h"
8#include "structure/arrangement/region_renderer.h"
9#include "utils/expandable_tick_range.h"
10#include "utils/qt.h"
11
12#include <farbot/RealtimeObject.hpp>
13
14namespace zrythm::structure::arrangement
15{
16
24class TimelineDataProvider : public QObject
25{
26 Q_OBJECT
27
28public:
29 using IntervalType = std::pair<units::sample_t, units::sample_t>;
30
31 explicit TimelineDataProvider (QObject * parent = nullptr) : QObject (parent)
32 {
33 }
34
35 ~TimelineDataProvider () override;
36
49 template <RegionObject RegionType>
51 this auto &self,
52 const dsp::TempoMap &tempo_map,
53 RangeOf<const RegionType *> auto regions,
54 utils::ExpandableTickRange affected_range)
55 {
56 // Convert tick range to sample range
57 const auto sample_interval = [&affected_range, &tempo_map] ()
58 -> std::pair<units::sample_t, units::sample_t> {
59 if (!affected_range.is_full_content ())
60 {
61 const auto tick_range = affected_range.range ().value ();
62 return std::make_pair (
63 tempo_map.tick_to_samples_rounded (units::ticks (tick_range.first)),
64 tempo_map.tick_to_samples_rounded (units::ticks (tick_range.second)));
65 }
66 return std::make_pair (
67 units::samples (static_cast<int64_t> (0)),
68 units::samples (std::numeric_limits<int64_t>::max ()));
69 }();
70
71 // Remove existing caches at given interval (or all caches if no interval
72 // given)
73
74 if (affected_range.is_full_content ())
75 {
76 self.get_base_cache ()->clear ();
77 }
78 else
79 {
80 self.get_base_cache ()->remove_sequences_matching_interval (
81 sample_interval);
82 }
83
84 const auto regions_inside_interval_filter_func =
85 [&affected_range, sample_interval] (const auto &region) {
86 if (affected_range.is_full_content ())
87 return true;
88
89 return region->bounds ()->is_hit_by_range (sample_interval);
90 };
91
92 const auto cache_region = [&] (const auto * r) {
93 // Skip muted regions
94 if (r->mute ()->muted ())
95 return;
96
97 if constexpr (
98 std::is_same_v<RegionType, arrangement::MidiRegion>
99 || std::is_same_v<RegionType, arrangement::ChordRegion>)
100 {
101 self.cache_midi_region (*r, tempo_map);
102 }
103 else if constexpr (std::is_same_v<RegionType, arrangement::AudioRegion>)
104 {
105 self.cache_audio_region (*r);
106 }
107 else if constexpr (
108 std::is_same_v<RegionType, arrangement::AutomationRegion>)
109 {
110 self.cache_automation_region (*r, tempo_map);
111 }
112 };
113
114 // Go through each region and add a cache
115 std::ranges::for_each (
116 std::views::filter (regions, regions_inside_interval_filter_func),
117 cache_region);
118
119 // Finalize
120 self.get_base_cache ()->finalize_changes ();
121 }
122
123 virtual void clear_all_caches () = 0;
124 virtual void
125 remove_sequences_matching_interval_from_all_caches (IntervalType interval) = 0;
126
127 virtual const dsp::TimelineDataCache * get_base_cache () const = 0;
128
129protected:
130 virtual dsp::TimelineDataCache * get_base_cache () = 0;
132 dsp::ITransport::PlayState last_seen_transport_state_{
133 dsp::ITransport::PlayState::Paused
134 };
135
138};
139
146class MidiTimelineDataProvider : public TimelineDataProvider
147{
148 friend class TimelineDataProvider;
149
150public:
151 explicit MidiTimelineDataProvider (QObject * parent = nullptr);
152
153 const dsp::TimelineDataCache * get_base_cache () const override
154 {
155 return midi_cache_.get ();
156 }
157
162 const dsp::graph::EngineProcessTimeInfo &time_nfo,
163 dsp::ITransport::PlayState transport_state,
164 dsp::MidiEventVector &output_buffer) noexcept [[clang::nonblocking]];
165
166 void clear_all_caches () override;
167 void remove_sequences_matching_interval_from_all_caches (
168 IntervalType interval) override;
169
170 const juce::MidiMessageSequence &get_midi_events () const;
171
183 const dsp::TempoMap &tempo_map,
185 utils::ExpandableTickRange affected_range)
186 {
188 tempo_map, midi_regions, affected_range);
189 set_midi_events (midi_cache_->get_midi_events ());
190 }
191
203 const dsp::TempoMap &tempo_map,
205 utils::ExpandableTickRange affected_range)
206 {
208 tempo_map, chord_regions, affected_range);
209 set_midi_events (midi_cache_->get_midi_events ());
210 }
211
212protected:
213 dsp::TimelineDataCache * get_base_cache () override
214 {
215 return midi_cache_.get ();
216 }
217
218private:
222 template <typename MidiRegionType>
223 void
224 cache_midi_region (const MidiRegionType &region, const dsp::TempoMap &tempo_map)
225 {
226 // MIDI region processing
227 juce::MidiMessageSequence region_seq;
228
229 // Serialize region (timings in timeline ticks)
231 region_seq.addTimeToMessages (region.position ()->ticks ());
232
233 // Convert timings to samples
234 for (auto &event : region_seq)
235 {
236 event->message.setTimeStamp (
237 static_cast<double> (
238 tempo_map
239 .tick_to_samples_rounded (
240 units::ticks (event->message.getTimeStamp ()))
241 .in (units::samples)));
242 }
243
244 // Add to cache
245 midi_cache_->add_midi_sequence (
246 std::make_pair (
247 units::samples (region.position ()->samples ()),
248 region.bounds ()->get_end_position_samples (true)),
249 region_seq);
250 }
251
257 void set_midi_events (const juce::MidiMessageSequence &events);
258
259 utils::QObjectUniquePtr<dsp::MidiTimelineDataCache> midi_cache_;
260
261 farbot::RealtimeObject<
262 juce::MidiMessageSequence,
263 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
264 active_midi_playback_sequence_;
265};
266
273class AudioTimelineDataProvider : public TimelineDataProvider
274{
275 friend class TimelineDataProvider;
276
277public:
278 explicit AudioTimelineDataProvider (QObject * parent = nullptr);
279
280 const dsp::TimelineDataCache * get_base_cache () const override
281 {
282 return audio_cache_.get ();
283 }
284
289 const dsp::graph::EngineProcessTimeInfo &time_nfo,
290 dsp::ITransport::PlayState transport_state,
291 std::span<float> output_left,
292 std::span<float> output_right) noexcept [[clang::nonblocking]];
293
294 void clear_all_caches () override;
295 void remove_sequences_matching_interval_from_all_caches (
296 IntervalType interval) override;
297
298 const std::vector<dsp::AudioTimelineDataCache::AudioRegionEntry> &
299 get_audio_regions () const;
300
312 const dsp::TempoMap &tempo_map,
314 utils::ExpandableTickRange affected_range)
315 {
317 tempo_map, audio_regions, affected_range);
318 set_audio_regions (audio_cache_->get_audio_regions ());
319 }
320
321protected:
322 dsp::TimelineDataCache * get_base_cache () override
323 {
324 return audio_cache_.get ();
325 }
326
327private:
331 void cache_audio_region (const arrangement::AudioRegion &region);
332
338 void set_audio_regions (
339 const std::vector<dsp::AudioTimelineDataCache::AudioRegionEntry> &regions);
340
342
343 farbot::RealtimeObject<
344 std::vector<dsp::AudioTimelineDataCache::AudioRegionEntry>,
345 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
346 active_audio_regions_;
347};
348
355class AutomationTimelineDataProvider : public TimelineDataProvider
356{
357 friend class TimelineDataProvider;
358
359public:
360 explicit AutomationTimelineDataProvider (QObject * parent = nullptr);
361
362 const dsp::TimelineDataCache * get_base_cache () const override
363 {
364 return automation_cache_.get ();
365 }
366
371 const dsp::graph::EngineProcessTimeInfo &time_nfo,
372 dsp::ITransport::PlayState transport_state,
373 std::span<float> output_values) noexcept [[clang::nonblocking]];
374
381 std::optional<float>
382 get_automation_value_rt (units::sample_t sample_position) noexcept
383 [[clang::nonblocking]];
384
385 void clear_all_caches () override;
386 void remove_sequences_matching_interval_from_all_caches (
387 IntervalType interval) override;
388
389 const std::vector<dsp::AutomationTimelineDataCache::AutomationCacheEntry> &
390 get_automation_sequences () const;
391
403 const dsp::TempoMap &tempo_map,
405 utils::ExpandableTickRange affected_range)
406 {
408 tempo_map, automation_regions, affected_range);
409 set_automation_sequences (automation_cache_->get_automation_sequences ());
410 }
411
412protected:
413 dsp::TimelineDataCache * get_base_cache () override
414 {
415 return automation_cache_.get ();
416 }
417
418private:
422 void cache_automation_region (
423 const arrangement::AutomationRegion &region,
424 const dsp::TempoMap &tempo_map);
425
431 void set_automation_sequences (
432 const std::vector<dsp::AutomationTimelineDataCache::AutomationCacheEntry>
433 &sequences);
434
436
437 farbot::RealtimeObject<
438 std::vector<dsp::AutomationTimelineDataCache::AutomationCacheEntry>,
439 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
440 active_automation_sequences_;
441};
442
443} // 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::EngineProcessTimeInfo &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::EngineProcessTimeInfo &time_nfo, dsp::ITransport::PlayState transport_state, std::span< float > output_values) noexcept
Process automation 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 process_midi_events(const dsp::graph::EngineProcessTimeInfo &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::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