Fix Win32 build
[lsnes.git] / src / platform / wxwidgets / vumeter.cpp
blobfb10c205c1ff381e9b262750736086dedf33a660
1 #include <wx/wx.h>
2 #include <wx/event.h>
3 #include <wx/control.h>
4 #include <wx/combobox.h>
6 #include "core/audioapi.hpp"
7 #include "core/audioapi-driver.hpp"
8 #include "core/dispatch.hpp"
9 #include "core/instance.hpp"
10 #include "core/window.hpp"
12 #include "library/string.hpp"
13 #include "platform/wxwidgets/platform.hpp"
15 namespace
17 std::set<emulator_instance*> vumeter_open;
19 unsigned vu_to_pixels(float vu)
21 if(vu < -100)
22 vu = -100;
23 if(vu > 20)
24 vu = 20;
25 unsigned _vu = 2 * (vu + 100);
26 if(_vu > 2000)
27 _vu = 0; //Overflow.
28 return _vu;
31 int to_db(float value)
33 if(value < 1e-10)
34 return -100;
35 int v = 20 / log(10) * log(value);
36 if(v < -100)
37 v = 100;
38 if(v > 50)
39 v = 50;
40 return v;
43 void connect_events(wxSlider* s, wxObjectEventFunction fun, wxEvtHandler* obj)
45 CHECK_UI_THREAD;
46 s->Connect(wxEVT_SCROLL_THUMBTRACK, fun, NULL, obj);
47 s->Connect(wxEVT_SCROLL_PAGEDOWN, fun, NULL, obj);
48 s->Connect(wxEVT_SCROLL_PAGEUP, fun, NULL, obj);
49 s->Connect(wxEVT_SCROLL_LINEDOWN, fun, NULL, obj);
50 s->Connect(wxEVT_SCROLL_LINEUP, fun, NULL, obj);
51 s->Connect(wxEVT_SCROLL_TOP, fun, NULL, obj);
52 s->Connect(wxEVT_SCROLL_BOTTOM, fun, NULL, obj);
55 uint32_t game_text_buf[16] = {
56 0x00000000, 0x00000000, 0x3c000000, 0x66000000,
57 0xc2000000, 0xc078ec7c, 0xc00cfec6, 0xde7cd6fe,
58 0xc6ccd6c0, 0xc6ccd6c0, 0x66ccd6c6, 0x3a76c67c,
59 0x00000000, 0x00000000, 0x00000000, 0x00000000
62 uint32_t vout_text_buf[16] = {
63 0x00000000, 0x00000000, 0xc6000010, 0xc6000030,
64 0xc6000030, 0xc67cccfc, 0xc6c6cc30, 0xc6c6cc30,
65 0xc6c6cc30, 0x6cc6cc30, 0x38c6cc36, 0x107c761c,
66 0x00000000, 0x00000000, 0x00000000, 0x00000000
69 uint32_t vin_text_buf[16] = {
70 0x00000000, 0x00000000, 0xc6180000, 0xc6180000,
71 0xc6000000, 0xc638dc00, 0xc6186600, 0xc6186600,
72 0xc6186600, 0xc6186600, 0x38186600, 0x103c6600,
73 0x00000000, 0x00000000, 0x00000000, 0x00000000
76 unsigned get_strip_color(unsigned i)
78 if(i < 80)
79 return 0;
80 if(i < 120)
81 return 51 * (i - 80) / 4;
82 if(i < 160)
83 return 510;
84 if(i < 200)
85 return 510 - 51 * (i - 160) / 4;
86 return 0;
90 class wxwin_vumeter : public wxDialog
92 public:
93 wxwin_vumeter(wxWindow* parent, emulator_instance& _inst);
94 ~wxwin_vumeter() throw();
95 bool ShouldPreventAppExit() const;
96 void on_close(wxCommandEvent& e);
97 void on_wclose(wxCloseEvent& e);
98 void refresh();
99 void on_game_change(wxScrollEvent& e);
100 void on_vout_change(wxScrollEvent& e);
101 void on_vin_change(wxScrollEvent& e);
102 void on_game_reset(wxCommandEvent& e);
103 void on_vout_reset(wxCommandEvent& e);
104 void on_vin_reset(wxCommandEvent& e);
105 void on_devsel(wxCommandEvent& e);
106 void on_mute(wxCommandEvent& e);
107 private:
108 struct _vupanel : public wxPanel
110 _vupanel(wxwin_vumeter* v, audioapi_instance& _audio)
111 : wxPanel(v, wxID_ANY, wxDefaultPosition, wxSize(320, 64)), audio(_audio)
113 CHECK_UI_THREAD;
114 obj = v;
115 buffer.resize(61440);
116 bufferstride = 960;
117 for(unsigned i = 0; i < 320; i++) {
118 unsigned color = get_strip_color(i);
119 if(color < 256) {
120 colorstrip[3 * i + 0] = 255;
121 colorstrip[3 * i + 1] = color;
122 } else {
123 colorstrip[3 * i + 0] = 255 - (color - 255);
124 colorstrip[3 * i + 1] = 255;
126 colorstrip[3 * i + 2] = 0;
128 this->Connect(wxEVT_PAINT, wxPaintEventHandler(_vupanel::on_paint), NULL, this);
131 void signal_repaint()
133 CHECK_UI_THREAD;
134 mleft = vu_to_pixels(audio.vu_mleft);
135 mright = vu_to_pixels(audio.vu_mright);
136 vout = vu_to_pixels(audio.vu_vout);
137 vin = vu_to_pixels(audio.vu_vin);
138 Refresh();
139 obj->update_sent = false;
142 void on_paint(wxPaintEvent& e)
144 CHECK_UI_THREAD;
145 wxPaintDC dc(this);
146 dc.Clear();
147 memset(&buffer[0], 255, buffer.size());
148 draw_text(0, 8, 32, 16, game_text_buf);
149 draw_text(0, 24, 32, 16, vout_text_buf);
150 draw_text(0, 40, 32, 16, vin_text_buf);
151 for(unsigned i = 32; i <= 272; i += 40)
152 draw_vline(i, 0, 63);
153 draw_vline(231, 0, 63); //0dB is thicker.
154 draw_meter(32, 8, 8, mleft);
155 draw_meter(32, 16, 8, mright);
156 draw_meter(32, 24, 16, vout);
157 draw_meter(32, 40, 16, vin);
158 wxBitmap bmp2(wxImage(320, 64, &buffer[0], true));
159 dc.DrawBitmap(bmp2, 0, 0, false);
162 wxwin_vumeter* obj;
163 volatile unsigned mleft;
164 volatile unsigned mright;
165 volatile unsigned vout;
166 volatile unsigned vin;
167 std::vector<unsigned char> buffer;
168 unsigned char colorstrip[960];
169 size_t bufferstride;
170 audioapi_instance& audio;
171 void draw_text(unsigned x, unsigned y, unsigned w, unsigned h, const uint32_t* buf)
173 unsigned spos = 0;
174 unsigned pos = y * bufferstride + 3 * x;
175 for(unsigned j = 0; j < h; j++) {
176 for(unsigned i = 0; i < w; i++) {
177 unsigned _spos = spos + i;
178 unsigned char val = ((buf[_spos >> 5] >> (31 - (_spos & 31))) & 1) ? 0 : 255;
179 buffer[pos + 3 * i + 0] = val;
180 buffer[pos + 3 * i + 1] = val;
181 buffer[pos + 3 * i + 2] = val;
183 pos += bufferstride;
184 spos += 32 * ((w + 31) / 32);
187 void draw_vline(unsigned x, unsigned y1, unsigned y2)
189 unsigned pos = y1 * bufferstride + 3 * x;
190 for(unsigned j = y1; j < y2; j++) {
191 buffer[pos + 0] = 0;
192 buffer[pos + 1] = 0;
193 buffer[pos + 2] = 0;
194 pos += bufferstride;
197 void draw_meter(unsigned x, unsigned y, unsigned h, unsigned val)
199 if(val > 320 - x)
200 val = 320 - x;
201 unsigned pos = y * bufferstride + 3 * x;
202 for(unsigned j = 0; j < h; j++) {
203 if(val)
204 memcpy(&buffer[pos], colorstrip, 3 * val);
205 pos += bufferstride;
209 emulator_instance& inst;
210 volatile bool update_sent;
211 bool closing;
212 wxButton* closebutton;
213 struct dispatch::target<> vulistener;
214 _vupanel* vupanel;
215 wxStaticText* rate;
216 wxSlider* gamevol;
217 wxSlider* voutvol;
218 wxSlider* vinvol;
219 wxButton* dgamevol;
220 wxButton* dvoutvol;
221 wxButton* dvinvol;
222 wxComboBox* pdev;
223 wxComboBox* rdev;
224 wxCheckBox* mute;
225 struct dispatch::target<bool> unmuted;
226 struct dispatch::target<std::pair<std::string, std::string>> devchange;
229 wxwin_vumeter::wxwin_vumeter(wxWindow* parent, emulator_instance& _inst)
230 : wxDialog(parent, wxID_ANY, wxT("lsnes: VU meter"), wxDefaultPosition, wxSize(-1, -1)), inst(_inst)
232 CHECK_UI_THREAD;
233 update_sent = false;
234 closing = false;
235 Centre();
236 wxFlexGridSizer* top_s = new wxFlexGridSizer(5, 1, 0, 0);
237 SetSizer(top_s);
239 top_s->Add(vupanel = new _vupanel(this, *inst.audio));
240 top_s->Add(rate = new wxStaticText(this, wxID_ANY, wxT("")), 0, wxGROW);
242 wxFlexGridSizer* slier_s = new wxFlexGridSizer(3, 3, 0, 0);
243 slier_s->Add(new wxStaticText(this, wxID_ANY, wxT("Game:")), 0, wxGROW);
244 slier_s->Add(gamevol = new wxSlider(this, wxID_ANY, to_db(inst.audio->music_volume()), -100, 50,
245 wxDefaultPosition, wxSize(320, -1)), 1, wxGROW);
246 slier_s->Add(dgamevol = new wxButton(this, wxID_ANY, wxT("Reset")));
247 slier_s->Add(new wxStaticText(this, wxID_ANY, wxT("Voice out:")), 1, wxGROW);
248 slier_s->Add(voutvol = new wxSlider(this, wxID_ANY, to_db(inst.audio->voicep_volume()), -100, 50,
249 wxDefaultPosition, wxSize(320, -1)), 1, wxGROW);
250 slier_s->Add(dvoutvol = new wxButton(this, wxID_ANY, wxT("Reset")));
251 slier_s->Add(new wxStaticText(this, wxID_ANY, wxT("Voice in:")), 1, wxGROW);
252 slier_s->Add(vinvol = new wxSlider(this, wxID_ANY, to_db(inst.audio->voicer_volume()), -100, 50,
253 wxDefaultPosition, wxSize(320, -1)), 1, wxGROW);
254 slier_s->Add(dvinvol = new wxButton(this, wxID_ANY, wxT("Reset")));
255 top_s->Add(slier_s, 1, wxGROW);
257 gamevol->SetLineSize(1);
258 vinvol->SetLineSize(1);
259 voutvol->SetLineSize(1);
260 gamevol->SetPageSize(10);
261 vinvol->SetPageSize(10);
262 voutvol->SetPageSize(10);
263 connect_events(gamevol, wxScrollEventHandler(wxwin_vumeter::on_game_change), this);
264 connect_events(voutvol, wxScrollEventHandler(wxwin_vumeter::on_vout_change), this);
265 connect_events(vinvol, wxScrollEventHandler(wxwin_vumeter::on_vin_change), this);
266 dgamevol->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxwin_vumeter::on_game_reset), NULL,
267 this);
268 dvoutvol->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxwin_vumeter::on_vout_reset), NULL,
269 this);
270 dvinvol->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxwin_vumeter::on_vin_reset), NULL,
271 this);
273 auto pdev_map = audioapi_driver_get_devices(false);
274 auto rdev_map = audioapi_driver_get_devices(true);
275 std::string current_pdev = pdev_map[audioapi_driver_get_device(false)];
276 std::string current_rdev = rdev_map[audioapi_driver_get_device(true)];
277 std::vector<wxString> available_pdevs;
278 std::vector<wxString> available_rdevs;
279 for(auto i : pdev_map)
280 available_pdevs.push_back(towxstring(i.second));
281 for(auto i : rdev_map)
282 available_rdevs.push_back(towxstring(i.second));
284 wxSizer* hw_s = new wxFlexGridSizer(3, 2, 0, 0);
285 hw_s->Add(new wxStaticText(this, wxID_ANY, wxT("Input device:")), 0, wxGROW);
286 hw_s->Add(rdev = new wxComboBox(this, wxID_ANY, towxstring(current_rdev), wxDefaultPosition,
287 wxSize(-1, -1), available_rdevs.size(), &available_rdevs[0], wxCB_READONLY), 1, wxGROW);
288 hw_s->Add(new wxStaticText(this, wxID_ANY, wxT("Output device:")), 0, wxGROW);
289 hw_s->Add(pdev = new wxComboBox(this, wxID_ANY, towxstring(current_pdev), wxDefaultPosition,
290 wxSize(-1, -1), available_pdevs.size(), &available_pdevs[0], wxCB_READONLY), 1, wxGROW);
291 hw_s->Add(new wxStaticText(this, wxID_ANY, wxT("")), 0, wxGROW);
292 hw_s->Add(mute = new wxCheckBox(this, wxID_ANY, wxT("Mute sounds")), 1, wxGROW);
293 mute->SetValue(!platform::is_sound_enabled());
294 top_s->Add(hw_s);
296 wxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
297 pbutton_s->AddStretchSpacer();
298 pbutton_s->Add(closebutton = new wxButton(this, wxID_OK, wxT("Close")), 0, wxGROW);
299 top_s->Add(pbutton_s, 1, wxGROW);
300 pbutton_s->SetSizeHints(this);
302 closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
303 wxCommandEventHandler(wxwin_vumeter::on_close), NULL, this);
304 rdev->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED,
305 wxCommandEventHandler(wxwin_vumeter::on_devsel), NULL, this);
306 pdev->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED,
307 wxCommandEventHandler(wxwin_vumeter::on_devsel), NULL, this);
308 mute->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED,
309 wxCommandEventHandler(wxwin_vumeter::on_mute), NULL, this);
310 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_vumeter::on_wclose));
312 unmuted.set(inst.dispatch->sound_unmute, [this](bool unmute) {
313 runuifun([this, unmute]() { if(!this->closing) this->mute->SetValue(!unmute); });
315 devchange.set(inst.dispatch->sound_change, [this](std::pair<std::string, std::string> d) {
316 runuifun([this, d]() {
317 if(this->closing) return;
318 auto pdevs = audioapi_driver_get_devices(false);
319 if(pdevs.count(d.second)) this->pdev->SetValue(towxstring(pdevs[d.second]));
320 auto rdevs = audioapi_driver_get_devices(true);
321 if(rdevs.count(d.first)) this->rdev->SetValue(towxstring(rdevs[d.first]));
325 top_s->SetSizeHints(this);
326 Fit();
327 vulistener.set(inst.dispatch->vu_change, [this]() {
328 if(!this->update_sent) {
329 this->update_sent = true;
330 runuifun([this]() -> void { this->refresh(); });
333 refresh();
336 wxwin_vumeter::~wxwin_vumeter() throw()
340 void wxwin_vumeter::on_devsel(wxCommandEvent& e)
342 CHECK_UI_THREAD;
343 std::string newpdev = tostdstring(pdev->GetValue());
344 std::string newrdev = tostdstring(rdev->GetValue());
345 std::string _newpdev = "null";
346 std::string _newrdev = "null";
347 for(auto i : audioapi_driver_get_devices(false))
348 if(i.second == newpdev)
349 _newpdev = i.first;
350 for(auto i : audioapi_driver_get_devices(true))
351 if(i.second == newrdev)
352 _newrdev = i.first;
353 platform::set_sound_device(_newpdev, _newrdev);
356 void wxwin_vumeter::on_game_change(wxScrollEvent& e)
358 CHECK_UI_THREAD;
359 inst.audio->music_volume(pow(10, gamevol->GetValue() / 20.0));
362 void wxwin_vumeter::on_vout_change(wxScrollEvent& e)
364 CHECK_UI_THREAD;
365 inst.audio->voicep_volume(pow(10, voutvol->GetValue() / 20.0));
368 void wxwin_vumeter::on_vin_change(wxScrollEvent& e)
370 CHECK_UI_THREAD;
371 inst.audio->voicer_volume(pow(10, vinvol->GetValue() / 20.0));
374 void wxwin_vumeter::on_game_reset(wxCommandEvent& e)
376 CHECK_UI_THREAD;
377 inst.audio->music_volume(1);
378 gamevol->SetValue(0);
381 void wxwin_vumeter::on_vout_reset(wxCommandEvent& e)
383 CHECK_UI_THREAD;
384 inst.audio->voicep_volume(1);
385 voutvol->SetValue(0);
388 void wxwin_vumeter::on_vin_reset(wxCommandEvent& e)
390 CHECK_UI_THREAD;
391 inst.audio->voicer_volume(1);
392 vinvol->SetValue(0);
395 void wxwin_vumeter::on_mute(wxCommandEvent& e)
397 CHECK_UI_THREAD;
398 platform::sound_enable(!mute->GetValue());
401 void wxwin_vumeter::refresh()
403 CHECK_UI_THREAD;
404 auto rate_cur = inst.audio->voice_rate();
405 unsigned rate_nom = inst.audio->orig_voice_rate();
406 rate->SetLabel(towxstring((stringfmt() << "Current: " << rate_cur.second << "Hz (nominal " << rate_nom
407 << "Hz), record: " << rate_cur.first << "Hz").str()));
408 vupanel->signal_repaint();
411 void wxwin_vumeter::on_close(wxCommandEvent& e)
413 CHECK_UI_THREAD;
414 closing = true;
415 Destroy();
416 vumeter_open.erase(&inst);
419 void wxwin_vumeter::on_wclose(wxCloseEvent& e)
421 CHECK_UI_THREAD;
422 bool wasc = closing;
423 closing = true;
424 if(!wasc)
425 Destroy();
426 vumeter_open.erase(&inst);
429 bool wxwin_vumeter::ShouldPreventAppExit() const { return false; }
431 void open_vumeter_window(wxWindow* parent, emulator_instance& inst)
433 CHECK_UI_THREAD;
434 if(vumeter_open.count(&inst))
435 return;
436 wxwin_vumeter* v = new wxwin_vumeter(parent, inst);
437 v->Show();
438 vumeter_open.insert(&inst);