OSSIA
Open Scenario System for Interactive Application
sound.hpp
1 #pragma once
2 #include <ossia/dataflow/audio_stretch_mode.hpp>
3 #include <ossia/dataflow/node_process.hpp>
4 #include <ossia/dataflow/nodes/media.hpp>
5 #include <ossia/dataflow/nodes/timestretch/raw_stretcher.hpp>
6 #include <ossia/dataflow/nodes/timestretch/repitch_stretcher.hpp>
7 #include <ossia/dataflow/nodes/timestretch/rubberband_stretcher.hpp>
8 #include <ossia/dataflow/port.hpp>
9 #include <ossia/dataflow/sample_to_float.hpp>
10 #include <ossia/detail/pod_vector.hpp>
11 #include <ossia/detail/variant.hpp>
12 
13 namespace ossia
14 {
15 namespace snd
16 {
17 struct sample_read_info
18 {
19  int64_t samples_to_read{};
20  int64_t samples_to_write{};
21 };
22 
23 inline auto
24 sample_info(int64_t bufferSize, double durationRatio, const ossia::token_request& t)
25 {
26  sample_read_info _;
27  if(t.paused())
28  return _;
29 
30  if(t.speed == 0.0)
31  return _;
32 
33  _.samples_to_read = t.physical_read_duration(durationRatio);
34  _.samples_to_write = std::min(
35  t.physical_write_duration(durationRatio),
36  t.safe_physical_write_duration(durationRatio, bufferSize));
37 
38  return _;
39 }
40 
41 inline void
42 perform_upmix(const std::size_t upmix, const std::size_t chan, ossia::audio_port& ap)
43 {
44  // Upmix
45  if(upmix != 0)
46  {
47  if(upmix < chan)
48  {
49  /* TODO
50  // Downmix
51  switch(upmix)
52  {
53  case 1:
54  {
55  for(std::size_t i = 1; i < chan; i++)
56  {
57  if(ap.channel(0).size() < ap.channel(i).size())
58  ap.channel(0).resize(ap.channel(i).size());
59 
60  for(std::size_t j = 0; j < ap.channel(i).size(); j++)
61  ap.channel(0)[j] += ap.channel(i)[j];
62  }
63  }
64  default:
65  // TODO
66  break;
67  }
68  */
69  }
70  else if(upmix > chan)
71  {
72  switch(chan)
73  {
74  case 1: {
75  for(std::size_t chan = 1; chan < upmix; ++chan)
76  ap.channel(chan).assign(ap.channel(0).begin(), ap.channel(0).end());
77  break;
78  }
79  default:
80  // TODO
81  break;
82  }
83  }
84  }
85 }
86 
87 inline void perform_start_offset(const std::size_t start, ossia::audio_port& ap)
88 {
89  if(start != 0)
90  {
91  ap.get().insert(ap.get().begin(), start, ossia::audio_channel{});
92  }
93 }
94 
95 OSSIA_EXPORT
96 void do_fade(
97  bool start_discontinuous, bool end_discontinuous, audio_channel& ap,
98  std::size_t start, std::size_t end);
99 
100 }
101 
102 template <typename T>
103 struct at_end
104 {
105  T func;
106  at_end(T t)
107  : func{t}
108  {
109  }
110  ~at_end() { func(); }
111 };
112 
113 struct resampler
114 {
115  enum
116  {
117  RawStretcher = 0,
118  RubberbandStretcher = 1,
119  RepitchStretcher = 2
120  };
121  [[nodiscard]] int64_t next_sample_to_read() const noexcept
122  {
123  return ossia::visit(
124  [](auto& stretcher) noexcept { return stretcher.next_sample_to_read; },
125  m_stretch);
126  }
127 
128  void transport(int64_t date)
129  {
130  ossia::visit(
131  [=](auto& stretcher) noexcept { return stretcher.transport(date); }, m_stretch);
132  }
133 
134  void reset(
135  int64_t date, ossia::audio_stretch_mode mode, std::size_t channels,
136  std::size_t fileSampleRate)
137  {
138  // TODO use the date parameter to buffer ! else transport won't work
139  switch(mode)
140  {
141  default:
142  case ossia::audio_stretch_mode::None: {
143  if(auto s = ossia::get_if<RawStretcher>(&m_stretch))
144  {
145  s->transport(date);
146  }
147  else
148  {
149  m_stretch.emplace<RawStretcher>(date);
150  }
151  break;
152  }
153 
154 #if defined(OSSIA_ENABLE_RUBBERBAND)
155  case ossia::audio_stretch_mode::RubberBandStandard:
156  case ossia::audio_stretch_mode::RubberBandPercussive:
157  case ossia::audio_stretch_mode::RubberBandStandardHQ:
158  case ossia::audio_stretch_mode::RubberBandPercussiveHQ: {
159  const auto preset = get_rubberband_preset(mode);
160  if(auto s = ossia::get_if<RubberbandStretcher>(&m_stretch);
161  s && s->options == preset)
162  {
163  s->transport(date);
164  }
165  else
166  {
167  m_stretch.emplace<rubberband_stretcher>(
168  preset, channels, fileSampleRate, date);
169  }
170  break;
171  }
172 #endif
173 
174 #if defined(OSSIA_ENABLE_LIBSAMPLERATE)
175  case ossia::audio_stretch_mode::Repitch: {
176  if(auto s = ossia::get_if<RepitchStretcher>(&m_stretch);
177  s && s->repitchers.size() == channels)
178  {
179  s->transport(date);
180  }
181  else
182  {
183  // FIXME why 1024 here ?!
184  m_stretch.emplace<repitch_stretcher>(channels, 1024, date);
185  }
186  break;
187  }
188 #endif
189  }
190  }
191 
192  template <typename T>
193  void
194  run(T& audio_fetcher, const ossia::token_request& t, ossia::exec_state_facade e,
195  double tempo_ratio, std::size_t chan, std::size_t len, int64_t samples_to_read,
196  int64_t samples_to_write, int64_t samples_offset,
197  const ossia::mutable_audio_span<double>& ap)
198  {
199  ossia::visit(
200  [&](auto& stretcher) {
201  stretcher.run(
202  audio_fetcher, t, e, tempo_ratio, chan, len, samples_to_read, samples_to_write,
203  samples_offset, ap);
204  },
205  m_stretch);
206  }
207 
208  [[nodiscard]] bool stretch() const noexcept { return m_stretch.index() != 0; }
209 
210 private:
211  ossia::variant<
212  raw_stretcher
213 #if defined(OSSIA_ENABLE_RUBBERBAND)
214  ,
215  rubberband_stretcher
216 #endif
217 #if defined(OSSIA_ENABLE_LIBSAMPLERATE)
218  ,
219  repitch_stretcher
220 #endif
221  >
222  m_stretch;
223 };
224 
225 struct sound_processing_info
226 {
227  time_value m_prev_date{time_value::infinite_min};
228 
229  time_value m_loop_duration{};
230  time_value m_start_offset{};
231 
232  double tempo{};
233 
234  int64_t m_loop_duration_samples{};
235  int64_t m_start_offset_samples{};
236 
237  ossia::resampler m_resampler{};
238 
239  bool m_loops{};
240 
241  void set_loop_info(
242  ossia::time_value loop_duration, ossia::time_value start_offset, bool loops)
243  {
244  m_loop_duration = loop_duration;
245  m_start_offset = start_offset;
246  m_loops = loops;
247  }
248 
249  void set_resampler(ossia::resampler&& r)
250  {
251  auto date = m_resampler.next_sample_to_read();
252  m_resampler = std::move(r);
253  m_resampler.transport(date);
254  }
255 
256  void set_native_tempo(double v) { tempo = v; }
257 
258  double update_stretch(
259  const ossia::token_request& t, const ossia::exec_state_facade& e) noexcept
260  {
261  double stretch_ratio = 1.;
262  double model_ratio = 1.;
263  if(tempo != 0.)
264  {
265  if(m_resampler.stretch())
266  {
267  model_ratio = ossia::root_tempo / this->tempo;
268  stretch_ratio = this->tempo / t.tempo;
269  }
270  else
271  {
272  model_ratio = ossia::root_tempo / t.tempo;
273  }
274  }
275 
276  m_loop_duration_samples = m_loop_duration.impl * e.modelToSamples() * model_ratio;
277  m_start_offset_samples = m_start_offset.impl * e.modelToSamples() * model_ratio;
278  return stretch_ratio;
279  }
280 };
281 
282 class sound_node
283  : public ossia::nonowning_graph_node
284  , public sound_processing_info
285 {
286 public:
287  virtual void transport(time_value date) = 0;
288 };
289 
290 class dummy_sound_node final : public sound_node
291 {
292 public:
293  ossia::audio_outlet audio_out;
294  dummy_sound_node()
295  {
296  // Add a dummy outlet so that interval can connect propagation to it
297  m_outlets.push_back(&audio_out);
298  }
299 
300  void transport(time_value date) override { }
301 
302  void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept override
303  {
304  }
305 };
306 
307 #if defined(OSSIA_SCENARIO_DATAFLOW)
308 class sound_process final : public ossia::node_process
309 {
310 public:
311  using ossia::node_process::node_process;
312 
313 protected:
314  void state(const ossia::token_request& req) override
315  {
316  // TODO here we should also pass the execution state so that we can
317  // leverage the timing info & transform loop_duration / start_offset in
318  // samples right here...
319  static_cast<sound_node&>(*this->node)
320  .set_loop_info(m_loop_duration, m_start_offset, m_loops);
321 
322  // Start offset and looping are done manually inside the sound nodes
323  // since it is much more efficient in this case
324  // (see fetch_audio)
325  node->request(req);
326  }
327 
328  void offset_impl(time_value date) override
329  {
330  static_cast<sound_node&>(*this->node).transport(date);
331  }
332  void transport_impl(time_value date) override
333  {
334  static_cast<sound_node&>(*this->node).transport(date);
335  }
336 };
337 #endif
338 
339 }
Definition: git_info.h:7
constexpr OSSIA_INLINE auto min(const T a, const U b) noexcept -> typename std::conditional<(sizeof(T) > sizeof(U)), T, U >::type
min function tailored for values
Definition: math.hpp:125
The time_value class.
Definition: ossia/editor/scenario/time_value.hpp:28