git.haldean.org qb / 8155421
loading/rendering audio haldean 1 year, 1 month ago
10 changed file(s) with 202 addition(s) and 37 deletion(s). Raw diff Collapse all Expand all
0 ((c++-mode
1 . ((compile-command . "cmake --build build")
2 ))
3 )
00 .vscode/
11 build/
2 test/*.wav
0 [submodule "AudioFile"]
1 path = AudioFile
2 url = https://github.com/adamstark/AudioFile
0 [submodule "AudioFile"]
1 path = AudioFile
2 url = https://github.com/adamstark/AudioFile
6161 qbshim.hpp
6262 nodeui.cpp
6363 nodeui.hpp
64 task.cpp
65 task.hpp
66 AudioFile/AudioFile.h
6467 )
6568 if(CMAKE_SYSTEM_NAME STREQUAL Windows)
6669 add_executable(qb WIN32 ${QB_SOURCES})
00 #include "qb.hpp"
11 #include "nodeui.hpp"
22
3 #include "AudioFile/AudioFile.h"
34 #include "cimgui/imgui/imgui.h"
45
56 namespace qb {
67
78 void draw(const std::shared_ptr<qb::audio_node> &node)
89 {
9 if (!node->loaded()) {
10 ImGui::Text("failed to load file %s", node->path().c_str());
10 ImGui::Text(node->path().c_str());
11
12 switch (node->loadstate().stage) {
13 case load_state::loading:
14 ImGui::Text("loading: %s", node->loadstate().message.c_str());
1115 return;
16
17 case load_state::failed:
18 ImGui::Text("failed to load file");
19 return;
20
21 case load_state::loaded: {
22 qb::audio_file_cptr data = node->data();
23 if (!data) {
24 ImGui::Text("audio file is invalid\n");
25 }
26 double seconds = data->getLengthInSeconds();
27 const int minutes = (int) floor(seconds / 60.0);
28 seconds -= 60 * minutes;
29
30 ImGui::Text("sample rate %d depth %d samples %d channels %d length %dm%0.3fs",
31 data->getSampleRate(),
32 data->getBitDepth(),
33 data->getNumSamplesPerChannel(),
34 data->getNumChannels(),
35 minutes, seconds);
36
37 static int len = 500;
38 static int off = 0;
39 ImGui::DragInt("len", &len, 1.0, 0, INT_MAX);
40 ImGui::DragInt("off", &off, 1.0, 0, INT_MAX);
41
42 const std::vector<float> &r = node->rendered();
43 ImGui::PlotLines("samples", r.data(),
44 std::min((int)r.size() - off, len),
45 std::min((int)r.size() - 1, off));
1246 }
13 ImGui::Text(node->path().c_str());
47 }
1448 }
1549
1650 template <class To, class From>
00 #include "qb.hpp"
11 #include "nodeui.hpp"
2 #include "task.hpp"
3 #include "AudioFile/AudioFile.h"
24
35 #include "cimgui/imgui/imgui.h"
46
57 namespace qb {
68 context::context() {
7 _nodes.push_back(std::make_shared<qb::audio_node>("test.wav"));
9 _nodes.push_back(std::make_shared<qb::audio_node>(this, "test/coc-stereo.wav"));
810 }
911
1012 void context::frame(int width, int height) {
13 taskpool::get().frame();
14
1115 ImGui::SetNextWindowPos(ImVec2(0, 0));
1216 ImGui::SetNextWindowSize(ImVec2((float) width, (float) height));
1317 ImGui::Begin("MainWindow", nullptr, ImGuiWindowFlags_NoDecoration);
2125 void context::shutdown() {
2226 }
2327
24 node::node(const std::string &n, node_type t) : _name(n), _type(t) {}
28 node::node(const context *c, const std::string &n, node_type t)
29 : _c(c), _name(n), _type(t) {}
2530
26 /*
27 class audio_file : public AudioFile<float> {
28 using AudioFile::AudioFile;
29 };
30 */
31 class audio_file {
32 public:
33 bool load(const std::string &path) { return false; }
34 };
31 audio_node::audio_node(const context *c, const std::string &path)
32 : node(c, path, qb::node_type::audio)
33 , _path(path)
34 , _loadstate(load_state::loading)
35 {
36 taskpool::get().submit_async([this]() { this->load_async(); });
37 }
3538
36 audio_node::audio_node(const std::string &path)
37 : node(path, qb::node_type::audio)
38 , _path(path)
39 {
39 void audio_node::load_async() {
40 taskpool::get().submit_frame([this]() {
41 this->_loadstate = load_state(load_state::loading, "loading file");
42 });
4043 _data.reset(new audio_file());
41 _loaded = _data->load(path);
44 const bool success = _data->load(_path);
45 if (!success) {
46 taskpool::get().submit_frame([this]() {
47 this->_loadstate = load_state(load_state::failed);
48 });
49 return;
50 }
51
52 taskpool::get().submit_frame([this]() {
53 this->_loadstate = load_state(load_state::loading, "generating preview");
54 });
55 render_samples();
56
57 // update load state on main thread: this is what governs whether the UI
58 // accesses the rest of the state here or not.
59 taskpool::get().submit_frame([this]() {
60 this->_loadstate = load_state(load_state::loaded);
61 });
62 }
63
64 void audio_node::render_samples() {
65 const size_t samples = _data->getNumSamplesPerChannel();
66 const double framerate = _c->framerate();
67
68 const double spf = (double)_data->getSampleRate() / framerate;
69 const int frames = 8 * (int) ceil(samples / spf);
70 const int channels = _data->getNumChannels();
71
72 _rendered.resize(frames);
73 for (int i = 0; i < frames; i++) {
74 const double s1 = i * spf;
75 const double s2 = (i + 1) * spf;
76 float sum = 0;
77 for (int chan = 0; chan < channels; chan++) {
78 for (size_t s = (size_t)floor(s1); s < (size_t)ceil(s2); s++) {
79 const float sample = s >= samples ? 0 : _data->samples[chan][s];
80 if (s == floor(s1) && (double)s != s1) {
81 sum += sample * (ceil(s1) - s1);
82 } else if (s == ceil(s2) && (double)s != s2) {
83 sum += sample * (s2 - floor(s2));
84 } else {
85 sum += sample;
86 }
87 }
88 }
89 _rendered[i] = (float)(sum / (spf * (double)channels));
90 }
4291 }
4392
4493 audio_node::~audio_node() = default;
44 #include <string>
55 #include <vector>
66
7 #include "AudioFile/AudioFile.h"
8
79 namespace qb {
810 enum class node_type {
911 audio,
1012 midi,
1113 osc,
1214 };
15
16 struct load_state {
17 enum stage_kind {
18 loading,
19 loaded,
20 failed,
21 };
22 stage_kind stage;
23 std::string message;
24
25 load_state(stage_kind s) : stage(s), message("") {};
26 load_state(stage_kind s, const std::string &m) : stage(s), message(m) {};
27 };
28
29 struct context;
1330
1431 struct node {
1532 node(const node&) = delete;
1936
2037 virtual ~node() = default;
2138
22 const std::string &name() const {
23 return _name;
24 }
25 node_type type() const {
26 return _type;
27 }
39 const std::string &name() const { return _name; }
40 node_type type() const { return _type; }
2841
2942 protected:
30 node(const std::string &n, node_type t);
43 node(const context *c, const std::string &n, node_type t);
44 const context *_c;
3145
3246 private:
3347 std::string _name;
3549 };
3650 using node_ptr = std::shared_ptr<node>;
3751
38 class audio_file;
52 using audio_file = AudioFile<float>;
53 using audio_file_ptr = std::shared_ptr<audio_file>;
54 using audio_file_cptr = std::shared_ptr<const audio_file>;
55
3956 struct audio_node : public node {
40 explicit audio_node(const std::string &path);
57 audio_node(const context *c, const std::string &path);
4158 ~audio_node() override;
4259
43 const std::string &path() const {
44 return _path;
45 }
46
47 bool loaded() const {
48 return _loaded;
49 }
60 const std::string &path() const { return _path; }
61 load_state loadstate() const { return _loadstate; }
62 const audio_file_cptr data() const { return _data; }
63 const std::vector<float>& rendered() const { return _rendered; }
5064
5165 private:
66 void load_async();
67 void render_samples();
68
5269 const std::string _path;
53 std::unique_ptr<audio_file> _data;
54 bool _loaded;
70 audio_file_ptr _data;
71 std::vector<float> _rendered;
72 load_state _loadstate;
5573 };
5674
5775 struct context
6482 context &operator=(const context &) = delete;
6583 context &operator=(context &&) = delete;
6684
85 int framerate() const { return _framerate; }
86 void framerate(int fr) { _framerate = fr; }
87
6788 void frame(int width, int height);
6889 void shutdown();
6990
7091 private:
92 int _framerate = 24;
7193 std::vector<qb::node_ptr> _nodes;
7294 };
7395
0 #include "task.hpp"
1
2 namespace qb {
3 taskpool& taskpool::get() {
4 static taskpool tp;
5 return tp;
6 }
7
8 void taskpool::submit_async(const task &t) {
9 std::thread(t).detach();
10 }
11
12 void taskpool::submit_frame(const task &t) {
13 std::lock_guard<std::mutex> guard(_locktasks);
14 _frametasks.push_back(t);
15 }
16
17 void taskpool::frame() {
18 std::lock_guard<std::mutex> guard(_locktasks);
19 for (const task &t : _frametasks) {
20 t();
21 }
22 _frametasks.clear();
23 }
24 }
0 #include <functional>
1 #include <mutex>
2 #include <thread>
3 #include <vector>
4
5 namespace qb
6 {
7
8 using task = std::function<void(void)>;
9 struct taskpool {
10 static taskpool& get();
11 void submit_async(const task &t);
12 void submit_frame(const task &t);
13 void frame();
14
15 private:
16 std::vector<task> _frametasks;
17 std::mutex _locktasks;
18 };
19
20 }