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