新增imgui-knobs, 优化mixer_track_effect_list

This commit is contained in:
Nanako 2024-06-14 18:33:09 +08:00
parent eac45751d2
commit 6a67438476
6 changed files with 431 additions and 44 deletions

View File

@ -52,6 +52,7 @@ void draw_imgui(float delta_time) {
ImGui::DockSpaceOverViewport();
draw_instrument_track();
draw_mixer();
draw_selected_mixer_track_effect_list();
}
void tick_imgui(float delta_time) {

View File

@ -0,0 +1,303 @@
#include "imgui-knobs.h"
#include <cmath>
#include <cstdlib>
#include <imgui.h>
#include <imgui_internal.h>
#define IMGUIKNOBS_PI 3.14159265358979323846f
namespace ImGuiKnobs {
namespace detail {
void draw_arc1(ImVec2 center, float radius, float start_angle, float end_angle, float thickness, ImColor color, int num_segments) {
ImVec2 start = {
center[0] + cosf(start_angle) * radius,
center[1] + sinf(start_angle) * radius,
};
ImVec2 end = {
center[0] + cosf(end_angle) * radius,
center[1] + sinf(end_angle) * radius,
};
// Calculate bezier arc points
auto ax = start[0] - center[0];
auto ay = start[1] - center[1];
auto bx = end[0] - center[0];
auto by = end[1] - center[1];
auto q1 = ax * ax + ay * ay;
auto q2 = q1 + ax * bx + ay * by;
auto k2 = (4.0f / 3.0f) * (sqrtf((2.0f * q1 * q2)) - q2) / (ax * by - ay * bx);
auto arc1 = ImVec2{center[0] + ax - k2 * ay, center[1] + ay + k2 * ax};
auto arc2 = ImVec2{center[0] + bx + k2 * by, center[1] + by - k2 * bx};
auto *draw_list = ImGui::GetWindowDrawList();
#if IMGUI_VERSION_NUM <= 18000
draw_list->AddBezierCurve(start, arc1, arc2, end, color, thickness, num_segments);
#else
draw_list->AddBezierCubic(start, arc1, arc2, end, color, thickness, num_segments);
#endif
}
void draw_arc(ImVec2 center, float radius, float start_angle, float end_angle, float thickness, ImColor color, int num_segments, int bezier_count) {
// Overlap and angle of ends of bezier curves needs work, only looks good when not transperant
auto overlap = thickness * radius * 0.00001f * IMGUIKNOBS_PI;
auto delta = end_angle - start_angle;
auto bez_step = 1.0f / bezier_count;
auto mid_angle = start_angle + overlap;
for (auto i = 0; i < bezier_count - 1; i++) {
auto mid_angle2 = delta * bez_step + mid_angle;
draw_arc1(center, radius, mid_angle - overlap, mid_angle2 + overlap, thickness, color, num_segments);
mid_angle = mid_angle2;
}
draw_arc1(center, radius, mid_angle - overlap, end_angle, thickness, color, num_segments);
}
template<typename DataType>
struct knob {
float radius;
bool value_changed;
ImVec2 center;
bool is_active;
bool is_hovered;
float angle_min;
float angle_max;
float t;
float angle;
float angle_cos;
float angle_sin;
knob(const char *_label, ImGuiDataType data_type, DataType *p_value, DataType v_min, DataType v_max, float speed, float _radius, const char *format, ImGuiKnobFlags flags) {
radius = _radius;
t = ((float) *p_value - v_min) / (v_max - v_min);
auto screen_pos = ImGui::GetCursorScreenPos();
// Handle dragging
ImGui::InvisibleButton(_label, {radius * 2.0f, radius * 2.0f});
auto gid = ImGui::GetID(_label);
ImGuiSliderFlags drag_flags = 0;
if (!(flags & ImGuiKnobFlags_DragHorizontal)) {
drag_flags |= ImGuiSliderFlags_Vertical;
}
value_changed = ImGui::DragBehavior(gid, data_type, p_value, speed, &v_min, &v_max, format, drag_flags);
angle_min = IMGUIKNOBS_PI * 0.75f;
angle_max = IMGUIKNOBS_PI * 2.25f;
center = {screen_pos[0] + radius, screen_pos[1] + radius};
is_active = ImGui::IsItemActive();
is_hovered = ImGui::IsItemHovered();
angle = angle_min + (angle_max - angle_min) * t;
angle_cos = cosf(angle);
angle_sin = sinf(angle);
}
void draw_dot(float size, float radius, float angle, color_set color, bool filled, int segments) {
auto dot_size = size * this->radius;
auto dot_radius = radius * this->radius;
ImGui::GetWindowDrawList()->AddCircleFilled(
{center[0] + cosf(angle) * dot_radius, center[1] + sinf(angle) * dot_radius},
dot_size,
is_active ? color.active : (is_hovered ? color.hovered : color.base),
segments);
}
void draw_tick(float start, float end, float width, float angle, color_set color) {
auto tick_start = start * radius;
auto tick_end = end * radius;
auto angle_cos = cosf(angle);
auto angle_sin = sinf(angle);
ImGui::GetWindowDrawList()->AddLine(
{center[0] + angle_cos * tick_end, center[1] + angle_sin * tick_end},
{center[0] + angle_cos * tick_start, center[1] + angle_sin * tick_start},
is_active ? color.active : (is_hovered ? color.hovered : color.base),
width * radius);
}
void draw_circle(float size, color_set color, bool filled, int segments) {
auto circle_radius = size * radius;
ImGui::GetWindowDrawList()->AddCircleFilled(
center,
circle_radius,
is_active ? color.active : (is_hovered ? color.hovered : color.base));
}
void draw_arc(float radius, float size, float start_angle, float end_angle, color_set color, int segments, int bezier_count) {
auto track_radius = radius * this->radius;
auto track_size = size * this->radius * 0.5f + 0.0001f;
detail::draw_arc(
center,
track_radius,
start_angle,
end_angle,
track_size,
is_active ? color.active : (is_hovered ? color.hovered : color.base),
segments,
bezier_count);
}
};
template<typename DataType>
knob<DataType> knob_with_drag(const char *label, ImGuiDataType data_type, DataType *p_value, DataType v_min, DataType v_max, float _speed, const char *format, float size, ImGuiKnobFlags flags) {
auto speed = _speed == 0 ? (v_max - v_min) / 250.f : _speed;
ImGui::PushID(label);
auto width = size == 0 ? ImGui::GetTextLineHeight() * 4.0f : size * ImGui::GetIO().FontGlobalScale;
ImGui::PushItemWidth(width);
ImGui::BeginGroup();
// There's an issue with `SameLine` and Groups, see https://github.com/ocornut/imgui/issues/4190.
// This is probably not the best solution, but seems to work for now
ImGui::GetCurrentWindow()->DC.CurrLineTextBaseOffset = 0;
// Draw title
if (!(flags & ImGuiKnobFlags_NoTitle)) {
auto title_size = ImGui::CalcTextSize(label, NULL, false, width);
// Center title
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (width - title_size[0]) * 0.5f);
ImGui::Text("%s", label);
}
// Draw knob
knob<DataType> k(label, data_type, p_value, v_min, v_max, speed, width * 0.5f, format, flags);
// Draw tooltip
if (flags & ImGuiKnobFlags_ValueTooltip && (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) || ImGui::IsItemActive())) {
ImGui::BeginTooltip();
ImGui::Text(format, *p_value);
ImGui::EndTooltip();
}
// Draw input
if (!(flags & ImGuiKnobFlags_NoInput)) {
ImGuiSliderFlags drag_flags = 0;
if (!(flags & ImGuiKnobFlags_DragHorizontal)) {
drag_flags |= ImGuiSliderFlags_Vertical;
}
auto changed = ImGui::DragScalar("###knob_drag", data_type, p_value, speed, &v_min, &v_max, format, drag_flags);
if (changed) {
k.value_changed = true;
}
}
ImGui::EndGroup();
ImGui::PopItemWidth();
ImGui::PopID();
return k;
}
color_set GetPrimaryColorSet() {
auto *colors = ImGui::GetStyle().Colors;
return {colors[ImGuiCol_ButtonActive], colors[ImGuiCol_ButtonHovered], colors[ImGuiCol_ButtonHovered]};
}
color_set GetSecondaryColorSet() {
auto *colors = ImGui::GetStyle().Colors;
auto active = ImVec4(
colors[ImGuiCol_ButtonActive].x * 0.5f,
colors[ImGuiCol_ButtonActive].y * 0.5f,
colors[ImGuiCol_ButtonActive].z * 0.5f,
colors[ImGuiCol_ButtonActive].w);
auto hovered = ImVec4(
colors[ImGuiCol_ButtonHovered].x * 0.5f,
colors[ImGuiCol_ButtonHovered].y * 0.5f,
colors[ImGuiCol_ButtonHovered].z * 0.5f,
colors[ImGuiCol_ButtonHovered].w);
return {active, hovered, hovered};
}
color_set GetTrackColorSet() {
auto *colors = ImGui::GetStyle().Colors;
return {colors[ImGuiCol_FrameBg], colors[ImGuiCol_FrameBg], colors[ImGuiCol_FrameBg]};
}
}// namespace detail
template<typename DataType>
bool BaseKnob(const char *label, ImGuiDataType data_type, DataType *p_value, DataType v_min, DataType v_max, float speed, const char *format, ImGuiKnobVariant variant, float size, ImGuiKnobFlags flags, int steps = 10) {
auto knob = detail::knob_with_drag(label, data_type, p_value, v_min, v_max, speed, format, size, flags);
switch (variant) {
case ImGuiKnobVariant_Tick: {
knob.draw_circle(0.85f, detail::GetSecondaryColorSet(), true, 32);
knob.draw_tick(0.5f, 0.85f, 0.08f, knob.angle, detail::GetPrimaryColorSet());
break;
}
case ImGuiKnobVariant_Dot: {
knob.draw_circle(0.85f, detail::GetSecondaryColorSet(), true, 32);
knob.draw_dot(0.12f, 0.6f, knob.angle, detail::GetPrimaryColorSet(), true, 12);
break;
}
case ImGuiKnobVariant_Wiper: {
knob.draw_circle(0.7f, detail::GetSecondaryColorSet(), true, 32);
knob.draw_arc(0.8f, 0.41f, knob.angle_min, knob.angle_max, detail::GetTrackColorSet(), 16, 2);
if (knob.t > 0.01f) {
knob.draw_arc(0.8f, 0.43f, knob.angle_min, knob.angle, detail::GetPrimaryColorSet(), 16, 2);
}
break;
}
case ImGuiKnobVariant_WiperOnly: {
knob.draw_arc(0.8f, 0.41f, knob.angle_min, knob.angle_max, detail::GetTrackColorSet(), 32, 2);
if (knob.t > 0.01) {
knob.draw_arc(0.8f, 0.43f, knob.angle_min, knob.angle, detail::GetPrimaryColorSet(), 16, 2);
}
break;
}
case ImGuiKnobVariant_WiperDot: {
knob.draw_circle(0.6f, detail::GetSecondaryColorSet(), true, 32);
knob.draw_arc(0.85f, 0.41f, knob.angle_min, knob.angle_max, detail::GetTrackColorSet(), 16, 2);
knob.draw_dot(0.1f, 0.85f, knob.angle, detail::GetPrimaryColorSet(), true, 12);
break;
}
case ImGuiKnobVariant_Stepped: {
for (auto n = 0.f; n < steps; n++) {
auto a = n / (steps - 1);
auto angle = knob.angle_min + (knob.angle_max - knob.angle_min) * a;
knob.draw_tick(0.7f, 0.9f, 0.04f, angle, detail::GetPrimaryColorSet());
}
knob.draw_circle(0.6f, detail::GetSecondaryColorSet(), true, 32);
knob.draw_dot(0.12f, 0.4f, knob.angle, detail::GetPrimaryColorSet(), true, 12);
break;
}
case ImGuiKnobVariant_Space: {
knob.draw_circle(0.3f - knob.t * 0.1f, detail::GetSecondaryColorSet(), true, 16);
if (knob.t > 0.01f) {
knob.draw_arc(0.4f, 0.15f, knob.angle_min - 1.0f, knob.angle - 1.0f, detail::GetPrimaryColorSet(), 16, 2);
knob.draw_arc(0.6f, 0.15f, knob.angle_min + 1.0f, knob.angle + 1.0f, detail::GetPrimaryColorSet(), 16, 2);
knob.draw_arc(0.8f, 0.15f, knob.angle_min + 3.0f, knob.angle + 3.0f, detail::GetPrimaryColorSet(), 16, 2);
}
break;
}
}
return knob.value_changed;
}
bool Knob(const char *label, float *p_value, float v_min, float v_max, float speed, const char *format, ImGuiKnobVariant variant, float size, ImGuiKnobFlags flags, int steps) {
const char *_format = format == NULL ? "%.3f" : format;
return BaseKnob(label, ImGuiDataType_Float, p_value, v_min, v_max, speed, _format, variant, size, flags, steps);
}
bool KnobInt(const char *label, int *p_value, int v_min, int v_max, float speed, const char *format, ImGuiKnobVariant variant, float size, ImGuiKnobFlags flags, int steps) {
const char *_format = format == NULL ? "%i" : format;
return BaseKnob(label, ImGuiDataType_S32, p_value, v_min, v_max, speed, _format, variant, size, flags, steps);
}
}// namespace ImGuiKnobs

View File

@ -0,0 +1,45 @@
#pragma once
#include <cstdlib>
#include <imgui.h>
typedef int ImGuiKnobFlags;
enum ImGuiKnobFlags_ {
ImGuiKnobFlags_NoTitle = 1 << 0,
ImGuiKnobFlags_NoInput = 1 << 1,
ImGuiKnobFlags_ValueTooltip = 1 << 2,
ImGuiKnobFlags_DragHorizontal = 1 << 3,
};
typedef int ImGuiKnobVariant;
enum ImGuiKnobVariant_ {
ImGuiKnobVariant_Tick = 1 << 0,
ImGuiKnobVariant_Dot = 1 << 1,
ImGuiKnobVariant_Wiper = 1 << 2,
ImGuiKnobVariant_WiperOnly = 1 << 3,
ImGuiKnobVariant_WiperDot = 1 << 4,
ImGuiKnobVariant_Stepped = 1 << 5,
ImGuiKnobVariant_Space = 1 << 6,
};
namespace ImGuiKnobs {
struct color_set {
ImColor base;
ImColor hovered;
ImColor active;
color_set(ImColor base, ImColor hovered, ImColor active) : base(base), hovered(hovered), active(active) {}
color_set(ImColor color) {
base = color;
hovered = color;
active = color;
}
};
bool Knob(const char *label, float *p_value, float v_min, float v_max, float speed = 0, const char *format = NULL, ImGuiKnobVariant variant = ImGuiKnobVariant_Tick, float size = 0, ImGuiKnobFlags flags = 0, int steps = 10);
bool KnobInt(const char *label, int *p_value, int v_min, int v_max, float speed = 0, const char *format = NULL, ImGuiKnobVariant variant = ImGuiKnobVariant_Tick, float size = 0, ImGuiKnobFlags flags = 0, int steps = 10);
}// namespace ImGuiKnobs

View File

@ -7,45 +7,11 @@
#include "audio/mixer/mixer_track.h"
#include "imgui_internal.h"
#include "audio/device/audio_device_manager.h"
#include "imgui-knobs.h"
bool show_instrument_track = true;
bool show_mixer = true;
void vertical_text(const char* text) {
if (!text)
return;
ImGuiWindow* window = ImGui::GetCurrentWindow();
ImGuiIO& io = ImGui::GetIO();
const ImFont* font = io.Fonts->Fonts[0];
float font_size = ImGui::GetFontSize();
ImVec2 cur_pos = window->DC.CursorPos;
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 text_size = ImGui::CalcTextSize(text, nullptr, true);
ImRect bb(cur_pos, cur_pos + text_size);
for (const char* p = text; *p; p++)
{
const ImFontGlyph* glyph = font->FindGlyph(*p);
if (!glyph)
continue;
draw_list->PrimReserve(6, 4);
draw_list->PrimRectUV(
cur_pos,
ImVec2(cur_pos.x + font_size, cur_pos.y + (glyph->Y1 - glyph->Y0) * io.FontGlobalScale),
ImVec2(glyph->U0, glyph->V0),
ImVec2(glyph->U1, glyph->V1),
ImGui::GetColorU32(ImGuiCol_Text)
);
cur_pos.y += (glyph->AdvanceX * io.FontGlobalScale);
}
ImGui::ItemSize(text_size, 0.0f);
ImGui::ItemAdd(bb, 0);
}
void draw_instrument_track() {
ImGui::Begin("Instrument", &show_instrument_track);
auto instruments = get_plugin_host_manager()->get_instrument_hosts();
@ -77,10 +43,6 @@ void draw_mixer() {
ImGui::End();
}
void update_mixer_track_peak(float delta_time, mixer_track* track) {
}
void draw_mixer_track(uint32_t delta_sample_count, mixer_track* track, int32_t index) {
float delta_time = ImGui::GetIO().DeltaTime;
ImGuiWindow* window = ImGui::GetCurrentWindow();
@ -138,6 +100,9 @@ void draw_mixer_track(uint32_t delta_sample_count, mixer_track* track, int32_t i
ImGui::SameLine();
ImGui::EndGroup();
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
get_mixer()->selected_track = track;
}
}
void draw_volume_bar(const char* id, float height, std::vector<float> sample_value, std::vector<float> sample_peak) {
@ -298,3 +263,72 @@ bool vertical_volume_slider(const char* id, ImVec2 size, float* volume, float mi
return true;
}
void draw_mixer_track_effect_list(mixer_track* track) {
ImGui::Begin("TrackEffects");
if (track) {
for (auto effect: track->effects) {
draw_mixer_track_effect(effect);
ImGui::SameLine();
}
if (ImGui::Button("Add")) {
auto effect = get_plugin_host_manager()->create_effect_plugin_host(R"(F:\VST\VST64\OTT_x64.dll)");
track->add_effect(effect);
}
} else {
ImGui::Text("No track selected");
}
ImGui::End();
}
void draw_selected_mixer_track_effect_list() {
mixer_track* track = get_mixer()->selected_track;
draw_mixer_track_effect_list(track);
}
void draw_mixer_track_effect(plugin_host* effect) {
auto effect_id = effect->get_imgui_id();
ImGui::BeginGroup();
if (ImGui::Button(effect->name.c_str())) {
effect->toggle_editor();
}
auto param_count = effect->get_param_count();
for (int i = 0; i < param_count; ++i) {
host_param param_info(effect, i);
const auto& param_name = param_info.get_name();
auto label = param_info.get_label();
if (label == "%") {
label += "%";
}
const auto& param_display = param_info.get_display() + label;
auto slider_id = param_name + "##" + std::to_string(i);
bool is_toggle = param_info.is_toggle();
if (is_toggle) {
bool param_value = param_info.get_value() > 0.5f;
if (ImGui::Checkbox(slider_id.c_str(), &param_value)) {
param_info.set_value(param_value);
}
} else {
float param_value = param_info.get_value();
if (ImGuiKnobs::Knob(param_name.c_str(), &param_value, param_info.min_value(), param_info.max_value(), 0,
param_display.c_str())) {
param_info.set_value(param_value);
}
}
// if (i % 2 == 0)
// ImGui::SameLine();
// else
// ImGui::NewLine();
}
ImGui::EndGroup();
// right click open menu
if (ImGui::BeginPopupContextItem(effect_id.c_str())) {
if (ImGui::MenuItem("Remove")) {
delete_effect(get_mixer()->selected_track, effect);
}
ImGui::EndPopup();
}
}

View File

@ -6,6 +6,11 @@
#include "imgui.h"
class mixer_track;
class plugin_host;
inline float volume_bar_peak_duration = 1.0f;
inline float volume_bar_peak_decay = 0.2f;
inline constexpr float volume_bar_inf = 0.0001f;
inline float mapping(float value, float in_min, float in_max, float out_min, float out_max) {
return (value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
@ -43,7 +48,6 @@ inline float percentage_to_db(float percentage, float min_db = -60.0f, float max
return db_value;
}
void vertical_text(const char* text);
bool vertical_volume_slider(const char* id, ImVec2 size, float* volume, float min_volume = 0.0f, float max_volume = 1.0f);
void draw_instrument_track();
@ -51,6 +55,6 @@ void draw_mixer();
void draw_mixer_track(uint32_t delta_sample_count, mixer_track* track, int32_t index);
void draw_volume_bar(const char* id, float height, std::vector<float> sample_value, std::vector<float> sample_peak);
inline float volume_bar_peak_duration = 1.0f;
inline float volume_bar_peak_decay = 0.2f;
inline constexpr float volume_bar_inf = 0.0001f;
void draw_mixer_track_effect_list(mixer_track* track);
void draw_selected_mixer_track_effect_list();
void draw_mixer_track_effect(plugin_host* effect);

@ -1 +1 @@
Subproject commit 1c76e93d291ace5df5ccf55ad6549bf28e8dc4fa
Subproject commit 1f05cae06721ef488a9d5b97c87b67aa0f64af7d