OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
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
13namespace ossia::nodes
14{
15
16class sound_mmap final : public ossia::sound_node
17{
18public:
19 sound_mmap() { m_outlets.push_back(&audio_out); }
20
21 ~sound_mmap() = default;
22
23 std::string label() const noexcept override { return "sound_mmap"; }
24
25 void set_start(std::size_t v) { start = v; }
26
27 void set_upmix(std::size_t v) { upmix = v; }
28
29 void set_sound(drwav_handle hdl)
30 {
31 using namespace snd;
32 m_handle = std::move(hdl);
33 if(m_handle)
34 {
35 switch(m_handle.translatedFormatTag())
36 {
37 case DR_WAVE_FORMAT_PCM: {
38 switch(m_handle.bitsPerSample())
39 {
40 case 8:
41 m_converter = read_u8;
42 break;
43 case 16:
44 m_converter = read_s16;
45 break;
46 case 24:
47 m_converter = read_s24;
48 break;
49 case 32:
50 m_converter = read_s32;
51 break;
52 }
53 break;
54 }
55 case DR_WAVE_FORMAT_IEEE_FLOAT: {
56 switch(m_handle.bitsPerSample())
57 {
58 case 32:
59 m_converter = read_f32;
60 break;
61 case 64:
62 m_converter = read_f64;
63 break;
64 }
65 break;
66 }
67 default:
68 m_converter = nullptr;
69 break;
70 }
71 }
72 }
73
74 void transport(time_value date) override
75 {
76 if(m_handle)
77 m_resampler.transport(to_sample(date, m_handle.sampleRate()));
78 }
79
80 void transport(time_value date, const ossia::tick_transport_info& tinfo) override
81 {
82 if(m_handle)
83 m_resampler.transport(file_sample_for_model_time(
84 date, tinfo.current_tempo, m_handle.sampleRate()));
85 }
86
87 void fetch_audio(
88 int64_t start, int64_t samples_to_write, double** audio_array_base) noexcept
89 {
90 const int channels = this->channels();
91 const int file_duration = this->duration();
92
93 m_resampleBuffer.resize(channels);
94 for(auto& buf : m_resampleBuffer)
95 buf.resize(samples_to_write);
96
97 float** audio_array = (float**)alloca(sizeof(float*) * channels);
98 for(int i = 0; i < channels; i++)
99 {
100 m_resampleBuffer[i].resize(samples_to_write);
101 audio_array[i] = m_resampleBuffer[i].data();
102 }
103
104 ossia::mutable_audio_span<float> source(channels);
105
106 double* frame_data{};
107 if(samples_to_write * channels > 10000)
108 {
109 m_safetyBuffer.resize(samples_to_write * channels);
110 frame_data = m_safetyBuffer.data();
111 // TODO detect if we happen to be in this case often, and if so, garbage
112 // collect at some point
113 }
114 else
115 {
116 frame_data = (double*)alloca(sizeof(double) * samples_to_write * channels);
117 }
118
119 if(m_loops)
120 {
121 for(int k = 0; k < samples_to_write; k++)
122 {
123 // TODO add a special case if [0; samples_to_write] don't loop around
124 int pos = this->m_start_offset_samples
125 + ((start + k) % this->m_loop_duration_samples);
126 if(pos >= file_duration)
127 {
128 for(int i = 0; i < channels; i++)
129 audio_array[i][k] = 0;
130 continue;
131 }
132
133 const bool ok = this->m_handle.seek_to_pcm_frame(pos);
134 if(!ok)
135 {
136 for(int i = 0; i < channels; i++)
137 audio_array[i][k] = 0;
138 continue;
139 }
140
141 const int max = 1;
142 const auto count = this->m_handle.read_pcm_frames(max, frame_data);
143 if(count >= 0)
144 {
145 for(int i = 0; i < channels; i++)
146 source[i] = std::span(audio_array[i] + k, count);
147 m_converter(source, frame_data, count);
148 }
149 else
150 {
151 for(int i = 0; i < channels; i++)
152 audio_array[i][k] = 0;
153 }
154 }
155 }
156 else
157 {
158 for(int i = 0; i < channels; i++)
159 {
160 source[i] = std::span(audio_array[i], samples_to_write);
161 }
162
163 bool ok = start + m_start_offset_samples < file_duration;
164 if(ok)
165 ok = ok && this->m_handle.seek_to_pcm_frame(start + m_start_offset_samples);
166
167 if(ok)
168 {
169 const auto count = this->m_handle.read_pcm_frames(samples_to_write, frame_data);
170 m_converter(source, frame_data, count);
171 for(int i = 0; i < channels; i++)
172 for(int k = count; k < samples_to_write; k++)
173 audio_array[i][k] = 0;
174 }
175 else
176 {
177 for(int i = 0; i < channels; i++)
178 for(int k = 0; k < samples_to_write; k++)
179 audio_array[i][k] = 0;
180 return;
181 }
182 }
183
184 for(int i = 0; i < channels; i++)
185 std::copy_n(audio_array[i], samples_to_write, audio_array_base[i]);
186 }
187
188 void fetch_audio(int64_t start, int64_t samples_to_write, float** audio_array) noexcept
189 {
190 const int channels = this->channels();
191 const int file_duration = this->duration();
192
193 ossia::mutable_audio_span<float> source(channels);
194
195 double* frame_data{};
196 if(samples_to_write * channels > 10000)
197 {
198 m_safetyBuffer.resize(samples_to_write * channels);
199 frame_data = m_safetyBuffer.data();
200 // TODO detect if we happen to be in this case often, and if so, garbage
201 // collect at some point
202 }
203 else
204 {
205 frame_data = (double*)alloca(sizeof(double) * samples_to_write * channels);
206 }
207
208 if(m_loops)
209 {
210 for(int k = 0; k < samples_to_write; k++)
211 {
212 // TODO add a special case if [0; samples_to_write] don't loop around
213 int pos = this->m_start_offset_samples
214 + ((start + k) % this->m_loop_duration_samples);
215 if(pos >= file_duration)
216 {
217 for(int i = 0; i < channels; i++)
218 audio_array[i][k] = 0;
219 continue;
220 }
221
222 const bool ok = this->m_handle.seek_to_pcm_frame(pos);
223 if(!ok)
224 {
225 for(int i = 0; i < channels; i++)
226 audio_array[i][k] = 0;
227 continue;
228 }
229
230 const int max = 1;
231 const auto count = this->m_handle.read_pcm_frames(max, frame_data);
232 if(count >= 0)
233 {
234 for(int i = 0; i < channels; i++)
235 source[i] = std::span(audio_array[i] + k, count);
236 m_converter(source, frame_data, count);
237 }
238 else
239 {
240 for(int i = 0; i < channels; i++)
241 audio_array[i][k] = 0;
242 }
243 }
244 }
245 else
246 {
247 for(int i = 0; i < channels; i++)
248 {
249 source[i] = std::span(audio_array[i], samples_to_write);
250 }
251
252 const bool ok = this->m_handle.seek_to_pcm_frame(start + m_start_offset_samples);
253 if(!ok)
254 {
255 for(int i = 0; i < channels; i++)
256 for(int k = 0; k < samples_to_write; k++)
257 audio_array[i][k] = 0;
258 return;
259 }
260
261 const auto count = this->m_handle.read_pcm_frames(samples_to_write, frame_data);
262 m_converter(source, frame_data, count);
263 for(int i = 0; i < channels; i++)
264 for(int k = count; k < samples_to_write; k++)
265 audio_array[i][k] = 0;
266 }
267 }
268
269 // Backward audio fetching - reads samples going backwards from 'start'
270 void fetch_audio_backward(
271 int64_t start, int64_t samples_to_write, double** audio_array_base) noexcept
272 {
273 const int channels = this->channels();
274 const int file_duration = this->duration();
275
276 m_resampleBuffer.resize(channels);
277 for(auto& buf : m_resampleBuffer)
278 buf.resize(samples_to_write);
279
280 float** audio_array = (float**)alloca(sizeof(float*) * channels);
281 for(int i = 0; i < channels; i++)
282 {
283 m_resampleBuffer[i].resize(samples_to_write);
284 audio_array[i] = m_resampleBuffer[i].data();
285 }
286
287 ossia::mutable_audio_span<float> source(channels);
288
289 double* frame_data{};
290 if(samples_to_write * channels > 10000)
291 {
292 m_safetyBuffer.resize(samples_to_write * channels);
293 frame_data = m_safetyBuffer.data();
294 }
295 else
296 {
297 frame_data = (double*)alloca(sizeof(double) * samples_to_write * channels);
298 }
299
300 if(m_loops && m_loop_duration_samples > 0)
301 {
302 // Looping backward: when we hit 0, wrap to loop_duration
303 for(int k = 0; k < samples_to_write; k++)
304 {
305 int64_t raw_pos = start - k;
306 int64_t wrapped_pos
307 = ((raw_pos % m_loop_duration_samples) + m_loop_duration_samples)
308 % m_loop_duration_samples;
309 int64_t pos = m_start_offset_samples + wrapped_pos;
310
311 if(pos < 0 || pos >= file_duration)
312 {
313 for(int i = 0; i < channels; i++)
314 audio_array[i][k] = 0;
315 continue;
316 }
317
318 const bool ok = this->m_handle.seek_to_pcm_frame(pos);
319 if(!ok)
320 {
321 for(int i = 0; i < channels; i++)
322 audio_array[i][k] = 0;
323 continue;
324 }
325
326 const auto count = this->m_handle.read_pcm_frames(1, frame_data);
327 if(count >= 0)
328 {
329 for(int i = 0; i < channels; i++)
330 source[i] = std::span(audio_array[i] + k, count);
331 m_converter(source, frame_data, count);
332 }
333 else
334 {
335 for(int i = 0; i < channels; i++)
336 audio_array[i][k] = 0;
337 }
338 }
339 }
340 else
341 {
342 // Non-looping backward: read sample by sample going backwards
343 for(int k = 0; k < samples_to_write; k++)
344 {
345 int64_t pos = m_start_offset_samples + start - k;
346
347 if(pos < 0 || pos >= file_duration)
348 {
349 for(int i = 0; i < channels; i++)
350 audio_array[i][k] = 0;
351 continue;
352 }
353
354 const bool ok = this->m_handle.seek_to_pcm_frame(pos);
355 if(!ok)
356 {
357 for(int i = 0; i < channels; i++)
358 audio_array[i][k] = 0;
359 continue;
360 }
361
362 const auto count = this->m_handle.read_pcm_frames(1, frame_data);
363 if(count >= 0)
364 {
365 for(int i = 0; i < channels; i++)
366 source[i] = std::span(audio_array[i] + k, count);
367 m_converter(source, frame_data, count);
368 }
369 else
370 {
371 for(int i = 0; i < channels; i++)
372 audio_array[i][k] = 0;
373 }
374 }
375 }
376
377 for(int i = 0; i < channels; i++)
378 std::copy_n(audio_array[i], samples_to_write, audio_array_base[i]);
379 }
380
381 void
382 fetch_audio_backward(int64_t start, int64_t samples_to_write, float** audio_array) noexcept
383 {
384 const int channels = this->channels();
385 const int file_duration = this->duration();
386
387 ossia::mutable_audio_span<float> source(channels);
388
389 double* frame_data{};
390 if(samples_to_write * channels > 10000)
391 {
392 m_safetyBuffer.resize(samples_to_write * channels);
393 frame_data = m_safetyBuffer.data();
394 }
395 else
396 {
397 frame_data = (double*)alloca(sizeof(double) * samples_to_write * channels);
398 }
399
400 if(m_loops && m_loop_duration_samples > 0)
401 {
402 // Looping backward
403 for(int k = 0; k < samples_to_write; k++)
404 {
405 int64_t raw_pos = start - k;
406 int64_t wrapped_pos
407 = ((raw_pos % m_loop_duration_samples) + m_loop_duration_samples)
408 % m_loop_duration_samples;
409 int64_t pos = m_start_offset_samples + wrapped_pos;
410
411 if(pos < 0 || pos >= file_duration)
412 {
413 for(int i = 0; i < channels; i++)
414 audio_array[i][k] = 0;
415 continue;
416 }
417
418 const bool ok = this->m_handle.seek_to_pcm_frame(pos);
419 if(!ok)
420 {
421 for(int i = 0; i < channels; i++)
422 audio_array[i][k] = 0;
423 continue;
424 }
425
426 const auto count = this->m_handle.read_pcm_frames(1, frame_data);
427 if(count >= 0)
428 {
429 for(int i = 0; i < channels; i++)
430 source[i] = std::span(audio_array[i] + k, count);
431 m_converter(source, frame_data, count);
432 }
433 else
434 {
435 for(int i = 0; i < channels; i++)
436 audio_array[i][k] = 0;
437 }
438 }
439 }
440 else
441 {
442 // Non-looping backward
443 for(int k = 0; k < samples_to_write; k++)
444 {
445 int64_t pos = m_start_offset_samples + start - k;
446
447 if(pos < 0 || pos >= file_duration)
448 {
449 for(int i = 0; i < channels; i++)
450 audio_array[i][k] = 0;
451 continue;
452 }
453
454 const bool ok = this->m_handle.seek_to_pcm_frame(pos);
455 if(!ok)
456 {
457 for(int i = 0; i < channels; i++)
458 audio_array[i][k] = 0;
459 continue;
460 }
461
462 const auto count = this->m_handle.read_pcm_frames(1, frame_data);
463 if(count >= 0)
464 {
465 for(int i = 0; i < channels; i++)
466 source[i] = std::span(audio_array[i] + k, count);
467 m_converter(source, frame_data, count);
468 }
469 else
470 {
471 for(int i = 0; i < channels; i++)
472 audio_array[i][k] = 0;
473 }
474 }
475 }
476 }
477
478 void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept override
479 {
480 if(!m_handle || !m_converter)
481 return;
482
483 const auto channels = m_handle.channels();
484 const auto len = m_handle.totalPCMFrameCount();
485
486 ossia::audio_port& ap = *audio_out;
487 ap.set_channels(std::max((std::size_t)upmix, (std::size_t)channels));
488
489 const auto [samples_to_read, samples_to_write]
490 = snd::sample_info(e.bufferSize(), e.modelToSamples(), t);
491 if(samples_to_write <= 0)
492 return;
493
494 assert(samples_to_write > 0);
495
496 const auto samples_offset = t.physical_start(e.modelToSamples());
497
498 if(t.forward())
499 {
500 if(t.prev_date < m_prev_date)
501 {
502 // First run after add_time_process() left the stretcher already
503 // primed; calling transport() again would reset it.
504 if(m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
505 m_prev_date = t.prev_date;
506 else
507 transport(t.prev_date);
508 }
509 }
510 else
511 {
512 if(t.prev_date > m_prev_date)
513 {
514 if(m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
515 m_prev_date = t.prev_date;
516 else
517 transport(t.prev_date);
518 }
519 }
520
521 for(std::size_t chan = 0; chan < channels; chan++)
522 {
523 ap.channel(chan).resize(e.bufferSize());
524 }
525
526 const double stretch_ratio = update_stretch(t, e);
527 const double abs_stretch_ratio = std::abs(stretch_ratio);
528
529 m_resampler.run(
530 *this, t, e, stretch_ratio, channels, len, samples_to_read, samples_to_write,
531 samples_offset, ap);
532
533 const bool start_discontinuous = t.start_discontinuous || (m_last_stretch > 70.);
534 const bool end_discontinuous = t.end_discontinuous || (abs_stretch_ratio > 70.);
535 if(abs_stretch_ratio > 70. && m_last_stretch > 70.)
536 {
537 [[unlikely]];
538 for(std::size_t i = 0; i < channels; i++)
539 {
540 ossia::snd::do_zero(ap.channel(i), samples_offset, samples_to_write);
541 }
542 }
543 else
544 {
545 [[likely]];
546 for(std::size_t chan = 0; chan < channels; chan++)
547 {
548 // fade
549 snd::do_fade(
550 start_discontinuous, end_discontinuous, ap.channel(chan), samples_offset,
551 samples_to_write);
552 }
553 }
554
555 ossia::snd::perform_upmix(this->upmix, channels, ap);
556 ossia::snd::perform_start_offset(this->start, ap);
557
558 m_prev_date = t.date;
559 m_last_stretch = abs_stretch_ratio;
560 }
561
562 [[nodiscard]] std::size_t channels() const
563 {
564 return m_handle ? m_handle.channels() : 0;
565 }
566 [[nodiscard]] std::size_t duration() const
567 {
568 return m_handle ? m_handle.totalPCMFrameCount() : 0;
569 }
570
571private:
572 drwav_handle m_handle{};
573
574 ossia::audio_outlet audio_out;
575
576 std::size_t start{};
577 std::size_t upmix{};
578
579 using read_fn_t
580 = void (*)(ossia::mutable_audio_span<float>& ap, void* data, int64_t samples);
581 read_fn_t m_converter{};
582 std::vector<double> m_safetyBuffer;
583 std::vector<std::vector<float>> m_resampleBuffer;
584};
585
586}
OSSIA_INLINE constexpr 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:30