git.haldean.org qb / main
cues first pass haldean 5 months ago
8 changed file(s) with 197 addition(s) and 54 deletion(s). Raw diff Collapse all Expand all
6767 audionode.hpp
6868 colors.cpp
6969 colors.hpp
70 cue.cpp
71 cue.hpp
7072 qb.cpp
7173 qb.hpp
7274 qbshim.cpp
99 case background: return "background";
1010 case waveform: return "waveform";
1111 case playhead: return "playhead";
12 case hit: return "track hit";
1213 }
1314 return "unknown";
1415 }
1718 colors[color::background] = IM_COL32(99, 0, 29, 255);
1819 colors[color::waveform] = IM_COL32(255, 158, 0, 255);
1920 colors[color::playhead] = IM_COL32(154, 0, 42, 255);
21 colors[color::hit] = IM_COL32(0, 255, 255, 255);
2022 }
2123
2224 void showeditor() {
55 background,
66 waveform,
77 playhead,
8 hit,
89 ncolors
910 };
1011
0 #include "cue.hpp"
1
2 namespace qb {
3 cue_node::cue_node(const context *c, const std::string &path)
4 : node(c, path, qb::node_type::cue), path(path) {}
5 cue_node::~cue_node() = default;
6
7 int cue_node::minframe() const {
8 int min = 0;
9 for (const auto &track : tracks) {
10 if (!track.hits.empty()) {
11 min = std::min(min, track.hits.front());
12 }
13 }
14 return min;
15 }
16
17 int cue_node::maxframe() const {
18 int max = 0;
19 for (const auto &track : tracks) {
20 if (!track.hits.empty()) {
21 max = std::max(max, track.hits.back());
22 }
23 }
24 return max;
25 }
26 }
0 #include "qb.hpp"
1
2 #include <istream>
3 #include <ostream>
4
5 namespace qb {
6 struct track {
7 std::vector<int> hits;
8 std::string name;
9
10 track() = default;
11 track(const std::string &name) : name(name) {}
12 };
13
14 struct cue_node : public node {
15 cue_node(const context *c, const std::string &path);
16 ~cue_node() override;
17
18 int minframe() const override;
19 int maxframe() const override;
20
21 const std::string path;
22 std::vector<track> tracks;
23
24 // since last write:
25 bool modified = false;
26 };
27 }
00 #include "qb.hpp"
11 #include "audionode.hpp"
22 #include "colors.hpp"
3 #include "cue.hpp"
34 #include "nodeui.hpp"
45
56 #include "AudioFile/AudioFile.h"
67 #include "imgui.h"
78
9 #include <algorithm>
810 #include <cmath>
911
1012 namespace qb {
1113
12 void draw(const std::shared_ptr<qb::audio_node> &node)
13 {
14 ImGui::Text(node->path().c_str());
14 void draw(const std::shared_ptr<qb::audio_node> &node) {
15 ImGui::Text(node->path().c_str());
16 const int rad = 40;
1517
16 switch (node->loadstate().stage) {
17 case load_state::loading:
18 ImGui::Text("%c loading: %s",
19 (node->ctx()->spinstep / 90 % 2) ? '.' : ' ',
20 node->loadstate().message.c_str());
21 return;
18 switch (node->loadstate().stage) {
19 case load_state::loading:
20 ImGui::Text("%c loading: %s",
21 (node->ctx()->spinstep / 90 % 2) ? '.' : ' ',
22 node->loadstate().message.c_str());
23 break;
2224
23 case load_state::failed:
24 ImGui::Text("failed to load file");
25 return;
25 case load_state::failed:
26 ImGui::Text("failed to load file");
27 break;
2628
27 case load_state::loaded: {
29 case load_state::loaded:
30 qb::audio_file_cptr data = node->data();
31 if (!data) {
32 ImGui::Text("audio file is invalid\n");
33 break;
34 }
35 double seconds = data->getLengthInSeconds();
36 const int minutes = (int) floor(seconds / 60.0);
37 seconds -= 60 * minutes;
38
39 ImGui::Text("sample rate %d depth %d samples %d channels %d length %dm%0.3fs",
40 data->getSampleRate(),
41 data->getBitDepth(),
42 data->getNumSamplesPerChannel(),
43 data->getNumChannels(),
44 minutes, seconds);
45 break;
46 }
47
48 ImGui::InvisibleButton("##empty", ImVec2(-1, 2 * rad));
49 const ImVec2 origin = ImVec2(0, ImGui::GetItemRectMin().y);
50 const ImVec2 corner =
51 ImVec2(ImGui::GetWindowWidth(), ImGui::GetItemRectMax().y);
52 ImDrawList * const drawlist = ImGui::GetWindowDrawList();
53
2854 qb::audio_file_cptr data = node->data();
29 if (!data) {
30 ImGui::Text("audio file is invalid\n");
55 if (node->loadstate().stage != load_state::loaded) {
56 drawlist->AddRectFilled(origin, corner,
57 theme::colors[theme::color::waveform]);
58 return;
3159 }
32 double seconds = data->getLengthInSeconds();
33 const int minutes = (int) floor(seconds / 60.0);
34 seconds -= 60 * minutes;
35
36 ImGui::Text("sample rate %d depth %d samples %d channels %d length %dm%0.3fs",
37 data->getSampleRate(),
38 data->getBitDepth(),
39 data->getNumSamplesPerChannel(),
40 data->getNumChannels(),
41 minutes, seconds);
4260
4361 const std::vector<float> &r = node->rendered();
4462
4563 const context *c = node->ctx();
46 const int rad = 40;
4764 const double rsamplerate = node->rsamplerate();
4865 const int64_t s0 = (long) floor(rsamplerate * (c->playhead - c->visrad));
4966 const int64_t s1 = (long) ceil(rsamplerate * (c->playhead + c->visrad));
5067
51 ImGui::InvisibleButton("##empty", ImVec2(-1, 2 * rad));
52 const ImVec2 origin = ImVec2(0, ImGui::GetItemRectMin().y);
53 const ImVec2 corner =
54 ImVec2(ImGui::GetWindowWidth(), ImGui::GetItemRectMax().y);
5568 const float width = corner.x - origin.x;
5669 const float scale = width / (s1 - s0);
5770
58 ImDrawList * const drawlist = ImGui::GetWindowDrawList();
5971 for (int64_t s = std::max(int64_t(1), s0 + 1);
6072 s <= s1 && s < (int64_t) r.size(); s++) {
6173 const ImVec2 a(origin.x + scale * (s - 1 - s0),
6577 drawlist->AddLine(a, b, theme::colors[theme::color::waveform]);
6678 }
6779 }
80
81 void draw(const std::shared_ptr<qb::cue_node> &node) {
82 ImGui::Text(node->name().c_str());
83
84 const qb::context *ctx = node->ctx();
85 const int width = ctx->width;
86 const float vismin = (float) (ctx->playhead - ctx->visrad);
87 const float vismax = (float) (ctx->playhead + ctx->visrad);
88 const float fadetime = 0.5f * ctx->framerate();
89
90 ImDrawList * const drawlist = ImGui::GetWindowDrawList();
91
92 for (const auto &track : node->tracks) {
93 ImGui::Text(track.name.c_str());
94
95 const ImVec2 min = ImGui::GetItemRectMin();
96 const ImVec2 max = ImGui::GetItemRectMax();
97 const float scale = width / (vismax - vismin); // pixels per frame
98 const float rad = (max.y - min.y) / 2.0f;
99
100 const bool hit = std::binary_search(track.hits.begin(),
101 track.hits.end(),
102 (int) ctx->playhead);
103 float hitdist = -1;
104 if (!hit) {
105 const auto iter =
106 std::lower_bound(track.hits.begin(),
107 track.hits.end(),
108 (int) ctx->playhead);
109 if (iter != track.hits.end() && iter != track.hits.begin()) {
110 hitdist = (float) (ctx->playhead - *std::prev(iter));
111 }
112 }
113
114 if (hit || (hitdist > 0 && hitdist < fadetime)) {
115 const float alpha =
116 hit ? 1.0f : 1.0f - sqrt(hitdist / (float) fadetime);
117 ImColor color(theme::colors[theme::color::hit]);
118 color.Value.w = alpha;
119 drawlist->AddCircleFilled(ImVec2(max.x + 2 * rad, min.y + rad), rad, color);
120 }
121
122 std::vector<int>::const_iterator iter =
123 std::lower_bound(track.hits.begin(),
124 track.hits.end(),
125 (int)vismin - 1);
126 for (; iter != track.hits.end() && *iter < vismax; ++iter) {
127 float x0 = roundf((*iter - vismin) * scale);
128 float x1 = x0 + scale;
129 if (x1 - x0 < 2) {
130 drawlist->AddLine(ImVec2(x0, min.y), ImVec2(x0, max.y),
131 theme::colors[theme::color::hit]);
132 } else {
133 drawlist->AddRectFilled(ImVec2(x0, min.y), ImVec2(x1, max.y),
134 theme::colors[theme::color::hit]);
135 }
136 }
137 }
68138 }
69 }
70139
71 template <class To, class From>
72 std::shared_ptr<To> checked_cast(const std::shared_ptr<From> &p) {
73 const auto res = std::dynamic_pointer_cast<To>(p);
74 if (!res) {
75 throw ("bad cast to " + std::string(typeid(To).name()));
140 template <class To, class From>
141 std::shared_ptr<To> checked_cast(const std::shared_ptr<From> &p) {
142 const auto res = std::dynamic_pointer_cast<To>(p);
143 if (!res) {
144 throw ("bad cast to " + std::string(typeid(To).name()));
145 }
146 return res;
76147 }
77 return res;
78 }
79148
80 void draw_node(const qb::node_ptr &node)
81 {
82 switch (node->type()) {
83 case qb::node_type::audio:
84 draw(checked_cast<qb::audio_node>(node));
85 return;
86 case qb::node_type::midi:
87 ImGui::Text("no drawing for midi nodes yet");
88 break;
89 case qb::node_type::osc:
90 ImGui::Text("no drawing for osc nodes yet");
91 break;
149 void draw_node(const qb::node_ptr &node)
150 {
151 switch (node->type()) {
152 case qb::node_type::audio:
153 draw(checked_cast<qb::audio_node>(node));
154 return;
155 case qb::node_type::cue:
156 draw(checked_cast<qb::cue_node>(node));
157 break;
158 }
92159 }
93 }
94160
95161 }
00 #include "qb.hpp"
1
12 #include "audionode.hpp"
23 #include "colors.hpp"
4 #include "cue.hpp"
35 #include "nodeui.hpp"
46 #include "task.hpp"
57
1921
2022 context::context() {
2123 _nodes.push_back(std::make_shared<qb::audio_node>(this, "test/coc-mono.wav"));
24
25 auto cuenode = std::make_shared<qb::cue_node>(this, "test/cues.txt");
26
27 qb::track t1("first");
28 for (int i = 0; i < 200; i += 4) {
29 t1.hits.push_back(i);
30 }
31 cuenode->tracks.push_back(t1);
32
33 qb::track t2("second");
34 for (int i = 400; i < 2000; i += 24) {
35 t2.hits.push_back(i);
36 }
37 cuenode->tracks.push_back(t2);
38 cuenode->tracks.push_back(qb::track("third"));
39 _nodes.push_back(cuenode);
2240 qb::theme::setdefault();
2341 ImGui::GetIO().KeyRepeatRate = 1.0f / _framerate;
2442 }
77 namespace qb {
88 enum class node_type {
99 audio,
10 midi,
11 osc,
10 cue,
1211 };
1312
1413 struct load_state {