mirror of
https://github.com/dabonetn/linapple-pie.git
synced 2024-06-14 13:29:32 +00:00
493 lines
13 KiB
C++
493 lines
13 KiB
C++
|
/*
|
||
|
AppleWin : An Apple //e emulator for Windows
|
||
|
|
||
|
Copyright (C) 1994-1996, Michael O'Brien
|
||
|
Copyright (C) 1999-2001, Oliver Schmidt
|
||
|
Copyright (C) 2002-2005, Tom Charlesworth
|
||
|
Copyright (C) 2006-2007, Tom Charlesworth, Michael Pohoreski
|
||
|
|
||
|
AppleWin 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.
|
||
|
|
||
|
AppleWin 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 AppleWin; if not, write to the Free Software
|
||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
*/
|
||
|
|
||
|
/* Description: Speaker emulation
|
||
|
*
|
||
|
* Author: Various
|
||
|
*/
|
||
|
|
||
|
/* Remake for SDL Audio for Linux (or other SDL-compliant OSes) by beom beotiger --bb */
|
||
|
|
||
|
#include "stdafx.h"
|
||
|
// for _ASSERT ion. (here _ASSERT means Unix(tm) assert) --bb
|
||
|
#include <assert.h>
|
||
|
|
||
|
// Notes:
|
||
|
//
|
||
|
// [OLD: 23.191 Apple CLKs == 44100Hz (CLK_6502/44100)]
|
||
|
// 23 Apple CLKS per PC sample (played back at 44.1KHz)
|
||
|
//
|
||
|
//
|
||
|
// The speaker's wave output drives how much 6502 emulation is done in real-time, eg:
|
||
|
// If the speaker's wave buffer is running out of sample-data, then more 6502 cycles
|
||
|
// need to be executed to top-up the wave buffer.
|
||
|
// This is in contrast to the AY8910 voices, which can simply generate more data if
|
||
|
// their buffers are running low.
|
||
|
|
||
|
|
||
|
// number of channels and buffer size for Apple][ Speakers
|
||
|
static const unsigned short g_nSPKR_NumChannels = 1;
|
||
|
//-------------------------------------
|
||
|
|
||
|
// Globals (SOUND_WAVE)
|
||
|
const short SPKR_DATA_INIT = (short)0x8000; // data written to speakers buffer
|
||
|
|
||
|
static short g_nSpeakerData = SPKR_DATA_INIT;
|
||
|
static short *g_pSpeakerBuffer = NULL;
|
||
|
static UINT g_nBufferIdx = 0;
|
||
|
|
||
|
static short *g_pStereoBuffer = NULL; // buffer for stereo samples
|
||
|
|
||
|
|
||
|
static short *g_pRemainderBuffer = NULL; // Remainder buffer
|
||
|
static UINT g_nRemainderBufferSize; // Setup in SpkrInitialize()
|
||
|
static UINT g_nRemainderBufferIdx; // Setup in SpkrInitialize()
|
||
|
|
||
|
|
||
|
// Application-wide globals:
|
||
|
DWORD soundtype = SOUND_WAVE; //default
|
||
|
double g_fClksPerSpkrSample; // Setup in SetClksPerSpkrSample()
|
||
|
|
||
|
// Globals
|
||
|
static unsigned __int64 g_nSpkrQuietCycleCount = 0;
|
||
|
static unsigned __int64 g_nSpkrLastCycle = 0;
|
||
|
static bool g_bSpkrToggleFlag = false;
|
||
|
|
||
|
static bool g_bSpkrAvailable = false;
|
||
|
static bool g_bSpkrRecentlyActive = false;
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
|
||
|
// Forward refs:
|
||
|
//ULONG Spkr_SubmitWaveBuffer_FullSpeed(short* pSpeakerBuffer, ULONG nNumSamples);
|
||
|
static ULONG Spkr_SubmitWaveBuffer(short* pSpeakerBuffer, ULONG nNumSamples);
|
||
|
static void Spkr_SetActive(bool bActive);
|
||
|
|
||
|
//=============================================================================
|
||
|
// Let us leave benchmark for the near future --bb ^_^
|
||
|
#if 0
|
||
|
static void DisplayBenchmarkResults ()
|
||
|
{
|
||
|
|
||
|
DWORD totaltime = GetTickCount() - extbench;
|
||
|
VideoRedrawScreen();
|
||
|
TCHAR buffer[64];
|
||
|
sprintf(buffer,
|
||
|
TEXT("This benchmark took %u.%02u seconds."),
|
||
|
(unsigned)(totaltime / 1000),
|
||
|
(unsigned)((totaltime / 10) % 100));
|
||
|
/* MessageBox(g_hFrameWindow,
|
||
|
buffer,
|
||
|
TEXT("Benchmark Results"),
|
||
|
MB_ICONINFORMATION | MB_SETFOREGROUND);*/
|
||
|
printf("This benchmark took %u.%02u seconds.",
|
||
|
(unsigned)(totaltime / 1000), (unsigned)((totaltime / 10) % 100));
|
||
|
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
//=============================================================================
|
||
|
|
||
|
static void SetClksPerSpkrSample()
|
||
|
{
|
||
|
// // 23.191 clks for 44.1Khz (when 6502 CLK=1.0Mhz)
|
||
|
// g_fClksPerSpkrSample = g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE;
|
||
|
|
||
|
// Use integer value: Better for MJ Mahon's RT.SYNTH.DSK (integer multiples of 1.023MHz Clk)
|
||
|
// . 23 clks @ 1.023MHz SPKR_SAMPLE_RATE = 44100Hz!?
|
||
|
g_fClksPerSpkrSample = (double) (UINT) (g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE);
|
||
|
}
|
||
|
|
||
|
//=============================================================================
|
||
|
|
||
|
static void InitRemainderBuffer()
|
||
|
{
|
||
|
delete [] g_pRemainderBuffer;
|
||
|
|
||
|
SetClksPerSpkrSample();
|
||
|
|
||
|
g_nRemainderBufferSize = (UINT) g_fClksPerSpkrSample;
|
||
|
if ((double)g_nRemainderBufferSize != g_fClksPerSpkrSample)
|
||
|
g_nRemainderBufferSize++;
|
||
|
|
||
|
g_pRemainderBuffer = new short [g_nRemainderBufferSize];
|
||
|
memset(g_pRemainderBuffer, 0, g_nRemainderBufferSize);
|
||
|
|
||
|
g_nRemainderBufferIdx = 0;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE -----
|
||
|
//
|
||
|
//=============================================================================
|
||
|
|
||
|
void SpkrDestroy ()
|
||
|
{
|
||
|
Spkr_DSUninit();
|
||
|
if(soundtype == SOUND_WAVE)
|
||
|
{
|
||
|
delete [] g_pSpeakerBuffer;
|
||
|
delete [] g_pStereoBuffer;
|
||
|
delete [] g_pRemainderBuffer;
|
||
|
|
||
|
g_pSpeakerBuffer = NULL;
|
||
|
g_pStereoBuffer = NULL;
|
||
|
g_pRemainderBuffer = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//=============================================================================
|
||
|
|
||
|
void SpkrInitialize ()
|
||
|
{
|
||
|
if(g_fh)
|
||
|
{
|
||
|
fprintf(g_fh, "Spkr Config: soundtype = %d ", (int)soundtype);
|
||
|
switch(soundtype)
|
||
|
{
|
||
|
case SOUND_NONE: fprintf(g_fh, "(NONE)\n"); break;
|
||
|
case SOUND_WAVE: fprintf(g_fh, "(WAVE)\n"); break;
|
||
|
default: fprintf(g_fh, "(UNDEFINED!)\n"); break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(g_bDisableDirectSound)
|
||
|
{
|
||
|
// SpeakerVoice.bMute = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//DSInit();
|
||
|
g_bSpkrAvailable = Spkr_DSInit();
|
||
|
}
|
||
|
|
||
|
if (soundtype == SOUND_WAVE)
|
||
|
{
|
||
|
InitRemainderBuffer();
|
||
|
// Buffer can hold a max of 1 seconds worth of samples
|
||
|
g_pSpeakerBuffer = new short [SPKR_SAMPLE_RATE];
|
||
|
g_pStereoBuffer = new short [SPKR_SAMPLE_RATE * 2]; // doubled for stereo
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//=============================================================================
|
||
|
// NB. Called when /g_fCurrentCLK6502/ changes
|
||
|
void SpkrReinitialize ()
|
||
|
{
|
||
|
if (soundtype == SOUND_WAVE)
|
||
|
{
|
||
|
InitRemainderBuffer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//=============================================================================
|
||
|
|
||
|
void SpkrReset()
|
||
|
{
|
||
|
g_nBufferIdx = 0;
|
||
|
g_nSpkrQuietCycleCount = 0;
|
||
|
g_bSpkrToggleFlag = false;
|
||
|
|
||
|
InitRemainderBuffer();
|
||
|
Spkr_SubmitWaveBuffer(NULL, 0);
|
||
|
Spkr_SetActive(false);
|
||
|
Spkr_Demute();
|
||
|
}
|
||
|
|
||
|
//=================2012 AD =========================================================
|
||
|
#if 0
|
||
|
BOOL SpkrSetEmulationType (DWORD newtype)
|
||
|
{
|
||
|
if (soundtype != SOUND_NONE)
|
||
|
SpkrDestroy();
|
||
|
soundtype = newtype;
|
||
|
if (soundtype != SOUND_NONE)
|
||
|
SpkrInitialize();
|
||
|
if (soundtype != newtype)
|
||
|
switch (newtype) { // some fault occured
|
||
|
case SOUND_WAVE:
|
||
|
/* MessageBox(window,
|
||
|
TEXT("The emulator is unable to initialize a waveform ")
|
||
|
TEXT("output device. Make sure you have a sound card ")
|
||
|
TEXT("and a driver installed and that windows is ")
|
||
|
TEXT("correctly configured to use the driver. Also ")
|
||
|
TEXT("ensure that no other program is currently using ")
|
||
|
TEXT("the device."),
|
||
|
TEXT("Configuration"),
|
||
|
MB_ICONEXCLAMATION | MB_SETFOREGROUND);*/
|
||
|
// Need to tuck SDL_GetError() hhere? ------------------------------------------
|
||
|
fprintf(stderr, "Unable to initialize a waveform output device.\n");
|
||
|
return 0;
|
||
|
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
#endif
|
||
|
//=============================================================================
|
||
|
|
||
|
static void ReinitRemainderBuffer(UINT nCyclesRemaining)
|
||
|
{
|
||
|
if(nCyclesRemaining == 0)
|
||
|
return;
|
||
|
|
||
|
for(g_nRemainderBufferIdx=0; g_nRemainderBufferIdx<nCyclesRemaining; g_nRemainderBufferIdx++)
|
||
|
g_pRemainderBuffer[g_nRemainderBufferIdx] = g_nSpeakerData;
|
||
|
|
||
|
_ASSERT(g_nRemainderBufferIdx < g_nRemainderBufferSize);
|
||
|
}
|
||
|
|
||
|
static void UpdateRemainderBuffer(ULONG* pnCycleDiff)
|
||
|
{
|
||
|
if(g_nRemainderBufferIdx)
|
||
|
{
|
||
|
while((g_nRemainderBufferIdx < g_nRemainderBufferSize) && *pnCycleDiff)
|
||
|
{
|
||
|
g_pRemainderBuffer[g_nRemainderBufferIdx] = g_nSpeakerData;
|
||
|
g_nRemainderBufferIdx++;
|
||
|
(*pnCycleDiff)--;
|
||
|
}
|
||
|
|
||
|
if(g_nRemainderBufferIdx == g_nRemainderBufferSize)
|
||
|
{
|
||
|
g_nRemainderBufferIdx = 0;
|
||
|
signed long nSampleMean = 0;
|
||
|
for(UINT i=0; i<g_nRemainderBufferSize; i++)
|
||
|
nSampleMean += (signed long) g_pRemainderBuffer[i];
|
||
|
nSampleMean /= (signed long) g_nRemainderBufferSize;
|
||
|
|
||
|
if(g_nBufferIdx < SPKR_SAMPLE_RATE-1)
|
||
|
g_pSpeakerBuffer[g_nBufferIdx++] = (short) nSampleMean;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void UpdateSpkr()
|
||
|
{
|
||
|
if(!g_bFullSpeed /*|| SoundCore_GetTimerState()*/)
|
||
|
{
|
||
|
ULONG nCycleDiff = (ULONG) (g_nCumulativeCycles - g_nSpkrLastCycle);
|
||
|
|
||
|
UpdateRemainderBuffer(&nCycleDiff);
|
||
|
|
||
|
ULONG nNumSamples = (ULONG) ((double)nCycleDiff / g_fClksPerSpkrSample);
|
||
|
|
||
|
ULONG nCyclesRemaining = (ULONG) ((double)nCycleDiff - (double)nNumSamples * g_fClksPerSpkrSample);
|
||
|
|
||
|
while((nNumSamples--) && (g_nBufferIdx < SPKR_SAMPLE_RATE-1))
|
||
|
g_pSpeakerBuffer[g_nBufferIdx++] = g_nSpeakerData;
|
||
|
|
||
|
ReinitRemainderBuffer(nCyclesRemaining); // Partially fill 1Mhz sample buffer
|
||
|
}
|
||
|
|
||
|
g_nSpkrLastCycle = g_nCumulativeCycles;
|
||
|
}
|
||
|
|
||
|
//=============================================================================
|
||
|
|
||
|
// Called by emulation code when Speaker I/O reg is accessed
|
||
|
BYTE SpkrToggle (WORD, WORD, BYTE, BYTE, ULONG nCyclesLeft)
|
||
|
{
|
||
|
g_bSpkrToggleFlag = true;
|
||
|
|
||
|
if(!g_bFullSpeed)
|
||
|
Spkr_SetActive(true);
|
||
|
|
||
|
needsprecision = cumulativecycles; // ?
|
||
|
|
||
|
// if (extbench)
|
||
|
// {
|
||
|
// DisplayBenchmarkResults();
|
||
|
// extbench = 0;
|
||
|
// }
|
||
|
|
||
|
if (soundtype == SOUND_WAVE)
|
||
|
{
|
||
|
CpuCalcCycles(nCyclesLeft);
|
||
|
|
||
|
UpdateSpkr();
|
||
|
|
||
|
g_nSpeakerData = ~g_nSpeakerData;
|
||
|
}
|
||
|
|
||
|
return MemReadFloatingBus(nCyclesLeft); // reading from $C030..$C03F retrurns unpredictable value?
|
||
|
}
|
||
|
|
||
|
//=============================================================================
|
||
|
|
||
|
// Called by ContinueExecution()
|
||
|
void SpkrUpdate (DWORD totalcycles)
|
||
|
{
|
||
|
if(!g_bSpkrToggleFlag)
|
||
|
{
|
||
|
if(!g_nSpkrQuietCycleCount)
|
||
|
{
|
||
|
g_nSpkrQuietCycleCount = g_nCumulativeCycles;
|
||
|
}
|
||
|
else if(g_nCumulativeCycles - g_nSpkrQuietCycleCount > (unsigned __int64)g_fCurrentCLK6502/5)
|
||
|
{
|
||
|
// After 0.2 sec of Apple time, deactivate spkr voice
|
||
|
// . This allows emulator to auto-switch to full-speed g_nAppMode for fast disk access
|
||
|
Spkr_SetActive(false);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_nSpkrQuietCycleCount = 0;
|
||
|
g_bSpkrToggleFlag = false;
|
||
|
}
|
||
|
|
||
|
if (soundtype == SOUND_WAVE)
|
||
|
{
|
||
|
UpdateSpkr();
|
||
|
ULONG nSamplesUsed;
|
||
|
|
||
|
if(g_bFullSpeed) g_nBufferIdx = 0; // try this --bb
|
||
|
// nSamplesUsed = Spkr_SubmitWaveBuffer/*_FullSpeed*/(g_pSpeakerBuffer, g_nBufferIdx);
|
||
|
else {
|
||
|
nSamplesUsed = Spkr_SubmitWaveBuffer(g_pSpeakerBuffer, g_nBufferIdx);
|
||
|
|
||
|
_ASSERT(nSamplesUsed <= g_nBufferIdx);
|
||
|
if(nSamplesUsed == 0) return;
|
||
|
memmove(g_pSpeakerBuffer, &g_pSpeakerBuffer[nSamplesUsed], g_nBufferIdx-nSamplesUsed);
|
||
|
g_nBufferIdx -= nSamplesUsed;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//=============================================================================
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static ULONG Spkr_SubmitWaveBuffer(short* pSpeakerBuffer, ULONG nNumSamples)
|
||
|
{
|
||
|
// submit nNumSamples (== 2bytes long each (sizeof short))??
|
||
|
// from pSpeakerBuffer to pDSSpkrBuf for callback DSPlaySnd
|
||
|
|
||
|
if(!g_bSpkrRecentlyActive) return nNumSamples;//if not active, just return?
|
||
|
|
||
|
if(pSpeakerBuffer == NULL)
|
||
|
{
|
||
|
// just init sound buffer and cursors??
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// conver mono Speakers sounds to stereo (mainly for Mockingboard support)
|
||
|
UINT len = nNumSamples * 2; // stereo = 2 * mono
|
||
|
UINT i;
|
||
|
|
||
|
for(i = 0; i < len; i += 2)
|
||
|
g_pStereoBuffer[i] = g_pStereoBuffer[i + 1] = pSpeakerBuffer[i >> 1];
|
||
|
|
||
|
// use code from OpenMSX
|
||
|
// DSUploadBuffer(pSpeakerBuffer, nNumSamples);
|
||
|
DSUploadBuffer(g_pStereoBuffer, len); // submit stereo wave data
|
||
|
return nNumSamples; // always return as if we've filled everything!? --bb
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////// Mute - set volume to MINIMUM, Demute - set volume to NORMAL STATE? -bb
|
||
|
void Spkr_Mute()
|
||
|
{
|
||
|
SDL_PauseAudio(1); // dangerous functiouse - will mute Mockingboard, too. Need to be changed
|
||
|
}
|
||
|
|
||
|
void Spkr_Demute()
|
||
|
{
|
||
|
SDL_PauseAudio(0);
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
static void Spkr_SetActive(bool bActive)
|
||
|
{ // yes, I know the right way is: g_bSpkrRecentlyActive = bActive;, but... ^_^ --bb
|
||
|
if(bActive)
|
||
|
{
|
||
|
// Called by SpkrToggle() or SpkrReset()
|
||
|
g_bSpkrRecentlyActive = true;
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Called by SpkrUpdate() after 0.2s of speaker inactivity
|
||
|
g_bSpkrRecentlyActive = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool Spkr_IsActive()
|
||
|
{
|
||
|
return g_bSpkrRecentlyActive;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// How to deal with volume in SDL Audio
|
||
|
// may be need to go to SDL Mixer?
|
||
|
DWORD SpkrGetVolume()
|
||
|
{
|
||
|
// return SpeakerVoice.dwUserVolume;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void SpkrSetVolume(DWORD dwVolume, DWORD dwVolumeMax)
|
||
|
{
|
||
|
/* SpeakerVoice.dwUserVolume = dwVolume;
|
||
|
|
||
|
SpeakerVoice.nVolume = NewVolume(dwVolume, dwVolumeMax);
|
||
|
|
||
|
if(SpeakerVoice.bActive)
|
||
|
SpeakerVoice.lpDSBvoice->SetVolume(SpeakerVoice.nVolume);*/
|
||
|
}
|
||
|
|
||
|
//=============================================================================
|
||
|
|
||
|
bool Spkr_DSInit()
|
||
|
{
|
||
|
//
|
||
|
// Create single Apple speaker voice
|
||
|
//
|
||
|
if(!g_bDSAvailable)
|
||
|
return false; // do not have DirectSound? Sorry, SDL Audio! ^_^ --bb
|
||
|
return true;
|
||
|
|
||
|
}
|
||
|
|
||
|
void Spkr_DSUninit()
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
//=============================================================================
|
||
|
|
||
|
DWORD SpkrGetSnapshot(SS_IO_Speaker* pSS)
|
||
|
{
|
||
|
pSS->g_nSpkrLastCycle = g_nSpkrLastCycle;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
DWORD SpkrSetSnapshot(SS_IO_Speaker* pSS)
|
||
|
{
|
||
|
g_nSpkrLastCycle = pSS->g_nSpkrLastCycle;
|
||
|
return 0;
|
||
|
}
|
||
|
|