OSSIA
Open Scenario System for Interactive Application
sound_mmap.hpp
1 #pragma once
2 #include <ossia/audio/audio_parameter.hpp>
3 #include <ossia/audio/drwav_handle.hpp>
4 #include <ossia/dataflow/audio_stretch_mode.hpp>
5 #include <ossia/dataflow/graph_node.hpp>
6 #include <ossia/dataflow/nodes/media.hpp>
7 #include <ossia/dataflow/nodes/sound.hpp>
8 #include <ossia/dataflow/port.hpp>
9 #include <ossia/detail/pod_vector.hpp>
10 
11 #include <type_traits>
12 
13 namespace ossia::nodes
14 {
15 
16 class sound_mmap final : public ossia::sound_node
17 {
18 public:
19  sound_mmap() { m_outlets.push_back(&audio_out); }
20 
21  ~sound_mmap() = default;
22 
23  void set_start(std::size_t v) { start = v; }
24 
25  void set_upmix(std::size_t v) { upmix = v; }
26 
27  void set_sound(drwav_handle hdl)
28  {
29  using namespace snd;
30  m_handle = std::move(hdl);
31  if(m_handle)
32  {
33  switch(m_handle.translatedFormatTag())
34  {
35  case DR_WAVE_FORMAT_PCM: {
36  switch(m_handle.bitsPerSample())
37  {
38  case 8:
39  m_converter = read_u8;
40  break;
41  case 16:
42  m_converter = read_s16;
43  break;
44  case 24:
45  m_converter = read_s24;
46  break;
47  case 32:
48  m_converter = read_s32;
49  break;
50  }
51  break;
52  }
53  case DR_WAVE_FORMAT_IEEE_FLOAT: {
54  switch(m_handle.bitsPerSample())
55  {
56  case 32:
57  m_converter = read_f32;
58  break;
59  case 64:
60  m_converter = read_f64;
61  break;
62  }
63  break;
64  }
65  default:
66  m_converter = nullptr;
67  break;
68  }
69  }
70  }
71 
72  void transport(time_value date) override
73  {
74  m_resampler.transport(to_sample(date, m_handle.sampleRate()));
75  }
76 
77  void fetch_audio(
78  int64_t start, int64_t samples_to_write, double** audio_array_base) noexcept
79  {
80  const int channels = this->channels();
81  const int file_duration = this->duration();
82 
83  m_resampleBuffer.resize(channels);
84  for(auto& buf : m_resampleBuffer)
85  buf.resize(samples_to_write);
86 
87  float** audio_array = (float**)alloca(sizeof(float*) * channels);
88  for(int i = 0; i < channels; i++)
89  {
90  m_resampleBuffer[i].resize(samples_to_write);
91  audio_array[i] = m_resampleBuffer[i].data();
92  }
93 
94  ossia::mutable_audio_span<float> source(channels);
95 
96  double* frame_data{};
97  if(samples_to_write * channels > 10000)
98  {
99  m_safetyBuffer.resize(samples_to_write * channels);
100  frame_data = m_safetyBuffer.data();
101  // TODO detect if we happen to be in this case often, and if so, garbage
102  // collect at some point
103  }
104  else
105  {
106  frame_data = (double*)alloca(sizeof(double) * samples_to_write * channels);
107  }
108 
109  if(m_loops)
110  {
111  for(int k = 0; k < samples_to_write; k++)
112  {
113  // TODO add a special case if [0; samples_to_write] don't loop around
114  int pos = this->m_start_offset_samples
115  + ((start + k) % this->m_loop_duration_samples);
116  if(pos >= file_duration)
117  {
118  for(int i = 0; i < channels; i++)
119  audio_array[i][k] = 0;
120  continue;
121  }
122 
123  const bool ok = this->m_handle.seek_to_pcm_frame(pos);
124  if(!ok)
125  {
126  for(int i = 0; i < channels; i++)
127  audio_array[i][k] = 0;
128  continue;
129  }
130 
131  const int max = 1;
132  const auto count = this->m_handle.read_pcm_frames(max, frame_data);
133  if(count >= 0)
134  {
135  for(int i = 0; i < channels; i++)
136  source[i] = tcb::span(audio_array[i] + k, count);
137  m_converter(source, frame_data, count);
138  }
139  else
140  {
141  for(int i = 0; i < channels; i++)
142  audio_array[i][k] = 0;
143  }
144  }
145  }
146  else
147  {
148  for(int i = 0; i < channels; i++)
149  {
150  source[i] = tcb::span(audio_array[i], samples_to_write);
151  }
152 
153  bool ok = start + m_start_offset_samples < file_duration;
154  if(ok)
155  ok = ok && this->m_handle.seek_to_pcm_frame(start + m_start_offset_samples);
156 
157  if(ok)
158  {
159  const auto count = this->m_handle.read_pcm_frames(samples_to_write, frame_data);
160  m_converter(source, frame_data, count);
161  for(int i = 0; i < channels; i++)
162  for(int k = count; k < samples_to_write; k++)
163  audio_array[i][k] = 0;
164  }
165  else
166  {
167  for(int i = 0; i < channels; i++)
168  for(int k = 0; k < samples_to_write; k++)
169  audio_array[i][k] = 0;
170  return;
171  }
172  }
173 
174  for(int i = 0; i < channels; i++)
175  std::copy_n(audio_array[i], samples_to_write, audio_array_base[i]);
176  }
177 
178  void fetch_audio(int64_t start, int64_t samples_to_write, float** audio_array) noexcept
179  {
180  const int channels = this->channels();
181  const int file_duration = this->duration();
182 
183  ossia::mutable_audio_span<float> source(channels);
184 
185  double* frame_data{};
186  if(samples_to_write * channels > 10000)
187  {
188  m_safetyBuffer.resize(samples_to_write * channels);
189  frame_data = m_safetyBuffer.data();
190  // TODO detect if we happen to be in this case often, and if so, garbage
191  // collect at some point
192  }
193  else
194  {
195  frame_data = (double*)alloca(sizeof(double) * samples_to_write * channels);
196  }
197 
198  if(m_loops)
199  {
200  for(int k = 0; k < samples_to_write; k++)
201  {
202  // TODO add a special case if [0; samples_to_write] don't loop around
203  int pos = this->m_start_offset_samples
204  + ((start + k) % this->m_loop_duration_samples);
205  if(pos >= file_duration)
206  {
207  for(int i = 0; i < channels; i++)
208  audio_array[i][k] = 0;
209  continue;
210  }
211 
212  const bool ok = this->m_handle.seek_to_pcm_frame(pos);
213  if(!ok)
214  {
215  for(int i = 0; i < channels; i++)
216  audio_array[i][k] = 0;
217  continue;
218  }
219 
220  const int max = 1;
221  const auto count = this->m_handle.read_pcm_frames(max, frame_data);
222  if(count >= 0)
223  {
224  for(int i = 0; i < channels; i++)
225  source[i] = tcb::span(audio_array[i] + k, count);
226  m_converter(source, frame_data, count);
227  }
228  else
229  {
230  for(int i = 0; i < channels; i++)
231  audio_array[i][k] = 0;
232  }
233  }
234  }
235  else
236  {
237  for(int i = 0; i < channels; i++)
238  {
239  source[i] = tcb::span(audio_array[i], samples_to_write);
240  }
241 
242  const bool ok = this->m_handle.seek_to_pcm_frame(start + m_start_offset_samples);
243  if(!ok)
244  {
245  for(int i = 0; i < channels; i++)
246  for(int k = 0; k < samples_to_write; k++)
247  audio_array[i][k] = 0;
248  return;
249  }
250 
251  const auto count = this->m_handle.read_pcm_frames(samples_to_write, frame_data);
252  m_converter(source, frame_data, count);
253  for(int i = 0; i < channels; i++)
254  for(int k = count; k < samples_to_write; k++)
255  audio_array[i][k] = 0;
256  }
257  }
258 
259  void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept override
260  {
261  if(!m_handle || !m_converter)
262  return;
263 
264  // TODO do the backwards play head
265  if(!t.forward())
266  return;
267 
268  const auto channels = m_handle.channels();
269  const auto len = m_handle.totalPCMFrameCount();
270 
271  ossia::audio_port& ap = *audio_out;
272  ap.set_channels(std::max((std::size_t)upmix, (std::size_t)channels));
273 
274  const auto [samples_to_read, samples_to_write]
275  = snd::sample_info(e.bufferSize(), e.modelToSamples(), t);
276  if(samples_to_write <= 0)
277  return;
278 
279  assert(samples_to_write > 0);
280 
281  const auto samples_offset = t.physical_start(e.modelToSamples());
282  if(t.tempo > 0)
283  {
284  if(t.prev_date < m_prev_date)
285  {
286  // Sentinel: we never played.
287  if(m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
288  {
289  if(t.prev_date != 0_tv)
290  {
291  transport(t.prev_date);
292  }
293  else
294  {
295  // Otherwise we don't need transport, everything is already at 0
296  m_prev_date = 0_tv;
297  }
298  }
299  else
300  {
301  transport(t.prev_date);
302  }
303  }
304 
305  for(std::size_t chan = 0; chan < channels; chan++)
306  {
307  ap.channel(chan).resize(e.bufferSize());
308  }
309 
310  double stretch_ratio = update_stretch(t, e);
311 
312  // Resample
313  m_resampler.run(
314  *this, t, e, stretch_ratio, channels, len, samples_to_read, samples_to_write,
315  samples_offset, ap);
316 
317  for(std::size_t chan = 0; chan < channels; chan++)
318  {
319  // fade
320  snd::do_fade(
321  t.start_discontinuous, t.end_discontinuous, ap.channel(chan), samples_offset,
322  samples_to_write);
323  }
324 
325  ossia::snd::perform_upmix(this->upmix, channels, ap);
326  ossia::snd::perform_start_offset(this->start, ap);
327 
328  m_prev_date = t.date;
329  }
330  else
331  {
332  /* TODO */
333  }
334  }
335 
336  [[nodiscard]] std::size_t channels() const
337  {
338  return m_handle ? m_handle.channels() : 0;
339  }
340  [[nodiscard]] std::size_t duration() const
341  {
342  return m_handle ? m_handle.totalPCMFrameCount() : 0;
343  }
344 
345 private:
346  drwav_handle m_handle{};
347 
348  ossia::audio_outlet audio_out;
349 
350  std::size_t start{};
351  std::size_t upmix{};
352 
353  using read_fn_t
354  = void (*)(ossia::mutable_audio_span<float>& ap, void* data, int64_t samples);
355  read_fn_t m_converter{};
356  std::vector<double> m_safetyBuffer;
357  std::vector<std::vector<float>> m_resampleBuffer;
358 };
359 
360 }
constexpr OSSIA_INLINE auto max(const T a, const U b) noexcept -> typename std::conditional<(sizeof(T) > sizeof(U)), T, U >::type
max function tailored for values
Definition: math.hpp:96
The time_value class.
Definition: ossia/editor/scenario/time_value.hpp:28