diff --git a/Arona/src/arona_application.cpp b/Arona/src/arona_application.cpp index 866a8ef..a657db9 100644 --- a/Arona/src/arona_application.cpp +++ b/Arona/src/arona_application.cpp @@ -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) { diff --git a/Arona/src/widget/imgui-knobs.cpp b/Arona/src/widget/imgui-knobs.cpp new file mode 100644 index 0000000..be7525b --- /dev/null +++ b/Arona/src/widget/imgui-knobs.cpp @@ -0,0 +1,303 @@ +#include "imgui-knobs.h" + +#include +#include +#include +#include + +#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 + 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 + knob 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 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 + 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 diff --git a/Arona/src/widget/imgui-knobs.h b/Arona/src/widget/imgui-knobs.h new file mode 100644 index 0000000..9587634 --- /dev/null +++ b/Arona/src/widget/imgui-knobs.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +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 diff --git a/Arona/src/widget/widgets.cpp b/Arona/src/widget/widgets.cpp index dcbf220..9630ddc 100644 --- a/Arona/src/widget/widgets.cpp +++ b/Arona/src/widget/widgets.cpp @@ -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 sample_value, std::vector 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(), ¶m_value)) { + param_info.set_value(param_value); + } + } else { + float param_value = param_info.get_value(); + if (ImGuiKnobs::Knob(param_name.c_str(), ¶m_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(); + } +} diff --git a/Arona/src/widget/widgets.h b/Arona/src/widget/widgets.h index 18bcfd4..5a35378 100644 --- a/Arona/src/widget/widgets.h +++ b/Arona/src/widget/widgets.h @@ -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 sample_value, std::vector 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); diff --git a/AronaCore b/AronaCore index 1c76e93..1f05cae 160000 --- a/AronaCore +++ b/AronaCore @@ -1 +1 @@ -Subproject commit 1c76e93d291ace5df5ccf55ad6549bf28e8dc4fa +Subproject commit 1f05cae06721ef488a9d5b97c87b67aa0f64af7d