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/types.h"
11
12#include <farbot/RealtimeObject.hpp>
13
14namespace zrythm::structure::arrangement
15{
16
25{
26public:
27 using IntervalType = std::pair<units::sample_t, units::sample_t>;
28
29 virtual ~TimelineDataProvider ();
30
43 template <RegionObject RegionType>
45 this auto &self,
46 const dsp::TempoMap &tempo_map,
47 RangeOf<const RegionType *> auto regions,
48 utils::ExpandableTickRange affected_range)
49 {
50 // Convert tick range to sample range
51 const auto sample_interval = [&affected_range, &tempo_map] ()
52 -> std::pair<units::sample_t, units::sample_t> {
53 if (!affected_range.is_full_content ())
54 {
55 const auto tick_range = affected_range.range ().value ();
56 return std::make_pair (
57 tempo_map.tick_to_samples_rounded (units::ticks (tick_range.first)),
58 tempo_map.tick_to_samples_rounded (units::ticks (tick_range.second)));
59 }
60 return std::make_pair (
61 units::samples (static_cast<int64_t> (0)),
62 units::samples (std::numeric_limits<int64_t>::max ()));
63 }();
64
65 // Remove existing caches at given interval (or all caches if no interval
66 // given)
67
68 if (affected_range.is_full_content ())
69 {
70 self.get_base_cache ()->clear ();
71 }
72 else
73 {
74 self.get_base_cache ()->remove_sequences_matching_interval (
75 sample_interval);
76 }
77
78 const auto regions_inside_interval_filter_func =
79 [&affected_range, sample_interval] (const auto &region) {
80 if (affected_range.is_full_content ())
81 return true;
82
83 return region->bounds ()->is_hit_by_range (sample_interval);
84 };
85
86 const auto cache_region = [&] (const auto * r) {
87 // Skip muted regions
88 if (r->mute ()->muted ())
89 return;
90
91 if constexpr (
92 std::is_same_v<RegionType, arrangement::MidiRegion>
93 || std::is_same_v<RegionType, arrangement::ChordRegion>)
94 {
95 self.cache_midi_region (*r, tempo_map);
96 }
97 else if constexpr (std::is_same_v<RegionType, arrangement::AudioRegion>)
98 {
99 self.cache_audio_region (*r);
100 }
101 else if constexpr (
102 std::is_same_v<RegionType, arrangement::AutomationRegion>)
103 {
104 self.cache_automation_region (*r, tempo_map);
105 }
106 };
107
108 // Go through each region and add a cache
109 std::ranges::for_each (
110 std::views::filter (regions, regions_inside_interval_filter_func),
111 cache_region);
112
113 // Finalize
114 self.get_base_cache ()->finalize_changes ();
115 }
116
117 virtual void clear_all_caches () = 0;
118 virtual void
119 remove_sequences_matching_interval_from_all_caches (IntervalType interval) = 0;
120
121protected:
122 virtual dsp::TimelineDataCache * get_base_cache () = 0;
123
124protected:
126 dsp::ITransport::PlayState last_seen_transport_state_{
127 dsp::ITransport::PlayState::Paused
128 };
129
132};
133
140class MidiTimelineDataProvider : public TimelineDataProvider
141{
142 friend class TimelineDataProvider;
143
144public:
149 const EngineProcessTimeInfo &time_nfo,
150 dsp::ITransport::PlayState transport_state,
151 dsp::MidiEventVector &output_buffer) noexcept [[clang::nonblocking]];
152
153 // Implementation of base class methods
154 void clear_all_caches () override;
155 void remove_sequences_matching_interval_from_all_caches (
156 IntervalType interval) override;
157
158 const juce::MidiMessageSequence &get_midi_events () const;
159
171 const dsp::TempoMap &tempo_map,
173 utils::ExpandableTickRange affected_range)
174 {
176 tempo_map, midi_regions, affected_range);
177 set_midi_events (midi_cache_.get_midi_events ());
178 }
179
191 const dsp::TempoMap &tempo_map,
193 utils::ExpandableTickRange affected_range)
194 {
196 tempo_map, chord_regions, affected_range);
197 set_midi_events (midi_cache_.get_midi_events ());
198 }
199
200protected:
201 dsp::TimelineDataCache * get_base_cache () override { return &midi_cache_; }
202
203private:
207 template <typename MidiRegionType>
208 void
209 cache_midi_region (const MidiRegionType &region, const dsp::TempoMap &tempo_map)
210 {
211 // MIDI region processing
212 juce::MidiMessageSequence region_seq;
213
214 // Serialize region (timings in timeline ticks)
216 region_seq.addTimeToMessages (region.position ()->ticks ());
217
218 // Convert timings to samples
219 for (auto &event : region_seq)
220 {
221 event->message.setTimeStamp (
222 static_cast<double> (
223 tempo_map
224 .tick_to_samples_rounded (
225 units::ticks (event->message.getTimeStamp ()))
226 .in (units::samples)));
227 }
228
229 // Add to cache
230 midi_cache_.add_midi_sequence (
231 std::make_pair (
232 units::samples (region.position ()->samples ()),
233 region.bounds ()->get_end_position_samples (true)),
234 region_seq);
235 }
236
242 void set_midi_events (const juce::MidiMessageSequence &events);
243
244 dsp::MidiTimelineDataCache midi_cache_;
245
246 farbot::RealtimeObject<
247 juce::MidiMessageSequence,
248 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
249 active_midi_playback_sequence_;
250};
251
258class AudioTimelineDataProvider : public TimelineDataProvider
259{
260 friend class TimelineDataProvider;
261
262public:
267 const EngineProcessTimeInfo &time_nfo,
268 dsp::ITransport::PlayState transport_state,
269 std::span<float> output_left,
270 std::span<float> output_right) noexcept [[clang::nonblocking]];
271
272 // Implementation of base class methods
273 void clear_all_caches () override;
274 void remove_sequences_matching_interval_from_all_caches (
275 IntervalType interval) override;
276
277 const std::vector<dsp::AudioTimelineDataCache::AudioRegionEntry> &
278 get_audio_regions () const;
279
291 const dsp::TempoMap &tempo_map,
293 utils::ExpandableTickRange affected_range)
294 {
296 tempo_map, audio_regions, affected_range);
297 set_audio_regions (audio_cache_.get_audio_regions ());
298 }
299
300protected:
301 dsp::TimelineDataCache * get_base_cache () override { return &audio_cache_; }
302
303private:
307 void cache_audio_region (const arrangement::AudioRegion &region)
308 {
309 // Audio region processing
310 auto audio_buffer = std::make_unique<juce::AudioSampleBuffer> ();
311
312 // Serialize the audio region
314
315 // Add to cache with proper timing
316 audio_cache_.add_audio_region (
317 std::make_pair (
318 units::samples (region.position ()->samples ()),
319 region.bounds ()->get_end_position_samples (true)),
320 *audio_buffer);
321 }
322
328 void set_audio_regions (
329 const std::vector<dsp::AudioTimelineDataCache::AudioRegionEntry> &regions);
330
331 dsp::AudioTimelineDataCache audio_cache_;
332
333 farbot::RealtimeObject<
334 std::vector<dsp::AudioTimelineDataCache::AudioRegionEntry>,
335 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
336 active_audio_regions_;
337};
338
345class AutomationTimelineDataProvider : public TimelineDataProvider
346{
347 friend class TimelineDataProvider;
348
349public:
354 const EngineProcessTimeInfo &time_nfo,
355 dsp::ITransport::PlayState transport_state,
356 std::span<float> output_values) noexcept [[clang::nonblocking]];
357
364 std::optional<float>
365 get_automation_value_rt (units::sample_t sample_position) noexcept
366 [[clang::nonblocking]];
367
368 // Implementation of base class methods
369 void clear_all_caches () override;
370 void remove_sequences_matching_interval_from_all_caches (
371 IntervalType interval) override;
372
373 const std::vector<dsp::AutomationTimelineDataCache::AutomationCacheEntry> &
374 get_automation_sequences () const;
375
387 const dsp::TempoMap &tempo_map,
389 utils::ExpandableTickRange affected_range)
390 {
392 tempo_map, automation_regions, affected_range);
393 set_automation_sequences (automation_cache_.get_automation_sequences ());
394 }
395
396protected:
397 dsp::TimelineDataCache * get_base_cache () override
398 {
399 return &automation_cache_;
400 }
401
402private:
406 void cache_automation_region (
407 const arrangement::AutomationRegion &region,
408 const dsp::TempoMap &tempo_map)
409 {
410 // Calculate number of samples needed
411 const auto start_sample = units::samples (region.position ()->samples ());
412 const auto end_sample = region.bounds ()->get_end_position_samples (true);
413 const auto num_samples = end_sample - start_sample;
414
415 std::vector<float> automation_values (num_samples.in (units::samples));
416
417 // Serialize automation region to sample-accurate values
419 region, automation_values);
420
421 automation_cache_.add_automation_sequence (
422 std::make_pair (start_sample, end_sample), automation_values);
423 }
424
430 void set_automation_sequences (
431 const std::vector<dsp::AutomationTimelineDataCache::AutomationCacheEntry>
432 &sequences);
433
434 dsp::AutomationTimelineDataCache automation_cache_;
435
436 farbot::RealtimeObject<
437 std::vector<dsp::AutomationTimelineDataCache::AutomationCacheEntry>,
438 farbot::RealtimeObjectOptions::nonRealtimeMutatable>
439 active_automation_sequences_;
440};
441
442} // namespace zrythm::structure::arrangement
Audio-specific timeline data cache.
void add_audio_region(IntervalType interval, const juce::AudioSampleBuffer &audio_buffer)
Adds an audio region for the given interval.
Automation-specific timeline data cache.
void add_automation_sequence(IntervalType interval, const std::vector< float > &automation_values)
Adds an automation sequence for the given interval.
A lock-free thread-safe vector of MidiEvents.
Definition midi_event.h:78
MIDI-specific timeline data cache.
void add_midi_sequence(IntervalType interval, const juce::MidiMessageSequence &sequence)
Adds a MIDI sequence for the given interval.
Base class for timeline data caches.
A region for playing back audio samples.
void process_audio_events(const 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 EngineProcessTimeInfo &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 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::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.
static void serialize_to_automation_values(const AutomationRegion &region, std::vector< float > &values, std::optional< std::pair< double, double > > timeline_range_ticks=std::nullopt)
Serializes an Automation region to sample-accurate automation values.
static void serialize_to_buffer(const AudioRegion &region, juce::AudioSampleBuffer &buffer, std::optional< std::pair< double, double > > timeline_range_ticks=std::nullopt)
Serializes an Audio region to an audio sample buffer.
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.
Common struct to pass around during processing to avoid repeating the data in function arguments.
Definition types.h:133