Refactor emulator status reporting (and fix the statusbar doesn't update bug)
[lsnes.git] / src / platform / wxwidgets / vumeter.cpp
blob4b89a013f53dd021511ed78cd74bddbb48bd0452
1 #include "core/audioapi.hpp"
2 #include "core/dispatch.hpp"
3 #include "core/window.hpp"
5 #include "library/string.hpp"
6 #include "platform/wxwidgets/platform.hpp"
8 #include <wx/wx.h>
9 #include <wx/event.h>
10 #include <wx/control.h>
11 #include <wx/combobox.h>
13 namespace
15 bool vumeter_open = false;
17 unsigned vu_to_pixels(float vu)
19 if(vu < -100)
20 vu = -100;
21 if(vu > 20)
22 vu = 20;
23 unsigned _vu = 2 * (vu + 100);
24 if(_vu > 2000)
25 _vu = 0; //Overflow.
26 return _vu;
29 int to_db(float value)
31 if(value < 1e-10)
32 return -100;
33 int v = 20 / log(10) * log(value);
34 if(v < -100)
35 v = 100;
36 if(v > 50)
37 v = 50;
38 return v;
41 void connect_events(wxSlider* s, wxObjectEventFunction fun, wxEvtHandler* obj)
43 s->Connect(wxEVT_SCROLL_THUMBTRACK, fun, NULL, obj);
44 s->Connect(wxEVT_SCROLL_PAGEDOWN, fun, NULL, obj);
45 s->Connect(wxEVT_SCROLL_PAGEUP, fun, NULL, obj);
46 s->Connect(wxEVT_SCROLL_LINEDOWN, fun, NULL, obj);
47 s->Connect(wxEVT_SCROLL_LINEUP, fun, NULL, obj);
48 s->Connect(wxEVT_SCROLL_TOP, fun, NULL, obj);
49 s->Connect(wxEVT_SCROLL_BOTTOM, fun, NULL, obj);
52 uint32_t game_text_buf[16] = {
53 0x00000000, 0x00000000, 0x3c000000, 0x66000000,
54 0xc2000000, 0xc078ec7c, 0xc00cfec6, 0xde7cd6fe,
55 0xc6ccd6c0, 0xc6ccd6c0, 0x66ccd6c6, 0x3a76c67c,
56 0x00000000, 0x00000000, 0x00000000, 0x00000000
59 uint32_t vout_text_buf[16] = {
60 0x00000000, 0x00000000, 0xc6000010, 0xc6000030,
61 0xc6000030, 0xc67cccfc, 0xc6c6cc30, 0xc6c6cc30,
62 0xc6c6cc30, 0x6cc6cc30, 0x38c6cc36, 0x107c761c,
63 0x00000000, 0x00000000, 0x00000000, 0x00000000
66 uint32_t vin_text_buf[16] = {
67 0x00000000, 0x00000000, 0xc6180000, 0xc6180000,
68 0xc6000000, 0xc638dc00, 0xc6186600, 0xc6186600,
69 0xc6186600, 0xc6186600, 0x38186600, 0x103c6600,
70 0x00000000, 0x00000000, 0x00000000, 0x00000000
73 unsigned get_strip_color(unsigned i)
75 if(i < 80)
76 return 0;
77 if(i < 120)
78 return 51 * (i - 80) / 4;
79 if(i < 160)
80 return 510;
81 if(i < 200)
82 return 510 - 51 * (i - 160) / 4;
83 return 0;
87 class wxwin_vumeter : public wxDialog
89 public:
90 wxwin_vumeter(wxWindow* parent);
91 ~wxwin_vumeter() throw();
92 bool ShouldPreventAppExit() const;
93 void on_close(wxCommandEvent& e);
94 void on_wclose(wxCloseEvent& e);
95 void refresh();
96 void on_game_change(wxScrollEvent& e);
97 void on_vout_change(wxScrollEvent& e);
98 void on_vin_change(wxScrollEvent& e);
99 void on_game_reset(wxCommandEvent& e);
100 void on_vout_reset(wxCommandEvent& e);
101 void on_vin_reset(wxCommandEvent& e);
102 void on_devsel(wxCommandEvent& e);
103 void on_mute(wxCommandEvent& e);
104 private:
105 struct _vupanel : public wxPanel
107 _vupanel(wxwin_vumeter* v)
108 : wxPanel(v, wxID_ANY, wxDefaultPosition, wxSize(320, 64))
110 obj = v;
111 buffer.resize(61440);
112 bufferstride = 960;
113 for(unsigned i = 0; i < 320; i++) {
114 unsigned color = get_strip_color(i);
115 if(color < 256) {
116 colorstrip[3 * i + 0] = 255;
117 colorstrip[3 * i + 1] = color;
118 } else {
119 colorstrip[3 * i + 0] = 255 - (color - 255);
120 colorstrip[3 * i + 1] = 255;
122 colorstrip[3 * i + 2] = 0;
124 this->Connect(wxEVT_PAINT, wxPaintEventHandler(_vupanel::on_paint), NULL, this);
127 void signal_repaint()
129 mleft = vu_to_pixels(audioapi_vu_mleft);
130 mright = vu_to_pixels(audioapi_vu_mright);
131 vout = vu_to_pixels(audioapi_vu_vout);
132 vin = vu_to_pixels(audioapi_vu_vin);
133 Refresh();
134 obj->update_sent = false;
137 void on_paint(wxPaintEvent& e)
139 wxPaintDC dc(this);
140 dc.Clear();
141 memset(&buffer[0], 255, buffer.size());
142 draw_text(0, 8, 32, 16, game_text_buf);
143 draw_text(0, 24, 32, 16, vout_text_buf);
144 draw_text(0, 40, 32, 16, vin_text_buf);
145 for(unsigned i = 32; i <= 272; i += 40)
146 draw_vline(i, 0, 63);
147 draw_vline(231, 0, 63); //0dB is thicker.
148 draw_meter(32, 8, 8, mleft);
149 draw_meter(32, 16, 8, mright);
150 draw_meter(32, 24, 16, vout);
151 draw_meter(32, 40, 16, vin);
152 wxBitmap bmp2(wxImage(320, 64, &buffer[0], true));
153 dc.DrawBitmap(bmp2, 0, 0, false);
156 wxwin_vumeter* obj;
157 volatile unsigned mleft;
158 volatile unsigned mright;
159 volatile unsigned vout;
160 volatile unsigned vin;
161 std::vector<unsigned char> buffer;
162 unsigned char colorstrip[960];
163 size_t bufferstride;
164 void draw_text(unsigned x, unsigned y, unsigned w, unsigned h, const uint32_t* buf)
166 unsigned spos = 0;
167 unsigned pos = y * bufferstride + 3 * x;
168 for(unsigned j = 0; j < h; j++) {
169 for(unsigned i = 0; i < w; i++) {
170 unsigned _spos = spos + i;
171 unsigned char val = ((buf[_spos >> 5] >> (31 - (_spos & 31))) & 1) ? 0 : 255;
172 buffer[pos + 3 * i + 0] = val;
173 buffer[pos + 3 * i + 1] = val;
174 buffer[pos + 3 * i + 2] = val;
176 pos += bufferstride;
177 spos += 32 * ((w + 31) / 32);
180 void draw_vline(unsigned x, unsigned y1, unsigned y2)
182 unsigned pos = y1 * bufferstride + 3 * x;
183 for(unsigned j = y1; j < y2; j++) {
184 buffer[pos + 0] = 0;
185 buffer[pos + 1] = 0;
186 buffer[pos + 2] = 0;
187 pos += bufferstride;
190 void draw_meter(unsigned x, unsigned y, unsigned h, unsigned val)
192 if(val > 320 - x)
193 val = 320 - x;
194 unsigned pos = y * bufferstride + 3 * x;
195 for(unsigned j = 0; j < h; j++) {
196 if(val)
197 memcpy(&buffer[pos], colorstrip, 3 * val);
198 pos += bufferstride;
202 volatile bool update_sent;
203 bool closing;
204 wxButton* closebutton;
205 struct dispatch::target<> vulistener;
206 _vupanel* vupanel;
207 wxStaticText* rate;
208 wxSlider* gamevol;
209 wxSlider* voutvol;
210 wxSlider* vinvol;
211 wxButton* dgamevol;
212 wxButton* dvoutvol;
213 wxButton* dvinvol;
214 wxComboBox* pdev;
215 wxComboBox* rdev;
216 wxCheckBox* mute;
217 struct dispatch::target<bool> unmuted;
218 struct dispatch::target<std::pair<std::string, std::string>> devchange;
221 wxwin_vumeter::wxwin_vumeter(wxWindow* parent)
222 : wxDialog(parent, wxID_ANY, wxT("lsnes: VU meter"), wxDefaultPosition, wxSize(-1, -1))
224 update_sent = false;
225 closing = false;
226 Centre();
227 wxFlexGridSizer* top_s = new wxFlexGridSizer(5, 1, 0, 0);
228 SetSizer(top_s);
230 top_s->Add(vupanel = new _vupanel(this));
231 top_s->Add(rate = new wxStaticText(this, wxID_ANY, wxT("")), 0, wxGROW);
233 wxFlexGridSizer* slier_s = new wxFlexGridSizer(3, 3, 0, 0);
234 slier_s->Add(new wxStaticText(this, wxID_ANY, wxT("Game:")), 0, wxGROW);
235 slier_s->Add(gamevol = new wxSlider(this, wxID_ANY, to_db(audioapi_music_volume()), -100, 50,
236 wxDefaultPosition, wxSize(320, -1)), 1, wxGROW);
237 slier_s->Add(dgamevol = new wxButton(this, wxID_ANY, wxT("Reset")));
238 slier_s->Add(new wxStaticText(this, wxID_ANY, wxT("Voice out:")), 1, wxGROW);
239 slier_s->Add(voutvol = new wxSlider(this, wxID_ANY, to_db(audioapi_voicep_volume()), -100, 50,
240 wxDefaultPosition, wxSize(320, -1)), 1, wxGROW);
241 slier_s->Add(dvoutvol = new wxButton(this, wxID_ANY, wxT("Reset")));
242 slier_s->Add(new wxStaticText(this, wxID_ANY, wxT("Voice in:")), 1, wxGROW);
243 slier_s->Add(vinvol = new wxSlider(this, wxID_ANY, to_db(audioapi_voicer_volume()), -100, 50,
244 wxDefaultPosition, wxSize(320, -1)), 1, wxGROW);
245 slier_s->Add(dvinvol = new wxButton(this, wxID_ANY, wxT("Reset")));
246 top_s->Add(slier_s, 1, wxGROW);
248 gamevol->SetLineSize(1);
249 vinvol->SetLineSize(1);
250 voutvol->SetLineSize(1);
251 gamevol->SetPageSize(10);
252 vinvol->SetPageSize(10);
253 voutvol->SetPageSize(10);
254 connect_events(gamevol, wxScrollEventHandler(wxwin_vumeter::on_game_change), this);
255 connect_events(voutvol, wxScrollEventHandler(wxwin_vumeter::on_vout_change), this);
256 connect_events(vinvol, wxScrollEventHandler(wxwin_vumeter::on_vin_change), this);
257 dgamevol->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxwin_vumeter::on_game_reset), NULL,
258 this);
259 dvoutvol->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxwin_vumeter::on_vout_reset), NULL,
260 this);
261 dvinvol->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxwin_vumeter::on_vin_reset), NULL,
262 this);
264 auto pdev_map = audioapi_driver_get_devices(false);
265 auto rdev_map = audioapi_driver_get_devices(true);
266 std::string current_pdev = pdev_map[audioapi_driver_get_device(false)];
267 std::string current_rdev = rdev_map[audioapi_driver_get_device(true)];
268 std::vector<wxString> available_pdevs;
269 std::vector<wxString> available_rdevs;
270 for(auto i : pdev_map)
271 available_pdevs.push_back(towxstring(i.second));
272 for(auto i : rdev_map)
273 available_rdevs.push_back(towxstring(i.second));
275 wxSizer* hw_s = new wxFlexGridSizer(3, 2, 0, 0);
276 hw_s->Add(new wxStaticText(this, wxID_ANY, wxT("Input device:")), 0, wxGROW);
277 hw_s->Add(rdev = new wxComboBox(this, wxID_ANY, towxstring(current_rdev), wxDefaultPosition,
278 wxSize(-1, -1), available_rdevs.size(), &available_rdevs[0], wxCB_READONLY), 1, wxGROW);
279 hw_s->Add(new wxStaticText(this, wxID_ANY, wxT("Output device:")), 0, wxGROW);
280 hw_s->Add(pdev = new wxComboBox(this, wxID_ANY, towxstring(current_pdev), wxDefaultPosition,
281 wxSize(-1, -1), available_pdevs.size(), &available_pdevs[0], wxCB_READONLY), 1, wxGROW);
282 hw_s->Add(new wxStaticText(this, wxID_ANY, wxT("")), 0, wxGROW);
283 hw_s->Add(mute = new wxCheckBox(this, wxID_ANY, wxT("Mute sounds")), 1, wxGROW);
284 mute->SetValue(!platform::is_sound_enabled());
285 top_s->Add(hw_s);
287 wxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
288 pbutton_s->AddStretchSpacer();
289 pbutton_s->Add(closebutton = new wxButton(this, wxID_OK, wxT("Close")), 0, wxGROW);
290 top_s->Add(pbutton_s, 1, wxGROW);
291 pbutton_s->SetSizeHints(this);
293 closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
294 wxCommandEventHandler(wxwin_vumeter::on_close), NULL, this);
295 rdev->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED,
296 wxCommandEventHandler(wxwin_vumeter::on_devsel), NULL, this);
297 pdev->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED,
298 wxCommandEventHandler(wxwin_vumeter::on_devsel), NULL, this);
299 mute->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED,
300 wxCommandEventHandler(wxwin_vumeter::on_mute), NULL, this);
301 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_vumeter::on_wclose));
303 unmuted.set(notify_sound_unmute, [this](bool unmute) {
304 runuifun([this, unmute]() { if(!this->closing) this->mute->SetValue(!unmute); });
306 devchange.set(notify_sound_change, [this](std::pair<std::string, std::string> d) {
307 runuifun([this, d]() {
308 if(this->closing) return;
309 auto pdevs = audioapi_driver_get_devices(false);
310 if(pdevs.count(d.second)) this->pdev->SetValue(towxstring(pdevs[d.second]));
311 auto rdevs = audioapi_driver_get_devices(true);
312 if(rdevs.count(d.first)) this->rdev->SetValue(towxstring(rdevs[d.first]));
316 top_s->SetSizeHints(this);
317 Fit();
318 vulistener.set(notify_vu_change, [this]() {
319 if(!this->update_sent) {
320 this->update_sent = true;
321 runuifun([this]() -> void { this->refresh(); });
324 refresh();
327 wxwin_vumeter::~wxwin_vumeter() throw()
331 void wxwin_vumeter::on_devsel(wxCommandEvent& e)
333 std::string newpdev = tostdstring(pdev->GetValue());
334 std::string newrdev = tostdstring(rdev->GetValue());
335 std::string _newpdev = "null";
336 std::string _newrdev = "null";
337 for(auto i : audioapi_driver_get_devices(false))
338 if(i.second == newpdev)
339 _newpdev = i.first;
340 for(auto i : audioapi_driver_get_devices(true))
341 if(i.second == newrdev)
342 _newrdev = i.first;
343 platform::set_sound_device(_newpdev, _newrdev);
346 void wxwin_vumeter::on_game_change(wxScrollEvent& e)
348 audioapi_music_volume(pow(10, gamevol->GetValue() / 20.0));
351 void wxwin_vumeter::on_vout_change(wxScrollEvent& e)
353 audioapi_voicep_volume(pow(10, voutvol->GetValue() / 20.0));
356 void wxwin_vumeter::on_vin_change(wxScrollEvent& e)
358 audioapi_voicer_volume(pow(10, vinvol->GetValue() / 20.0));
361 void wxwin_vumeter::on_game_reset(wxCommandEvent& e)
363 audioapi_music_volume(1);
364 gamevol->SetValue(0);
367 void wxwin_vumeter::on_vout_reset(wxCommandEvent& e)
369 audioapi_voicep_volume(1);
370 voutvol->SetValue(0);
373 void wxwin_vumeter::on_vin_reset(wxCommandEvent& e)
375 audioapi_voicer_volume(1);
376 vinvol->SetValue(0);
379 void wxwin_vumeter::on_mute(wxCommandEvent& e)
381 platform::sound_enable(!mute->GetValue());
384 void wxwin_vumeter::refresh()
386 auto rate_cur = audioapi_voice_rate();
387 unsigned rate_nom = audioapi_orig_voice_rate();
388 rate->SetLabel(towxstring((stringfmt() << "Current: " << rate_cur.second << "Hz (nominal " << rate_nom
389 << "Hz), record: " << rate_cur.first << "Hz").str()));
390 vupanel->signal_repaint();
393 void wxwin_vumeter::on_close(wxCommandEvent& e)
395 closing = true;
396 Destroy();
397 vumeter_open = false;
400 void wxwin_vumeter::on_wclose(wxCloseEvent& e)
402 bool wasc = closing;
403 closing = true;
404 if(!wasc)
405 Destroy();
406 vumeter_open = false;
409 bool wxwin_vumeter::ShouldPreventAppExit() const { return false; }
411 void open_vumeter_window(wxWindow* parent)
413 if(vumeter_open)
414 return;
415 wxwin_vumeter* v = new wxwin_vumeter(parent);
416 v->Show();
417 vumeter_open = true;