mirror of
https://github.com/Oibaf66/frodo-wii.git
synced 2024-11-29 15:04:23 +01:00
557 lines
12 KiB
OpenEdge ABL
557 lines
12 KiB
OpenEdge ABL
|
/*
|
||
|
* SID_WIN32.i - 6581 emulation, WIN32 specific stuff
|
||
|
*
|
||
|
* Frodo (C) 1994-1997,2002 Christian Bauer
|
||
|
* WIN32 code by J. Richard Sladkey <jrs@world.std.com>
|
||
|
*/
|
||
|
|
||
|
#include <dsound.h>
|
||
|
|
||
|
#include "VIC.h"
|
||
|
#include "main.h"
|
||
|
|
||
|
#define FRAG_FREQ SCREEN_FREQ // one frag per frame
|
||
|
|
||
|
#define FRAGMENT_SIZE (SAMPLE_FREQ/FRAG_FREQ) // samples, not bytes
|
||
|
#define FRAG_INTERVAL (1000/FRAG_FREQ) // in milliseconds
|
||
|
#define BUFFER_FRAGS FRAG_FREQ // frags the in buffer
|
||
|
#define BUFFER_SIZE (2*FRAGMENT_SIZE*BUFFER_FRAGS) // bytes, not samples
|
||
|
#define MAX_LEAD_AVG BUFFER_FRAGS // lead average count
|
||
|
|
||
|
// This won't hurt DirectX 2 but it will help when using the DirectX 3 runtime.
|
||
|
#if !defined(DSBCAPS_GETCURRENTPOSITION2)
|
||
|
#define DSBCAPS_GETCURRENTPOSITION2 0x00010000
|
||
|
#endif
|
||
|
|
||
|
class DigitalPlayer {
|
||
|
|
||
|
public:
|
||
|
virtual ~DigitalPlayer() = 0;
|
||
|
virtual BOOL Ready() = 0;
|
||
|
virtual int GetCurrentPosition() = 0;
|
||
|
virtual void Write(void *buffer, int position, int length) = 0;
|
||
|
virtual void Pause() = 0;
|
||
|
virtual void Resume() = 0;
|
||
|
};
|
||
|
|
||
|
DigitalPlayer::~DigitalPlayer()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
class DirectSound: public DigitalPlayer {
|
||
|
|
||
|
public:
|
||
|
DirectSound();
|
||
|
~DirectSound();
|
||
|
BOOL Ready();
|
||
|
int GetCurrentPosition();
|
||
|
void Write(void *buffer, int position, int length);
|
||
|
void Pause();
|
||
|
void Resume();
|
||
|
|
||
|
private:
|
||
|
BOOL ready;
|
||
|
LPDIRECTSOUND pDS;
|
||
|
LPDIRECTSOUNDBUFFER pPrimaryBuffer;
|
||
|
LPDIRECTSOUNDBUFFER pSoundBuffer;
|
||
|
};
|
||
|
|
||
|
class WaveOut: public DigitalPlayer {
|
||
|
|
||
|
public:
|
||
|
WaveOut();
|
||
|
~WaveOut();
|
||
|
BOOL Ready();
|
||
|
int GetCurrentPosition();
|
||
|
void Write(void *buffer, int position, int length);
|
||
|
void Pause();
|
||
|
void Resume();
|
||
|
|
||
|
private:
|
||
|
void UnprepareHeader(int index);
|
||
|
void UnprepareHeaders();
|
||
|
|
||
|
private:
|
||
|
BOOL ready;
|
||
|
HWAVEOUT hWaveOut;
|
||
|
char wave_buffer[BUFFER_SIZE];
|
||
|
WAVEHDR wave_header[SCREEN_FREQ];
|
||
|
int last_unprepared;
|
||
|
};
|
||
|
|
||
|
void DigitalRenderer::init_sound()
|
||
|
{
|
||
|
ready = FALSE;
|
||
|
sound_buffer = new SWORD[2*FRAGMENT_SIZE];
|
||
|
ThePlayer = 0;
|
||
|
to_output = 0;
|
||
|
divisor = 0;
|
||
|
lead = new int[MAX_LEAD_AVG];
|
||
|
|
||
|
StartPlayer();
|
||
|
}
|
||
|
|
||
|
DigitalRenderer::~DigitalRenderer()
|
||
|
{
|
||
|
StopPlayer();
|
||
|
|
||
|
delete[] sound_buffer;
|
||
|
delete[] lead;
|
||
|
}
|
||
|
|
||
|
void DigitalRenderer::StartPlayer()
|
||
|
{
|
||
|
direct_sound = ThePrefs.DirectSound;
|
||
|
if (ThePrefs.DirectSound)
|
||
|
ThePlayer = new DirectSound;
|
||
|
else
|
||
|
ThePlayer = new WaveOut;
|
||
|
ready = ThePlayer->Ready();
|
||
|
sb_pos = 0;
|
||
|
memset(lead, 0, sizeof(lead));
|
||
|
lead_pos = 0;
|
||
|
}
|
||
|
|
||
|
void DigitalRenderer::StopPlayer()
|
||
|
{
|
||
|
delete ThePlayer;
|
||
|
ready = FALSE;
|
||
|
}
|
||
|
|
||
|
void DigitalRenderer::EmulateLine()
|
||
|
{
|
||
|
if (!ready)
|
||
|
return;
|
||
|
|
||
|
sample_buf[sample_in_ptr] = volume;
|
||
|
sample_in_ptr = (sample_in_ptr + 1) % SAMPLE_BUF_SIZE;
|
||
|
|
||
|
#if 0
|
||
|
// Now see how many samples have to be added for this line.
|
||
|
// XXX: This is too much computation here, precompute it.
|
||
|
divisor += SAMPLE_FREQ;
|
||
|
while (divisor >= 0)
|
||
|
divisor -= TOTAL_RASTERS*SCREEN_FREQ, to_output++;
|
||
|
|
||
|
// Calculate the sound data only when we have enough to fill
|
||
|
// the buffer entirely.
|
||
|
if (to_output < FRAGMENT_SIZE)
|
||
|
return;
|
||
|
to_output -= FRAGMENT_SIZE;
|
||
|
|
||
|
VBlank();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void DigitalRenderer::VBlank()
|
||
|
{
|
||
|
if (!ready)
|
||
|
return;
|
||
|
|
||
|
// Delete and recreate the player if preferences have changed.
|
||
|
if (direct_sound != ThePrefs.DirectSound) {
|
||
|
StopPlayer();
|
||
|
StartPlayer();
|
||
|
}
|
||
|
|
||
|
// Convert latency preferences from milliseconds to frags.
|
||
|
int lead_smooth = ThePrefs.LatencyAvg/FRAG_INTERVAL;
|
||
|
int lead_hiwater = ThePrefs.LatencyMax/FRAG_INTERVAL;
|
||
|
int lead_lowater = ThePrefs.LatencyMin/FRAG_INTERVAL;
|
||
|
|
||
|
// Compute the current lead in frags.
|
||
|
int current_position = ThePlayer->GetCurrentPosition();
|
||
|
if (current_position == -1)
|
||
|
return;
|
||
|
int lead_in_bytes = (sb_pos - current_position + BUFFER_SIZE) % BUFFER_SIZE;
|
||
|
if (lead_in_bytes >= BUFFER_SIZE/2)
|
||
|
lead_in_bytes -= BUFFER_SIZE;
|
||
|
int lead_in_frags = lead_in_bytes / int(2*FRAGMENT_SIZE);
|
||
|
lead[lead_pos++] = lead_in_frags;
|
||
|
if (lead_pos == lead_smooth)
|
||
|
lead_pos = 0;
|
||
|
|
||
|
// Compute the average lead in frags.
|
||
|
int avg_lead = 0;
|
||
|
for (int i = 0; i < lead_smooth; i++)
|
||
|
avg_lead += lead[i];
|
||
|
avg_lead /= lead_smooth;
|
||
|
//Debug("lead = %d, avg = %d\n", lead_in_frags, avg_lead);
|
||
|
|
||
|
// If we're getting too far ahead of the audio skip a frag.
|
||
|
if (avg_lead > lead_hiwater) {
|
||
|
for (int i = 0; i < lead_smooth; i++)
|
||
|
lead[i]--;
|
||
|
Debug("Skipping a frag...\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Calculate one frag.
|
||
|
int nsamples = FRAGMENT_SIZE;
|
||
|
calc_buffer(sound_buffer, 2*FRAGMENT_SIZE);
|
||
|
|
||
|
// If we're getting too far behind the audio add an extra frag.
|
||
|
if (avg_lead < lead_lowater) {
|
||
|
for (int i = 0; i < lead_smooth; i++)
|
||
|
lead[i]++;
|
||
|
Debug("Adding an extra frag...\n");
|
||
|
calc_buffer(sound_buffer + FRAGMENT_SIZE, 2*FRAGMENT_SIZE);
|
||
|
nsamples += FRAGMENT_SIZE;
|
||
|
}
|
||
|
|
||
|
// Write the frags to the player and update out write position.
|
||
|
ThePlayer->Write(sound_buffer, sb_pos, 2*nsamples);
|
||
|
sb_pos = (sb_pos + 2*nsamples) % BUFFER_SIZE;
|
||
|
}
|
||
|
|
||
|
void DigitalRenderer::Pause()
|
||
|
{
|
||
|
if (!ready)
|
||
|
return;
|
||
|
ThePlayer->Pause();
|
||
|
}
|
||
|
|
||
|
void DigitalRenderer::Resume()
|
||
|
{
|
||
|
if (!ready)
|
||
|
return;
|
||
|
ThePlayer->Resume();
|
||
|
}
|
||
|
|
||
|
// Direct sound implemenation.
|
||
|
|
||
|
DirectSound::DirectSound()
|
||
|
{
|
||
|
ready = FALSE;
|
||
|
pDS = NULL;
|
||
|
pPrimaryBuffer = NULL;
|
||
|
pSoundBuffer = NULL;
|
||
|
|
||
|
HRESULT dsrval = DirectSoundCreate(NULL, &pDS, NULL);
|
||
|
if (dsrval != DS_OK) {
|
||
|
DebugResult("DirectSoundCreate failed", dsrval);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Set cooperative level trying to get exclusive or normal mode.
|
||
|
DWORD level = ThePrefs.ExclusiveSound ? DSSCL_EXCLUSIVE : DSSCL_NORMAL;
|
||
|
dsrval = pDS->SetCooperativeLevel(hwnd, level);
|
||
|
if (dsrval != DS_OK) {
|
||
|
DebugResult("SetCooperativeLevel failed", dsrval);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
WAVEFORMATEX wfx;
|
||
|
memset(&wfx, 0, sizeof(wfx));
|
||
|
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||
|
wfx.nChannels = 1;
|
||
|
wfx.nSamplesPerSec = 44100;
|
||
|
wfx.wBitsPerSample = 16;
|
||
|
wfx.nBlockAlign = wfx.nChannels*wfx.wBitsPerSample/8;
|
||
|
wfx.nAvgBytesPerSec = wfx.nBlockAlign*wfx.nSamplesPerSec;
|
||
|
wfx.cbSize = 0;
|
||
|
|
||
|
DSBUFFERDESC dsbd;
|
||
|
memset(&dsbd, 0, sizeof(dsbd));
|
||
|
dsbd.dwSize = sizeof(dsbd);
|
||
|
dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
||
|
dsbd.dwBufferBytes = 0;
|
||
|
dsbd.lpwfxFormat = NULL;
|
||
|
|
||
|
dsrval = pDS->CreateSoundBuffer(&dsbd, &pPrimaryBuffer, NULL);
|
||
|
if (dsrval != DS_OK) {
|
||
|
DebugResult("CreateSoundBuffer for primary failed", dsrval);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
dsrval = pPrimaryBuffer->SetFormat(&wfx);
|
||
|
if (dsrval != DS_OK) {
|
||
|
DebugResult("SetFormat on primary failed", dsrval);
|
||
|
//return;
|
||
|
}
|
||
|
|
||
|
dsrval = pPrimaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
|
||
|
if (dsrval != DS_OK) {
|
||
|
DebugResult("Play primary failed", dsrval);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
dsbd.dwSize = sizeof(dsbd);
|
||
|
dsbd.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
|
||
|
dsbd.dwBufferBytes = BUFFER_SIZE;
|
||
|
dsbd.lpwfxFormat = &wfx;
|
||
|
|
||
|
dsrval = pDS->CreateSoundBuffer(&dsbd, &pSoundBuffer, NULL);
|
||
|
if (dsrval != DS_OK) {
|
||
|
DebugResult("CreateSoundBuffer failed", dsrval);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ready = TRUE;
|
||
|
}
|
||
|
|
||
|
DirectSound::~DirectSound()
|
||
|
{
|
||
|
if (pDS != NULL) {
|
||
|
if (pSoundBuffer != NULL) {
|
||
|
pSoundBuffer->Release();
|
||
|
pSoundBuffer = NULL;
|
||
|
}
|
||
|
if (pPrimaryBuffer != NULL) {
|
||
|
pPrimaryBuffer->Release();
|
||
|
pPrimaryBuffer = NULL;
|
||
|
}
|
||
|
pDS->Release();
|
||
|
pDS = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BOOL DirectSound::Ready()
|
||
|
{
|
||
|
return ready;
|
||
|
}
|
||
|
|
||
|
int DirectSound::GetCurrentPosition()
|
||
|
{
|
||
|
DWORD dwPlayPos, dwWritePos;
|
||
|
HRESULT dsrval = pSoundBuffer->GetCurrentPosition(&dwPlayPos, &dwWritePos);
|
||
|
if (dsrval != DS_OK) {
|
||
|
DebugResult("GetCurrentPostion failed", dsrval);
|
||
|
return -1;
|
||
|
}
|
||
|
return dwWritePos;
|
||
|
}
|
||
|
|
||
|
void DirectSound::Write(void *buffer, int position, int length)
|
||
|
{
|
||
|
// Lock sound buffer.
|
||
|
LPVOID pMem1, pMem2;
|
||
|
DWORD dwSize1, dwSize2;
|
||
|
HRESULT dsrval = pSoundBuffer->Lock(position, length,
|
||
|
&pMem1, &dwSize1, &pMem2, &dwSize2, 0);
|
||
|
if (dsrval != DS_OK) {
|
||
|
DebugResult("Sound Lock failed", dsrval);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Copy the sample buffer into the sound buffer.
|
||
|
BYTE *pSample = (BYTE *) buffer;
|
||
|
memcpy(pMem1, pSample, dwSize1);
|
||
|
if (dwSize2 != 0)
|
||
|
memcpy(pMem2, pSample + dwSize1, dwSize2);
|
||
|
|
||
|
// Unlock the sound buffer.
|
||
|
dsrval = pSoundBuffer->Unlock(pMem1, dwSize1, pMem2, dwSize2);
|
||
|
if (dsrval != DS_OK) {
|
||
|
DebugResult("Unlock failed\n", dsrval);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Play the sound buffer.
|
||
|
dsrval = pSoundBuffer->Play(0, 0, DSBPLAY_LOOPING);
|
||
|
if (dsrval != DS_OK) {
|
||
|
DebugResult("Play failed", dsrval);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DirectSound::Pause()
|
||
|
{
|
||
|
HRESULT dsrval = pSoundBuffer->Stop();
|
||
|
if (dsrval != DS_OK)
|
||
|
DebugResult("Stop failed", dsrval);
|
||
|
dsrval = pPrimaryBuffer->Stop();
|
||
|
if (dsrval != DS_OK)
|
||
|
DebugResult("Stop primary failed", dsrval);
|
||
|
}
|
||
|
|
||
|
void DirectSound::Resume()
|
||
|
{
|
||
|
HRESULT dsrval = pPrimaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
|
||
|
if (dsrval != DS_OK)
|
||
|
DebugResult("Play primary failed", dsrval);
|
||
|
dsrval = pSoundBuffer->Play(0, 0, DSBPLAY_LOOPING);
|
||
|
if (dsrval != DS_OK)
|
||
|
DebugResult("Play failed", dsrval);
|
||
|
}
|
||
|
|
||
|
// Wave output implemenation.
|
||
|
|
||
|
WaveOut::WaveOut()
|
||
|
{
|
||
|
ready = FALSE;
|
||
|
|
||
|
WAVEFORMATEX wfx;
|
||
|
memset(&wfx, 0, sizeof(wfx));
|
||
|
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||
|
wfx.nChannels = 1;
|
||
|
wfx.nSamplesPerSec = 44100;
|
||
|
wfx.wBitsPerSample = 16;
|
||
|
wfx.nBlockAlign = wfx.nChannels*wfx.wBitsPerSample/8;
|
||
|
wfx.nAvgBytesPerSec = wfx.nBlockAlign*wfx.nSamplesPerSec;
|
||
|
wfx.cbSize = 0;
|
||
|
MMRESULT worval = waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0, 0, 0);
|
||
|
if (worval != MMSYSERR_NOERROR) {
|
||
|
Debug("waveOutOpen returned %d\n", worval);
|
||
|
return;
|
||
|
}
|
||
|
memset(wave_header, 0, sizeof(wave_header));
|
||
|
|
||
|
last_unprepared = 0;
|
||
|
|
||
|
ready = TRUE;
|
||
|
}
|
||
|
|
||
|
WaveOut::~WaveOut()
|
||
|
{
|
||
|
waveOutReset(hWaveOut);
|
||
|
UnprepareHeaders();
|
||
|
waveOutClose(hWaveOut);
|
||
|
}
|
||
|
|
||
|
BOOL WaveOut::Ready()
|
||
|
{
|
||
|
return ready;
|
||
|
}
|
||
|
|
||
|
int WaveOut::GetCurrentPosition()
|
||
|
{
|
||
|
MMTIME mmtime;
|
||
|
memset(&mmtime, 0, sizeof(mmtime));
|
||
|
mmtime.wType = TIME_BYTES;
|
||
|
MMRESULT worval = waveOutGetPosition(hWaveOut, &mmtime, sizeof(mmtime));
|
||
|
if (worval != MMSYSERR_NOERROR) {
|
||
|
Debug("waveOutGetPosition(%d) returned %d\n", worval);
|
||
|
return -1;
|
||
|
}
|
||
|
int current_position = mmtime.u.cb % BUFFER_SIZE;
|
||
|
return current_position;
|
||
|
}
|
||
|
|
||
|
void WaveOut::Write(void *buffer, int position, int length)
|
||
|
{
|
||
|
// If we are called for a double length buffer split it in half.
|
||
|
if (length == 4*FRAGMENT_SIZE) {
|
||
|
int half = length/2;
|
||
|
Write(buffer, position, half);
|
||
|
Write(((char *) buffer) + half, position + half, half);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Free up as many previous frags as possible.
|
||
|
for (;;) {
|
||
|
WAVEHDR &wh = wave_header[last_unprepared];
|
||
|
if (!(wh.dwFlags & WHDR_DONE))
|
||
|
break;
|
||
|
UnprepareHeader(last_unprepared);
|
||
|
last_unprepared++;
|
||
|
if (last_unprepared == BUFFER_FRAGS)
|
||
|
last_unprepared = 0;
|
||
|
}
|
||
|
|
||
|
// Make sure the current header isn't in use.
|
||
|
int index = position/(2*FRAGMENT_SIZE);
|
||
|
WAVEHDR &wh = wave_header[index];
|
||
|
if (wh.dwFlags & WHDR_DONE)
|
||
|
UnprepareHeader(index);
|
||
|
if (wh.dwFlags != 0) {
|
||
|
Debug("wave header %d is in use!\n", index);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Prepare the header and write the sound data.
|
||
|
wh.lpData = wave_buffer + position;
|
||
|
wh.dwBufferLength = length;
|
||
|
wh.dwFlags = 0;
|
||
|
memcpy(wh.lpData, buffer, length);
|
||
|
MMRESULT worval = waveOutPrepareHeader(hWaveOut, &wh, sizeof(wh));
|
||
|
if (worval != MMSYSERR_NOERROR) {
|
||
|
Debug("waveOutPrepareHeader(%d) returned %d\n", index, worval);
|
||
|
return;
|
||
|
}
|
||
|
worval = waveOutWrite(hWaveOut, &wh, sizeof(wh));
|
||
|
if (worval != MMSYSERR_NOERROR) {
|
||
|
Debug("waveOutWrite(%d) returned %d\n", index, worval);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WaveOut::Pause()
|
||
|
{
|
||
|
waveOutPause(hWaveOut);
|
||
|
}
|
||
|
|
||
|
void WaveOut::Resume()
|
||
|
{
|
||
|
waveOutRestart(hWaveOut);
|
||
|
}
|
||
|
|
||
|
void WaveOut::UnprepareHeader(int index)
|
||
|
{
|
||
|
WAVEHDR &wh = wave_header[index];
|
||
|
MMRESULT worval = waveOutUnprepareHeader(hWaveOut, &wh, sizeof(wh));
|
||
|
if (worval != MMSYSERR_NOERROR) {
|
||
|
Debug("waveOutUnprepareHeader(%d) returned %d\n", index, worval);
|
||
|
return;
|
||
|
}
|
||
|
memset(&wh, 0, sizeof(wh));
|
||
|
}
|
||
|
|
||
|
void WaveOut::UnprepareHeaders()
|
||
|
{
|
||
|
for (int i = 0; i < BUFFER_FRAGS; i++) {
|
||
|
WAVEHDR &wh = wave_header[i];
|
||
|
if (wh.dwFlags & WHDR_DONE)
|
||
|
UnprepareHeader(i);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
|
||
|
// Log player implemenation.
|
||
|
|
||
|
void Log::Write()
|
||
|
{
|
||
|
// Dump the sound output to the log for debugging.
|
||
|
{
|
||
|
int last = sound_buffer[0];
|
||
|
int count = 1;
|
||
|
for (int i = 1; i < nsamples; i++) {
|
||
|
if (sound_buffer[i] == last) {
|
||
|
count++;
|
||
|
continue;
|
||
|
}
|
||
|
Debug("[%dx%d] ", count, last);
|
||
|
count = 1;
|
||
|
last = sound_buffer[i];
|
||
|
}
|
||
|
Debug("[%dx%d] ", count, last);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
#if 0
|
||
|
|
||
|
// File player implemenation.
|
||
|
|
||
|
void Log::Write()
|
||
|
{
|
||
|
// Log the sound output to a file for debugging.
|
||
|
{
|
||
|
static FILE *ofp = NULL;
|
||
|
if (ofp == NULL) {
|
||
|
ofp = fopen("debug.sid", "wb");
|
||
|
if (ofp == NULL)
|
||
|
Debug("fopen failed\n");
|
||
|
}
|
||
|
if (ofp != NULL) {
|
||
|
Debug("Write sound data to file\n");
|
||
|
if (fwrite(sound_buffer, 1, 2*nsamples, ofp) != 2*nsamples)
|
||
|
Debug("fwrite failed\n");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|