Timing model back to nanosleep for sound fidelity

This is part 1 of 2

    * Proper emulator timing is critical for emulating the Apple2 speaker with a
      square wave on a modern soundcard.  The AppleWin sources provided
      reference, inspiration, and source for both timing and speaker emulation
      code.

    * cpu.S, cpu.h, timing.c : OK so I flip-flopped quite a bit here on how best
      to do proper emulator timing, but the previous spinloop implementation was
      not one of my more enlightened decisions :P ... cpu/power consumption
      nightmare ...

    * ds-shim.h, win-shim.h -- Shims for Windows-isms and DirectSound-isms.  I
      assume these will eventually be refactored away...

    * soundcore.[hc], speaker.[hc] -- Directly ported from a recent version of
      AppleWin.  Everything I've added should be braced with #ifdef APPLE2IX.

    * soundcore-alsa.[hc] -- An ALSA backend using mmap'ed soundcard access.
This commit is contained in:
Aaron Culliney 2013-10-05 23:22:08 -07:00
parent 0f26899420
commit 41a1f3d598
13 changed files with 3696 additions and 151 deletions

View File

@ -27,14 +27,17 @@
.comm SN(cpu65_delay),4
.comm SN(cpu65__opcodes),1024
.comm SN(cpu65__signal),1
.comm SN(cpu65_debug),5
.comm SN(cpu65_do_reboot),1
.comm SN(cpu65_cycle_count),2
.comm SN(cpu65_cycles_to_execute),2
.comm SN(cpu65_debug),7
.comm SN(cpu65_current),7
#define DebugCurrEA SN(cpu65_debug)
#define DebugCurrByte SN(cpu65_debug)+2
#define DebugCurrRW SN(cpu65_debug)+3
#define DebugCurrOpcode SN(cpu65_debug)+4
#define DebugCycleCount SN(cpu65_debug)+5
#define DebugCycleCount SN(cpu65_debug)+5 // TODO FIXME : need a better name here and possibly in the .h file too (reflecting the fact that this is an xtra cycles counter
/* -------------------------------------------------------------------------
CPU (6502) Helper Routines
@ -55,7 +58,13 @@
decw EffectiveAddr; \
movb %al, %ah; \
call *SN(cpu65_vmem) \
(,EffectiveAddr_E,8); \
(,EffectiveAddr_E,8);
#define JumpNextInstruction \
GetFromPC_B \
movb %al, DebugCurrOpcode; \
movb $0, DebugCycleCount; \
jmp *cpu65__opcodes(,%eax,4);
#define GetFromEA_B \
orb $1, DebugCurrRW; \
@ -91,8 +100,6 @@
call *SN(cpu65_vmem) \
(,EffectiveAddr_E,8); \
// NOTE: the orb functions as a move, but we want to
// set the flags and we know %ah is zero
#define Continue \
xorl %eax, %eax; \
orb SN(cpu65__signal), %ah; \
@ -2654,20 +2661,26 @@ op_UNK_XMA_imm:
#pragma mark cpu main entry
/* -------------------------------------------------------------------------
CPU throttling and continue
CPU continue
Keep executing until we've executed >= cpu65_cycles_to_execute
------------------------------------------------------------------------- */
continue: SaveState
call SN(timing_throttle)
ReplaceState
xorb %ah, %ah
movb $0, DebugCurrRW
movb $0, DebugCycleCount
GetFromPC_B
movb %al, DebugCurrOpcode
jmp *cpu65__opcodes(,%eax,4)
continue: xorl %eax, %eax
movb DebugCurrOpcode, %al
movb SN(cpu65__opcycles)(,%eax,1), %al
addb DebugCycleCount, %al
addw %ax, SN(cpu65_cycle_count) // TODO: cycle counting is slightly incorrect, it should be done earlier per instruction ...
subw %ax, SN(cpu65_cycles_to_execute)
jle 1f
JumpNextInstruction
1: SaveState
popal
ret
/* -------------------------------------------------------------------------
Exception handler
------------------------------------------------------------------------- */
/* Exception handler */
exception: cmpb $RebootSig, %ah
jz ex_reboot
cmpb $ResetSig, %ah
@ -2688,11 +2701,11 @@ ex_enter: SaveState
popal
ReplaceState
xorb %ah, %ah
GetFromPC_B
jmp *cpu65__opcodes(,%eax,4)
JumpNextInstruction
ex_reboot: popal // EXIT CPURUN
movb $0, SN(cpu65__signal)
ex_reboot: movb $0, SN(cpu65__signal) // EXIT CPURUN
movb $1, SN(cpu65_do_reboot)
popal
ret
ex_reset: movb $0, SN(cpu65__signal)
@ -2700,14 +2713,18 @@ ex_reset: movb $0, SN(cpu65__signal)
GetFromEA_W
movw %ax, PC_Reg
xorb %ah, %ah
GetFromPC_B
jmp *cpu65__opcodes(,%eax,4)
JumpNextInstruction
/* -------------------------------------------------------------------------
CPU thread run "loop"
------------------------------------------------------------------------- */
E(cpu65_run)
pushal // ENTER CPURUN
cmpb $0, SN(cpu65_do_reboot)
jnz 1f
ReplaceState
JumpNextInstruction
1: movb $0, SN(cpu65_do_reboot)
/* Zero all registers, as well as the unused 32-bit parts
* of variables. (which may need to be kept 0)
*
@ -2720,7 +2737,7 @@ E(cpu65_run)
movl $0x1FF, %edx # Stack pointer
jmp ex_reset
E (cpu65_direct_write)
E(cpu65_direct_write)
/* NB: dependent on register choices */
pushl %edi
movl 8(%esp),%edi
@ -2732,10 +2749,9 @@ E (cpu65_direct_write)
// steps the simulation while remaining in the debugger's control
E(cpu65_step)
pushal
movb $DebugStepSig,SN(cpu65__signal)
movb $DebugStepSig, SN(cpu65__signal)
ReplaceState
GetFromPC_B
jmp *cpu65__opcodes(,%eax,4)
JumpNextInstruction
#pragma mark -
#pragma mark tables

View File

@ -75,6 +75,9 @@ extern unsigned char cpu65_flags_encode[256];
extern unsigned char cpu65_flags_decode[256];
extern unsigned int cpu65_delay;
extern int16_t cpu65_cycle_count;
extern int16_t cpu65_cycles_to_execute;
extern uint8_t cpu65_do_reboot;
#endif /* !__ASSEMBLER__ */

200
src/ds-shim.h Normal file
View File

@ -0,0 +1,200 @@
/*
* Apple // emulator for *nix
*
* This software package is subject to the GNU General Public License
* version 2 or later (your choice) as published by the Free Software
* Foundation.
*
* THERE ARE NO WARRANTIES WHATSOEVER.
*
*/
#ifndef _DS_SHIM_H_
#define _DS_SHIM_H_
#include "win-shim.h"
// 2013/09/19 - http://msdn.microsoft.com/en-us/library/ms897820.aspx
typedef struct IDirectSoundBuffer {
void *implementation_specific;
HRESULT (*SetVolume)(LONG lVolume);
HRESULT (*GetVolume)(LPLONG lplVolume);
HRESULT (*GetCurrentPosition)(LPDWORD lpdwCurrentPlayCursor, LPDWORD lpdwCurrentWriteCursor);
HRESULT (*SetCurrentPosition)(DWORD dwNewPosition);
HRESULT (*Stop)();
// This method restores the memory allocation for a lost sound buffer for the specified DirectSoundBuffer object.
HRESULT (*Restore)();
HRESULT (*Play)(DWORD dwReserved1, DWORD dwReserved2, DWORD dwFlags);
// This method obtains a valid write pointer to the sound buffer's audio data
HRESULT (*Lock)(DWORD dwWriteCursor, DWORD dwWriteBytes, LPVOID* lplpvAudioPtr1, LPDWORD lpdwAudioBytes1, LPVOID* lplpvAudioPtr2, LPDWORD lpdwAudioBytes2, DWORD dwFlags);
// This method releases a locked sound buffer.
HRESULT (*Unlock)(LPVOID lpvAudioPtr1, DWORD dwAudioBytes1, LPVOID lpvAudioPtr2, DWORD dwAudioBytes2);
HRESULT (*GetStatus)(LPDWORD lpdwStatus);
} IDirectSoundBuffer, *LPDIRECTSOUNDBUFFER, **LPLPDIRECTSOUNDBUFFER;
#define DS_OK 0
// The call failed because resources (such as a priority level)
// were already being used by another caller.
#define DSERR_ALLOCATED 0x8878000A
// The control (vol,pan,etc.) requested by the caller is not available.
#define DSERR_CONTROLUNAVAIL 0x8878001E
// An invalid parameter was passed to the returning function
#define DSERR_INVALIDPARAM 0x80070057
// This call is not valid for the current state of this object
#define DSERR_INVALIDCALL 0x88780032
// An undetermined error occured inside the DirectSound subsystem
#define DSERR_GENERIC 0x80004005
// The caller does not have the priority level required for the function to
// succeed.
#define DSERR_PRIOLEVELNEEDED 0x88780046
// Not enough free memory is available to complete the operation
#define DSERR_OUTOFMEMORY 0x8007000E
// The specified WAVE format is not supported
#define DSERR_BADFORMAT 0x88780064
// The function called is not supported at this time
#define DSERR_UNSUPPORTED 0x80004001
// No sound driver is available for use
#define DSERR_NODRIVER 0x88780078
// This object is already initialized
#define DSERR_ALREADYINITIALIZED 0x88780082
// This object does not support aggregation
#define DSERR_NOAGGREGATION 0x80040110
// The buffer memory has been lost, and must be restored.
#define DSERR_BUFFERLOST 0x88780096
// Another app has a higher priority level, preventing this call from
// succeeding.
#define DSERR_OTHERAPPHASPRIO 0x887800A0
// This object has not been initialized
#define DSERR_UNINITIALIZED 0x887800AA
// The requested COM interface is not available
#define DSERR_NOINTERFACE 0x80000004
// Access is denied
#define DSERR_ACCESSDENIED 0x80070005
// Tried to create a DSBCAPS_CTRLFX buffer shorter than DSBSIZE_FX_MIN milliseconds
#define DSERR_BUFFERTOOSMALL 0x887800B4
// Attempt to use DirectSound 8 functionality on an older DirectSound object
#define DSERR_DS8_REQUIRED 0x887800BE
// A circular loop of send effects was detected
#define DSERR_SENDLOOP 0x887800C8
// The GUID specified in an audiopath file does not match a valid MIXIN buffer
#define DSERR_BADSENDBUFFERGUID 0x887800D2
// The object requested was not found (numerically equal to DMUS_E_NOT_FOUND)
#define DSERR_OBJECTNOTFOUND 0x88781193
#define DSBLOCK_FROMWRITECURSOR 0x1 // Locks from the current write position, making a call to IDirectSoundBuffer::GetCurrentPosition unnecessary. If this flag is specified, the dwWriteCursor parameter is ignored.
#define DSBLOCK_ENTIREBUFFER 0x2 // Locks the entire buffer. The dwWriteBytes parameter is ignored.
#define DSBSTATUS_BUFFERLOST 0x00000002
#define DSBSTATUS_LOOPING 0x00000004
#define DSBSTATUS_PLAYING 0x00000001
#define DSBPLAY_LOOPING 0x00000001
#define DSBVOLUME_MIN -10000
#define DSBVOLUME_MAX 0
#define DSSCL_NORMAL 0x00000001
static inline bool FAILED(HRESULT x) { return x != DS_OK; }
static inline bool SUCCEEDED(HRESULT x) { return x == DS_OK; }
#define WAVE_FORMAT_PCM 0x0001
#define DSBCAPS_GETCURRENTPOSITION2 0x00010000
#define DSBCAPS_STICKYFOCUS 0x00004000
#define DSBCAPS_CTRLVOLUME 0x00000080
typedef struct {
WORD wFormatTag;
WORD nChannels;
DWORD nSamplesPerSec;
DWORD nAvgBytesPerSec;
WORD nBlockAlign;
WORD wBitsPerSample;
WORD cbSize;
} WAVEFORMATEX, *LPWAVEFORMATEX;
typedef struct DSBUFFERDESC {
DWORD dwSize;
DWORD dwFlags;
DWORD dwBufferBytes;
DWORD dwReserved;
LPWAVEFORMATEX lpwfxFormat;
} DSBUFFERDESC;
typedef DWORD DSCAPS_MASK;
typedef struct _DSCAPS
{
DWORD dwSize;
DSCAPS_MASK dwFlags;
DWORD dwMinSecondarySampleRate;
DWORD dwMaxSecondarySampleRate;
DWORD dwPrimaryBuffers;
DWORD dwMaxHwMixingAllBuffers;
DWORD dwMaxHwMixingStaticBuffers;
DWORD dwMaxHwMixingStreamingBuffers;
DWORD dwFreeHwMixingAllBuffers;
DWORD dwFreeHwMixingStaticBuffers;
DWORD dwFreeHwMixingStreamingBuffers;
DWORD dwMaxHw3DAllBuffers;
DWORD dwMaxHw3DStaticBuffers;
DWORD dwMaxHw3DStreamingBuffers;
DWORD dwFreeHw3DAllBuffers;
DWORD dwFreeHw3DStaticBuffers;
DWORD dwFreeHw3DStreamingBuffers;
DWORD dwTotalHwMemBytes;
DWORD dwFreeHwMemBytes;
DWORD dwMaxContigFreeHwMemBytes;
DWORD dwUnlockTransferRateHwBuffers;
DWORD dwPlayCpuOverheadSwBuffers;
DWORD dwReserved1;
DWORD dwReserved2;
} DSCAPS, *LPDSCAPS;
typedef DSCAPS *LPCDSCAPS;
typedef struct IDirectSound {
void *implementation_specific;
#define LPUNKNOWN void*
HRESULT (*CreateSoundBuffer)(DSBUFFERDESC *pcDSBufferDesc, LPDIRECTSOUNDBUFFER * ppDSBuffer, LPUNKNOWN pUnkOuter);
HRESULT (*DestroySoundBuffer)(LPDIRECTSOUNDBUFFER * ppDSBuffer);
} IDirectSound, *LPDIRECTSOUND;
#endif /* whole file */

View File

@ -198,9 +198,9 @@ E(read_random)
ret
E(read_speaker_toggle_pc)
inb $0x61, %al
xorb $0x2, %al
outb %al, $0x61
pushal
call SN(SpkrToggle)
popal // FIXME, do we need to set %al to something here?
ret
E(read_switch_primary_page)

780
src/soundcore-alsa.c Normal file
View File

@ -0,0 +1,780 @@
/*
* Apple // emulator for *nix
*
* This software package is subject to the GNU General Public License
* version 2 or later (your choice) as published by the Free Software
* Foundation.
*
* THERE ARE NO WARRANTIES WHATSOEVER.
*
*/
#include "soundcore-alsa.h"
// HACK : this is an ugly shoehorning into DirectSound assumptions... Needta rework this once I have a better
// understanding of the workings of the sound system :)
static int resample = 1; /* enable alsa-lib resampling */
static int period_event = 0; /* produce poll event after each period */
static snd_pcm_format_t format = SND_PCM_FORMAT_S16;
static int format_bits = 0;
static int bytes_per_sample = 0;
static int shift_per_sample = 0;
static snd_pcm_uframes_t buffer_size;
static snd_pcm_uframes_t period_size;
static snd_pcm_hw_params_t *hwparams = NULL;
static snd_pcm_sw_params_t *swparams = NULL;
extern SoundSystemStruct *g_lpDS; // HACK
snd_pcm_uframes_t play_offset;
static long alsa_create_sound_buffer(ALSABufferParamsStruct *params_struct, ALSASoundBufferStruct **soundbuf_struct, void *extra_data);
static long alsa_destroy_sound_buffer(ALSASoundBufferStruct **soundbuf_struct);
long SoundSystemCreate(const char *sound_device, SoundSystemStruct **sound_struct)
{
// ugly assumption : this sets the extern g_lpDS ...
assert(*sound_struct == NULL);
snd_pcm_t *handle = NULL;
int err = -1;
if ((*sound_struct = malloc(sizeof(SoundSystemStruct))) == NULL)
{
ERRLOG("Not enough memory");
return -1;
}
if ((err = snd_pcm_open(&handle, sound_device, SND_PCM_STREAM_PLAYBACK, 0)) < 0)
{
ERRLOG("Playback open error: %s", snd_strerror(err));
Free(*sound_struct);
return -1;
}
(*sound_struct)->implementation_specific = handle;
(*sound_struct)->CreateSoundBuffer = alsa_create_sound_buffer;
(*sound_struct)->DestroySoundBuffer = alsa_destroy_sound_buffer;
return 0;
}
long SoundSystemDestroy(SoundSystemStruct **sound_struct)
{
// ugly assumption : this sets the extern g_lpDS ...
assert(*sound_struct != NULL);
int err = 0;
snd_pcm_t *handle = (snd_pcm_t*) (*sound_struct)->implementation_specific;
assert(handle != NULL);
if ((err = snd_pcm_drop(handle)) < 0)
{
ERRLOG("snd_pcm_drop: %s", snd_strerror(err));
}
if ((err = snd_pcm_close(handle))< 0)
{
ERRLOG("snd_pcm_drop: %s", snd_strerror(err));
}
(*sound_struct)->implementation_specific = NULL;
Free(*sound_struct);
return 0;
}
long SoundSystemEnumerate(char ***device_list, const int limit)
{
assert(*device_list == NULL);
// HACK NOTE : it appears that "plughw:0,0" is the magic Linux device ...
*device_list = malloc(sizeof(char*)*2);
(*device_list)[0] = strdup("plughw:0,0");
unsigned int num_devices = 1;
#if 0
char **hints = NULL;
int err = -1;
if ((err = snd_device_name_hint(-1, "pcm", (void***)&hints)) < 0)
{
ERRLOG("snd_device_name_hint: %s", snd_strerror(err));
return err;
}
*device_list = malloc(sizeof(char*) * (limit+1));
if (!*device_list)
{
ERRLOG("Not enough memory");
return -1;
}
char** p = hints;
unsigned int num_devices = 0;
while (*p != NULL)
{
char *name = snd_device_name_get_hint(*p, "NAME");
LOG("PCM device : %s", name);
if ((name != NULL) && (0 != strcmp("null", name)))
{
if (num_devices < limit)
{
(*device_list)[num_devices++] = name;
}
else
{
LOG("Ignoring device : %s", name);
Free(name);
}
}
++p;
}
snd_device_name_free_hint((void**)hints);
#endif
(*device_list)[num_devices] = NULL; // sentinel
return num_devices;
}
// ----------------------------------------------------------------------------
static long _alsa_create_volume_refs(snd_mixer_t **handle, snd_mixer_selem_id_t **sid, snd_mixer_elem_t **elem)
{
// TODO http://stackoverflow.com/questions/6787318/set-alsa-master-volume-from-c-code#6787957
// TODO NOTE that I had to add a free -- snd_mixer_selem_id_free()
// TODO : iterate over mixers?
static const char *card = "default";
static const char *selem_name = "Master";
int err = 0;
do {
if ((err = snd_mixer_open(handle, 0)) < 0)
{
ERRLOG("Error opening mixer: %s", snd_strerror(err));
break;
}
if ((err = snd_mixer_attach(*handle, card)) < 0)
{
ERRLOG("Error attaching to mixer: %s", snd_strerror(err));
break;
}
if ((err = snd_mixer_selem_register(*handle, NULL, NULL)) < 0)
{
ERRLOG("Error mixer register: %s", snd_strerror(err));
break;
}
if ((err = snd_mixer_load(*handle)) < 0)
{
ERRLOG("Error mixer load: %s", snd_strerror(err));
break;
}
if ((err = snd_mixer_selem_id_malloc(sid)) < 0)
{
ERRLOG("Error mixer register: %s", snd_strerror(err));
break;
}
snd_mixer_selem_id_set_index(*sid, 0);
snd_mixer_selem_id_set_name(*sid, selem_name);
if ((*elem = snd_mixer_find_selem(*handle, *sid)) == NULL)
{
ERRLOG("Error mixer find volume: %s", snd_strerror(err));
break;
}
return 0;
} while (0);
if (*handle) { snd_mixer_close(*handle); }
if (*sid) { snd_mixer_selem_id_free(*sid); }
return err ?: -1;
}
static long alsa_get_volume(long *volume)
{
assert(volume != NULL);
snd_mixer_t *handle = NULL;
snd_mixer_selem_id_t *sid = NULL;
snd_mixer_elem_t *elem = NULL;
long min = 0;
long max = 0;
int err = 0;
do {
if ((err = _alsa_create_volume_refs(&handle, &sid, &elem)) < 0)
{
break;
}
if ((err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max)) < 0)
{
ERRLOG("Error mixer get playback volume range: %s", snd_strerror(err));
break;
}
if ((err = snd_mixer_selem_set_playback_volume_all(elem, *volume * max / 100)) < 0)
{
ERRLOG("Error setting playback volume: %s", snd_strerror(err));
break;
}
return 0;
} while (0);
if (handle) { snd_mixer_close(handle); }
if (sid) { snd_mixer_selem_id_free(sid); }
return err;
}
static long alsa_set_volume(long volume)
{
// UNIMPLEMENTED
return 0;
}
/*
* Underrun and suspend recovery
*/
static int xrun_recovery(snd_pcm_t *handle, int err)
{
if (err == -EPIPE)
{
ERRLOG("stream recovery from under-run...");
err = snd_pcm_prepare(handle);
if (err < 0)
{
ERRLOG("Can't recover from underrun, prepare failed: %s", snd_strerror(err));
}
return 0;
}
else if (err == -ESTRPIPE)
{
ERRLOG("stream recovery from SUSPENDED...");
while ((err = snd_pcm_resume(handle)) == -EAGAIN)
{
struct timespec dt = { .tv_nsec=1, .tv_sec=0 };
nanosleep(&dt, NULL); // wait until the suspend flag is released
}
if (err < 0)
{
err = snd_pcm_prepare(handle);
if (err < 0)
{
ERRLOG("Can't recover from suspend, prepare failed: %s", snd_strerror(err));
}
}
return 0;
}
else
{
ERRLOG("stream recovery from %d ?", err);
}
return err;
}
static long alsa_set_position(unsigned long write_cursor)
{
// UNIMPLEMENTED
assert(false);
return 0;
}
static long alsa_stop()
{
// this is a no-op at the moment, just let the sound flow!
return 0;
}
static long alsa_restore()
{
return 0;
}
static long alsa_play(unsigned long reserved1, unsigned long reserved2, unsigned long flags)
{
// this is a no-op presumably because all the alsa setup give us a buffer ready to play
return 0;
}
// returns buffer position in bytes
static long alsa_get_position(unsigned long *play_cursor, unsigned long *unused_write_cursor)
{
snd_pcm_t *handle = (snd_pcm_t*)g_lpDS->implementation_specific;
snd_pcm_sframes_t avail = 0;
long err = 0;
*play_cursor = 0;
*unused_write_cursor = 0;
do {
#if 0
snd_pcm_state_t state = snd_pcm_state(handle);
if (state == SND_PCM_STATE_XRUN)
{
err = xrun_recovery(handle, -EPIPE);
if (err < 0)
{
ERRLOG("XRUN recovery failed: %s", snd_strerror(err));
break;
}
}
else if (state == SND_PCM_STATE_SUSPENDED)
{
err = xrun_recovery(handle, -ESTRPIPE);
if (err < 0)
{
ERRLOG("SUSPEND recovery failed: %s", snd_strerror(err));
break;
}
}
#endif
#if 0
else if ( !((state == SND_PCM_STATE_PREPARED) || (state == SND_PCM_STATE_RUNNING)) )
{
ERRLOG("PCM state is not running!");
break;
}
#endif
if ((avail = snd_pcm_avail_update(handle)) < 0)
{
if (avail == -EPIPE)
{
ERRLOG("NOTE snd_pcm_avail_update() < 0 ...");
// underrun ... whole buffer available
return 0;
}
}
if (avail < 1024) // HACK MAGICK CONSTANT
{
ERRLOG("OOPS avail(%ld) < period_size(%lu) ... ", avail, period_size);
ERRLOG("performing snd_pcm_wait() ...");
err = snd_pcm_wait(handle, -1);
if (err < 0)
{
ERRLOG("\tOOPS performing xrun_recovery() ...");
if ((err = xrun_recovery(handle, err)) < 0)
{
ERRLOG("snd_pcm_wait error: %s", snd_strerror(err));
break;
}
}
break;
}
avail = buffer_size - avail;
*play_cursor = avail<<shift_per_sample;
return 0;
} while (0);
return err ?: -1;
}
// HACK NOTE : audio_ptr2 is unused
// DS->Lock()
static long alsa_begin(unsigned long unused, unsigned long write_bytes, void **audio_ptr1, unsigned long *audio_bytes1, void **unused_audio_ptr2, unsigned long *audio_bytes2, unsigned long flags)
{
int err = 0;
const snd_pcm_channel_area_t *areas;
uint8_t *bytes = NULL;
snd_pcm_uframes_t offset_frames = 0;
snd_pcm_uframes_t size_frames = write_bytes>>shift_per_sample; // HACK : assuming 16bit samples
if (write_bytes == 0)
{
size_frames = buffer_size;
}
*audio_ptr1 = NULL;
if (unused_audio_ptr2)
{
*unused_audio_ptr2 = NULL;
}
*audio_bytes1 = 0;
*audio_bytes2 = 0;
do {
snd_pcm_t *handle = (snd_pcm_t*)g_lpDS->implementation_specific;
while ((err = snd_pcm_mmap_begin(handle, &areas, &offset_frames, &size_frames)) < 0)
{
if ((err = xrun_recovery(handle, err)) < 0)
{
ERRLOG("MMAP begin avail error: %s", snd_strerror(err));
break;
}
}
//LOG("addr:%p first:%u offset:%lu frames:%lu", areas[0].addr, areas[0].first, offset_frames, size_frames);
const snd_pcm_channel_area_t area = areas[0];// assuming mono ...
assert((area.first % 8) == 0);
bytes = (((uint8_t *)area.addr) + (area.first>>3));
assert(format_bits == area.step);
assert(bytes_per_sample == (area.step>>3));
bytes += offset_frames * bytes_per_sample;
*audio_ptr1 = (void*)bytes;
*audio_bytes1 = size_frames<<shift_per_sample;
*audio_bytes2 = offset_frames;
return 0;
} while(0);
return err ?: -1;
}
// DS->Unlock()
static long alsa_commit(void *unused_audio_ptr1, unsigned long audio_bytes1, void *unused_audio_ptr2, unsigned long audio_bytes2)
{
assert(unused_audio_ptr2 == NULL);
int err = 0;
snd_pcm_uframes_t size_frames = audio_bytes1>>shift_per_sample;
snd_pcm_uframes_t offset_frames = audio_bytes2;
do {
snd_pcm_t *handle = (snd_pcm_t*)g_lpDS->implementation_specific;
err = snd_pcm_mmap_commit(handle, offset_frames, size_frames);
if (err < 0 || (snd_pcm_uframes_t)err != size_frames)
{
if ((err = xrun_recovery(handle, err >= 0 ? -EPIPE : err)) < 0)
{
ERRLOG("MMAP commit error: %s", snd_strerror(err));
break;
}
}
if (snd_pcm_state(handle) != SND_PCM_STATE_RUNNING)
{
if ((err = snd_pcm_start(handle)) < 0)
{
ERRLOG("Start error: %s", snd_strerror(err));
break;
}
}
return 0;
} while(0);
return err ?: -1;
}
static long alsa_get_status(unsigned long *status)
{
// this is actually tested in the alsa_get_position() call
return 0;
}
// ----------------------------------------------------------------------------
static int set_hwparams(snd_pcm_t *handle, ALSABufferParamsStruct *params_struct)
{
int err = 0;
/* choose all parameters */
if ((err = snd_pcm_hw_params_any(handle, hwparams)) < 0)
{
ERRLOG("Broken configuration for playback: no configurations available: %s", snd_strerror(err));
return err;
}
/* set hardware resampling */
if ((err = snd_pcm_hw_params_set_rate_resample(handle, hwparams, resample)) < 0)
{
ERRLOG("Resampling setup failed for playback: %s", snd_strerror(err));
return err;
}
/* set the interleaved read/write format */
if ((err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) < 0)
{
ERRLOG("Access type not available for playback: %s", snd_strerror(err));
return err;
}
/* set the sample format as signed 16bit samples */
if ((err = snd_pcm_hw_params_set_format(handle, hwparams, format)) < 0)
{
ERRLOG("Sample format not available for playback: %s", snd_strerror(err));
return err;
}
format_bits = snd_pcm_format_physical_width(format);
bytes_per_sample = format_bits / 8;
shift_per_sample = (bytes_per_sample>>1); // HACK : ASSUMES 16bit samples ...
/* set the count of channels */
if ((err = snd_pcm_hw_params_set_channels(handle, hwparams, params_struct->lpwfxFormat->nChannels)) < 0)
{
ERRLOG("Channels count of 1 not available for playbacks: %s", snd_strerror(err));
return err;
}
/* set the stream rate */
unsigned int rrate = params_struct->lpwfxFormat->nSamplesPerSec;
if ((err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &rrate, 0)) < 0)
{
ERRLOG("Rate %luHz not available for playback: %s", params_struct->lpwfxFormat->nSamplesPerSec, snd_strerror(err));
return err;
}
if (rrate != params_struct->lpwfxFormat->nSamplesPerSec)
{
ERRLOG("Rate doesn't match (requested %luHz, get %iHz)", params_struct->lpwfxFormat->nSamplesPerSec, err);
return -EINVAL;
}
/* set the buffer time */
int ignored;
buffer_size = params_struct->dwBufferBytes;
err = snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, &buffer_size);
if (err < 0)
{
ERRLOG("Unable to set buffer size %d for playback: %s", (int)buffer_size, snd_strerror(err));
}
snd_pcm_uframes_t size;
if ((err = snd_pcm_hw_params_get_buffer_size(hwparams, &size)) < 0)
{
ERRLOG("Unable to get buffer size for playback: %s", snd_strerror(err));
return err;
}
if (size != buffer_size)
{
ERRLOG("Note: buffer_size fetch mismatch using %d -- (%d requested)", (int)size, (int)buffer_size);
buffer_size = size;
}
period_size = buffer_size / 4;
err = snd_pcm_hw_params_set_period_size_near(handle, hwparams, &period_size, &ignored);
if (err < 0)
{
ERRLOG("Unable to set period size %d for playback: %s", (int)period_size, snd_strerror(err));
}
if ((err = snd_pcm_hw_params_get_period_size(hwparams, &size, &ignored)) < 0)
{
ERRLOG("Unable to get period size for playback: %s", snd_strerror(err));
return err;
}
if (size != period_size)
{
ERRLOG("Note: period_size fetch mismatch using %d -- (%d requested)", (int)size, (int)period_size);
period_size = size;
}
/* write the parameters to device */
if ((err = snd_pcm_hw_params(handle, hwparams)) < 0)
{
ERRLOG("Unable to set hwparams for playback: %s", snd_strerror(err));
return err;
}
return 0;
}
static int set_swparams(snd_pcm_t *handle, ALSABufferParamsStruct *params_struct)
{
int err = -1;
/* get the current swparams */
if ((err = snd_pcm_sw_params_current(handle, swparams)) < 0)
{
ERRLOG("Unable to determine current swparams for playback: %s", snd_strerror(err));
return err;
}
/* start the transfer when the buffer is almost full: */
/* (buffer_size / avail_min) * avail_min */
if ((err = snd_pcm_sw_params_set_start_threshold(handle, swparams, (buffer_size / period_size) * period_size)) < 0)
{
ERRLOG("Unable to set start threshold mode for playback: %s", snd_strerror(err));
return err;
}
/* allow the transfer when at least period_size samples can be processed */
/* or disable this mechanism when period event is enabled (aka interrupt like style processing) */
if ((err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_event ? buffer_size : period_size)) < 0)
{
ERRLOG("Unable to set avail min for playback: %s", snd_strerror(err));
return err;
}
/* enable period events when requested */
if (period_event)
{
if ((err = snd_pcm_sw_params_set_period_event(handle, swparams, 1)) < 0)
{
ERRLOG("Unable to set period event: %s", snd_strerror(err));
return err;
}
}
/* write the parameters to the playback device */
if ((err = snd_pcm_sw_params(handle, swparams)) < 0)
{
ERRLOG("Unable to set swparams for playback: %s", snd_strerror(err));
return err;
}
return 0;
}
static long alsa_create_sound_buffer(ALSABufferParamsStruct *params_struct, ALSASoundBufferStruct **soundbuf_struct, void *extra_data)
{
assert(*soundbuf_struct == NULL);
const SoundSystemStruct *sound_struct = (SoundSystemStruct*)extra_data;
snd_pcm_t *handle = (snd_pcm_t*)(sound_struct->implementation_specific);
assert(handle != NULL);
int err = -1;
#if 0
ALSASoundStructExtras *extras = NULL;
#endif
if (hwparams == NULL)
{
snd_pcm_hw_params_alloca(&hwparams);
}
if (swparams == NULL)
{
snd_pcm_sw_params_alloca(&swparams);
}
do {
#if 0
if ((extras = malloc(sizeof(ALSASoundStructExtras))) == NULL)
{
ERRLOG("Not enough memory");
break;
}
signed short *samples = NULL;
if ((samples = malloc((period_size * format_bits) / 8)) == NULL)
{
ERRLOG("Not enough memory");
break;
}
snd_pcm_channel_area_t *area;
if ((area = malloc(sizeof(snd_pcm_channel_area_t))) == NULL)
{
ERRLOG("Not enough memory");
break;
}
area->addr = samples;
area->first = 0;
area->step = format_bits;
#endif
if ((*soundbuf_struct = malloc(sizeof(ALSASoundBufferStruct))) == NULL)
{
ERRLOG("Not enough memory");
break;
}
#if 0
extras->hwparams = hwparams;
extras->swparams = swparams;
extras->area = area;
(*soundbuf_struct)->implementation_specific = extras;
#endif
(*soundbuf_struct)->SetVolume = alsa_set_volume;
(*soundbuf_struct)->GetVolume = alsa_get_volume;
(*soundbuf_struct)->GetCurrentPosition = alsa_get_position;
(*soundbuf_struct)->SetCurrentPosition = alsa_set_position;
(*soundbuf_struct)->Stop = alsa_stop;
(*soundbuf_struct)->Restore = alsa_restore;
(*soundbuf_struct)->Play = alsa_play;
(*soundbuf_struct)->Lock = alsa_begin;
(*soundbuf_struct)->Unlock = alsa_commit;
(*soundbuf_struct)->GetStatus = alsa_get_status;
// configuration ...
if ((err = set_hwparams(handle, params_struct)) < 0)
{
ERRLOG("Setting of hwparams failed: %s", snd_strerror(err));
break;
}
if ((err = set_swparams(handle, params_struct)) < 0)
{
ERRLOG("Setting of swparams failed: %s", snd_strerror(err));
break;
}
#ifndef NDEBUG
snd_output_t *output = NULL;
err = snd_output_stdio_attach(&output, stdout, 0);
if (err < 0)
{
LOG("Output failed: %s", snd_strerror(err));
}
snd_pcm_dump(handle, output);
#endif
return 0;
} while(0);
if (*soundbuf_struct)
{
alsa_destroy_sound_buffer(soundbuf_struct);
}
else
{
#if 0
if (hwparams) { Free(hwparams); }
if (swparams) { Free(swparams); }
if (area) { Free(area); }
if (samples) { Free(samples); }
if (extras) { Free(extras); }
#endif
}
return err ?: -1;
}
static long alsa_destroy_sound_buffer(ALSASoundBufferStruct **soundbuf_struct)
{
#if 0
ALSASoundStructExtras *extras = (ALSASoundStructExtras*)((*soundbuf_struct)->implementation_specific);
Free(extras->area->addr);
Free(extras->area);
Free(extras);
Free(hwparams);
Free(swparams);
#endif
//snd_pcm_hw_free(handle); TODO : are we leaking anything ???
Free(*soundbuf_struct);
return 0;
}

36
src/soundcore-alsa.h Normal file
View File

@ -0,0 +1,36 @@
/*
* Apple // emulator for Linux
*
* This software package is subject to the GNU General Public License
* version 2 or later (your choice) as published by the Free Software
* Foundation.
*
* THERE ARE NO WARRANTIES WHATSOEVER.
*
*/
#ifndef _SOUNDCORE_ALSA_H_
#define _SOUNDCORE_ALSA_H_
#include "common.h"
#include "soundcore.h"
#include <alsa/asoundlib.h>
#undef DSBVOLUME_MIN
#define DSBVOLUME_MIN 0
#undef DSBVOLUME_MAX
#define DSBVOLUME_MAX 100
typedef struct IDirectSoundBuffer ALSASoundBufferStruct;
typedef struct DSBUFFERDESC ALSABufferParamsStruct;
typedef struct {
snd_pcm_hw_params_t *hwparams;
snd_pcm_sw_params_t *swparams;
snd_pcm_channel_area_t *area;
} ALSASoundStructExtras;
#endif /* whole file */

845
src/soundcore.c Normal file
View File

@ -0,0 +1,845 @@
/*
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: Core sound related functionality
*
* Author: Tom Charlesworth
* Linux ALSA Port : Aaron Culliney
*/
#ifdef APPLE2IX
#ifdef USE_ALSA
#include "soundcore-alsa.h"
#endif
#include "common.h"
#include "win-shim.h"
#include "soundcore.h"
#include "speaker.h"
#include "misc.h"
#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wformat"
#elif defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wformat"
#endif
#else
#include "StdAfx.h"
#endif
//-----------------------------------------------------------------------------
#define MAX_SOUND_DEVICES 100
#ifdef APPLE2IX
static char **sound_devices = NULL;
#else
static char *sound_devices[MAX_SOUND_DEVICES];
static GUID sound_device_guid[MAX_SOUND_DEVICES];
#endif
static int num_sound_devices = 0;
#ifdef APPLE2IX
LPDIRECTSOUND g_lpDS = NULL;
#else
static LPDIRECTSOUND g_lpDS = NULL;
#endif
//-------------------------------------
// Used for muting & fading:
#ifdef APPLE2IX
#define uMAX_VOICES 66
#else
static const UINT uMAX_VOICES = 66; // 64 phonemes + spkr + mockingboard
#endif
static UINT g_uNumVoices = 0;
static VOICE* g_pVoices[uMAX_VOICES] = {NULL};
static VOICE* g_pSpeakerVoice = NULL;
//-------------------------------------
bool g_bDSAvailable = false;
#ifdef APPLE2IX
bool g_bDisableDirectSound = false;
FILE *g_fh = NULL;
#endif
//-----------------------------------------------------------------------------
#ifndef APPLE2IX
static BOOL CALLBACK DSEnumProc(LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID lpContext)
{
int i = num_sound_devices;
if(i == MAX_SOUND_DEVICES)
return TRUE;
if(lpGUID != NULL)
memcpy(&sound_device_guid[i], lpGUID, sizeof (GUID));
sound_devices[i] = _strdup(lpszDesc);
if(g_fh) fprintf(g_fh, "%d: %s - %s\n",i,lpszDesc,lpszDrvName);
num_sound_devices++;
return TRUE;
}
#endif
//-----------------------------------------------------------------------------
#ifdef _DEBUG
static char *DirectSound_ErrorText (HRESULT error)
{
switch( error )
{
case DSERR_ALLOCATED:
return "Allocated";
case DSERR_CONTROLUNAVAIL:
return "Control Unavailable";
case DSERR_INVALIDPARAM:
return "Invalid Parameter";
case DSERR_INVALIDCALL:
return "Invalid Call";
case DSERR_GENERIC:
return "Generic";
case DSERR_PRIOLEVELNEEDED:
return "Priority Level Needed";
case DSERR_OUTOFMEMORY:
return "Out of Memory";
case DSERR_BADFORMAT:
return "Bad Format";
case DSERR_UNSUPPORTED:
return "Unsupported";
case DSERR_NODRIVER:
return "No Driver";
case DSERR_ALREADYINITIALIZED:
return "Already Initialized";
case DSERR_NOAGGREGATION:
return "No Aggregation";
case DSERR_BUFFERLOST:
return "Buffer Lost";
case DSERR_OTHERAPPHASPRIO:
return "Other Application Has Priority";
case DSERR_UNINITIALIZED:
return "Uninitialized";
case DSERR_NOINTERFACE:
return "No Interface";
default:
return "Unknown";
}
}
#endif
//-----------------------------------------------------------------------------
bool DSGetLock(LPDIRECTSOUNDBUFFER pVoice, DWORD dwOffset, DWORD dwBytes,
SHORT** ppDSLockedBuffer0, DWORD* pdwDSLockedBufferSize0,
SHORT** ppDSLockedBuffer1, DWORD* pdwDSLockedBufferSize1)
{
DWORD nStatus;
HRESULT hr = pVoice->GetStatus(&nStatus);
if(hr != DS_OK)
return false;
if(nStatus & DSBSTATUS_BUFFERLOST)
{
do
{
hr = pVoice->Restore();
if(hr == DSERR_BUFFERLOST)
Sleep(10);
}
while(hr != DS_OK);
}
// Get write only pointer(s) to sound buffer
if(dwBytes == 0)
{
if(FAILED(hr = pVoice->Lock(0, 0,
(void**)ppDSLockedBuffer0, pdwDSLockedBufferSize0,
(void**)ppDSLockedBuffer1, pdwDSLockedBufferSize1,
DSBLOCK_ENTIREBUFFER)))
return false;
}
else
{
if(FAILED(hr = pVoice->Lock(dwOffset, dwBytes,
(void**)ppDSLockedBuffer0, pdwDSLockedBufferSize0,
(void**)ppDSLockedBuffer1, pdwDSLockedBufferSize1,
0)))
return false;
}
return true;
}
//-----------------------------------------------------------------------------
HRESULT DSGetSoundBuffer(VOICE* pVoice, DWORD dwFlags, DWORD dwBufferSize, DWORD nSampleRate, int nChannels)
{
WAVEFORMATEX wavfmt;
DSBUFFERDESC dsbdesc;
wavfmt.wFormatTag = WAVE_FORMAT_PCM;
wavfmt.nChannels = nChannels;
wavfmt.nSamplesPerSec = nSampleRate;
wavfmt.wBitsPerSample = 16;
wavfmt.nBlockAlign = wavfmt.nChannels==1 ? 2 : 4;
wavfmt.nAvgBytesPerSec = wavfmt.nBlockAlign * wavfmt.nSamplesPerSec;
memset (&dsbdesc, 0, sizeof (dsbdesc));
dsbdesc.dwSize = sizeof (dsbdesc);
dsbdesc.dwBufferBytes = dwBufferSize;
dsbdesc.lpwfxFormat = &wavfmt;
dsbdesc.dwFlags = dwFlags | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STICKYFOCUS;
// Are buffers released when g_lpDS OR pVoice->lpDSBvoice is released?
// . From DirectX doc:
// "Buffer objects are owned by the device object that created them. When the
// device object is released, all buffers created by that object are also released..."
#ifdef APPLE2IX
if (pVoice->lpDSBvoice)
{
g_lpDS->DestroySoundBuffer(&pVoice->lpDSBvoice);
}
HRESULT hr = g_lpDS->CreateSoundBuffer(&dsbdesc, &pVoice->lpDSBvoice, g_lpDS);
#else
HRESULT hr = g_lpDS->CreateSoundBuffer(&dsbdesc, &pVoice->lpDSBvoice, NULL);
#endif
if(FAILED(hr))
return hr;
//
_ASSERT(g_uNumVoices < uMAX_VOICES);
if(g_uNumVoices < uMAX_VOICES)
g_pVoices[g_uNumVoices++] = pVoice;
if(pVoice->bIsSpeaker)
g_pSpeakerVoice = pVoice;
return hr;
}
void DSReleaseSoundBuffer(VOICE* pVoice)
{
if(pVoice->bIsSpeaker)
g_pSpeakerVoice = NULL;
for(UINT i=0; i<g_uNumVoices; i++)
{
if(g_pVoices[i] == pVoice)
{
g_pVoices[i] = g_pVoices[g_uNumVoices-1];
g_pVoices[g_uNumVoices-1] = NULL;
g_uNumVoices--;
break;
}
}
#ifdef APPLE2IX
if (g_lpDS)
{
g_lpDS->DestroySoundBuffer(&pVoice->lpDSBvoice);
}
#else
SAFE_RELEASE(pVoice->lpDSBvoice);
#endif
}
//-----------------------------------------------------------------------------
bool DSZeroVoiceBuffer(PVOICE Voice, char* pszDevName, DWORD dwBufferSize)
{
#ifdef APPLE2IX
// HACK FIXME is this function necessary?
return true;
#endif
DWORD dwDSLockedBufferSize = 0; // Size of the locked DirectSound buffer
SHORT* pDSLockedBuffer;
#ifdef APPLE2IX
DWORD argX = 0;
HRESULT hr = Voice->lpDSBvoice->Stop();
if(FAILED(hr))
{
if(g_fh) fprintf(g_fh, "%s: DSStop failed (%08X)\n",pszDevName,hr);
return false;
}
hr = !DSGetLock(Voice->lpDSBvoice, 0, 0, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, &argX);
#else
hr = DSGetLock(Voice->lpDSBvoice, 0, 0, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, 0);
#endif
if(FAILED(hr))
{
if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n",pszDevName,hr);
return false;
}
_ASSERT(dwDSLockedBufferSize == dwBufferSize);
memset(pDSLockedBuffer, 0x00, dwDSLockedBufferSize);
#ifdef APPLE2IX
hr = Voice->lpDSBvoice->Unlock((void*)pDSLockedBuffer, dwDSLockedBufferSize, NULL, argX);
#else
hr = Voice->lpDSBvoice->Unlock((void*)pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0);
#endif
if(FAILED(hr))
{
if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n",pszDevName,hr);
return false;
}
hr = Voice->lpDSBvoice->Play(0,0,DSBPLAY_LOOPING);
if(FAILED(hr))
{
if(g_fh) fprintf(g_fh, "%s: DSPlay failed (%08X)\n",pszDevName,hr);
return false;
}
return true;
}
//-----------------------------------------------------------------------------
bool DSZeroVoiceWritableBuffer(PVOICE Voice, char* pszDevName, DWORD dwBufferSize)
{
DWORD dwDSLockedBufferSize0=0, dwDSLockedBufferSize1=0;
SHORT *pDSLockedBuffer0, *pDSLockedBuffer1;
HRESULT hr = DSGetLock(Voice->lpDSBvoice,
0, dwBufferSize,
&pDSLockedBuffer0, &dwDSLockedBufferSize0,
&pDSLockedBuffer1, &dwDSLockedBufferSize1);
#ifdef APPLE2IX
hr = !hr;
#endif
if(FAILED(hr))
{
if(g_fh) fprintf(g_fh, "%s: DSGetLock failed (%08X)\n",pszDevName,hr);
return false;
}
memset(pDSLockedBuffer0, 0x00, dwDSLockedBufferSize0);
if(pDSLockedBuffer1)
memset(pDSLockedBuffer1, 0x00, dwDSLockedBufferSize1);
hr = Voice->lpDSBvoice->Unlock((void*)pDSLockedBuffer0, dwDSLockedBufferSize0,
(void*)pDSLockedBuffer1, dwDSLockedBufferSize1);
if(FAILED(hr))
{
if(g_fh) fprintf(g_fh, "%s: DSUnlock failed (%08X)\n",pszDevName,hr);
return false;
}
return true;
}
//-----------------------------------------------------------------------------
static bool g_bTimerActive = false;
static eFADE g_FadeType = FADE_NONE;
static UINT_PTR g_nTimerID = 0;
//-------------------------------------
#ifndef APPLE2IX
static VOID CALLBACK SoundCore_TimerFunc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
#endif
#ifndef APPLE2IX
static bool SoundCore_StartTimer()
{
if(g_bTimerActive)
return true;
g_nTimerID = SetTimer(NULL, 0, 1, SoundCore_TimerFunc); // 1ms interval
if(g_nTimerID == 0)
{
fprintf(stderr, "Error creating timer\n");
_ASSERT(0);
return false;
}
g_bTimerActive = true;
return true;
}
#endif
static void SoundCore_StopTimer()
{
#ifdef APPLE2IX
// using our own timing and nanosleep() ...
#else
if(!g_bTimerActive)
return;
if(KillTimer(NULL, g_nTimerID) == FALSE)
{
fprintf(stderr, "Error killing timer\n");
_ASSERT(0);
return;
}
g_bTimerActive = false;
#endif
}
bool SoundCore_GetTimerState()
{
return g_bTimerActive;
}
//-------------------------------------
// [OLD: Used to fade volume in/out]
// FADE_OUT : Just keep filling speaker soundbuffer with same value
// FADE_IN : Switch to FADE_NONE & StopTimer()
#ifndef APPLE2IX
static VOID CALLBACK SoundCore_TimerFunc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
if((g_pSpeakerVoice == NULL) || (g_pSpeakerVoice->bActive == false))
g_FadeType = FADE_NONE;
// Timer expired
if(g_FadeType == FADE_NONE)
{
SoundCore_StopTimer();
return;
}
//
#if 1
if(g_FadeType == FADE_IN)
g_FadeType = FADE_NONE;
else
SpkrUpdate_Timer();
#else
const LONG nFadeUnit_Fast = (DSBVOLUME_MAX - DSBVOLUME_MIN) / 10;
const LONG nFadeUnit_Slow = (DSBVOLUME_MAX - DSBVOLUME_MIN) / 1000; // Less noisy for 'silence'
LONG nFadeUnit = g_pSpeakerVoice->bRecentlyActive ? nFadeUnit_Fast : nFadeUnit_Slow;
LONG nFadeVolume = g_pSpeakerVoice->nFadeVolume;
if(g_FadeType == FADE_IN)
{
if(nFadeVolume == g_pSpeakerVoice->nVolume)
{
g_FadeType = FADE_NONE;
SoundCore_StopTimer();
return;
}
nFadeVolume += nFadeUnit;
if(nFadeVolume > g_pSpeakerVoice->nVolume)
nFadeVolume = g_pSpeakerVoice->nVolume;
}
else // FADE_OUT
{
if(nFadeVolume == DSBVOLUME_MIN)
{
g_FadeType = FADE_NONE;
SoundCore_StopTimer();
return;
}
nFadeVolume -= nFadeUnit;
if(nFadeVolume < DSBVOLUME_MIN)
nFadeVolume = DSBVOLUME_MIN;
}
g_pSpeakerVoice->nFadeVolume = nFadeVolume;
g_pSpeakerVoice->lpDSBvoice->SetVolume(nFadeVolume);
#endif
}
#endif
//-----------------------------------------------------------------------------
#ifndef APPLE2IX
void SoundCore_SetFade(eFADE FadeType)
{
static int nLastMode = -1;
if(g_nAppMode == MODE_DEBUG)
return;
// Fade in/out just for speaker, the others are demuted/muted
if(FadeType != FADE_NONE)
{
for(UINT i=0; i<g_uNumVoices; i++)
{
// Note: Kludge for fading speaker if curr/last g_nAppMode is/was MODE_LOGO:
// . Bug in DirectSound? SpeakerVoice.lpDSBvoice->SetVolume() doesn't work without this!
// . See SoundCore_TweakVolumes() - could be this?
if((g_pVoices[i]->bIsSpeaker) && (g_nAppMode != MODE_LOGO) && (nLastMode != MODE_LOGO))
{
g_pVoices[i]->lpDSBvoice->GetVolume(&g_pVoices[i]->nFadeVolume);
g_FadeType = FadeType;
SoundCore_StartTimer();
}
else if(FadeType == FADE_OUT)
{
g_pVoices[i]->lpDSBvoice->SetVolume(DSBVOLUME_MIN);
g_pVoices[i]->bMute = true;
}
else // FADE_IN
{
g_pVoices[i]->lpDSBvoice->SetVolume(g_pVoices[i]->nVolume);
g_pVoices[i]->bMute = false;
}
}
}
else // FadeType == FADE_NONE
{
if( (g_FadeType != FADE_NONE) && // Currently fading-in/out
(g_pSpeakerVoice && g_pSpeakerVoice->bActive) )
{
g_FadeType = FADE_NONE; // TimerFunc will call StopTimer()
g_pSpeakerVoice->lpDSBvoice->SetVolume(g_pSpeakerVoice->nVolume);
}
}
nLastMode = g_nAppMode;
}
#endif
//-----------------------------------------------------------------------------
// If AppleWin started by double-clicking a .dsk, then our window won't have focus when volumes are set (so gets ignored).
// Subsequent setting (to the same volume) will get ignored, as DirectSound thinks that volume is already set.
void SoundCore_TweakVolumes()
{
for (UINT i=0; i<g_uNumVoices; i++)
{
g_pVoices[i]->lpDSBvoice->SetVolume(g_pVoices[i]->nVolume-1);
g_pVoices[i]->lpDSBvoice->SetVolume(g_pVoices[i]->nVolume);
}
}
//-----------------------------------------------------------------------------
static UINT g_uDSInitRefCount = 0;
bool DSInit()
{
#ifdef APPLE2IX
if (!g_fh)
{
g_fh = stderr;
}
#endif
if(g_bDSAvailable)
{
g_uDSInitRefCount++;
return true; // Already initialised successfully
}
#ifdef APPLE2IX
if (sound_devices)
{
LOG("Destroying old device names...");
char **ptr = sound_devices;
while (*ptr)
{
Free(*ptr);
++ptr;
}
Free(sound_devices);
sound_devices = NULL;
}
num_sound_devices = SoundSystemEnumerate(&sound_devices, MAX_SOUND_DEVICES);
HRESULT hr = (num_sound_devices <= 0);
#else
HRESULT hr = DirectSoundEnumerate((LPDSENUMCALLBACK)DSEnumProc, NULL);
#endif
if(FAILED(hr))
{
if(g_fh) fprintf(g_fh, "DSEnumerate failed (%08X)\n",hr);
return false;
}
if(g_fh)
{
fprintf(g_fh, "Number of sound devices = %d\n",num_sound_devices);
}
bool bCreatedOK = false;
for(int x=0; x<num_sound_devices; x++)
{
#ifdef APPLE2IX
if (g_lpDS)
{
SoundSystemDestroy((SoundSystemStruct**)&g_lpDS);
}
hr = SoundSystemCreate(sound_devices[x], (SoundSystemStruct**)&g_lpDS);
#else
hr = DirectSoundCreate(&sound_device_guid[x], &g_lpDS, NULL);
#endif
if(SUCCEEDED(hr))
{
if(g_fh) fprintf(g_fh, "DSCreate succeeded for sound device #%d\n",x);
bCreatedOK = true;
break;
}
if(g_fh) fprintf(g_fh, "DSCreate failed for sound device #%d (%08X)\n",x,hr);
}
if(!bCreatedOK)
{
if(g_fh) fprintf(g_fh, "DSCreate failed for all sound devices\n");
return false;
}
#ifndef APPLE2IX
hr = g_lpDS->SetCooperativeLevel(g_hFrameWindow, DSSCL_NORMAL);
if(FAILED(hr))
{
if(g_fh) fprintf(g_fh, "SetCooperativeLevel failed (%08X)\n",hr);
return false;
}
DSCAPS DSCaps;
ZeroMemory(&DSCaps, sizeof(DSCAPS));
DSCaps.dwSize = sizeof(DSCAPS);
hr = g_lpDS->GetCaps(&DSCaps);
if(FAILED(hr))
{
if(g_fh) fprintf(g_fh, "GetCaps failed (%08X)\n",hr);
// Not fatal: so continue...
}
#endif
g_bDSAvailable = true;
g_uDSInitRefCount = 1;
return true;
}
//-----------------------------------------------------------------------------
void DSUninit()
{
if(!g_bDSAvailable)
return;
_ASSERT(g_uDSInitRefCount);
if(g_uDSInitRefCount == 0)
return;
g_uDSInitRefCount--;
if(g_uDSInitRefCount)
return;
//
_ASSERT(g_uNumVoices == 0);
#ifdef APPLE2IX
SoundSystemDestroy((SoundSystemStruct**)&g_lpDS);
#else
SAFE_RELEASE(g_lpDS);
#endif
g_bDSAvailable = false;
SoundCore_StopTimer();
}
//-----------------------------------------------------------------------------
LONG NewVolume(DWORD dwVolume, DWORD dwVolumeMax)
{
float fVol = (float) dwVolume / (float) dwVolumeMax; // 0.0=Max, 1.0=Min
return (LONG) ((float) DSBVOLUME_MIN * fVol);
}
//=============================================================================
static int g_nErrorInc = 20; // Old: 1
static int g_nErrorMax = 200; // Old: 50
int SoundCore_GetErrorInc()
{
return g_nErrorInc;
}
void SoundCore_SetErrorInc(const int nErrorInc)
{
g_nErrorInc = nErrorInc < g_nErrorMax ? nErrorInc : g_nErrorMax;
if(g_fh) fprintf(g_fh, "Speaker/MB Error Inc = %d\n", g_nErrorInc);
}
int SoundCore_GetErrorMax()
{
return g_nErrorMax;
}
void SoundCore_SetErrorMax(const int nErrorMax)
{
g_nErrorMax = nErrorMax < MAX_SAMPLES ? nErrorMax : MAX_SAMPLES;
if(g_fh) fprintf(g_fh, "Speaker/MB Error Max = %d\n", g_nErrorMax);
}
//=============================================================================
static DWORD g_dwAdviseToken;
#ifndef APPLE2IX
static IReferenceClock *g_pRefClock = NULL;
static HANDLE g_hSemaphore = NULL;
#endif
static bool g_bRefClockTimerActive = false;
static DWORD g_dwLastUsecPeriod = 0;
bool SysClk_InitTimer()
{
#ifdef APPLE2IX
// Not using timers ...
return false;
#else
g_hSemaphore = CreateSemaphore(NULL, 0, 1, NULL); // Max count = 1
if (g_hSemaphore == NULL)
{
fprintf(stderr, "Error creating semaphore\n");
return false;
}
if (CoCreateInstance(CLSID_SystemClock, NULL, CLSCTX_INPROC,
IID_IReferenceClock, (LPVOID*)&g_pRefClock) != S_OK)
{
fprintf(stderr, "Error initialising COM\n");
return false; // Fails for Win95!
}
return true;
#endif
}
void SysClk_UninitTimer()
{
#ifdef APPLE2IX
// Not using timers ...
#else
SysClk_StopTimer();
SAFE_RELEASE(g_pRefClock);
if (CloseHandle(g_hSemaphore) == 0)
fprintf(stderr, "Error closing semaphore handle\n");
#endif
}
//
void SysClk_WaitTimer()
{
#ifdef APPLE2IX
// Not using timers ...
#else
if(!g_bRefClockTimerActive)
return;
WaitForSingleObject(g_hSemaphore, INFINITE);
#endif
}
//
void SysClk_StartTimerUsec(DWORD dwUsecPeriod)
{
#ifdef APPLE2IX
// Not using timers ...
#else
if(g_bRefClockTimerActive && (g_dwLastUsecPeriod == dwUsecPeriod))
return;
SysClk_StopTimer();
REFERENCE_TIME rtPeriod = (REFERENCE_TIME) (dwUsecPeriod * 10); // In units of 100ns
REFERENCE_TIME rtNow;
HRESULT hr = g_pRefClock->GetTime(&rtNow);
// S_FALSE : Returned time is the same as the previous value
if ((hr != S_OK) && (hr != S_FALSE))
{
_ASSERT(0);
return;
}
if (g_pRefClock->AdvisePeriodic(rtNow, rtPeriod, g_hSemaphore, &g_dwAdviseToken) != S_OK)
{
fprintf(stderr, "Error creating timer\n");
_ASSERT(0);
return;
}
g_dwLastUsecPeriod = dwUsecPeriod;
g_bRefClockTimerActive = true;
#endif
}
void SysClk_StopTimer()
{
#ifdef APPLE2IX
// Not using timers ...
#else
if(!g_bRefClockTimerActive)
return;
if (g_pRefClock->Unadvise(g_dwAdviseToken) != S_OK)
{
fprintf(stderr, "Error deleting timer\n");
_ASSERT(0);
return;
}
g_bRefClockTimerActive = false;
#endif
}
#ifdef APPLE2IX
# if defined(__GNUC__)
# pragma GCC diagnostic pop
# elif defined(__clang__)
# pragma clang diagnostic pop
# endif
#endif

100
src/soundcore.h Normal file
View File

@ -0,0 +1,100 @@
/*
* Apple // emulator for *nix
*
* This software package is subject to the GNU General Public License
* version 2 or later (your choice) as published by the Free Software
* Foundation.
*
* THERE ARE NO WARRANTIES WHATSOEVER.
*
*/
/*
* Sources derived from AppleWin emulator
* Ported by Aaron Culliney
*/
#ifndef _SOUNDCORE_H_
#define _SOUNDCORE_H_
#include "ds-shim.h"
#define MAX_SAMPLES (16*1024)
#if defined(APPLE2IX)
#define SAFE_RELEASE(p) Free(p)
#else
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }
#endif
// Define max 1 of these:
//#define RIFF_SPKR
//#define RIFF_MB
typedef struct
{
LPDIRECTSOUNDBUFFER lpDSBvoice;
#ifdef APPLE2IX
// apparently lpDSNotify isn't used...
#define LPDIRECTSOUNDNOTIFY void*
#endif
LPDIRECTSOUNDNOTIFY lpDSNotify;
bool bActive; // Playback is active
bool bMute;
LONG nVolume; // Current volume (as used by DirectSound)
LONG nFadeVolume; // Current fade volume (as used by DirectSound)
DWORD dwUserVolume; // Volume from slider on Property Sheet (0=Max)
bool bIsSpeaker;
bool bRecentlyActive; // (Speaker only) false after 0.2s of speaker inactivity
} VOICE, *PVOICE;
bool DSGetLock(LPDIRECTSOUNDBUFFER pVoice, DWORD dwOffset, DWORD dwBytes,
SHORT** ppDSLockedBuffer0, DWORD* pdwDSLockedBufferSize0,
SHORT** ppDSLockedBuffer1, DWORD* pdwDSLockedBufferSize1);
HRESULT DSGetSoundBuffer(VOICE* pVoice, DWORD dwFlags, DWORD dwBufferSize, DWORD nSampleRate, int nChannels);
void DSReleaseSoundBuffer(VOICE* pVoice);
bool DSZeroVoiceBuffer(PVOICE Voice, char* pszDevName, DWORD dwBufferSize);
bool DSZeroVoiceWritableBuffer(PVOICE Voice, char* pszDevName, DWORD dwBufferSize);
#if defined(APPLE2IX)
typedef enum eFADE {FADE_NONE, FADE_IN, FADE_OUT} eFADE;
#else
enum eFADE {FADE_NONE, FADE_IN, FADE_OUT};
#endif
void SoundCore_SetFade(eFADE FadeType);
bool SoundCore_GetTimerState();
void SoundCore_TweakVolumes();
int SoundCore_GetErrorInc();
void SoundCore_SetErrorInc(const int nErrorInc);
int SoundCore_GetErrorMax();
void SoundCore_SetErrorMax(const int nErrorMax);
bool DSInit();
void DSUninit();
LONG NewVolume(DWORD dwVolume, DWORD dwVolumeMax);
void SysClk_WaitTimer();
bool SysClk_InitTimer();
void SysClk_UninitTimer();
void SysClk_StartTimerUsec(DWORD dwUsecPeriod);
void SysClk_StopTimer();
//
extern bool g_bDSAvailable;
#ifdef APPLE2IX
typedef struct IDirectSound SoundSystemStruct;
int SoundSystemInitialize();
long SoundSystemCreate(const char *sound_device, SoundSystemStruct **sound_struct);
long SoundSystemDestroy(SoundSystemStruct **sound_struct);
long SoundSystemEnumerate(char ***sound_devices, const int maxcount);
#endif
#endif /* whole file */

1315
src/speaker.c Normal file

File diff suppressed because it is too large Load Diff

54
src/speaker.h Normal file
View File

@ -0,0 +1,54 @@
/*
* Apple // emulator for *nix
*
* This software package is subject to the GNU General Public License
* version 2 or later (your choice) as published by the Free Software
* Foundation.
*
* THERE ARE NO WARRANTIES WHATSOEVER.
*
*/
#ifndef _SPEAKER_H_
#define _SPEAKER_H_
#ifdef APPLE2IX
#include "win-shim.h"
#endif
extern DWORD soundtype;
extern double g_fClksPerSpkrSample;
void SpkrDestroy ();
void SpkrInitialize ();
void SpkrReinitialize ();
void SpkrReset();
BOOL SpkrSetEmulationType (HWND,DWORD);
void SpkrUpdate (DWORD);
void SpkrUpdate_Timer();
void Spkr_SetErrorInc(const int nErrorInc);
void Spkr_SetErrorMax(const int nErrorMax);
DWORD SpkrGetVolume();
void SpkrSetVolume(DWORD dwVolume, DWORD dwVolumeMax);
void Spkr_Mute();
void Spkr_Demute();
bool Spkr_IsActive();
bool Spkr_DSInit();
void Spkr_DSUninit();
#ifdef APPLE2IX
#define __int64
typedef struct {
unsigned __int64 g_nSpkrLastCycle;
} SS_IO_Speaker;
#endif
DWORD SpkrGetSnapshot(SS_IO_Speaker* pSS);
DWORD SpkrSetSnapshot(SS_IO_Speaker* pSS);
#ifdef APPLE2IX
void SpkrToggle();
#else
BYTE __stdcall SpkrToggle (WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nCyclesLeft);
#endif
#endif /* whole file */

View File

@ -1,46 +1,66 @@
/*
* Apple // emulator for Linux
* Apple // emulator for *nix
*
* CPU Timing Support.
* This software package is subject to the GNU General Public License
* version 2 or later (your choice) as published by the Free Software
* Foundation.
*
* Mostly this adds support for specifically throttling the emulator speed to
* match a 1.02MHz Apple //e.
*
* Added 2013 by Aaron Culliney
* THERE ARE NO WARRANTIES WHATSOEVER.
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <time.h>
#include <pthread.h>
#include <limits.h>
/*
* 65c02 CPU Timing Support. Some source derived from AppleWin.
*
* Copyleft 2013 Aaron Culliney
*
*/
#include "timing.h"
#include "misc.h"
#include "cpu.h"
#include "speaker.h"
#include "keys.h"
#define CALIBRATE_HZ 100
#define EXECUTION_PERIOD_NSECS 1000000 // AppleWin: nExecutionPeriodUsec
static unsigned long CPU_TARGET_HZ = APPLE2_HZ; // target clock speed
static unsigned long CALIBRATE_INTERVAL_NSECS = NANOSECONDS / CALIBRATE_HZ; // calibration interval for drifting
static float CYCLE_NSECS = NANOSECONDS / (float)APPLE2_HZ; // nanosecs per cycle
extern void cpu65_run();
static struct timespec ti;
static float spin_ratio=0.0;
double g_fCurrentCLK6502 = CLK_6502;
bool g_bFullSpeed = false; // HACK TODO FIXME : prolly shouldn't be global anymore -- don't think it's necessary for speaker/soundcore/etc anymore ...
uint64_t g_nCumulativeCycles = 0; // cumulative cycles since emulator (re)start
int g_nCpuCyclesFeedback = 0;
double cpu_scale_factor = 1.0;
static unsigned int g_nCyclesExecuted; // # of cycles executed up to last IO access
// -----------------------------------------------------------------------------
// assuming end > start, returns end - start
static inline struct timespec timespec_diff(struct timespec start, struct timespec end) {
struct timespec timespec_diff(struct timespec start, struct timespec end, bool *negative) {
struct timespec t;
if (*negative)
{
*negative = false;
}
// if start > end, swizzle...
if ( (start.tv_sec > end.tv_sec) || ((start.tv_sec == end.tv_sec) && (start.tv_nsec > end.tv_nsec)) )
{
t=start;
start=end;
end=t;
if (negative)
{
*negative = true;
}
}
// assuming time_t is signed ...
if (end.tv_nsec < start.tv_nsec)
{
t.tv_sec = end.tv_sec - start.tv_sec - 1;
t.tv_nsec = NANOSECONDS + end.tv_nsec - start.tv_nsec;
t.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec;
}
else
{
@ -51,114 +71,176 @@ static inline struct timespec timespec_diff(struct timespec start, struct timesp
return t;
}
// spin loop to throttle to target CPU Hz
static inline void _spin_loop(unsigned long c)
{
static volatile unsigned int spinney=0; // volatile to prevent being optimized away
for (unsigned long i=0; i<c; i++)
static inline struct timespec timespec_add(struct timespec start, unsigned long nsecs) {
start.tv_nsec += nsecs;
if (start.tv_nsec > NANOSECONDS)
{
++spinney;
start.tv_sec += (start.tv_nsec / NANOSECONDS);
start.tv_nsec %= NANOSECONDS;
}
return start;
}
bool timing_is_fullspeed()
{
return g_bFullSpeed;
}
void timing_enable_fullspeed()
{
if (!g_bFullSpeed)
{
g_bFullSpeed = true;
c_disable_sound_hooks();
timing_initialize();
}
}
static void _determine_initial_spinloop_counter()
void timing_enable_regular_speed()
{
struct timespec s0, s1, deltat;
if (g_bFullSpeed)
{
g_bFullSpeed = false;
c_initialize_sound_hooks();
timing_initialize();
}
}
// time the spinloop to determine a good starting value for the spin counter
void timing_initialize()
{
if (g_bFullSpeed)
{
LOG("timing_initialize() emulation at fullspeed ...");
return;
}
g_fCurrentCLK6502 = CLK_6502 * cpu_scale_factor;
// this is extracted out of SetClksPerSpkrSample -- speaker.c
g_fClksPerSpkrSample = (double) (UINT) (g_fCurrentCLK6502 / (double)SPKR_SAMPLE_RATE);
SpkrReinitialize();
LOG("timing_initialize() ... ClockRate:%0.2lf ClockCyclesPerSpeakerSample:%0.2lf", g_fCurrentCLK6502, g_fClksPerSpkrSample);
}
void cpu_thread(void *dummyptr) {
struct timespec deltat;
struct timespec t0; // the target timer
struct timespec ti, tj; // actual time samples
bool negative = false;
long drift_adj_nsecs = 0; // generic drift adjustment between target and actual
unsigned long dbg_ticks = 0;
unsigned long avg_spin_nsecs = 0;
unsigned int const samples = 5;
unsigned int i=0;
unsigned int spinloop_count = 250000000;
do
{
clock_gettime(CLOCK_MONOTONIC, &s0);
_spin_loop(spinloop_count);
clock_gettime(CLOCK_MONOTONIC, &s1);
deltat = timespec_diff(s0, s1);
g_nCumulativeCycles = 0;
static int16_t cycles_adjust = 0;
LOG("cpu_thread : entering cpu65_run()...");
if (deltat.tv_sec > 0)
{
LOG("oops long wait (>= %lu sec) adjusting loop count (%d -> %d)", deltat.tv_sec, spinloop_count, spinloop_count>>1);
spinloop_count >>= 1;
i = 0;
avg_spin_nsecs = 0;
continue;
}
clock_gettime(CLOCK_MONOTONIC, &t0);
do {
// -LOCK----------------------------------------------------------------------------------------- SAMPLE ti
pthread_mutex_lock(&interface_mutex);
clock_gettime(CLOCK_MONOTONIC, &ti);
LOG("spinloop = %lu nsec", deltat.tv_nsec);
avg_spin_nsecs += deltat.tv_nsec;
++i;
} while (i<samples);
deltat = timespec_diff(t0, ti, &negative);
if (deltat.tv_sec)
{
// TODO FIXME : this is innocuous when coming out of interface menus, but are there any other edge cases?
LOG("NOTE : serious divergence from target time ...");
t0 = ti;
deltat = timespec_diff(t0, ti, &negative);
}
t0 = timespec_add(t0, EXECUTION_PERIOD_NSECS); // expected interval
drift_adj_nsecs = negative ? ~deltat.tv_nsec : deltat.tv_nsec;
avg_spin_nsecs = (avg_spin_nsecs / samples);
LOG("average = %lu nsec , spinloop_count = %u , samples = %u", avg_spin_nsecs, spinloop_count, samples);
// set up increment & decrement counters
cpu65_cycles_to_execute = (g_fCurrentCLK6502 / 1000); // g_fCurrentCLK6502 * EXECUTION_PERIOD_NSECS / NANOSECONDS
cpu65_cycles_to_execute += g_nCpuCyclesFeedback;
cpu65_cycles_to_execute -= cycles_adjust;
if (cpu65_cycles_to_execute < 0)
{
cpu65_cycles_to_execute = 0;
}
// counter for 1 nsec
spin_ratio = avg_spin_nsecs / ((float)spinloop_count);
cpu65_cycle_count = 0;
g_nCyclesExecuted = 0;
//MB_StartOfCpuExecute();
LOG("%fns cycle spins for average %f", CYCLE_NSECS, spin_ratio);
cpu65_run(); // run emulation for cpu65_cycles_to_execute cycles ...
cycles_adjust = cpu65_cycles_to_execute; // counter is decremented in cpu65_run()
if (cycles_adjust < 0)
{
cycles_adjust = ~cycles_adjust +1; // cycles_adjust *= -1
}
unsigned int uExecutedCycles = cpu65_cycle_count;
//MB_UpdateCycles(uExecutedCycles); // Update 6522s (NB. Do this before updating g_nCumulativeCycles below)
// N.B.: IO calls that depend on accurate timing will update g_nCyclesExecuted
const unsigned int nRemainingCycles = uExecutedCycles - g_nCyclesExecuted;
g_nCumulativeCycles += nRemainingCycles;
if (!g_bFullSpeed)
{
SpkrUpdate(uExecutedCycles); // play audio
}
clock_gettime(CLOCK_MONOTONIC, &tj);
pthread_mutex_unlock(&interface_mutex);
// -UNLOCK--------------------------------------------------------------------------------------- SAMPLE tj
deltat = timespec_diff(ti, tj, &negative);
assert(!negative);
long sleepfor = 0;
if (!deltat.tv_sec && !g_bFullSpeed)
{
sleepfor = EXECUTION_PERIOD_NSECS - drift_adj_nsecs - deltat.tv_nsec;
}
if (sleepfor <= 0)
{
// lagging ...
static time_t throttle_warning = 0;
if (t0.tv_sec - throttle_warning > 0)
{
LOG("lagging... %ld . %ld", deltat.tv_sec, deltat.tv_nsec);
throttle_warning = t0.tv_sec;
}
}
else
{
deltat.tv_sec = 0;
deltat.tv_nsec = sleepfor;
nanosleep(&deltat, NULL);
}
#ifndef NDEBUG
dbg_ticks += EXECUTION_PERIOD_NSECS;
if ((dbg_ticks % NANOSECONDS) == 0)
{
dbg_ticks = 0;
LOG("tick (%ld . %ld) real: (%ld . %ld)", t0.tv_sec, t0.tv_nsec, ti.tv_sec, ti.tv_nsec);
}
#endif
} while (!cpu65_do_reboot);
reinitialize();
} while (1);
}
void timing_initialize() {
// should do this only on startup
_determine_initial_spinloop_counter();
clock_gettime(CLOCK_MONOTONIC, &ti);
}
void timing_set_cpu_scale(unsigned int scale)
// From AppleWin...
// Called when an IO-reg is accessed & accurate cycle info is needed
void CpuCalcCycles(const unsigned long nExecutedCycles)
{
// ...
}
/*
* Throttles 6502 CPU down to the target CPU frequency (default is speed of original Apple //e).
*
* This uses an adaptive spin loop to stay closer to the target CPU frequency.
*/
void timing_throttle()
{
struct timespec tj, deltat;
clock_gettime(CLOCK_MONOTONIC, &tj);
deltat = timespec_diff(ti, tj);
ti=tj;
static time_t severe_lag=0;
if (deltat.tv_sec != 0)
{
// severely lagging...
if (severe_lag < time(NULL))
{
severe_lag = time(NULL)+2;
LOG("Severe lag detected...");
}
return;
}
uint8_t opcycles = cpu65__opcycles[cpu65_debug.opcode] + cpu65_debug.opcycles;
unsigned long opcycles_nsecs = opcycles * (CYCLE_NSECS/2);
if (deltat.tv_nsec >= opcycles_nsecs)
{
// lagging
return;
}
unsigned long diff_nsec = opcycles_nsecs - deltat.tv_nsec;
static time_t sample_time=0;
if (sample_time < time(NULL))
{
sample_time = time(NULL)+1;
//LOG("sample diff_nsec : %lu", diff_nsec);
}
unsigned long spin_count = spin_ratio * diff_nsec;
// spin for the rest of the interval ...
_spin_loop(spin_count);
// Calc # of cycles executed since this func was last called
const long nCycles = nExecutedCycles - g_nCyclesExecuted;
assert(nCycles >= 0);
g_nCumulativeCycles += nCycles;
g_nCyclesExecuted = nExecutedCycles;
}

View File

@ -1,26 +1,61 @@
/*
* Apple // emulator for Linux
* Apple // emulator for *nix
*
* CPU Timing Support.
* This software package is subject to the GNU General Public License
* version 2 or later (your choice) as published by the Free Software
* Foundation.
*
* Mostly this adds support for specifically throttling the emulator speed to
* match a 1.02MHz Apple //e.
* THERE ARE NO WARRANTIES WHATSOEVER.
*
* Added 2013 by Aaron Culliney
*/
/*
* 65c02 CPU Timing Support.
*
* Copyleft 2013 Aaron Culliney
*
*/
#ifndef _TIMING_H_
#define _TIMING_H_
#define APPLE2_HZ 1020000
#include "common.h"
#define NANOSECONDS 1000000000
// 0 = run as fast as possible, 1 = approximate apple, X = 1/X rate
void timing_set_cpu_scale(unsigned int scale);
// timing values cribbed from AppleWin
// 14318181.81...
#define _M14 (157500000.0 / 11.0)
// 65 cycles per 912 14M clocks = 1020484.45...
#define CLK_6502 ((_M14 * 65.0) / 912.0)
#define CPU_SCALE_SLOWEST 0.25
#define CPU_SCALE_FASTEST 4.005
#define CPU_SCALE_STEP_DIV 0.01
#define CPU_SCALE_STEP 0.05
#define SPKR_SAMPLE_RATE 44100
extern double g_fCurrentCLK6502;
extern bool g_bFullSpeed;
extern uint64_t g_nCumulativeCycles;
extern int g_nCpuCyclesFeedback;
extern double cpu_scale_factor;
struct timespec timespec_diff(struct timespec start, struct timespec end, bool *negative);
bool timing_is_fullspeed();
void timing_enable_fullspeed();
void timing_enable_regular_speed();
void timing_initialize();
void timing_throttle();
void cpu_thread();
void CpuCalcCycles(const unsigned long nExecutedCycles);
#endif // whole file

79
src/win-shim.h Normal file
View File

@ -0,0 +1,79 @@
/*
* Apple // emulator for *nix
*
* This software package is subject to the GNU General Public License
* version 2 or later (your choice) as published by the Free Software
* Foundation.
*
* THERE ARE NO WARRANTIES WHATSOEVER.
*
*/
#ifndef _WINSHIM_H_
#define _WINSHIM_H_
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
// 2013/09/19 - http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx
typedef unsigned long DWORD;
typedef unsigned long ULONG;
typedef long LONG;
typedef long HRESULT;
typedef unsigned int UINT;
typedef bool BOOL;
typedef char TCHAR;
typedef short SHORT;
typedef unsigned short WORD;
typedef unsigned char BYTE;
typedef long *LPLONG;
typedef void *LPVOID;
typedef void *LPDVOID;
typedef DWORD *LPDWORD;
typedef char *GUID; // HACK
typedef GUID IID;
typedef IID* REFIID;
typedef GUID *LPGUID;
typedef char *LPCSTR;
typedef LPCSTR LPCTSTR;
typedef unsigned int UINT_PTR;
typedef void *HWND; // HACK
typedef int64_t __int64;
#define VOID void
// unneeded ???
#define __stdcall
#define WINAPI
#define CALLBACK
#define FAR
typedef bool BOOL;
#define TRUE true
#define FALSE false
extern FILE *g_fh;
#define _strdup strdup
#define _ASSERT assert
#define Sleep(x) sleep(x)
typedef void *IUnknown;
#define TEXT(X) X
#define MB_ICONEXCLAMATION 0x00000030L
#define MB_SETFOREGROUND 0x00010000L
#define MessageBox(window, message, group, flags) LOG("%s", message)
#endif /* whole file */