2#include <ossia/audio/fade.hpp>
3#include <ossia/dataflow/audio_stretch_mode.hpp>
4#include <ossia/dataflow/node_process.hpp>
5#include <ossia/dataflow/nodes/media.hpp>
6#include <ossia/dataflow/nodes/timestretch/raw_stretcher.hpp>
7#include <ossia/dataflow/nodes/timestretch/repitch_stretcher.hpp>
8#include <ossia/dataflow/nodes/timestretch/rubberband_stretcher.hpp>
9#include <ossia/dataflow/port.hpp>
10#include <ossia/dataflow/sample_to_float.hpp>
11#include <ossia/detail/pod_vector.hpp>
12#include <ossia/detail/variant.hpp>
20struct sample_read_info
22 int64_t samples_to_read{};
23 int64_t samples_to_write{};
27sample_info(int64_t bufferSize,
double durationRatio,
const ossia::token_request& t)
36 _.samples_to_read = t.physical_read_duration(durationRatio);
38 const auto room = t.safe_physical_write_duration(durationRatio, bufferSize);
39 const auto tick_dur = t.physical_write_duration(durationRatio);
40 _.samples_to_write = std::min(tick_dur, std::max<int64_t>(0, room));
46inline double rate_ratio(int64_t material_rate, int64_t graph_rate)
noexcept
48 if(material_rate <= 0 || graph_rate <= 0 || material_rate == graph_rate)
50 return double(graph_rate) / double(material_rate);
54perform_upmix(
const std::size_t upmix,
const std::size_t chan, ossia::audio_port& ap)
87 for(std::size_t chan = 1; chan < upmix; ++chan)
88 ap.channel(chan).assign(ap.channel(0).begin(), ap.channel(0).end());
99inline void perform_start_offset(
const std::size_t start, ossia::audio_port& ap)
103 ap.get().insert(ap.get().begin(), start, ossia::audio_channel{});
116 ~at_end() { func(); }
124 RubberbandStretcher = 1,
127 [[nodiscard]] int64_t next_sample_to_read() const noexcept
130 [](
auto& stretcher)
noexcept {
return stretcher.next_sample_to_read; },
135 [[nodiscard]] int64_t start_delay() const noexcept
138 [](
auto& stretcher)
noexcept -> int64_t {
return stretcher.start_delay(); },
142 void transport(int64_t date)
145 [=](
auto& stretcher)
noexcept {
return stretcher.transport(date); }, m_stretch);
153 int64_t date, ossia::audio_stretch_mode mode, std::size_t channels,
154 std::size_t materialSampleRate, std::size_t graphSampleRate = 0)
156 if(graphSampleRate == 0)
157 graphSampleRate = materialSampleRate;
158 [[maybe_unused]]
const bool converts
159 = materialSampleRate != graphSampleRate && materialSampleRate > 0
160 && graphSampleRate > 0;
165 case ossia::audio_stretch_mode::None: {
166#if defined(OSSIA_ENABLE_LIBSAMPLERATE)
169 constexpr auto preset = get_samplerate_preset(
170 ossia::audio_stretch_mode::Repitch);
171 if(
auto s = ossia::get_if<RepitchStretcher>(&m_stretch);
172 s && s->repitchers.size() == channels && s->preset == preset)
178 m_stretch.emplace<repitch_stretcher>(preset, channels, 1024, date);
183 if(
auto s = ossia::get_if<RawStretcher>(&m_stretch))
189 m_stretch.emplace<RawStretcher>(date);
194#if defined(OSSIA_ENABLE_RUBBERBAND)
195 case ossia::audio_stretch_mode::RubberBandStandard:
196 case ossia::audio_stretch_mode::RubberBandPercussive:
197 case ossia::audio_stretch_mode::RubberBandStandardHQ:
198 case ossia::audio_stretch_mode::RubberBandPercussiveHQ: {
199 const auto preset = get_rubberband_preset(mode);
200 if(
auto s = ossia::get_if<RubberbandStretcher>(&m_stretch);
201 s && s->options == preset)
207 m_stretch.emplace<rubberband_stretcher>(
208 preset, channels, materialSampleRate, date);
210 ossia::get<RubberbandStretcher>(m_stretch)
211 .set_rate_ratio(snd::rate_ratio(materialSampleRate, graphSampleRate));
216#if defined(OSSIA_ENABLE_LIBSAMPLERATE)
217 case ossia::audio_stretch_mode::Repitch:
218 case ossia::audio_stretch_mode::RepitchMediumQ:
219 case ossia::audio_stretch_mode::RepitchFastestQ: {
220 const auto preset = get_samplerate_preset(mode);
221 if(
auto s = ossia::get_if<RepitchStretcher>(&m_stretch);
222 s && s->repitchers.size() == channels && s->preset == preset)
229 m_stretch.emplace<repitch_stretcher>(preset, channels, 1024, date);
237 m_stretching = (mode != ossia::audio_stretch_mode::None)
238 && (m_stretch.index() != RawStretcher);
241 template <
typename T>
243 run(T& audio_fetcher,
const ossia::token_request& t, ossia::exec_state_facade e,
244 double tempo_ratio,
double rate_ratio, std::size_t chan, std::size_t len,
245 int64_t samples_to_read, int64_t samples_to_write, int64_t samples_offset,
246 const ossia::mutable_audio_span<double>& ap)
249 [&](
auto& stretcher) {
251 audio_fetcher, t, e, tempo_ratio, rate_ratio, chan, len, samples_to_read,
252 samples_to_write, samples_offset, ap);
258 [[nodiscard]]
bool stretch() const noexcept {
return m_stretching; }
265#if defined(OSSIA_ENABLE_RUBBERBAND)
269#if defined(OSSIA_ENABLE_LIBSAMPLERATE)
277struct sound_processing_info
279 time_value m_prev_date{time_value::infinite_min};
281 time_value m_loop_duration{};
282 time_value m_start_offset{};
286 int64_t m_loop_duration_samples{};
287 int64_t m_start_offset_samples{};
289 double m_last_stretch{1.0};
290 ossia::resampler m_resampler{};
297 m_loop_duration = loop_duration;
298 m_start_offset = start_offset;
302 void set_resampler(ossia::resampler&& r)
304 auto date = m_resampler.next_sample_to_read();
305 m_resampler = std::move(r);
306 m_resampler.transport(date);
309 void set_native_tempo(
double v) { tempo = v; }
328 [[nodiscard]] int64_t file_sample_for_model_time(
329 time_value date,
double timeline_tempo,
330 int file_sample_rate)
const noexcept
332 const int64_t base = to_sample(date, file_sample_rate);
333 if(m_resampler.stretch() && tempo > 0.0)
334 return int64_t(std::llround(
double(base) * ossia::root_tempo / tempo));
336 const double abs_tempo = std::abs(timeline_tempo);
338 return int64_t(std::llround(
double(base) * ossia::root_tempo / abs_tempo));
344 double update_stretch(
345 const ossia::token_request& t,
const ossia::exec_state_facade& e,
346 double rate_ratio = 1.) noexcept
348 double stretch_ratio = 1.;
349 double model_ratio = 1.;
350 if(tempo != 0. && m_resampler.stretch())
352 model_ratio = ossia::root_tempo / this->tempo;
353 stretch_ratio = this->tempo / t.tempo;
356 if(!(rate_ratio > 0.))
358 const double to_material = e.modelToSamples() * model_ratio / rate_ratio;
360 m_loop_duration_samples = m_loop_duration.impl * to_material;
361 m_start_offset_samples = m_start_offset.impl * to_material;
362 return stretch_ratio;
367 :
public ossia::nonowning_graph_node
368 ,
public sound_processing_info
371 virtual void transport(time_value date) = 0;
376 virtual void transport(
377 time_value date,
const ossia::tick_transport_info& transport_info)
379 (void)transport_info;
384class dummy_sound_node final :
public sound_node
387 ossia::audio_outlet audio_out;
391 m_outlets.push_back(&audio_out);
394 std::string label() const noexcept
override {
return "dummy_sound_node"; }
396 void transport(time_value date)
override { }
398 void run(
const ossia::token_request& t, ossia::exec_state_facade e)
noexcept override
403#if defined(OSSIA_SCENARIO_DATAFLOW)
404class sound_process final :
public ossia::node_process
407 using ossia::node_process::node_process;
410 void state(
const ossia::token_request& req)
override
415 static_cast<sound_node&
>(*this->node)
416 .set_loop_info(m_loop_duration, m_start_offset, m_loops);
424 void offset_impl(time_value date)
override
426 static_cast<sound_node&
>(*this->node).transport(date);
428 void transport_impl(time_value date)
override
430 static_cast<sound_node&
>(*this->node).transport(date);
433 time_value date,
const ossia::tick_transport_info& info)
override
435 static_cast<sound_node&
>(*this->node).transport(date, info);
The time_value class.
Definition ossia/editor/scenario/time_value.hpp:30