/****************************************************************************
File    : SoundDlg.cpp
/*
@(#) #SY# Atari800Win PLus
@(#) #IS# CSoundDlg implementation file
@(#) #BY# Richard Lawrence, Tomasz Szymankowski
@(#) #LM# 17.03.2002
*/

#include "StdAfx.h"
#include "Atari800Win.h"
#include "Helpers.h"
#include "WarningDlg.h"
#include "SoundDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define IDC_SOUND_FIRST		IDC_SOUND_VOLUMESLIDER
#define IDC_SOUND_LAST		IDC_SOUND_CANCEL

#define MIN_SOUND_LATENCY	2
#define MAX_SOUND_LATENCY	6

#define DT_WAVEOUT			0
#define DT_DIRECTX			1


/////////////////////////////////////////////////////////////////////////////
// Static objects

static CSoundDlg::SndModeInfo_t s_aSndModeInfo[] =
{
	{ 8000,  6 },
	{ 11025, 4 },
	{ 21280, 2 },
	{ 22050, 2 },
	{ 31920, 2 },
	{ 44100, 1 },
	{ 48000, 1 }
};

static const int s_nSndModeInfoNo = sizeof(s_aSndModeInfo)/sizeof(s_aSndModeInfo[0]);


/////////////////////////////////////////////////////////////////////////////
// CSoundDlg dialog

BEGIN_MESSAGE_MAP(CSoundDlg, CCommonDlg)
	//{{AFX_MSG_MAP(CSoundDlg)
	ON_CBN_SELCHANGE(IDC_SOUND_DRIVERTYPE, OnSelchangeDriverType)
	ON_BN_CLICKED(IDC_SOUND_MUTE, OnSoundMute)
	ON_CBN_SELCHANGE(IDC_SOUND_PLAYBACK, OnSelchangeSoundPlayback)
	ON_BN_CLICKED(IDC_SOUND_VOLUMEONLY, OnSoundDigitized)
	ON_BN_CLICKED(IDC_SOUND_STEREO, OnSoundStereo)
	ON_NOTIFY(UDN_DELTAPOS, IDC_SOUND_POKEYSPIN, OnDeltaposPokeyDivisorSpin)
	ON_NOTIFY(UDN_DELTAPOS, IDC_SOUND_LATENCYSPIN, OnDeltaposSoundLatencySpin)
	ON_EN_KILLFOCUS(IDC_SOUND_POKEYDIVISOR, OnKillfocusPokeyDivisor)
	ON_EN_KILLFOCUS(IDC_SOUND_LATENCY, OnKillfocusSoundLatency)
	ON_WM_HSCROLL()
	//}}AFX_MSG_MAP
	ON_BN_CLICKED(IDC_SOUND_OK, OnOK)
	ON_BN_CLICKED(IDC_SOUND_CANCEL, CCommonDlg::OnCancel)
END_MESSAGE_MAP()

/*========================================================
Method   : CSoundDlg::CSoundDlg
=========================================================*/
/* #FN#
   Standard constructor */
CSoundDlg::
CSoundDlg(
	CWnd *pParent /*=NULL*/ /* #IN# Pointer to the parent window */
)
	: CCommonDlg( CSoundDlg::IDD, pParent )
{
	//{{AFX_DATA_INIT(CSoundDlg)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT

	m_pSndModeInfo = s_aSndModeInfo;
	m_bModeChanged = FALSE;
	m_nFirstCtrl   = IDC_SOUND_FIRST;
	m_nLastCtrl    = IDC_SOUND_LAST;
} /* #OF# CSoundDlg::CSoundDlg */

/*========================================================
Method   : CSoundDlg::DoDataExchange
=========================================================*/
/* #FN#
   Dynamic Data Exchange (not used) */
void
/* #AS#
   Nothing */
CSoundDlg::
DoDataExchange(
	CDataExchange *pDX /* #IN# Pointer to CDataExchange object */
)
{
	CCommonDlg::DoDataExchange( pDX );
	//{{AFX_DATA_MAP(CSoundDlg)
		// NOTE: the ClassWizard will add DDX and DDV calls here
	//}}AFX_DATA_MAP
} /* #OF# CSoundDlg::DoDataExchange */


/////////////////////////////////////////////////////////////////////////////
// CSoundDlg implementation

/*========================================================
Method   : CSoundDlg::SetDlgState
=========================================================*/
/* #FN#
   Sets up the state of the dialog controls */
void
/* #AS#
   Nothing */
CSoundDlg::
SetDlgState()
{
	CSliderCtrl *pSlider = (CSliderCtrl *)GetDlgItem( IDC_SOUND_VOLUMESLIDER );
	CButton     *pButton = NULL;
	CComboBox   *pCombo  = NULL;
	CWnd        *pWnd    = NULL;

	ASSERT(pSlider);

	/* Enable/Disable appriopriate controls */
	pWnd = GetDlgItem( IDC_SOUND_LATENCY );
	ASSERT(pWnd);
	pWnd->EnableWindow( m_ulSoundState & SOUND_DSSOUND );

	pWnd = GetDlgItem( IDC_SOUND_LATENCYSPIN );
	ASSERT(pWnd);
	pWnd->EnableWindow( m_ulSoundState & SOUND_DSSOUND );

	pWnd = GetDlgItem( IDC_SOUND_LATENCY_LABEL );
	ASSERT(pWnd);
	pWnd->EnableWindow( m_ulSoundState & SOUND_DSSOUND );

	/* Set up Volume slider */
	pSlider->EnableWindow( Sound_VolumeCapable() );
	pSlider->SetRange( 0, 100, FALSE );
	pSlider->SetTicFreq( 10 );
	pSlider->SetPageSize( 10 );
	pSlider->SetPos( 100 + m_nSoundVol );

	pButton = (CButton *)GetDlgItem( IDC_SOUND_VOLUMEONLY );
	ASSERT(pButton);
	pButton->SetCheck( m_bDigitized );

	pButton = (CButton *)GetDlgItem( IDC_SOUND_STEREO );
	ASSERT(pButton);
	pButton->SetCheck( m_bEnableStereo );

	pButton = (CButton *)GetDlgItem( IDC_SOUND_MUTE );
	ASSERT(pButton);
	pButton->SetCheck( m_ulSoundState & SOUND_NOSOUND );

	/* Set up Driver Type combo */
	pCombo = (CComboBox *)GetDlgItem( IDC_SOUND_DRIVERTYPE );
	ASSERT(pCombo);
	if( m_ulSoundState & SOUND_MMSOUND )
		pCombo->SetCurSel( DT_WAVEOUT );
	else
		pCombo->SetCurSel( DT_DIRECTX );

	/* Set up Playback Rate combo */
	pCombo = (CComboBox *)GetDlgItem( IDC_SOUND_PLAYBACK );
	ASSERT(pCombo);
	switch( m_nSoundRate )
	{
		case 8000:
			pCombo->SetCurSel( 0 );
			m_nLowSkipLimit = 6;
			break;

		case 11025:
			pCombo->SetCurSel( 1 );
			m_nLowSkipLimit = 4;
			break;

		case 21280:
			pCombo->SetCurSel( 2 );
			m_nLowSkipLimit = 2;
			break;

		case 22050:
			pCombo->SetCurSel( 3 );
			m_nLowSkipLimit = 2;
			break;

		case 31920:
			pCombo->SetCurSel( 4 );
			m_nLowSkipLimit = 2;
			break;

		case 44100:
			pCombo->SetCurSel( 5 );
			m_nLowSkipLimit = 1;
			break;

		case 48000:
			pCombo->SetCurSel( 6 );
			m_nLowSkipLimit = 1;
			break;

		default:
			pCombo->SetCurSel( 5 );
			m_nSoundRate = 44100;
			m_nLowSkipLimit = 1;
	}
	SetPokeyDivisor();

	SetDlgItemInt( IDC_SOUND_LATENCY, m_nSoundLatency, FALSE );
} /* #OF# CSoundDlg::SetDlgState */

/*========================================================
Method   : CSoundDlg::SetPokeyDivisor
=========================================================*/
/* #FN#
   Sets the proper Pokey update divisor value */
void
/* #AS#
   Nothing */
CSoundDlg::
SetPokeyDivisor()
{
	if( m_nSkipUpdate < m_nLowSkipLimit )
		m_nSkipUpdate = m_nLowSkipLimit;
	if( m_nSkipUpdate > m_nHighSkipLimit )
		m_nSkipUpdate = m_nHighSkipLimit;

	SetDlgItemInt( IDC_SOUND_POKEYDIVISOR, m_nSkipUpdate, FALSE );
} /* #OF# CSoundDlg::SetPokeyDivisor */


/////////////////////////////////////////////////////////////////////////////
// CSoundDlg message handlers

/*========================================================
Method   : CSoundDlg::OnInitDialog
=========================================================*/
/* #FN#
   Performs special processing when the dialog box is initialized */
BOOL
/* #AS#
   TRUE unless you set the focus to a control */
CSoundDlg::
OnInitDialog() 
{
	CCommonDlg::OnInitDialog();
	
	m_nLowSkipLimit  = 1;
	m_nHighSkipLimit = tv_mode;
	m_ulSoundState   = g_Sound.ulState;
	m_nSoundRate     = g_Sound.nRate;
	m_nSoundVol      = g_Sound.nVolume;
	m_nSkipUpdate    = g_Sound.nSkipUpdate;
	m_nSoundLatency  = g_Sound.nLatency;
	m_bDigitized     = (BOOL)g_Sound.nDigitized;
	m_bEnableStereo  = (BOOL)stereo_enabled;

	SetDlgState();
	
	return TRUE;  // return TRUE unless you set the focus to a control
	              // EXCEPTION: OCX Property Pages should return FALSE
} /* #OF# CSoundDlg::OnInitDialog */

/*========================================================
Method   : CSoundDlg::OnHScroll
=========================================================*/
/* #FN#
   The framework calls this member function when the user
   clicks a window’s horizontal scroll bar */
void
/* #AS#
   Nothing */
CSoundDlg::
OnHScroll(
	UINT        nSBCode,   /* #IN# A scroll-bar code that indicates the user’s scrolling request */
	UINT        nPos,      /* #IN# Specifies the scroll-box position */
	CScrollBar *pScrollBar /* #IN# A pointer to the scroll-bar control control */
)
{
	if( TB_THUMBTRACK == nSBCode || SB_THUMBPOSITION == nSBCode )
		m_nSoundVol = nPos - 100;
	else
	{
//		CSliderCtrl *pSlider = (CSliderCtrl *)GetDlgItem( IDC_SOUND_VOLUMESLIDER );
//		ASSERT(pSlider);
		if( NULL != pScrollBar )
			m_nSoundVol = ((CSliderCtrl *)pScrollBar)->GetPos() - 100;
	}
	CCommonDlg::OnHScroll( nSBCode, nPos, pScrollBar );
} /* #OF# CSoundDlg::OnHScroll */

/*========================================================
Method   : CSoundDlg::OnSelchangeSoundPlayback
=========================================================*/
/* #FN#
   Sets a state of the object regarding to an appropriate combo box */
void
/* #AS#
   Nothing */
CSoundDlg::
OnSelchangeSoundPlayback()
{
	CComboBox *pCombo = (CComboBox *)GetDlgItem( IDC_SOUND_PLAYBACK );
	ASSERT(pCombo);

	m_nLowSkipLimit = m_pSndModeInfo[ pCombo->GetCurSel() ].nLowLimit;
	m_nSoundRate    = m_pSndModeInfo[ pCombo->GetCurSel() ].nSoundRate;
	SetPokeyDivisor();

	if( m_nSoundRate == 22050 )
		m_ulSoundState &= ~SOUND_CUSTOM_RATE;
	else
		m_ulSoundState |= SOUND_CUSTOM_RATE;
} /* #OF# CSoundDlg::OnSelchangeSoundPlayback */

/*========================================================
Method   : CSoundDlg::OnSoundMute
=========================================================*/
/* #FN#
   Sets a state of the object regarding to an appropriate check box */
void
/* #AS#
   Nothing */
CSoundDlg::
OnSoundMute()
{
	_ClickButton( IDC_SOUND_MUTE, m_ulSoundState, SOUND_NOSOUND );
} /* #OF# CSoundDlg::OnSoundMute */

/*========================================================
Method   : CSoundDlg::OnSelchangeDriverType
=========================================================*/
/* #FN#
   Sets a state of the object regarding to an appropriate combo box */
void
/* #AS#
   Nothing */
CSoundDlg::
OnSelchangeDriverType()
{
	CComboBox *pCombo = (CComboBox *)GetDlgItem( IDC_SOUND_DRIVERTYPE );
	ASSERT(pCombo);

	int nSelection = pCombo->GetCurSel();

	if( CB_ERR == nSelection )
		return;

	if( DT_WAVEOUT == nSelection )
	{
		m_ulSoundState &= ~SOUND_DSSOUND;
		m_ulSoundState |= SOUND_MMSOUND;
	}
	else
	{
		ASSERT(DT_DIRECTX == nSelection);

		m_ulSoundState &= ~SOUND_MMSOUND;
		m_ulSoundState |= SOUND_DSSOUND;
	}
	SetDlgState();
} /* #OF# CSoundDlg::OnSelchangeDriverType */

/*========================================================
Method   : CSoundDlg::OnKillfocusPokeyDivisor
=========================================================*/
/* #FN#
	Sets a state of the object regarding to an appropriate edit control */
void
/* #AS#
   Nothing */
CSoundDlg::
OnKillfocusPokeyDivisor()
{
	BOOL bResult;

	ASSERT(GetDlgItem( IDC_SOUND_POKEYDIVISOR ));
	m_nSkipUpdate = GetDlgItemInt( IDC_SOUND_POKEYDIVISOR, &bResult, FALSE );

	SetPokeyDivisor();
} /* #OF# CSoundDlg::OnKillfocusPokeyDivisor */

/*========================================================
Method   : CSoundDlg::OnKillfocusSoundLatency
=========================================================*/
/* #FN#
	Sets a state of the object regarding to an appropriate edit control */
void
/* #AS#
   Nothing */
CSoundDlg::
OnKillfocusSoundLatency()
{
	_KillfocusSpin( IDC_SOUND_LATENCY, m_nSoundLatency, MIN_SOUND_LATENCY, MAX_SOUND_LATENCY );
} /* #OF# CSoundDlg::OnKillfocusSoundLatency */

/*========================================================
Method   : CSoundDlg::OnDeltaposPokeyDivisorSpin
=========================================================*/
/* #FN#
   Sets a state of the object regarding to an appropriate spin control */
void
/* #AS#
   Nothing */
CSoundDlg::
OnDeltaposPokeyDivisorSpin(
	NMHDR   *pNMHDR,
	LRESULT *pResult
)
{
	NM_UPDOWN *pNMUpDown = (NM_UPDOWN *)pNMHDR;
	m_nSkipUpdate -= pNMUpDown->iDelta;

	SetPokeyDivisor();

	*pResult = m_nSkipUpdate;
} /* #OF# CSoundDlg::OnDeltaposPokeyDivisorSpin */

/*========================================================
Method   : CSoundDlg::OnDeltaposSoundLatency
=========================================================*/
/* #FN#
   Sets a state of the object regarding to an appropriate spin control */
void
/* #AS#
   Nothing */
CSoundDlg::
OnDeltaposSoundLatencySpin(
	NMHDR   *pNMHDR, /* #IN#  */
	LRESULT *pResult /* #OUT# */
)
{
	_DeltaposSpin( pNMHDR, IDC_SOUND_LATENCY, m_nSoundLatency, MIN_SOUND_LATENCY, MAX_SOUND_LATENCY );
	*pResult = 0;
} /* #OF# CSoundDlg::OnDeltaposSoundLatency */

/*========================================================
Method   : CSoundDlg::OnSoundDigitized
=========================================================*/
/* #FN#
   Sets a state of the object regarding to an appropriate check box */
void
/* #AS#
   Nothing */
CSoundDlg::
OnSoundDigitized() 
{
	CButton *pButton = (CButton*)GetDlgItem( IDC_SOUND_VOLUMEONLY );
	ASSERT(pButton);
	m_bDigitized = pButton->GetCheck();
} /* #OF# CSoundDlg::OnSoundDigitized */

/*========================================================
Method   : CSoundDlg::OnSoundStereo
=========================================================*/
/* #FN#
   Sets a state of the object regarding to an appropriate check box */
void
/* #AS#
   Nothing */
CSoundDlg::
OnSoundStereo() 
{
	CButton *pButton = (CButton*)GetDlgItem( IDC_SOUND_STEREO );
	ASSERT(pButton);
	m_bEnableStereo = pButton->GetCheck();
} /* #OF# CSoundDlg::OnSoundStereo */

/*========================================================
Method   : CSoundDlg::ReceiveFocused
=========================================================*/
/* #FN#
   Receive the edit controls content again. The user could press
   'Enter' or 'Alt-O' and then all changes he's made in the last
   edited control would be lost. */
void
/* #AS#
   Nothing */
CSoundDlg::
ReceiveFocused()
{
	CWnd *pWnd    = GetFocus();
	UINT  nCtrlID = pWnd ? pWnd->GetDlgCtrlID() : 0;

	switch( nCtrlID )
	{
		case IDC_SOUND_POKEYDIVISOR:
			OnKillfocusPokeyDivisor();
			break;
		case IDC_SOUND_LATENCY:
			OnKillfocusSoundLatency();
			break;
	}
} /* #OF# CSoundDlg::ReceiveFocused */

/*========================================================
Method   : CSoundDlg::OnOK
=========================================================*/
/* #FN#
   Called when the user clicks the OK button */
void
/* #AS#
   Nothing */
CSoundDlg::
OnOK() 
{
	CSliderCtrl	*pSlider = (CSliderCtrl *)GetDlgItem( IDC_SOUND_VOLUMESLIDER );

	ASSERT(pSlider);

	/* Unfortunately, edit controls do not lose the focus before
	   handling this when the user uses accelerators */
	ReceiveFocused();

	if( m_ulSoundState != g_Sound.ulState )
	{
		g_Sound.ulState = m_ulSoundState;
		WriteRegDWORD( NULL, REG_SOUND_STATE, g_Sound.ulState );
		m_bModeChanged = TRUE;
	}
	if( m_nSoundRate != g_Sound.nRate )
	{
		g_Sound.nRate = m_nSoundRate;
		WriteRegDWORD( NULL, REG_SOUND_RATE, g_Sound.nRate );
		m_bModeChanged = TRUE;
	}
	if( m_nSkipUpdate != g_Sound.nSkipUpdate )
	{
		g_Sound.nSkipUpdate = m_nSkipUpdate;
		WriteRegDWORD( NULL, REG_SOUND_UPDATE, g_Sound.nSkipUpdate );
		m_bModeChanged = TRUE;
	}
	if( m_nSoundLatency != g_Sound.nLatency )
	{
		g_Sound.nLatency = m_nSoundLatency;
		WriteRegDWORD( NULL, REG_SOUND_LATENCY, g_Sound.nLatency );
		m_bModeChanged = TRUE;
	}
	if( m_bDigitized != (BOOL)g_Sound.nDigitized )
	{
		g_Sound.nDigitized = m_bDigitized;
		WriteRegDWORD( NULL, REG_USE_VOLUME_ONLY, g_Sound.nDigitized );
		m_bModeChanged = TRUE;
	}
	if( m_bEnableStereo != (BOOL)stereo_enabled )
	{
		stereo_enabled = (int)m_bEnableStereo;
		WriteRegDWORD( NULL, REG_ENABLE_STEREO, stereo_enabled );
		m_bModeChanged = TRUE;
	}
	/* Set new sound volume */
	if( m_nSoundVol != g_Sound.nVolume )
	{
		g_Sound.nVolume = m_nSoundVol;
		WriteRegDWORD( NULL, REG_SOUND_VOLUME, g_Sound.nVolume );

		Sound_SetVolume();
	}
	CCommonDlg::OnOK();
} /* #OF# CSoundDlg::OnOK */
