mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
475 lines
14 KiB
C++
475 lines
14 KiB
C++
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 <algorithm>
|
|
#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<Sound> messages[];
|
|
static const MsgHandler::Entry<Sound> commands[];
|
|
};
|
|
|
|
const MsgHandler::Entry<Sound> Sound::Handlers::messages[] =
|
|
{
|
|
{ WM_INITDIALOG, &Sound::OnInitDialog },
|
|
{ WM_VSCROLL, &Sound::OnVScroll }
|
|
};
|
|
|
|
const MsgHandler::Entry<Sound> 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<Recorder> messages[];
|
|
static const MsgHandler::Entry<Recorder> commands[];
|
|
};
|
|
|
|
const MsgHandler::Entry<Sound::Recorder> Sound::Recorder::Handlers::messages[] =
|
|
{
|
|
{ WM_INITDIALOG, &Recorder::OnInitDialog }
|
|
};
|
|
|
|
const MsgHandler::Entry<Sound::Recorder> 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;
|
|
}
|
|
}
|
|
}
|