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