mirror of
https://github.com/mauiaaron/apple2.git
synced 2024-06-26 00:29:27 +00:00
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:
parent
0f26899420
commit
41a1f3d598
68
src/cpu.S
68
src/cpu.S
|
@ -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
|
||||
|
|
|
@ -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
200
src/ds-shim.h
Normal 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 */
|
||||
|
|
@ -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
780
src/soundcore-alsa.c
Normal 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
36
src/soundcore-alsa.h
Normal 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
845
src/soundcore.c
Normal 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
100
src/soundcore.h
Normal 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
1315
src/speaker.c
Normal file
File diff suppressed because it is too large
Load Diff
54
src/speaker.h
Normal file
54
src/speaker.h
Normal 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 */
|
||||
|
308
src/timing.c
308
src/timing.c
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
53
src/timing.h
53
src/timing.h
|
@ -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
79
src/win-shim.h
Normal 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 */
|
||||
|
Loading…
Reference in New Issue
Block a user