#include "qb.hpp"
#include "task.hpp"
#include "AudioFile/AudioFile.h"
namespace qb {
audio_node::audio_node(const context *c, const std::string &path)
: node(c, path, qb::node_type::audio)
, _path(path)
, _loadstate(load_state::loading)
{
taskpool::get().submit_async([this]() { this->load_async(); });
}
const std::vector<float>& audio_node::packed() const {
if (!_data || _data->getNumChannels() != 1) {
return _packed;
}
return _data->samples[0];
}
static constexpr double render_rate_hi = 16;
static constexpr double render_rate_lo = 4;
static constexpr int hires_frame_limit = 1000;
const std::vector<float>& audio_node::rendered() const {
double visframes = 2 * _c->visrad;
return visframes > hires_frame_limit ? _rlo : _rhi;
}
double audio_node::rsamplerate() const {
double visframes = 2 * _c->visrad;
return (visframes > hires_frame_limit ? render_rate_lo : render_rate_hi);
}
void audio_node::load_async() {
taskpool::get().submit_frame([this]() {
this->_loadstate = load_state(load_state::loading, "loading file");
});
_data.reset(new audio_file());
const bool success = _data->load(_path);
if (!success) {
taskpool::get().submit_frame([this]() {
this->_loadstate = load_state(load_state::failed);
});
return;
}
taskpool::get().submit_frame([this]() {
this->_loadstate =
load_state(load_state::loading, "generating hi-res preview");
});
_rhi = render_samples(render_rate_hi);
taskpool::get().submit_frame([this]() {
this->_loadstate =
load_state(load_state::loading, "generating lo-res preview");
});
_rlo = render_samples(render_rate_lo);
taskpool::get().submit_frame([this]() {
this->_loadstate =
load_state(load_state::loading, "packing samples for playback");
});
if (_data->getNumChannels() != 1) {
const size_t channels = _data->getNumChannels();
const size_t n = _data->getNumSamplesPerChannel();
_packed.reserve(channels * n);
for (size_t i = 0; i < n; i++) {
for (size_t c = 0; c < channels; c++) {
_packed.push_back(_data->samples[c][i]);
}
}
}
// update load state on main thread: this is what governs whether the UI
// accesses the rest of the state here or not.
taskpool::get().submit_frame([this]() {
this->_loadstate = load_state(load_state::loaded);
});
}
std::vector<float> audio_node::render_samples(double framesamples) {
const size_t samples = _data->getNumSamplesPerChannel();
const double spr =
(double)_data->getSampleRate() / (framesamples * _c->framerate());
const size_t rsamples = (size_t) ceil((double)samples / spr);
const int channels = _data->getNumChannels();
std::vector<float> rendered;
rendered.resize(rsamples);
float absmax = 0;
for (int i = 0; i < rsamples; i++) {
const double s1 = i * spr;
const double s2 = (i + 1) * spr;
double sum = 0;
for (int chan = 0; chan < channels; chan++) {
for (size_t s = (size_t)floor(s1);
s < (size_t)ceil(s2) && s < _data->samples[chan].size();
s++) {
double x = _data->samples[chan][s];
sum += x;
}
}
const float r = (float) (sum / (spr * channels));
absmax = std::max(absmax, fabsf(r));
rendered[i] = r;
}
for (float &f : rendered) {
f /= absmax;
}
return rendered;
}
int audio_node::minframe() const {
return 0;
}
int audio_node::maxframe() const {
if (_loadstate.stage != load_state::loaded) {
return 0;
}
return (int) ceil(_data->getLengthInSeconds() * _c->framerate());
}
audio_node::~audio_node() = default;
}