OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
sound_sampler.hpp
1#pragma once
2#include <ossia/dataflow/graph_node.hpp>
3#include <ossia/dataflow/nodes/sound.hpp>
4#include <ossia/dataflow/nodes/sound_utils.hpp>
5
6namespace ossia::nodes
7{
8
9struct sound_sampler
10{
11 void set_start(std::size_t v) { start = v; }
12
13 void set_upmix(std::size_t v) { upmix = v; }
14
15 [[nodiscard]] std::size_t channels() const { return m_data.size(); }
16
17 [[nodiscard]] std::size_t duration() const
18 {
19 return m_data.empty() ? 0 : m_data[0].size();
20 }
21
22 void transport(time_value date)
23 {
24 // No transport info: the live tempo is unknown, but the stretching seek
25 // only needs the file's own tempo (see file_sample_for_model_time).
26 info->m_resampler.transport(
27 info->file_sample_for_model_time(date, 0., m_dataSampleRate));
28 }
29
30 void transport(time_value date, const ossia::tick_transport_info& tinfo)
31 {
32 info->m_resampler.transport(info->file_sample_for_model_time(
33 date, tinfo.current_tempo, m_dataSampleRate));
34 }
35
36 // Used for testing only
37 void set_sound(audio_array data)
38 {
39 m_handle = std::make_shared<audio_data>();
40 m_handle->data = std::move(data);
41 m_data.clear();
42 {
43 m_dataSampleRate = 44100;
44 m_data.assign(m_handle->data.begin(), m_handle->data.end());
45 info->m_resampler.reset(
46 0, audio_stretch_mode::None, m_handle->data.size(), m_dataSampleRate);
47 }
48 }
49
50 void set_sound(const audio_handle& hdl, int channels, int sampleRate)
51 {
52 m_handle = hdl;
53 m_data.clear();
54 if(hdl)
55 {
56 m_dataSampleRate = sampleRate;
57 m_data.assign(m_handle->data.begin(), m_handle->data.end());
58 }
59 }
60
61 template <typename T>
62 void fetch_audio(
63 const int64_t start, const int64_t samples_to_write,
64 T** const audio_array) const noexcept
65 {
66 read_audio_from_buffer(
67 m_data, start, samples_to_write, info->m_start_offset_samples,
68 info->m_loop_duration_samples, info->m_loops, audio_array);
69 }
70
71 template <typename T>
72 void fetch_audio_backward(
73 const int64_t start, const int64_t samples_to_write,
74 T** const audio_array) const noexcept
75 {
76 read_audio_from_buffer_backward(
77 m_data, start, samples_to_write, info->m_start_offset_samples,
78 info->m_loop_duration_samples, info->m_loops, audio_array);
79 }
80
81 void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept
82 {
83 if(m_data.empty())
84 return;
85
86 const std::size_t chan = m_data.size();
87 const std::size_t len = m_data[0].size();
88 ossia::audio_port& ap = *audio_out;
89 ap.set_channels(std::max(this->upmix, chan));
90
91 const auto [samples_to_read, samples_to_write]
92 = snd::sample_info(e.bufferSize(), e.modelToSamples(), t);
93 // Only the write count decides whether there is anything to do, as in
94 // sound_mmap and sound_libav. The read count is a floor over absolute model
95 // time while the write count is a floor over the offset into the buffer, so
96 // a tick can legitimately cover a sample without consuming a whole new one;
97 // bailing here left a hole and a stale m_prev_date.
98 if(samples_to_write <= 0)
99 return;
100
101 assert(samples_to_write > 0);
102
103 const auto samples_offset = t.physical_start(e.modelToSamples());
104
105 if(t.forward())
106 {
107 if(t.prev_date < info->m_prev_date)
108 {
109 // First run after add_time_process() left the stretcher already
110 // primed; calling transport() again would reset it.
111 if(info->m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
112 info->m_prev_date = t.prev_date;
113 else
114 transport(t.prev_date);
115 }
116 }
117 else
118 {
119 if(t.prev_date > info->m_prev_date)
120 {
121 if(info->m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
122 info->m_prev_date = t.prev_date;
123 else
124 transport(t.prev_date);
125 }
126 }
127
128 for(std::size_t i = 0; i < chan; ++i)
129 {
130 ap.channel(i).resize(e.bufferSize());
131 }
132
133 const double stretch_ratio = info->update_stretch(t, e);
134 const double abs_stretch_ratio = std::abs(stretch_ratio);
135
136 info->m_resampler.run(
137 *this, t, e, stretch_ratio, chan, len, samples_to_read, samples_to_write,
138 samples_offset, ap);
139
140 const bool start_discontinuous = t.start_discontinuous || (m_last_stretch > 70.);
141 const bool end_discontinuous = t.end_discontinuous || (abs_stretch_ratio > 70.);
142 if(abs_stretch_ratio > 70. && m_last_stretch > 70.)
143 {
144 [[unlikely]];
145 for(std::size_t i = 0; i < chan; i++)
146 {
147 ossia::snd::do_zero(ap.channel(i), samples_offset, samples_to_write);
148 }
149 }
150 else
151 {
152 [[likely]];
153 for(std::size_t i = 0; i < chan; i++)
154 {
155 ossia::snd::do_fade(
156 start_discontinuous, end_discontinuous, ap.channel(i), samples_offset,
157 samples_to_write);
158 }
159 }
160
161 ossia::snd::perform_upmix(this->upmix, chan, ap);
162 ossia::snd::perform_start_offset(this->start, ap);
163
164 info->m_prev_date = t.date;
165 m_last_stretch = abs_stretch_ratio;
166 }
167
168 sound_processing_info* info{};
169 ossia::audio_port* audio_out{};
170
171 audio_span<float> m_data{};
172
173 std::size_t start{};
174 std::size_t upmix{};
175
176 std::size_t m_dataSampleRate{};
177 audio_handle m_handle{};
178 double m_last_stretch{1.0};
179};
180}
The time_value class.
Definition ossia/editor/scenario/time_value.hpp:30