0 | 0 |
#include "qb.hpp"
|
1 | 1 |
#include "audionode.hpp"
|
2 | 2 |
#include "colors.hpp"
|
|
3 |
#include "cue.hpp"
|
3 | 4 |
#include "nodeui.hpp"
|
4 | 5 |
|
5 | 6 |
#include "AudioFile/AudioFile.h"
|
6 | 7 |
#include "imgui.h"
|
7 | 8 |
|
|
9 |
#include <algorithm>
|
8 | 10 |
#include <cmath>
|
9 | 11 |
|
10 | 12 |
namespace qb {
|
11 | 13 |
|
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;
|
15 | 17 |
|
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;
|
22 | 24 |
|
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;
|
26 | 28 |
|
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 |
|
28 | 54 |
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;
|
31 | 59 |
}
|
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);
|
42 | 60 |
|
43 | 61 |
const std::vector<float> &r = node->rendered();
|
44 | 62 |
|
45 | 63 |
const context *c = node->ctx();
|
46 | |
const int rad = 40;
|
47 | 64 |
const double rsamplerate = node->rsamplerate();
|
48 | 65 |
const int64_t s0 = (long) floor(rsamplerate * (c->playhead - c->visrad));
|
49 | 66 |
const int64_t s1 = (long) ceil(rsamplerate * (c->playhead + c->visrad));
|
50 | 67 |
|
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);
|
55 | 68 |
const float width = corner.x - origin.x;
|
56 | 69 |
const float scale = width / (s1 - s0);
|
57 | 70 |
|
58 | |
ImDrawList * const drawlist = ImGui::GetWindowDrawList();
|
59 | 71 |
for (int64_t s = std::max(int64_t(1), s0 + 1);
|
60 | 72 |
s <= s1 && s < (int64_t) r.size(); s++) {
|
61 | 73 |
const ImVec2 a(origin.x + scale * (s - 1 - s0),
|
|
65 | 77 |
drawlist->AddLine(a, b, theme::colors[theme::color::waveform]);
|
66 | 78 |
}
|
67 | 79 |
}
|
|
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 |
}
|
68 | 138 |
}
|
69 | |
}
|
70 | 139 |
|
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;
|
76 | 147 |
}
|
77 | |
return res;
|
78 | |
}
|
79 | 148 |
|
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 |
}
|
92 | 159 |
}
|
93 | |
}
|
94 | 160 |
|
95 | 161 |
}
|