From c065f0398f95de9bfa3a49cce4d19c2fff6ccdbd Mon Sep 17 00:00:00 2001 From: Ilari Liusvaara Date: Sat, 26 Jan 2013 13:20:39 +0200 Subject: [PATCH] Wxwidgets: VU meters & volume adjustment window --- include/core/audioapi.hpp | 36 ++++ include/core/dispatch.hpp | 10 + include/platform/wxwidgets/platform.hpp | 1 + src/core/audioapi.cpp | 80 +++++++- src/core/dispatch.cpp | 16 ++ src/platform/wxwidgets/mainwindow.cpp | 31 +-- src/platform/wxwidgets/vumeter.cpp | 321 ++++++++++++++++++++++++++++++++ 7 files changed, 467 insertions(+), 28 deletions(-) create mode 100644 src/platform/wxwidgets/vumeter.cpp diff --git a/include/core/audioapi.hpp b/include/core/audioapi.hpp index 8882cd34..3a871036 100644 --- a/include/core/audioapi.hpp +++ b/include/core/audioapi.hpp @@ -36,6 +36,42 @@ struct audioapi_buffer double rate; }; +/** + * Audio API VU calculator. + */ +struct audioapi_vumeter +{ +/** + * Initialize. + */ + audioapi_vumeter(); +/** + * Submit samples. + * + * Parameter samples: The samples to submit. If NULL, reads all samples as 0. + * Parameter count: Number of samples. + * Parameter stereo: If true, read only every other sample (but still read count samples). + * Parameter rate: Sound sampling rate. + * Parameter scale: Value to scale the samples by. + */ + void operator()(float* samples, size_t count, bool stereo, double rate, double scale); +/** + * Get VU value in dB. + */ + operator float() const throw() { return vu; } +private: + double accumulator; + size_t samples; + float vu; + void update_vu(); +}; + +//VU values. +extern audioapi_vumeter audioapi_vu_mleft; +extern audioapi_vumeter audioapi_vu_mright; +extern audioapi_vumeter audioapi_vu_vout; +extern audioapi_vumeter audioapi_vu_vin; + //Resampler. class audioapi_resampler { diff --git a/include/core/dispatch.hpp b/include/core/dispatch.hpp index 7b2014fa..4f005fc6 100644 --- a/include/core/dispatch.hpp +++ b/include/core/dispatch.hpp @@ -433,6 +433,16 @@ public: * Call on_subtitle_change on all objects. */ static void do_subtitle_change() throw(); +/** + * Notify about changes to VU levels. + * + * Default implementation does nothing. + */ + virtual void on_vu_change(); +/** + * Call on_vu_change on all objects. + */ + static void do_vu_change() throw(); protected: /** diff --git a/include/platform/wxwidgets/platform.hpp b/include/platform/wxwidgets/platform.hpp index 3ec3c488..1641c19a 100644 --- a/include/platform/wxwidgets/platform.hpp +++ b/include/platform/wxwidgets/platform.hpp @@ -46,6 +46,7 @@ std::string wxeditor_keyselect(wxWindow* parent, bool clearable); void wxsetingsdialog_display(wxWindow* parent, bool hotkeys_only); void show_wxeditor_voicesub(wxWindow* parent); void open_rom_select_window(); +void open_vumeter_window(wxWindow* parent); //Auxillary windows. void wxwindow_memorysearch_display(); diff --git a/src/core/audioapi.cpp b/src/core/audioapi.cpp index 593cc206..13eababa 100644 --- a/src/core/audioapi.cpp +++ b/src/core/audioapi.cpp @@ -1,7 +1,9 @@ #include "core/audioapi.hpp" +#include "core/dispatch.hpp" #include "core/framerate.hpp" #include "library/minmax.hpp" #include +#include #include #include #include @@ -305,6 +307,7 @@ void audioapi_get_voice(float* samples, size_t count) void audioapi_put_voice(float* samples, size_t count) { unsigned ptr = voicer_put; + audioapi_vu_vin(samples, count, false, voice_rate, voicer_volume); for(size_t i = 0; i < count; i++) { voicer_buffer[ptr++] = samples ? voicer_volume * samples[i] : 0.0; if(ptr == voicer_bufsize) @@ -359,7 +362,7 @@ void audioapi_voicep_volume(float volume) float audioapi_voicep_volume() { - return voicep_volume; + return voicep_volume / 32767; } void audioapi_voicer_volume(float volume) @@ -369,7 +372,7 @@ void audioapi_voicer_volume(float volume) float audioapi_voicer_volume() { - return voicer_volume; + return voicer_volume * 32768; } void audioapi_get_mixed(int16_t* samples, size_t count, bool stereo) @@ -399,6 +402,11 @@ void audioapi_get_mixed(int16_t* samples, size_t count, bool stereo) outdata_used -= outdata; audioapi_get_music(indata_used); audioapi_get_voice(intbuf, outdata_used); + + audioapi_vu_mleft(intbuf2, outdata_used, true, voice_rate, 1 / 32768.0); + audioapi_vu_mright(intbuf2 + 1, outdata_used, true, voice_rate, 1 / 32768.0); + audioapi_vu_vout(intbuf, outdata_used, false, voice_rate, 1 / 32768.0); + for(size_t i = 0; i < outdata_used * (stereo ? 2 : 1); i++) intbuf2[i] = max(min(intbuf2[i] + intbuf[i / 2], 32766.0f), -32767.0f); if(stereo) @@ -423,6 +431,11 @@ void audioapi_get_mixed(int16_t* samples, size_t count, bool stereo) outdata_used -= outdata; audioapi_get_music(indata_used); audioapi_get_voice(intbuf, outdata_used); + + audioapi_vu_mleft(intbuf2, outdata_used, false, voice_rate, 1 / 32768.0); + audioapi_vu_mright(intbuf2, outdata_used, false, voice_rate, 1 / 32768.0); + audioapi_vu_vout(intbuf, outdata_used, false, voice_rate, 1 / 32768.0); + for(size_t i = 0; i < outdata_used; i++) intbuf2[i] = max(min(intbuf2[i] + intbuf[i], 32766.0f), -32767.0f); if(stereo) @@ -438,3 +451,66 @@ void audioapi_get_mixed(int16_t* samples, size_t count, bool stereo) count -= outdata_used; } } + +audioapi_vumeter::audioapi_vumeter() +{ + accumulator = 0; + samples = 0; + vu = -999.0; +} + +void audioapi_vumeter::operator()(float* asamples, size_t count, bool stereo, double rate, double scale) +{ + size_t limit = rate / 25; + //If we already at or exceed limit, cut immediately. + if(samples >= limit) + update_vu(); + if(asamples) { + double sscale = scale * scale; + size_t j = 0; + if(stereo) + for(size_t i = 0; i < count; i++) { + accumulator += sscale * asamples[j] * asamples[j]; + j += 2; + samples++; + if(samples >= limit) + update_vu(); + } + else + for(size_t i = 0; i < count; i++) { + accumulator += sscale * asamples[i] * asamples[i]; + samples++; + if(samples >= limit) + update_vu(); + } + } else + for(size_t i = 0; i < count; i++) { + samples++; + if(samples >= limit) + update_vu(); + } +} + +void audioapi_vumeter::update_vu() +{ + if(!samples) { + vu = -999.0; + accumulator = 0; + } else { + double a = accumulator; + if(a < 1e-120) + a = 1e-120; //Don't take log of zero. + vu = 10 / log(10) * (log(a) - log(samples)); + if(vu < -999.0) + vu = -999.0; + accumulator = 0; + samples = 0; + } + information_dispatch::do_vu_change(); +} + +//VU values. +audioapi_vumeter audioapi_vu_mleft; +audioapi_vumeter audioapi_vu_mright; +audioapi_vumeter audioapi_vu_vout; +audioapi_vumeter audioapi_vu_vin; diff --git a/src/core/dispatch.cpp b/src/core/dispatch.cpp index cc6aef73..4321cda7 100644 --- a/src/core/dispatch.cpp +++ b/src/core/dispatch.cpp @@ -570,3 +570,19 @@ void information_dispatch::do_subtitle_change() throw() END_EH_BLOCK(i, "on_subtitle_change"); } } + +void information_dispatch::on_vu_change() +{ + //Do nothing. +} + +void information_dispatch::do_vu_change() throw() +{ + if(in_global_ctors()) + return; + for(auto& i : dispatch()) { + START_EH_BLOCK + i->on_vu_change(); + END_EH_BLOCK(i, "on_vu_change"); + } +} diff --git a/src/platform/wxwidgets/mainwindow.cpp b/src/platform/wxwidgets/mainwindow.cpp index c187770b..28fbfc4f 100644 --- a/src/platform/wxwidgets/mainwindow.cpp +++ b/src/platform/wxwidgets/mainwindow.cpp @@ -49,7 +49,6 @@ enum wxID_NEXTPOLL, wxID_ERESET, wxID_AUDIO_ENABLED, - wxID_SHOW_AUDIO_STATUS, wxID_AUDIODEV_FIRST, wxID_AUDIODEV_LAST = wxID_AUDIODEV_FIRST + 255, wxID_SAVE_STATE, @@ -81,9 +80,6 @@ enum wxID_CANCEL_SAVES, wxID_SHOW_STATUS, wxID_SET_SPEED, - wxID_SET_VOLUME, - wxID_SET_VOLUME_VOICE, - wxID_SET_VOLUME_RECORD, wxID_SPEED_5, wxID_SPEED_10, wxID_SPEED_17, @@ -110,6 +106,7 @@ enum wxID_RMOVIE_LAST = wxID_RMOVIE_FIRST + 16, wxID_RROM_FIRST, wxID_RROM_LAST = wxID_RROM_FIRST + 16, + wxID_VUDISPLAY }; @@ -937,11 +934,8 @@ wxwin_mainwindow::wxwin_mainwindow() if(audioapi_driver_initialized()) { menu_separator(); menu_entry_check(wxID_AUDIO_ENABLED, wxT("Sounds enabled")); + menu_entry(wxID_VUDISPLAY, wxT("VU display / volume controls")); menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled()); - menu_entry(wxID_SET_VOLUME, wxT("Set Sound volume")); - menu_entry(wxID_SET_VOLUME_VOICE, wxT("Set Voice volume")); - menu_entry(wxID_SET_VOLUME_RECORD, wxT("Set Record volume")); - menu_entry(wxID_SHOW_AUDIO_STATUS, wxT("Show audio status")); menu_special_sub(wxT("Select sound device"), reinterpret_cast(sounddev = new sound_select_menu(this))); blistener->set_sound_select(reinterpret_cast(sounddev)); @@ -1044,9 +1038,6 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e) case wxID_AUDIO_ENABLED: platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED)); return; - case wxID_SHOW_AUDIO_STATUS: - platform::queue("show-sound-status"); - return; case wxID_CANCEL_SAVES: platform::queue("cancel-saves"); return; @@ -1226,21 +1217,6 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e) } return; } - case wxID_SET_VOLUME: - parsed = pick_volume(this, "Set volume", last_volume); - if(parsed >= -1e-10) - runemufn([parsed]() { audioapi_music_volume(parsed); }); - return; - case wxID_SET_VOLUME_RECORD: - parsed = pick_volume(this, "Set recording volume", last_volume_record); - if(parsed >= -1e-10) - audioapi_voicer_volume(parsed); - return; - case wxID_SET_VOLUME_VOICE: - parsed = pick_volume(this, "Set voice volume", last_volume_voice); - if(parsed >= -1e-10) - audioapi_voicep_volume(parsed); - return; case wxID_SPEED_5: set_speed(5); break; @@ -1310,5 +1286,8 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e) case wxID_SHOW_MESSAGES: msg_window->reshow(); return; + case wxID_VUDISPLAY: + open_vumeter_window(this); + return; }; } diff --git a/src/platform/wxwidgets/vumeter.cpp b/src/platform/wxwidgets/vumeter.cpp new file mode 100644 index 00000000..d556b2af --- /dev/null +++ b/src/platform/wxwidgets/vumeter.cpp @@ -0,0 +1,321 @@ +#include "core/audioapi.hpp" +#include "core/dispatch.hpp" + +#include "platform/wxwidgets/platform.hpp" + +#include +#include +#include +#include + +namespace +{ + bool vumeter_open = false; + + unsigned vu_to_pixels(float vu) + { + if(vu < -100) + vu = -100; + if(vu > 20) + vu = 20; + unsigned _vu = 2 * (vu + 100); + if(_vu > 2000) + _vu = 0; //Overflow. + return _vu; + } + + int to_db(float value) + { + if(value < 1e-10) + return -100; + int v = 20 / log(10) * log(value); + if(v < -100) + v = 100; + if(v > 50) + v = 50; + return v; + } + + void connect_events(wxSlider* s, wxObjectEventFunction fun, wxEvtHandler* obj) + { + s->Connect(wxEVT_SCROLL_THUMBTRACK, fun, NULL, obj); + s->Connect(wxEVT_SCROLL_PAGEDOWN, fun, NULL, obj); + s->Connect(wxEVT_SCROLL_PAGEUP, fun, NULL, obj); + s->Connect(wxEVT_SCROLL_LINEDOWN, fun, NULL, obj); + s->Connect(wxEVT_SCROLL_LINEUP, fun, NULL, obj); + s->Connect(wxEVT_SCROLL_TOP, fun, NULL, obj); + s->Connect(wxEVT_SCROLL_BOTTOM, fun, NULL, obj); + } + + uint32_t game_text_buf[16] = { + 0x00000000, 0x00000000, 0x3c000000, 0x66000000, + 0xc2000000, 0xc078ec7c, 0xc00cfec6, 0xde7cd6fe, + 0xc6ccd6c0, 0xc6ccd6c0, 0x66ccd6c6, 0x3a76c67c, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 + }; + + uint32_t vout_text_buf[16] = { + 0x00000000, 0x00000000, 0xc6000010, 0xc6000030, + 0xc6000030, 0xc67cccfc, 0xc6c6cc30, 0xc6c6cc30, + 0xc6c6cc30, 0x6cc6cc30, 0x38c6cc36, 0x107c761c, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 + }; + + uint32_t vin_text_buf[16] = { + 0x00000000, 0x00000000, 0xc6180000, 0xc6180000, + 0xc6000000, 0xc638dc00, 0xc6186600, 0xc6186600, + 0xc6186600, 0xc6186600, 0x38186600, 0x103c6600, + 0x00000000, 0x00000000, 0x00000000, 0x00000000 + }; + + unsigned get_strip_color(unsigned i) + { + if(i < 80) + return 0; + if(i < 120) + return 51 * (i - 80) / 4; + if(i < 160) + return 510; + if(i < 200) + return 510 - 51 * (i - 160) / 4; + return 0; + } +} + +class wxwin_vumeter : public wxDialog +{ +public: + wxwin_vumeter(wxWindow* parent); + ~wxwin_vumeter() throw(); + bool ShouldPreventAppExit() const; + void on_close(wxCommandEvent& e); + void on_wclose(wxCloseEvent& e); + void refresh(); + void on_game_change(wxScrollEvent& e); + void on_vout_change(wxScrollEvent& e); + void on_vin_change(wxScrollEvent& e); +private: + struct _vupanel : public wxPanel + { + _vupanel(wxwin_vumeter* v) + : wxPanel(v, wxID_ANY, wxDefaultPosition, wxSize(320, 64)) + { + obj = v; + buffer.resize(61440); + bufferstride = 960; + for(unsigned i = 0; i < 320; i++) { + unsigned color = get_strip_color(i); + if(color < 256) { + colorstrip[3 * i + 0] = 255; + colorstrip[3 * i + 1] = color; + } else { + colorstrip[3 * i + 0] = 255 - (color - 255); + colorstrip[3 * i + 1] = 255; + } + colorstrip[3 * i + 2] = 0; + } + this->Connect(wxEVT_PAINT, wxPaintEventHandler(_vupanel::on_paint), NULL, this); + } + + void signal_repaint() + { + mleft = vu_to_pixels(audioapi_vu_mleft); + mright = vu_to_pixels(audioapi_vu_mright); + vout = vu_to_pixels(audioapi_vu_vout); + vin = vu_to_pixels(audioapi_vu_vin); + Refresh(); + obj->update_sent = false; + } + + void on_paint(wxPaintEvent& e) + { + wxPaintDC dc(this); + dc.Clear(); + memset(&buffer[0], 255, buffer.size()); + draw_text(0, 8, 32, 16, game_text_buf); + draw_text(0, 24, 32, 16, vout_text_buf); + draw_text(0, 40, 32, 16, vin_text_buf); + for(unsigned i = 32; i <= 272; i += 40) + draw_vline(i, 0, 63); + draw_vline(231, 0, 63); //0dB is thicker. + draw_meter(32, 8, 8, mleft); + draw_meter(32, 16, 8, mright); + draw_meter(32, 24, 16, vout); + draw_meter(32, 40, 16, vin); + wxBitmap bmp2(wxImage(320, 64, &buffer[0], true)); + dc.DrawBitmap(bmp2, 0, 0, false); + } + + wxwin_vumeter* obj; + volatile unsigned mleft; + volatile unsigned mright; + volatile unsigned vout; + volatile unsigned vin; + std::vector buffer; + unsigned char colorstrip[960]; + size_t bufferstride; + void draw_text(unsigned x, unsigned y, unsigned w, unsigned h, const uint32_t* buf) + { + unsigned spos = 0; + unsigned pos = y * bufferstride + 3 * x; + for(unsigned j = 0; j < h; j++) { + for(unsigned i = 0; i < w; i++) { + unsigned _spos = spos + i; + unsigned char val = ((buf[_spos >> 5] >> (31 - (_spos & 31))) & 1) ? 0 : 255; + buffer[pos + 3 * i + 0] = val; + buffer[pos + 3 * i + 1] = val; + buffer[pos + 3 * i + 2] = val; + } + pos += bufferstride; + spos += 32 * ((w + 31) / 32); + } + } + void draw_vline(unsigned x, unsigned y1, unsigned y2) + { + unsigned pos = y1 * bufferstride + 3 * x; + for(unsigned j = y1; j < y2; j++) { + buffer[pos + 0] = 0; + buffer[pos + 1] = 0; + buffer[pos + 2] = 0; + pos += bufferstride; + } + } + void draw_meter(unsigned x, unsigned y, unsigned h, unsigned val) + { + if(val > 320 - x) + val = 320 - x; + unsigned pos = y * bufferstride + 3 * x; + for(unsigned j = 0; j < h; j++) { + if(val) + memcpy(&buffer[pos], colorstrip, 3 * val); + pos += bufferstride; + } + + } + }; + struct refresh_listener : public information_dispatch + { + refresh_listener(wxwin_vumeter* v) + : information_dispatch("voicesub-editor-change-listner") + { + obj = v; + } + void on_vu_change() + { + wxwin_vumeter* _obj = obj; + if(!obj->update_sent) { + obj->update_sent = true; + runuifun([_obj]() -> void { _obj->refresh(); }); + } + } + wxwin_vumeter* obj; + }; + volatile bool update_sent; + bool closing; + wxButton* closebutton; + refresh_listener* rlistener; + _vupanel* vupanel; + wxSlider* gamevol; + wxSlider* voutvol; + wxSlider* vinvol; +}; + +wxwin_vumeter::wxwin_vumeter(wxWindow* parent) + : wxDialog(parent, wxID_ANY, wxT("lsnes: VU meter"), wxDefaultPosition, wxSize(-1, -1)) +{ + update_sent = false; + closing = false; + Centre(); + wxFlexGridSizer* top_s = new wxFlexGridSizer(3, 1, 0, 0); + SetSizer(top_s); + + top_s->Add(vupanel = new _vupanel(this)); + + wxFlexGridSizer* slier_s = new wxFlexGridSizer(3, 2, 0, 0); + slier_s->Add(new wxStaticText(this, wxID_ANY, wxT("Game:")), 0, wxGROW); + slier_s->Add(gamevol = new wxSlider(this, wxID_ANY, to_db(audioapi_music_volume()), -100, 50, + wxDefaultPosition, wxSize(320, -1)), 1, wxGROW); + slier_s->Add(new wxStaticText(this, wxID_ANY, wxT("Voice out:")), 1, wxGROW); + slier_s->Add(voutvol = new wxSlider(this, wxID_ANY, to_db(audioapi_voicep_volume()), -100, 50, + wxDefaultPosition, wxSize(320, -1)), 1, wxGROW); + slier_s->Add(new wxStaticText(this, wxID_ANY, wxT("Voice in:")), 1, wxGROW); + slier_s->Add(vinvol = new wxSlider(this, wxID_ANY, to_db(audioapi_voicer_volume()), -100, 50, + wxDefaultPosition, wxSize(320, -1)), 1, wxGROW); + top_s->Add(slier_s, 1, wxGROW); + + gamevol->SetLineSize(1); + vinvol->SetLineSize(1); + voutvol->SetLineSize(1); + gamevol->SetPageSize(10); + vinvol->SetPageSize(10); + voutvol->SetPageSize(10); + connect_events(gamevol, wxScrollEventHandler(wxwin_vumeter::on_game_change), this); + connect_events(voutvol, wxScrollEventHandler(wxwin_vumeter::on_vout_change), this); + connect_events(vinvol, wxScrollEventHandler(wxwin_vumeter::on_vin_change), this); + + wxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL); + pbutton_s->AddStretchSpacer(); + pbutton_s->Add(closebutton = new wxButton(this, wxID_OK, wxT("Close")), 0, wxGROW); + top_s->Add(pbutton_s, 1, wxGROW); + pbutton_s->SetSizeHints(this); + + closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(wxwin_vumeter::on_close), NULL, this); + Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_vumeter::on_wclose)); + + top_s->SetSizeHints(this); + Fit(); + rlistener = new refresh_listener(this); + refresh(); +} + +wxwin_vumeter::~wxwin_vumeter() throw() +{ + delete rlistener; +} + +void wxwin_vumeter::on_game_change(wxScrollEvent& e) +{ + audioapi_music_volume(pow(10, gamevol->GetValue() / 20.0)); +} + +void wxwin_vumeter::on_vout_change(wxScrollEvent& e) +{ + audioapi_voicep_volume(pow(10, voutvol->GetValue() / 20.0)); +} + +void wxwin_vumeter::on_vin_change(wxScrollEvent& e) +{ + audioapi_voicer_volume(pow(10, vinvol->GetValue() / 20.0)); +} + +void wxwin_vumeter::refresh() +{ + vupanel->signal_repaint(); +} + +void wxwin_vumeter::on_close(wxCommandEvent& e) +{ + Destroy(); + vumeter_open = false; +} + +void wxwin_vumeter::on_wclose(wxCloseEvent& e) +{ + bool wasc = closing; + closing = true; + if(!wasc) + Destroy(); + vumeter_open = false; +} + +bool wxwin_vumeter::ShouldPreventAppExit() const { return false; } + +void open_vumeter_window(wxWindow* parent) +{ + if(vumeter_open) + return; + wxwin_vumeter* v = new wxwin_vumeter(parent); + v->Show(); + vumeter_open = true; +} -- 2.11.4.GIT