git.haldean.org qb / main nodeui.cpp
main

Tree @main (Download .tar.gz)

nodeui.cpp @mainraw · history · blame

#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.end() && iter != track.hits.begin()) {
          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;
    }
  }

}