#include "qb.hpp"
#include "audionode.hpp"
#include "colors.hpp"
#include "cue.hpp"
#include "nodeui.hpp"
#include "AudioFile/AudioFile.h"
#include "imgui.h"
#include <algorithm>
#include <cmath>
namespace qb {
void draw(const std::shared_ptr<qb::audio_node> &node) {
ImGui::Text(node->path().c_str());
const int rad = 40;
switch (node->loadstate().stage) {
case load_state::loading:
ImGui::Text("%c loading: %s",
(node->ctx()->spinstep() / 90 % 2) ? '.' : ' ',
node->loadstate().message.c_str());
break;
case load_state::failed:
ImGui::Text("failed to load file");
break;
case load_state::loaded:
qb::audio_file_cptr data = node->data();
if (!data) {
ImGui::Text("audio file is invalid\n");
break;
}
double seconds = data->getLengthInSeconds();
const int minutes = (int) floor(seconds / 60.0);
seconds -= 60 * minutes;
ImGui::Text("sample rate %d depth %d samples %d channels %d length %dm%0.3fs",
data->getSampleRate(),
data->getBitDepth(),
data->getNumSamplesPerChannel(),
data->getNumChannels(),
minutes, seconds);
break;
}
ImGui::InvisibleButton("##empty", ImVec2(-1, 2 * rad));
const ImVec2 origin = ImVec2(0, ImGui::GetItemRectMin().y);
const ImVec2 corner =
ImVec2(ImGui::GetWindowWidth(), ImGui::GetItemRectMax().y);
ImDrawList * const drawlist = ImGui::GetWindowDrawList();
qb::audio_file_cptr data = node->data();
if (node->loadstate().stage != load_state::loaded) {
drawlist->AddRectFilled(origin, corner,
theme::colors[theme::color::waveform]);
return;
}
const std::vector<float> &r = node->rendered();
const context *c = node->ctx();
const double rsamplerate = node->rsamplerate();
const int64_t s0 = (long) floor(rsamplerate * (c->playhead() - c->visrad()));
const int64_t s1 = (long) ceil(rsamplerate * (c->playhead() + c->visrad()));
const float width = corner.x - origin.x;
const float scale = width / (s1 - s0);
for (int64_t s = std::max(int64_t(1), s0 + 1);
s <= s1 && s < (int64_t) r.size(); s++) {
const ImVec2 a(origin.x + scale * (s - 1 - s0),
origin.y + rad * (1.0f - r[s - 1]));
const ImVec2 b(origin.x + scale * (s - s0),
origin.y + rad * (1.0f - r[s]));
drawlist->AddLine(a, b, theme::colors[theme::color::waveform]);
}
}
void draw(const std::shared_ptr<qb::cue_node> &node) {
ImGui::Text(node->name().c_str());
const qb::context *ctx = node->ctx();
const int width = ctx->width();
const float vismin = (float) (ctx->playhead() - ctx->visrad());
const float vismax = (float) (ctx->playhead() + ctx->visrad());
const float fadetime = 0.5f * ctx->framerate();
ImDrawList * const drawlist = ImGui::GetWindowDrawList();
for (const auto &track : node->tracks) {
ImGui::Text(track.name.c_str());
const ImVec2 min = ImGui::GetItemRectMin();
const ImVec2 max = ImGui::GetItemRectMax();
const float scale = width / (vismax - vismin); // pixels per frame
const float rad = (max.y - min.y) / 2.0f;
const bool hit = std::binary_search(track.hits.begin(),
track.hits.end(),
(int) ctx->playhead());
float hitdist = -1;
if (!hit) {
const auto iter =
std::lower_bound(track.hits.begin(),
track.hits.end(),
(int) ctx->playhead());
if (iter != track.hits.begin() && !track.hits.empty()) {
hitdist = (float) (ctx->playhead() - *std::prev(iter));
}
}
if (hit || (hitdist > 0 && hitdist < fadetime)) {
const float alpha =
hit ? 1.0f : 1.0f - sqrt(hitdist / (float) fadetime);
ImColor color(theme::colors[theme::color::hit]);
color.Value.w = alpha;
drawlist->AddCircleFilled(ImVec2(max.x + 2 * rad, min.y + rad), rad, color);
}
std::vector<int>::const_iterator iter =
std::lower_bound(track.hits.begin(),
track.hits.end(),
(int)vismin - 1);
for (; iter != track.hits.end() && *iter < vismax; ++iter) {
float x0 = roundf((*iter - vismin) * scale);
float x1 = x0 + scale;
if (x1 - x0 < 2) {
drawlist->AddLine(ImVec2(x0, min.y), ImVec2(x0, max.y),
theme::colors[theme::color::hit]);
} else {
drawlist->AddRectFilled(ImVec2(x0, min.y), ImVec2(x1, max.y),
theme::colors[theme::color::hit]);
}
}
}
}
template <class To, class From>
std::shared_ptr<To> checked_cast(const std::shared_ptr<From> &p) {
const auto res = std::dynamic_pointer_cast<To>(p);
if (!res) {
throw ("bad cast to " + std::string(typeid(To).name()));
}
return res;
}
void draw_node(const qb::node_ptr &node)
{
switch (node->type()) {
case qb::node_type::audio:
draw(checked_cast<qb::audio_node>(node));
return;
case qb::node_type::cue:
draw(checked_cast<qb::cue_node>(node));
break;
}
}
}