335 lines
12 KiB
C++
335 lines
12 KiB
C++
#include "widgets.h"
|
||
|
||
#include "audio/plugin_host/plugin_host_manager.h"
|
||
#include "audio/plugin_host/plugin_host.h"
|
||
#include "arona_application.h"
|
||
#include "audio/mixer/mixer.h"
|
||
#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 draw_instrument_track() {
|
||
ImGui::Begin("Instrument", &show_instrument_track);
|
||
auto instruments = get_plugin_host_manager()->get_instrument_hosts();
|
||
for (int32_t i = 0; i < instruments.size(); ++i) {
|
||
plugin_host* host = instruments.at(i);
|
||
std::string button_label = host->name + "##" + std::to_string(i);
|
||
if (ImGui::Button(button_label.c_str())) {
|
||
host->toggle_editor();
|
||
}
|
||
}
|
||
if (ImGui::Button("Add")) {
|
||
get_arona_application()->test();
|
||
}
|
||
ImGui::End();
|
||
}
|
||
|
||
void draw_mixer() {
|
||
ImGui::Begin("Mixer", &show_mixer);
|
||
auto mixer = get_mixer();
|
||
auto mixer_tracks = mixer->get_tracks();
|
||
float delta_time = ImGui::GetIO().DeltaTime;
|
||
int32_t delta_sample_count = get_audio_device_manager()->get_sample_rate() * delta_time;
|
||
|
||
for (int32_t i = 0; i < mixer_tracks.size(); ++i) {
|
||
mixer_track* track = mixer_tracks.at(i);
|
||
draw_mixer_track(delta_sample_count, track, i);
|
||
ImGui::SameLine();
|
||
}
|
||
ImGui::End();
|
||
}
|
||
|
||
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();
|
||
|
||
std::string track_label = track->get_name();
|
||
std::string child_id = "MixerTrack" + std::to_string(index);
|
||
std::string slider_id = "MixerTrackVolume##" + std::to_string(index);
|
||
std::string volume_bar_id = "VolumeBar" + std::to_string(index);
|
||
|
||
ImGui::BeginGroup();
|
||
window->DC.CurrLineTextBaseOffset = 0;
|
||
ImGui::Text(track_label.c_str());
|
||
|
||
float widget_height = ImGui::GetContentRegionAvail().y;
|
||
const ImVec2 slider_size = ImGui::CalcItemSize(ImVec2(25, widget_height), 25, widget_height);
|
||
float volume = track->get_volume();
|
||
if (vertical_volume_slider(slider_id.c_str(), slider_size, &volume, 0.f, 1.25f)) {
|
||
track->set_volume(volume);
|
||
}
|
||
ImGui::SameLine();
|
||
|
||
std::vector<sample_t> temp_buffer;
|
||
temp_buffer.reserve(delta_sample_count);
|
||
std::vector<float> sample_value;
|
||
std::vector<float> sample_peak;
|
||
for (int32_t i = 0; i < track->ui_buffers->size(); ++i) {
|
||
auto& buffer = track->ui_buffers->at(i);
|
||
auto& peak_info = track->ui_buffer_peaks.at(i);
|
||
|
||
// calculate peak
|
||
uint32_t count = std::min(delta_sample_count, buffer.Num());
|
||
temp_buffer.resize(count);
|
||
buffer.Pop(temp_buffer.data(), count);
|
||
auto peak_it = std::max_element(temp_buffer.begin(), temp_buffer.end(), [](sample_t a, sample_t b) {
|
||
return std::abs(a) < std::abs(b);
|
||
});
|
||
sample_t peak = std::abs(*peak_it);
|
||
|
||
// update peak
|
||
if (peak > peak_info.peak) {
|
||
peak_info.peak = peak;
|
||
peak_info.left_time = volume_bar_peak_duration;
|
||
} else {
|
||
peak_info.left_time -= delta_time;
|
||
if (peak_info.left_time < 0.0f) {
|
||
peak_info.peak -= delta_time * volume_bar_peak_decay;
|
||
peak_info.left_time = 0.0f;
|
||
}
|
||
}
|
||
|
||
sample_value.push_back(peak);
|
||
sample_peak.push_back(peak_info.peak);
|
||
}
|
||
draw_volume_bar(volume_bar_id.c_str(), widget_height, sample_value, sample_peak);
|
||
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) {
|
||
#if BUILD_DEBUG
|
||
if (sample_value.size() != sample_peak.size()) {
|
||
spdlog::error("sample_value size is not equal to sample_peak size");
|
||
return;
|
||
}
|
||
#endif
|
||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||
|
||
const ImGuiStyle& style = ImGui::GetStyle();
|
||
int32_t channel_count = sample_value.size();
|
||
float channel_width = 9.0f;
|
||
float widget_width = channel_width * channel_count;
|
||
float widget_height = height; // TODO: calculate the height
|
||
ImVec2 size_arg = ImVec2(widget_width, widget_height);
|
||
|
||
ImVec2 pos = window->DC.CursorPos;
|
||
ImVec2 size = ImGui::CalcItemSize(size_arg, widget_width, widget_height);
|
||
ImRect bb(pos, pos + size);
|
||
ImGui::ItemSize(bb);
|
||
if (!ImGui::ItemAdd(bb, ImGui::GetID(id))) {
|
||
return;
|
||
}
|
||
|
||
// 绘制背景
|
||
ImGui::RenderFrame(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
|
||
|
||
constexpr float safe_level = 0.6f; // 60% 以下绘制绿色
|
||
constexpr float warning_level = 0.8f; // 80% 以下绘制黄色
|
||
constexpr float danger_level = 1.0f; // 100% 以下绘制红色
|
||
const ImColor safe_color = ImColor(0, 255, 0, 255); // green
|
||
const ImColor warning_color = ImColor(255, 255, 0, 255); // yellow
|
||
const ImColor danger_color = ImColor(255, 0, 0, 255); // red
|
||
const ImColor bar_split_color = ImColor(0, 0, 0, 255); // white
|
||
const ImColor boarder_color = ImColor(0, 140, 120, 255); // black
|
||
|
||
window->DrawList->PushClipRect(bb.Min, bb.Max, true);
|
||
ImVec2 temp_pos = pos;
|
||
// 坐标系原点在左上角
|
||
for (int32_t i = 0; i < channel_count; ++i) {
|
||
float value_db = gain_to_db(sample_value.at(i));
|
||
float value = db_to_percentage_log(value_db, -75.0f, 6.0f);
|
||
|
||
float peak_db = gain_to_db(sample_peak.at(i));
|
||
float peak = db_to_percentage_log(peak_db, -75.0f, 6.0f);
|
||
|
||
// 绘制矩形(60%以下的部分绘制绿色,80%以下的部分绘制黄色,100%以下的部分绘制红色)
|
||
if (value > volume_bar_inf) {
|
||
ImVec2 clip_bar_start = ImVec2(temp_pos.x, temp_pos.y + widget_height * (1.0f - value));
|
||
ImVec2 clip_bar_end = ImVec2(temp_pos.x + channel_width, temp_pos.y + widget_height);
|
||
ImRect bar_rect(clip_bar_start, clip_bar_end);
|
||
window->DrawList->PushClipRect(bar_rect.Min, bar_rect.Max, true);
|
||
// 绘制100%的部分
|
||
{
|
||
float bar_height = widget_height * danger_level;
|
||
ImVec2 bar_start = ImVec2(temp_pos.x, temp_pos.y + widget_height - bar_height);
|
||
ImVec2 bar_end = ImVec2(temp_pos.x + channel_width, temp_pos.y + widget_height);
|
||
window->DrawList->AddRectFilled(bar_start, bar_end, danger_color);
|
||
}
|
||
// 绘制80%的部分
|
||
{
|
||
float bar_height = widget_height * warning_level;
|
||
ImVec2 bar_start = ImVec2(temp_pos.x, temp_pos.y + widget_height - bar_height);
|
||
ImVec2 bar_end = ImVec2(temp_pos.x + channel_width, temp_pos.y + widget_height);
|
||
window->DrawList->AddRectFilled(bar_start, bar_end, warning_color);
|
||
}
|
||
// 绘制60%以下的部分
|
||
{
|
||
float bar_height = widget_height * safe_level;
|
||
ImVec2 bar_start = ImVec2(temp_pos.x, temp_pos.y + widget_height - bar_height);
|
||
ImVec2 bar_end = ImVec2(temp_pos.x + channel_width, temp_pos.y + widget_height);
|
||
window->DrawList->AddRectFilled(bar_start, bar_end, safe_color);
|
||
}
|
||
window->DrawList->PopClipRect();
|
||
}
|
||
|
||
// 绘制Peak
|
||
if (peak > volume_bar_inf) {
|
||
const ImColor peak_color = peak <= safe_level ? safe_color : peak <= warning_level ? warning_color : danger_color;
|
||
float peak_height = temp_pos.y + widget_height * (1 - peak);
|
||
ImVec2 peak_start = ImVec2(temp_pos.x, peak_height);
|
||
ImVec2 peak_end = ImVec2(temp_pos.x + channel_width, peak_height + 1);
|
||
window->DrawList->AddRectFilled(peak_start, peak_end, peak_color);
|
||
}
|
||
|
||
temp_pos.x += channel_width;
|
||
}
|
||
temp_pos = pos;
|
||
|
||
// 绘制垂直分割线
|
||
for (int32_t i = 0; i < channel_count; ++i) {
|
||
if (i != channel_count - 1) {
|
||
ImVec2 line_start = ImVec2(temp_pos.x + channel_width - 0.5f, temp_pos.y);
|
||
ImVec2 line_end = ImVec2(temp_pos.x + channel_width - 0.5f, temp_pos.y + widget_height);
|
||
window->DrawList->AddLine(line_start, line_end, bar_split_color);
|
||
}
|
||
}
|
||
|
||
// 绘制边框
|
||
window->DrawList->AddRect(bb.Min, bb.Max, boarder_color);
|
||
|
||
window->DrawList->PopClipRect();
|
||
}
|
||
|
||
bool vertical_volume_slider(const char* id, ImVec2 size, float* volume, float min_volume, float max_volume) {
|
||
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
||
ImGuiIO &io = ImGui::GetIO();
|
||
|
||
ImVec2 cur_pos = window->DC.CursorPos;
|
||
ImRect bb(cur_pos, cur_pos + size);
|
||
ImGui::ItemSize(size, 0.0f);
|
||
if (!ImGui::ItemAdd(bb, 0))
|
||
return false;
|
||
|
||
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||
ImGui::RenderFrame(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_FrameBg), true, 0.0f);
|
||
|
||
float percent = mapping(*volume, min_volume, max_volume, 0.f, 1.0f);
|
||
ImVec2 pos = ImVec2(bb.Min.x, bb.Min.y + size.y * (1 - percent));
|
||
draw_list->AddRectFilled(pos, bb.Max, ImGui::GetColorU32(ImGuiCol_SliderGrabActive), 0.0f);
|
||
|
||
bool on_hovered = ImGui::IsItemHovered();
|
||
|
||
if (on_hovered) {
|
||
ImGui::BeginTooltip();
|
||
ImGui::Text("Volume: %.3fdb", 20.0f * log10f(*volume));
|
||
ImGui::EndTooltip();
|
||
}
|
||
|
||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||
*volume = 1.0f;
|
||
return true;
|
||
}
|
||
|
||
static float drag_start_value = 0.0f;
|
||
static ImGuiID dragging = 0;
|
||
|
||
const bool clicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||
const bool released = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||
ImGuiID current_id = ImGui::GetID(id);
|
||
|
||
if (released) {
|
||
dragging = 0;
|
||
} else if (!dragging && on_hovered && clicked) {
|
||
drag_start_value = *volume;
|
||
dragging = current_id;
|
||
}
|
||
|
||
if (dragging != current_id) {
|
||
return false;
|
||
}
|
||
|
||
float mouse_drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left).y;
|
||
float delta = (mouse_drag_delta / size.y) * (max_volume - min_volume);
|
||
*volume = std::clamp(drag_start_value - delta, min_volume, max_volume);
|
||
|
||
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();
|
||
}
|
||
}
|