git.haldean.org qb / b340f54
refactor out the audio bits haldean 5 months ago
6 changed file(s) with 148 addition(s) and 125 deletion(s). Raw diff Collapse all Expand all
33 [submodule "implot"]
44 path = implot
55 url = https://github.com/epezent/implot
6 [submodule "AudioFFT"]
7 path = AudioFFT
8 url = https://github.com/HiFi-LoFi/AudioFFT
0 [submodule "AudioFile"]
1 path = AudioFile
2 url = https://github.com/adamstark/AudioFile
3 [submodule "implot"]
4 path = implot
5 url = https://github.com/epezent/implot
6 [submodule "AudioFFT"]
7 path = AudioFFT
8 url = https://github.com/HiFi-LoFi/AudioFFT
5454 target_link_libraries(sokol PUBLIC cimgui)
5555 target_include_directories(sokol INTERFACE sokol)
5656
57 #=== LIBRARY: AudioFFT
58 add_library(audiofft STATIC
59 AudioFFT/AudioFFT.h
60 AudioFFT/AudioFFT.cpp)
61 target_include_directories(cimgui INTERFACE AudioFFT)
62
5763 #=== EXECUTABLE: qb
5864 set(QB_SOURCES
5965 launch.c
66 audionode.cpp
6067 colors.cpp
6168 colors.hpp
6269 qb.cpp
7481 else()
7582 add_executable(qb ${QB_SOURCES})
7683 endif()
77 target_include_directories(qb PRIVATE cimgui/imgui implot)
78 target_link_libraries(qb sokol)
84 target_include_directories(qb PRIVATE cimgui/imgui implot AudioFFT)
85 target_link_libraries(qb sokol audiofft)
7986
8087 # explicitly strip dead code
8188 if (CMAKE_C_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_SYSTEM_NAME STREQUAL Emscripten)
0 #include "qb.hpp"
1 #include "task.hpp"
2 #include "AudioFile/AudioFile.h"
3
4 namespace qb {
5 audio_node::audio_node(const context *c, const std::string &path)
6 : node(c, path, qb::node_type::audio)
7 , _path(path)
8 , _loadstate(load_state::loading)
9 {
10 taskpool::get().submit_async([this]() { this->load_async(); });
11 }
12
13 const std::vector<float>& audio_node::packed() const {
14 if (!_data || _data->getNumChannels() != 1) {
15 return _packed;
16 }
17 return _data->samples[0];
18 }
19
20 static constexpr double render_rate_hi = 16;
21 static constexpr double render_rate_lo = 4;
22 static constexpr int hires_frame_limit = 1000;
23
24 const std::vector<float>& audio_node::rendered() const {
25 double visframes = 2 * _c->visrad;
26 return visframes > hires_frame_limit ? _rlo : _rhi;
27 }
28
29 double audio_node::rsamplerate() const {
30 double visframes = 2 * _c->visrad;
31 return (visframes > hires_frame_limit ? render_rate_lo : render_rate_hi);
32 }
33
34 void audio_node::load_async() {
35 taskpool::get().submit_frame([this]() {
36 this->_loadstate = load_state(load_state::loading, "loading file");
37 });
38 _data.reset(new audio_file());
39 const bool success = _data->load(_path);
40 if (!success) {
41 taskpool::get().submit_frame([this]() {
42 this->_loadstate = load_state(load_state::failed);
43 });
44 return;
45 }
46
47 taskpool::get().submit_frame([this]() {
48 this->_loadstate =
49 load_state(load_state::loading, "generating hi-res preview");
50 });
51 _rhi = render_samples(render_rate_hi);
52
53 taskpool::get().submit_frame([this]() {
54 this->_loadstate =
55 load_state(load_state::loading, "generating lo-res preview");
56 });
57 _rlo = render_samples(render_rate_lo);
58
59 taskpool::get().submit_frame([this]() {
60 this->_loadstate =
61 load_state(load_state::loading, "packing samples for playback");
62 });
63 if (_data->getNumChannels() != 1) {
64 const size_t channels = _data->getNumChannels();
65 const size_t n = _data->getNumSamplesPerChannel();
66 _packed.reserve(channels * n);
67 for (size_t i = 0; i < n; i++) {
68 for (size_t c = 0; c < channels; c++) {
69 _packed.push_back(_data->samples[c][i]);
70 }
71 }
72 }
73
74 // update load state on main thread: this is what governs whether the UI
75 // accesses the rest of the state here or not.
76 taskpool::get().submit_frame([this]() {
77 this->_loadstate = load_state(load_state::loaded);
78 });
79 }
80
81 std::vector<float> audio_node::render_samples(double framesamples) {
82 const size_t samples = _data->getNumSamplesPerChannel();
83 const double spr =
84 (double)_data->getSampleRate() / (framesamples * _c->framerate());
85 const size_t rsamples = (size_t) ceil((double)samples / spr);
86 const int channels = _data->getNumChannels();
87
88 std::vector<float> rendered;
89 rendered.resize(rsamples);
90 float absmax = 0;
91 for (int i = 0; i < rsamples; i++) {
92 const double s1 = i * spr;
93 const double s2 = (i + 1) * spr;
94 double sum = 0;
95 for (int chan = 0; chan < channels; chan++) {
96 for (size_t s = (size_t)floor(s1);
97 s < (size_t)ceil(s2) && s < _data->samples[chan].size();
98 s++) {
99 double x = _data->samples[chan][s];
100 sum += x;
101 }
102 }
103 const float r = (float) (sum / (spr * channels));
104 absmax = std::max(absmax, fabsf(r));
105 rendered[i] = r;
106 }
107 for (float &f : rendered) {
108 f /= absmax;
109 }
110 return rendered;
111 }
112
113 int audio_node::minframe() const {
114 return 0;
115 }
116
117 int audio_node::maxframe() const {
118 if (_loadstate.stage != load_state::loaded) {
119 return 0;
120 }
121 return (int) ceil(_data->getLengthInSeconds() * _c->framerate());
122 }
123
124 audio_node::~audio_node() = default;
125 }
4242 const std::vector<float> &r = node->rendered();
4343
4444 const context *c = node->ctx();
45 const int rad = 100;
45 const int rad = 40;
4646 const double rsamplerate = node->rsamplerate();
4747 const int64_t s0 = (long) floor(rsamplerate * (c->playhead - c->visrad));
4848 const int64_t s1 = (long) ceil(rsamplerate * (c->playhead + c->visrad));
11 #include "colors.hpp"
22 #include "nodeui.hpp"
33 #include "task.hpp"
4 #include "AudioFile/AudioFile.h"
54
65 #include "sokol/sokol_app.h"
76 #include "sokol/sokol_audio.h"
195194
196195 node::node(const context *c, const std::string &n, node_type t)
197196 : _c(c), _name(n), _type(t) {}
198
199 audio_node::audio_node(const context *c, const std::string &path)
200 : node(c, path, qb::node_type::audio)
201 , _path(path)
202 , _loadstate(load_state::loading)
203 {
204 taskpool::get().submit_async([this]() { this->load_async(); });
205 }
206
207 const std::vector<float>& audio_node::packed() const {
208 if (!_data || _data->getNumChannels() != 1) {
209 return _packed;
210 }
211 return _data->samples[0];
212 }
213
214 static constexpr double render_rate_hi = 8;
215 static constexpr double render_rate_lo = 2;
216 static constexpr int hires_frame_limit = 1000;
217
218 const std::vector<float>& audio_node::rendered() const {
219 double visframes = 2 * _c->visrad;
220 return visframes > hires_frame_limit ? _rlo : _rhi;
221 }
222
223 double audio_node::rsamplerate() const {
224 double visframes = 2 * _c->visrad;
225 return (visframes > hires_frame_limit ? render_rate_lo : render_rate_hi);
226 }
227
228 void audio_node::load_async() {
229 taskpool::get().submit_frame([this]() {
230 this->_loadstate = load_state(load_state::loading, "loading file");
231 });
232 _data.reset(new audio_file());
233 const bool success = _data->load(_path);
234 if (!success) {
235 taskpool::get().submit_frame([this]() {
236 this->_loadstate = load_state(load_state::failed);
237 });
238 return;
239 }
240
241 taskpool::get().submit_frame([this]() {
242 this->_loadstate =
243 load_state(load_state::loading, "generating hi-res preview");
244 });
245 _rhi = render_samples(render_rate_hi);
246
247 taskpool::get().submit_frame([this]() {
248 this->_loadstate =
249 load_state(load_state::loading, "generating lo-res preview");
250 });
251 _rlo = render_samples(render_rate_lo);
252
253 taskpool::get().submit_frame([this]() {
254 this->_loadstate =
255 load_state(load_state::loading, "packing samples for playback");
256 });
257 if (_data->getNumChannels() != 1) {
258 const size_t channels = _data->getNumChannels();
259 const size_t n = _data->getNumSamplesPerChannel();
260 _packed.reserve(channels * n);
261 for (size_t i = 0; i < n; i++) {
262 for (size_t c = 0; c < channels; c++) {
263 _packed.push_back(_data->samples[c][i]);
264 }
265 }
266 }
267
268 // update load state on main thread: this is what governs whether the UI
269 // accesses the rest of the state here or not.
270 taskpool::get().submit_frame([this]() {
271 this->_loadstate = load_state(load_state::loaded);
272 });
273 }
274
275 std::vector<float> audio_node::render_samples(double framesamples) {
276 const size_t samples = _data->getNumSamplesPerChannel();
277 const double spr =
278 (double)_data->getSampleRate() / (framesamples * _c->framerate());
279 const size_t rsamples = (size_t) ceil((double)samples / spr);
280 const int channels = _data->getNumChannels();
281
282 std::vector<float> rendered;
283 rendered.resize(rsamples);
284 float absmax = 0;
285 for (int i = 0; i < rsamples; i++) {
286 const double s1 = i * spr;
287 const double s2 = (i + 1) * spr;
288 double sum = 0;
289 for (int chan = 0; chan < channels; chan++) {
290 for (size_t s = (size_t)floor(s1);
291 s < (size_t)ceil(s2) && s < _data->samples[chan].size();
292 s++) {
293 double x = _data->samples[chan][s];
294 sum += (x < 0 ? -1.0 : 1.0) * sqrt(fabs(x));
295 }
296 }
297 const float r = (float) (sum / (spr * channels));
298 absmax = std::max(absmax, fabsf(r));
299 rendered[i] = r;
300 }
301 for (float &f : rendered) {
302 f /= absmax;
303 }
304 return rendered;
305 }
306
307 int audio_node::minframe() const {
308 return 0;
309 }
310
311 int audio_node::maxframe() const {
312 if (_loadstate.stage != load_state::loaded) {
313 return 0;
314 }
315 return (int) ceil(_data->getLengthInSeconds() * _c->framerate());
316 }
317
318 audio_node::~audio_node() = default;
319197 }
320198