diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp index f5cbc3861aa3..2d23693ea751 100644 --- a/editor/editor_audio_buses.cpp +++ b/editor/editor_audio_buses.cpp @@ -34,6 +34,7 @@ #include "core/os/keyboard.h" #include "editor_node.h" #include "filesystem_dock.h" +#include "scene/resources/font.h" #include "servers/audio_server.h" void EditorAudioBus::_update_visible_channels() { @@ -71,7 +72,6 @@ void EditorAudioBus::_notification(int p_what) { channel[i].vu_r->set_progress_texture(get_icon("BusVuFull", "EditorIcons")); channel[i].prev_active = true; } - scale->set_texture(get_icon("BusVuDb", "EditorIcons")); disabled_vu = get_icon("BusVuFrozen", "EditorIcons"); @@ -167,7 +167,6 @@ void EditorAudioBus::_notification(int p_what) { channel[i].vu_r->set_progress_texture(get_icon("BusVuFull", "EditorIcons")); channel[i].prev_active = true; } - scale->set_texture(get_icon("BusVuDb", "EditorIcons")); disabled_vu = get_icon("BusVuFrozen", "EditorIcons"); @@ -211,7 +210,8 @@ void EditorAudioBus::update_bus() { int index = get_index(); - slider->set_value(AudioServer::get_singleton()->get_bus_volume_db(index)); + float db_value = AudioServer::get_singleton()->get_bus_volume_db(index); + slider->set_value(_scaled_db_to_normalized_volume(db_value)); track_name->set_text(AudioServer::get_singleton()->get_bus_name(index)); if (is_master) track_name->set_editable(false); @@ -300,13 +300,15 @@ void EditorAudioBus::_name_changed(const String &p_new_name) { track_name->release_focus(); } -void EditorAudioBus::_volume_db_changed(float p_db) { +void EditorAudioBus::_volume_changed(float p_normalized) { if (updating_bus) return; updating_bus = true; + float p_db = this->_normalized_volume_to_scaled_db(p_normalized); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); ur->create_action(TTR("Change Audio Bus Volume"), UndoRedo::MERGE_ENDS); ur->add_do_method(AudioServer::get_singleton(), "set_bus_volume_db", get_index(), p_db); @@ -317,6 +319,69 @@ void EditorAudioBus::_volume_db_changed(float p_db) { updating_bus = false; } + +float EditorAudioBus::_normalized_volume_to_scaled_db(float normalized) { + /* There are three different formulas for the conversion from normalized + * values to relative decibal values. + * One formula is an exponential graph which intends to counteract + * the logorithmic nature of human hearing. This is an approximation + * of the behaviour of a 'logarithmic potentiometer' found on most + * musical instruments and also emulated in popular software. + * The other two equations are hand-tuned linear tapers that intend to + * try to ease the exponential equation in areas where it makes sense.*/ + + if (normalized > 0.6f) { + return 22.22f * normalized - 16.2f; + } else if (normalized < 0.05f) { + return 830.72 * normalized - 80.0f; + } else { + return 45.0f * Math::pow(normalized - 1.0, 3); + } +} + +float EditorAudioBus::_scaled_db_to_normalized_volume(float db) { + /* Inversion of equations found in _normalized_volume_to_scaled_db. + * IMPORTANT: If one function changes, the other much change to reflect it. */ + if (db > -2.88) { + return (db + 16.2f) / 22.22f; + } else if (db < -38.602f) { + return (db + 80.00f) / 830.72f; + } else { + if (db < 0.0) { + /* To acommodate for NaN on negative numbers for root, we will mirror the + * results of the postive db range in order to get the desired numerical + * value on the negative side. */ + float positive_x = Math::pow(Math::abs(db) / 45.0f, 1.0f / 3.0f) + 1.0f; + Vector2 translation = Vector2(1.0f, 0.0f) - Vector2(positive_x, Math::abs(db)); + Vector2 reflected_position = Vector2(1.0, 0.0f) + translation; + return reflected_position.x; + } else { + return Math::pow(db / 45.0f, 1.0f / 3.0f) + 1.0f; + } + } +} + +void EditorAudioBus::_show_value(float slider_value) { + String text = vformat("%10.1f dB", _normalized_volume_to_scaled_db(slider_value)); + + audio_value_preview_label->set_text(text); + Vector2 slider_size = slider->get_size(); + Vector2 slider_position = slider->get_global_position(); + float left_padding = 5.0f; + float vert_padding = 10.0f; + Vector2 box_position = Vector2(slider_size.x + left_padding, (slider_size.y - vert_padding) * (1.0f - slider_value) - vert_padding); + audio_value_preview_box->set_position(slider_position + box_position); + audio_value_preview_box->set_size(audio_value_preview_label->get_size()); + if (slider->has_focus() && !audio_value_preview_box->is_visible()) { + audio_value_preview_box->show(); + } + preview_timer->start(); +} + +void EditorAudioBus::_hide_value_preview() { + audio_value_preview_box->hide(); +} + void EditorAudioBus::_solo_toggled() { updating_bus = true; @@ -653,7 +718,9 @@ void EditorAudioBus::_bind_methods() { ClassDB::bind_method("update_bus", &EditorAudioBus::update_bus); ClassDB::bind_method("update_send", &EditorAudioBus::update_send); ClassDB::bind_method("_name_changed", &EditorAudioBus::_name_changed); - ClassDB::bind_method("_volume_db_changed", &EditorAudioBus::_volume_db_changed); + ClassDB::bind_method("_volume_changed", &EditorAudioBus::_volume_changed); + ClassDB::bind_method("_show_value", &EditorAudioBus::_show_value); + ClassDB::bind_method("_hide_value_preview", &EditorAudioBus::_hide_value_preview); ClassDB::bind_method("_solo_toggled", &EditorAudioBus::_solo_toggled); ClassDB::bind_method("_mute_toggled", &EditorAudioBus::_mute_toggled); ClassDB::bind_method("_bypass_toggled", &EditorAudioBus::_bypass_toggled); @@ -738,11 +805,42 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) { HBoxContainer *hb = memnew(HBoxContainer); vb->add_child(hb); slider = memnew(VSlider); - slider->set_min(-80); - slider->set_max(24); - slider->set_step(0.1); + slider->set_min(0.0); + slider->set_max(1.0); + slider->set_step(0.0001); + slider->set_clip_contents(false); - slider->connect("value_changed", this, "_volume_db_changed"); + audio_value_preview_box = memnew(Panel); + { + HBoxContainer *audioprev_hbc = memnew(HBoxContainer); + audioprev_hbc->set_v_size_flags(SIZE_EXPAND_FILL); + audioprev_hbc->set_h_size_flags(SIZE_EXPAND_FILL); + audioprev_hbc->set_mouse_filter(MOUSE_FILTER_PASS); + audio_value_preview_box->add_child(audioprev_hbc); + + audio_value_preview_label = memnew(Label); + audio_value_preview_label->set_v_size_flags(SIZE_EXPAND_FILL); + audio_value_preview_label->set_h_size_flags(SIZE_EXPAND_FILL); + audio_value_preview_label->set_mouse_filter(MOUSE_FILTER_PASS); + + audioprev_hbc->add_child(audio_value_preview_label); + } + slider->add_child(audio_value_preview_box); + audio_value_preview_box->set_as_toplevel(true); + Ref panel_style = memnew(StyleBoxFlat); + panel_style->set_bg_color(Color(0.0f, 0.0f, 0.0f, 0.8f)); + audio_value_preview_box->add_style_override("panel", panel_style); + audio_value_preview_box->set_mouse_filter(MOUSE_FILTER_PASS); + audio_value_preview_box->hide(); + + preview_timer = memnew(Timer); + preview_timer->set_wait_time(0.8f); + preview_timer->set_one_shot(true); + add_child(preview_timer); + + slider->connect("value_changed", this, "_volume_changed"); + slider->connect("value_changed", this, "_show_value"); + preview_timer->connect("timeout", this, "_hide_value_preview"); hb->add_child(slider); cc = 0; @@ -765,7 +863,12 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) { channel[i].peak_r = 0.0f; } - scale = memnew(TextureRect); + scale = memnew(EditorAudioMeterNotches); + + for (float db = 6.0f; db >= -80.0f; db -= 6.0f) { + bool renderNotch = (db >= -6.0f || db == -24.0f || db == -72.0f); + scale->add_notch(_scaled_db_to_normalized_volume(db), db, renderNotch); + } hb->add_child(scale); effects = memnew(Tree); @@ -1226,6 +1329,7 @@ EditorAudioBuses::EditorAudioBuses() { set_process(true); } + void EditorAudioBuses::open_layout(const String &p_path) { EditorNode::get_singleton()->make_bottom_panel_item_visible(this); @@ -1270,3 +1374,50 @@ AudioBusesEditorPlugin::AudioBusesEditorPlugin(EditorAudioBuses *p_node) { AudioBusesEditorPlugin::~AudioBusesEditorPlugin() { } + +void EditorAudioMeterNotches::add_notch(float normalized_offset, float db_value, bool render_value) { + notches.push_back(AudioNotch(normalized_offset, db_value, render_value)); +} + +void EditorAudioMeterNotches::_bind_methods() { + ClassDB::bind_method("add_notch", &EditorAudioMeterNotches::add_notch); + ClassDB::bind_method("_draw_audio_notches", &EditorAudioMeterNotches::_draw_audio_notches); +} + +void EditorAudioMeterNotches::_notification(int p_what) { + if (p_what == NOTIFICATION_DRAW) { + notch_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1.0f, 1.0f, 1.0f, 0.8f) : Color(0.0f, 0.0f, 0.0f, 0.8f); + _draw_audio_notches(); + } +} + +void EditorAudioMeterNotches::_draw_audio_notches() { + Ref font = get_font("source", "EditorFonts"); + float font_height = font->get_height(); + + for (uint8_t i = 0; i < notches.size(); i++) { + AudioNotch n = notches[i]; + draw_line(Vector2(0.0f, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding), + Vector2(line_length, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding), + notch_color, + 1.0f); + + if (n.render_db_value) { + draw_string(font, + Vector2(line_length + label_space, + (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + (font_height / 4) + top_padding), + String("{0}dB").format(varray(Math::abs(n.db_value))), + notch_color); + } + } +} + +EditorAudioMeterNotches::EditorAudioMeterNotches() : + line_length(5.0f), + label_space(2.0f), + btm_padding(9.0f), + top_padding(5.0f) { + this->set_v_size_flags(SIZE_EXPAND_FILL); + this->set_h_size_flags(SIZE_EXPAND_FILL); + notch_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1.0f, 1.0f, 1.0f, 0.8f) : Color(0.0f, 0.0f, 0.0f, 0.8f); +} diff --git a/editor/editor_audio_buses.h b/editor/editor_audio_buses.h index 37ff9b28dc20..50f2101fd81b 100644 --- a/editor/editor_audio_buses.h +++ b/editor/editor_audio_buses.h @@ -35,6 +35,7 @@ #include "editor_plugin.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" +#include "scene/gui/control.h" #include "scene/gui/line_edit.h" #include "scene/gui/menu_button.h" #include "scene/gui/option_button.h" @@ -71,13 +72,17 @@ class EditorAudioBus : public PanelContainer { TextureProgress *vu_r; } channel[CHANNELS_MAX]; - TextureRect *scale; + class EditorAudioMeterNotches *scale; OptionButton *send; PopupMenu *effect_options; PopupMenu *bus_popup; PopupMenu *delete_effect_popup; + Panel *audio_value_preview_box; + Label *audio_value_preview_label; + Timer *preview_timer; + Button *solo; Button *mute; Button *bypass; @@ -93,7 +98,11 @@ class EditorAudioBus : public PanelContainer { void _name_changed(const String &p_new_name); void _name_focus_exit() { _name_changed(track_name->get_text()); } - void _volume_db_changed(float p_db); + void _volume_changed(float p_normalized); + float _normalized_volume_to_scaled_db(float normalized); + float _scaled_db_to_normalized_volume(float db); + void _show_value(float slider_value); + void _hide_value_preview(); void _solo_toggled(); void _mute_toggled(); void _bypass_toggled(); @@ -200,6 +209,50 @@ public: EditorAudioBuses(); }; +class EditorAudioMeterNotches : public Control { + GDCLASS(EditorAudioMeterNotches, Control); + +private: + struct AudioNotch { + float relative_position; + float db_value; + bool render_db_value; + + _FORCE_INLINE_ AudioNotch(float r_pos, float db_v, bool rndr_val) { + relative_position = r_pos; + db_value = db_v; + render_db_value = rndr_val; + } + + _FORCE_INLINE_ AudioNotch(const AudioNotch &n) { + relative_position = n.relative_position; + db_value = n.db_value; + render_db_value = n.render_db_value; + } + + _FORCE_INLINE_ AudioNotch() {} + }; + + List notches; + +public: + float line_length; + float label_space; + float btm_padding; + float top_padding; + Color notch_color; + + void add_notch(float normalized_offset, float db_value, bool render_value = false); + +private: + static void _bind_methods(); + void _notification(int p_what); + void _draw_audio_notches(); + +public: + EditorAudioMeterNotches(); +}; + class AudioBusesEditorPlugin : public EditorPlugin { GDCLASS(AudioBusesEditorPlugin, EditorPlugin);