OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
sound_sampler.hpp
1#pragma once
2#include <ossia/dataflow/graph_node.hpp>
3#include <ossia/dataflow/nodes/sound.hpp>
4#include <ossia/dataflow/nodes/sound_utils.hpp>
5
6namespace ossia::nodes
7{
8
9struct sound_sampler
10{
11 void set_start(std::size_t v) { start = v; }
12
13 void set_upmix(std::size_t v) { upmix = v; }
14
15 [[nodiscard]] std::size_t channels() const { return m_data.size(); }
16
17 [[nodiscard]] std::size_t duration() const
18 {
19 return m_data.empty() ? 0 : m_data[0].size();
20 }
21
22 void transport(time_value date)
23 {
24 // No transport info: the live tempo is unknown, but the stretching seek
25 // only needs the file's own tempo (see file_sample_for_model_time).
26 info->m_resampler.transport(
27 info->file_sample_for_model_time(date, 0., m_dataSampleRate));
28 }
29
30 void transport(time_value date, const ossia::tick_transport_info& tinfo)
31 {
32 info->m_resampler.transport(info->file_sample_for_model_time(
33 date, tinfo.current_tempo, m_dataSampleRate));
34 }
35
36 // Used for testing only
37 void set_sound(audio_array data)
38 {
39 m_handle = std::make_shared<audio_data>();
40 m_handle->data = std::move(data);
41 m_data.clear();
42 {
43 m_dataSampleRate = 44100;
44 m_data.assign(m_handle->data.begin(), m_handle->data.end());
45 info->m_resampler.reset(
46 0, audio_stretch_mode::None, m_handle->data.size(), m_dataSampleRate,
47 m_dataSampleRate);
48 }
49 }
50
51 void set_sound(const audio_handle& hdl, int channels, int sampleRate)
52 {
53 m_handle = hdl;
54 m_data.clear();
55 if(hdl)
56 {
57 m_dataSampleRate = sampleRate;
58 m_data.assign(m_handle->data.begin(), m_handle->data.end());
59 }
60 }
61
62 template <typename T>
63 void fetch_audio(
64 const int64_t start, const int64_t samples_to_write,
65 T** const audio_array) const noexcept
66 {
67 read_audio_from_buffer(
68 m_data, start, samples_to_write, info->m_start_offset_samples,
69 info->m_loop_duration_samples, info->m_loops, audio_array);
70 }
71
72 template <typename T>
73 void fetch_audio_backward(
74 const int64_t start, const int64_t samples_to_write,
75 T** const audio_array) const noexcept
76 {
77 read_audio_from_buffer_backward(
78 m_data, start, samples_to_write, info->m_start_offset_samples,
79 info->m_loop_duration_samples, info->m_loops, audio_array);
80 }
81
82 void run(const ossia::token_request& t, ossia::exec_state_facade e) noexcept
83 {
84 if(m_data.empty())
85 return;
86
87 const std::size_t chan = m_data.size();
88 const std::size_t len = m_data[0].size();
89 ossia::audio_port& ap = *audio_out;
90 ap.set_channels(std::max(this->upmix, chan));
91
92 const auto [samples_to_read, samples_to_write]
93 = snd::sample_info(e.bufferSize(), e.modelToSamples(), t);
94 // Only the write count decides whether there is anything to do, as in
95 // sound_mmap and sound_libav. The read count is a floor over absolute model
96 // time while the write count is a floor over the offset into the buffer, so
97 // a tick can legitimately cover a sample without consuming a whole new one;
98 // bailing here left a hole and a stale m_prev_date.
99 if(samples_to_write <= 0)
100 return;
101
102 assert(samples_to_write > 0);
103
104 const auto samples_offset = t.physical_start(e.modelToSamples());
105
106 if(t.forward())
107 {
108 if(t.prev_date < info->m_prev_date)
109 {
110 // First run after add_time_process() left the stretcher already
111 // primed; calling transport() again would reset it.
112 if(info->m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
113 info->m_prev_date = t.prev_date;
114 else
115 transport(t.prev_date);
116 }
117 }
118 else
119 {
120 if(t.prev_date > info->m_prev_date)
121 {
122 if(info->m_prev_date == ossia::time_value{ossia::time_value::infinite_min})
123 info->m_prev_date = t.prev_date;
124 else
125 transport(t.prev_date);
126 }
127 }
128
129 for(std::size_t i = 0; i < chan; ++i)
130 {
131 ap.channel(i).resize(e.bufferSize());
132 }
133
134 const double rate_ratio = snd::rate_ratio(m_dataSampleRate, e.sampleRate());
135 const double stretch_ratio = info->update_stretch(t, e, rate_ratio);
136 const double abs_stretch_ratio = std::abs(stretch_ratio);
137
138 info->m_resampler.run(
139 *this, t, e, stretch_ratio, rate_ratio, chan, len, samples_to_read,
140 samples_to_write, samples_offset, ap);
141
142 const bool start_discontinuous = t.start_discontinuous || (m_last_stretch > 70.);
143 const bool end_discontinuous = t.end_discontinuous || (abs_stretch_ratio > 70.);
144 if(abs_stretch_ratio > 70. && m_last_stretch > 70.)
145 {
146 [[unlikely]];
147 for(std::size_t i = 0; i < chan; i++)
148 {
149 ossia::snd::do_zero(ap.channel(i), samples_offset, samples_to_write);
150 }
151 }
152 else
153 {
154 [[likely]];
155 for(std::size_t i = 0; i < chan; i++)
156 {
157 ossia::snd::do_fade(
158 start_discontinuous, end_discontinuous, ap.channel(i), samples_offset,
159 samples_to_write);
160 }
161 }
162
163 ossia::snd::perform_upmix(this->upmix, chan, ap);
164 ossia::snd::perform_start_offset(this->start, ap);
165
166 info->m_prev_date = t.date;
167 m_last_stretch = abs_stretch_ratio;
168 }
169
170 sound_processing_info* info{};
171 ossia::audio_port* audio_out{};
172
173 audio_span<float> m_data{};
174
175 std::size_t start{};
176 std::size_t upmix{};
177
178 std::size_t m_dataSampleRate{};
179 audio_handle m_handle{};
180 double m_last_stretch{1.0};
181};
182}