//////////////////////////////////////////////////////////////////////////////////////// // // Nestopia - NES/Famicom emulator written in C++ // // Copyright (C) 2003-2008 Martin Freij // // This file is part of Nestopia. // // Nestopia is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // Nestopia is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Nestopia; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // //////////////////////////////////////////////////////////////////////////////////////// #include #include "NstApplicationInstance.hpp" #include "NstResourceString.hpp" #include "NstWindowParam.hpp" #include "NstManagerPaths.hpp" #include "NstDialogSound.hpp" namespace Nestopia { namespace Window { NST_COMPILE_ASSERT ( IDC_SOUND_LATENCY == IDC_SOUND_SAMPLE_RATE + 3 && IDC_SOUND_LATENCY_ONE == IDC_SOUND_SAMPLE_RATE + 4 && IDC_SOUND_LATENCY_TEN == IDC_SOUND_SAMPLE_RATE + 5 && IDC_SOUND_MONO == IDC_SOUND_SAMPLE_RATE + 6 && IDC_SOUND_STEREO == IDC_SOUND_SAMPLE_RATE + 7 && IDC_SOUND_POOL_SYSTEM == IDC_SOUND_SAMPLE_RATE + 8 && IDC_SOUND_POOL_HARDWARE == IDC_SOUND_SAMPLE_RATE + 9 && IDC_SOUND_ADJUST_PITCH == IDC_SOUND_SAMPLE_RATE + 10 ); const Sound::ChannelLut Sound::channelLut[NUM_CHANNELS] = { { NULL, "master", IDC_SOUND_MASTER_SLIDER, IDC_SOUND_MASTER_VALUE, IDC_SOUND_MASTER_TEXT, 0 }, { "apu", "square-1", IDC_SOUND_SQUARE1_SLIDER, IDC_SOUND_SQUARE1_VALUE, IDC_SOUND_SQUARE1_TEXT, Nes::Sound::CHANNEL_SQUARE1 }, { "apu", "square-2", IDC_SOUND_SQUARE2_SLIDER, IDC_SOUND_SQUARE2_VALUE, IDC_SOUND_SQUARE2_TEXT, Nes::Sound::CHANNEL_SQUARE2 }, { "apu", "triangle", IDC_SOUND_TRIANGLE_SLIDER, IDC_SOUND_TRIANGLE_VALUE, IDC_SOUND_TRIANGLE_TEXT, Nes::Sound::CHANNEL_TRIANGLE }, { "apu", "noise", IDC_SOUND_NOISE_SLIDER, IDC_SOUND_NOISE_VALUE, IDC_SOUND_NOISE_TEXT, Nes::Sound::CHANNEL_NOISE }, { "apu", "dpcm", IDC_SOUND_DPCM_SLIDER, IDC_SOUND_DPCM_VALUE, IDC_SOUND_DPCM_TEXT, Nes::Sound::CHANNEL_DPCM }, { "external", "fds", IDC_SOUND_FDS_SLIDER, IDC_SOUND_FDS_VALUE, IDC_SOUND_FDS_TEXT, Nes::Sound::CHANNEL_FDS }, { "external", "mmc5", IDC_SOUND_MMC5_SLIDER, IDC_SOUND_MMC5_VALUE, IDC_SOUND_MMC5_TEXT, Nes::Sound::CHANNEL_MMC5 }, { "external", "vrc6", IDC_SOUND_VRC6_SLIDER, IDC_SOUND_VRC6_VALUE, IDC_SOUND_VRC6_TEXT, Nes::Sound::CHANNEL_VRC6 }, { "external", "vrc7", IDC_SOUND_VRC7_SLIDER, IDC_SOUND_VRC7_VALUE, IDC_SOUND_VRC7_TEXT, Nes::Sound::CHANNEL_VRC7 }, { "external", "n163", IDC_SOUND_N163_SLIDER, IDC_SOUND_N163_VALUE, IDC_SOUND_N163_TEXT, Nes::Sound::CHANNEL_N163 }, { "external", "s5b", IDC_SOUND_S5B_SLIDER, IDC_SOUND_S5B_VALUE, IDC_SOUND_S5B_TEXT, Nes::Sound::CHANNEL_S5B } }; struct Sound::Handlers { static const MsgHandler::Entry messages[]; static const MsgHandler::Entry commands[]; }; const MsgHandler::Entry Sound::Handlers::messages[] = { { WM_INITDIALOG, &Sound::OnInitDialog }, { WM_VSCROLL, &Sound::OnVScroll } }; const MsgHandler::Entry Sound::Handlers::commands[] = { { IDC_SOUND_DEVICE, &Sound::OnCmdDevice }, { IDC_SOUND_DEFAULT, &Sound::OnCmdDefault }, { IDC_SOUND_RESET_SLIDERS, &Sound::OnCmdResetSliders }, { IDOK, &Sound::OnCmdOk } }; Sound::Sound(Managers::Emulator& e,const Adapters& a,const Managers::Paths& paths,const Configuration& cfg) : adapters ( a ), nes ( e ), dialog ( IDD_SOUND, this, Handlers::messages, Handlers::commands ), recorder ( paths ) { Configuration::ConstSection sound( cfg["sound"] ); settings.adapter = UINT_MAX; if (adapters.size()) { const GenericString device( sound["device"].Str() ); if (device != L"none") { const Adapters::const_iterator it( std::find( adapters.begin(), adapters.end(), System::Guid(device) )); if (it != adapters.end()) settings.adapter = it - adapters.begin(); else settings.adapter = GetDefaultAdapter(); } } switch (uint rate = sound["sample-rate"].Int()) { case 44100: case 48000: case 88200: case 96000: nes.SetSampleRate( rate ); break; } nes.SetAutoTranspose( sound["adjust-pitch"].Yes() ); nes.SetSpeaker( sound["speakers"].Str() == L"stereo" ? Nes::Sound::SPEAKER_STEREO : Nes::Sound::SPEAKER_MONO ); settings.pool = (sound["memory-pool"].Str() == L"hardware" ? DirectSound::POOL_HARDWARE : DirectSound::POOL_SYSTEM); settings.latency = sound["buffers"].Int( DEFAULT_LATENCY ); if (settings.latency > LATENCY_MAX) settings.latency = DEFAULT_LATENCY; Configuration::ConstSection volumes( sound["volumes"] ); for (uint i=0; i < NUM_CHANNELS; ++i) { uint volume; if (channelLut[i].cfgCategory) volume = volumes[channelLut[i].cfgCategory][channelLut[i].cfgChannel].Int( DEFAULT_VOLUME ); else volume = volumes[channelLut[i].cfgChannel].Int( DEFAULT_VOLUME ); if (volume > VOLUME_MAX) volume = DEFAULT_VOLUME; settings.volumes[i] = volume; if (channelLut[i].channel) nes.SetVolume( channelLut[i].channel, GetVolume( channelLut[i].channel ) ); } } Sound::~Sound() { } void Sound::Save(Configuration& cfg) const { Configuration::Section sound( cfg["sound"] ); if (settings.adapter < adapters.size()) sound["device"].Str() = adapters[settings.adapter].guid.GetString(); else sound["device"].Str() = "none"; sound[ "sample-rate" ].Int() = nes.GetSampleRate(); sound[ "buffers" ].Int() = settings.latency; sound[ "speakers" ].Str() = (nes.GetSpeaker() == Nes::Sound::SPEAKER_STEREO ? "stereo" : "mono"); sound[ "adjust-pitch" ].YesNo() = nes.IsAutoTransposing(); sound[ "memory-pool" ].Str() = (settings.pool == DirectSound::POOL_SYSTEM ? "system" : "hardware"); Configuration::Section volumes( sound["volumes"] ); for (uint i=0; i < NUM_CHANNELS; ++i) { if (channelLut[i].cfgCategory) volumes[channelLut[i].cfgCategory][channelLut[i].cfgChannel].Int() = settings.volumes[i]; else volumes[channelLut[i].cfgChannel].Int() = settings.volumes[i]; } } uint Sound::GetVolume(const uint channel) const { if (settings.adapter != UINT_MAX) { for (uint i=1; i < NUM_CHANNELS; ++i) { if (channelLut[i].channel == channel) { const uint volume = (settings.volumes[i] * settings.volumes[0] + DEFAULT_VOLUME/2) / DEFAULT_VOLUME; return NST_MIN(volume,VOLUME_MAX); } } } return 0; } uint Sound::GetDefaultAdapter() const { const Adapters::const_iterator it( std::find( adapters.begin(), adapters.end(), System::Guid() ) ); return it != adapters.end() ? it - adapters.begin() : 0; } ibool Sound::OnInitDialog(Param&) { if (adapters.size()) { { const Control::ComboBox comboBox( dialog.ComboBox(IDC_SOUND_DEVICE) ); comboBox.Add( Resource::String(IDS_TEXT_NONE) ); for (Adapters::const_iterator it(adapters.begin()), end(adapters.end()); it != end; ++it) comboBox.Add( it->name.Ptr() ); comboBox[settings.adapter+1].Select(); } { static const wchar_t rates[][6] = { L"44100", L"48000", L"88200", L"96000" }; uint index; switch (nes.GetSampleRate()) { case 48000: index = 1; break; case 88200: index = 2; break; case 96000: index = 3; break; default: index = 0; break; } const Control::ComboBox comboBox( dialog.ComboBox(IDC_SOUND_SAMPLE_RATE) ); comboBox.Add( rates, sizeof(array(rates)) ); comboBox[index].Select(); } dialog.RadioButton( nes.GetSpeaker() == Nes::Sound::SPEAKER_STEREO ? IDC_SOUND_STEREO : IDC_SOUND_MONO ).Check(); dialog.RadioButton( settings.pool == DirectSound::POOL_HARDWARE ? IDC_SOUND_POOL_HARDWARE : IDC_SOUND_POOL_SYSTEM ).Check(); for (uint i=0; i < NUM_CHANNELS; ++i) { const Control::Slider control( dialog.Slider(channelLut[i].ctrlSlider) ); control.SetRange( 0, VOLUME_MAX ); control.Position() = VOLUME_MAX - settings.volumes[i]; dialog.Edit( channelLut[i].ctrlValue ) << uint(settings.volumes[i]); } { const Control::Slider control( dialog.Slider(IDC_SOUND_LATENCY) ); control.SetRange( 1, LATENCY_MAX ); control.Position() = settings.latency; } dialog.CheckBox( IDC_SOUND_ADJUST_PITCH ).Check( nes.IsAutoTransposing() ); if (settings.adapter != UINT_MAX) UpdateVolumeReset(); else Enable( false ); } else { dialog.Control( IDC_SOUND_DEVICE ).Disable(); dialog.Control( IDC_SOUND_DEFAULT ).Disable(); Enable( false ); } return true; } void Sound::Enable(const bool state) const { for (uint i=IDC_SOUND_SAMPLE_RATE; i <= IDC_SOUND_ADJUST_PITCH; ++i) dialog.Control( i ).Enable( state ); for (uint i=0; i < NUM_CHANNELS; ++i) { dialog.Control( channelLut[i].ctrlSlider ).Enable( state ); dialog.Control( channelLut[i].ctrlValue ).Enable( state ); dialog.Control( channelLut[i].ctrlText ).Enable( state ); } if (state) UpdateVolumeReset(); else dialog.Control( IDC_SOUND_RESET_SLIDERS ).Disable(); } void Sound::ResetVolumeSliders() const { for (uint i=0; i < NUM_CHANNELS; ++i) { dialog.Slider( channelLut[i].ctrlSlider ).Position() = VOLUME_MAX-DEFAULT_VOLUME; dialog.Edit( channelLut[i].ctrlValue ) << uint(DEFAULT_VOLUME); } dialog.Control( IDC_SOUND_RESET_SLIDERS ).Disable(); } void Sound::UpdateVolumeReset() const { uint reset; for (reset=0; reset < NUM_CHANNELS; ++reset) { if (dialog.Slider( channelLut[reset].ctrlSlider ).Position() != VOLUME_MAX-DEFAULT_VOLUME) break; } dialog.Control( IDC_SOUND_RESET_SLIDERS ).Enable( reset != NUM_CHANNELS ); } ibool Sound::OnVScroll(Param& param) { for (uint i=0, id=param.Slider().GetId(); i < NUM_CHANNELS; ++i) { if (id == channelLut[i].ctrlSlider) { dialog.Edit( channelLut[i].ctrlValue ) << (VOLUME_MAX - param.Slider().Scroll()); UpdateVolumeReset(); break; } } return true; } ibool Sound::OnCmdDevice(Param&) { Enable( dialog.ComboBox( IDC_SOUND_DEVICE ).Selection().GetIndex() != 0 ); return true; } ibool Sound::OnCmdResetSliders(Param& param) { if (param.Button().Clicked()) ResetVolumeSliders(); return true; } ibool Sound::OnCmdDefault(Param& param) { NST_VERIFY( adapters.size() ); if (param.Button().Clicked()) { Enable( true ); dialog.ComboBox( IDC_SOUND_DEVICE )[GetDefaultAdapter()+1].Select(); dialog.ComboBox( IDC_SOUND_SAMPLE_RATE )[1].Select(); dialog.RadioButton( IDC_SOUND_MONO ).Check(); dialog.RadioButton( IDC_SOUND_STEREO ).Uncheck(); dialog.RadioButton( IDC_SOUND_POOL_HARDWARE ).Uncheck(); dialog.RadioButton( IDC_SOUND_POOL_SYSTEM ).Check(); dialog.CheckBox( IDC_SOUND_ADJUST_PITCH ).Uncheck(); dialog.Slider( IDC_SOUND_LATENCY ).Position() = DEFAULT_LATENCY; ResetVolumeSliders(); } return true; } ibool Sound::OnCmdOk(Param& param) { if (param.Button().Clicked()) { if (adapters.size()) { settings.adapter = dialog.ComboBox( IDC_SOUND_DEVICE ).Selection().GetIndex() - 1U; settings.latency = dialog.Slider( IDC_SOUND_LATENCY ).Position(); settings.pool = (dialog.RadioButton( IDC_SOUND_POOL_HARDWARE ).Checked() ? DirectSound::POOL_HARDWARE : DirectSound::POOL_SYSTEM); static const uint rates[] = {44100,48000,88200,96000}; nes.SetSampleRate( rates[dialog.ComboBox( IDC_SOUND_SAMPLE_RATE ).Selection().GetIndex()] ); nes.SetSpeaker( dialog.RadioButton( IDC_SOUND_STEREO ).Checked() ? Nes::Sound::SPEAKER_STEREO : Nes::Sound::SPEAKER_MONO ); nes.SetAutoTranspose( dialog.CheckBox( IDC_SOUND_ADJUST_PITCH ).Checked() ); for (uint i=0; i < NUM_CHANNELS; ++i) settings.volumes[i] = VOLUME_MAX - dialog.Slider( channelLut[i].ctrlSlider ).Position(); for (uint i=1; i < NUM_CHANNELS; ++i) nes.SetVolume( channelLut[i].channel, GetVolume( channelLut[i].channel ) ); } dialog.Close(); } return true; } struct Sound::Recorder::Handlers { static const MsgHandler::Entry messages[]; static const MsgHandler::Entry commands[]; }; const MsgHandler::Entry Sound::Recorder::Handlers::messages[] = { { WM_INITDIALOG, &Recorder::OnInitDialog } }; const MsgHandler::Entry Sound::Recorder::Handlers::commands[] = { { IDC_SOUND_CAPTURE_CLEAR, &Recorder::OnCmdClear }, { IDC_SOUND_CAPTURE_BROWSE, &Recorder::OnCmdBrowse }, { IDOK, &Recorder::OnCmdOk } }; Sound::Recorder::Recorder(const Managers::Paths& p) : dialog(IDD_SOUND_RECORDER,this,Handlers::messages,Handlers::commands), paths(p) {} Sound::Recorder::~Recorder() { } const Path Sound::Recorder::WaveFile() const { return Application::Instance::GetFullPath( waveFile ); } ibool Sound::Recorder::OnInitDialog(Param&) { dialog.Edit( IDC_SOUND_CAPTURE_FILE ) << waveFile.Ptr(); dialog.Edit( IDC_SOUND_CAPTURE_FILE ).Limit( MAX_PATH ); return true; } ibool Sound::Recorder::OnCmdClear(Param& param) { if (param.Button().Clicked()) dialog.Edit(IDC_SOUND_CAPTURE_FILE).Clear(); return true; } ibool Sound::Recorder::OnCmdBrowse(Param& param) { if (param.Button().Clicked()) { Path tmp; dialog.Edit(IDC_SOUND_CAPTURE_FILE) >> tmp; dialog.Edit(IDC_SOUND_CAPTURE_FILE).Try() << paths.BrowseSave( Managers::Paths::File::WAVE, Managers::Paths::SUGGEST, Application::Instance::GetFullPath(tmp) ).Ptr(); } return true; } ibool Sound::Recorder::OnCmdOk(Param& param) { if (param.Button().Clicked()) { dialog.Edit(IDC_SOUND_CAPTURE_FILE) >> waveFile; paths.FixFile( Managers::Paths::File::WAVE, waveFile ); dialog.Close(); } return true; } } }