1
0
mirror of synced 2024-12-18 10:45:52 +01:00
lindbergh-loader/src/libsegaapi/segaapi.c

1173 lines
34 KiB
C
Raw Normal View History

2022-07-28 13:43:10 +02:00
/*
Segaapi audio library emulator
Parts stolen from Sega, teknogods and jayfoxrox
Modified by doozer in 2022 to work with Outrun 2 SP under modern Linux
2022-07-28 13:59:04 +02:00
https://www.openal.org/documentation/
https://github.com/teknogods/OpenSegaAPI/blob/master/Opensegaapi/src/opensegaapi.cpp
https://web.archive.org/web/20070218003259/http://www.devmaster.net/articles.php?catID=6
2022-07-28 13:43:10 +02:00
*/
2022-07-28 19:54:00 +02:00
#define TSF_IMPLEMENTATION
2022-07-28 13:43:10 +02:00
#include <AL/al.h>
#include <AL/alc.h>
#include <AL/alext.h>
#include <AL/alut.h>
2022-07-28 19:54:00 +02:00
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
2022-07-28 13:43:10 +02:00
2022-07-28 19:54:00 +02:00
#include "segaapi.h"
2022-07-28 13:43:10 +02:00
#include "segadef.h"
#include "segaeax.h"
#include "tsf.h"
//#define DEBUG_SAMPLE
#define DEBUG_OUTPUT
//#define DUMP_WAV
//#define DUMP_BUFFER
#define CHECK() \
{ \
printf("%s %d\n", __func__, __LINE__); \
unsigned int err; \
if ((err = alGetError()) != AL_NO_ERROR) \
{ \
printf("%s %d\n", __func__, __LINE__); \
dbgPrint(":%i AL Error: 0x%08X\n", __LINE__, err); \
exit(2); \
} \
}
2022-07-28 19:54:00 +02:00
int g_LastStatus = SEGA_SUCCESS;
2022-07-28 13:43:10 +02:00
// outrun2 will complain if these aren't present
const GUID EAX_NULL_GUID;
// DEFINE_GUID(EAX_NULL_GUID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
// DEFINE_GUID(EAXPROPERTYID_EAX40_FXSlot2, 0x883b431d, 0xf6f0, 0x3746, 0x91, 0x9f, 0x60, 0xe7, 0xe0, 0x6b, 0x5e, 0xdd);
const GUID EAX_FREQUENCYSHIFTER_EFFECT;
const GUID EAX_ECHO_EFFECT;
const GUID EAX_REVERB_EFFECT;
const GUID EAX_EQUALIZER_EFFECT;
const GUID EAX_DISTORTION_EFFECT;
const GUID EAX_AGCCOMPRESSOR_EFFECT;
const GUID EAX_PITCHSHIFTER_EFFECT;
const GUID EAX_FLANGER_EFFECT;
const GUID EAX_VOCALMORPHER_EFFECT;
const GUID EAX_AUTOWAH_EFFECT;
const GUID EAX_RINGMODULATOR_EFFECT;
const GUID EAX_CHORUS_EFFECT;
const GUID EAXPROPERTYID_EAX40_FXSlot0;
const GUID EAXPROPERTYID_EAX40_FXSlot1;
const GUID EAXPROPERTYID_EAX40_FXSlot2;
const GUID EAXPROPERTYID_EAX40_FXSlot3;
typedef struct
{
void *userData;
HAWOSEGABUFFERCALLBACK callback;
bool synthesizer;
bool loop;
unsigned int channels;
unsigned int startLoop;
unsigned int endLoop;
unsigned int endOffset;
unsigned int sampleRate;
unsigned int sampleFormat;
uint8_t *data;
size_t size;
bool playing;
bool paused;
bool playWithSetup;
ALuint alBuffer;
ALuint alSource;
tsf *synth;
struct tsf_region *region;
} segaapiContext_t;
LPALBUFFERSAMPLESSOFT alBufferSamplesSOFT = NULL;
LPALBUFFERSUBSAMPLESSOFT alBufferSubSamplesSOFT = NULL;
LPALGETBUFFERSAMPLESSOFT alGetBufferSamplesSOFT = NULL;
#ifdef DEBUG_OUTPUT
void dbgPrint(const char *format, ...)
{
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
printf("\n");
}
#else
void dbgPrint(const char *format, ...)
{
return;
}
#endif
#ifdef DUMP_BUFFER
static void dumpBuffer(const char *path, void *data, size_t size)
{
FILE *soundFile = NULL;
soundFile = fopen(path, "wb");
fwrite(data, size, 1, soundFile);
fclose(soundFile);
}
#endif
#ifdef DUMP_WAV
static void dumpWaveBuffer(const char *path, unsigned int channels, unsigned int sampleRate, unsigned int sampleBits, void *data, size_t size)
{
struct RIFF_Header
{
char chunkID[4];
long chunkSize; // size not including chunkSize or chunkID
char format[4];
};
struct WAVE_Format
{
char subChunkID[4];
long subChunkSize;
short audioFormat;
short numChannels;
long sampleRate;
long byteRate;
short blockAlign;
short bitsPerSample;
};
struct WAVE_Data
{
char subChunkID[4]; // should contain the word data
long subChunk2Size; // Stores the size of the data block
};
// Local Declarations
FILE *soundFile = NULL;
struct WAVE_Format wave_format;
struct RIFF_Header riff_header;
struct WAVE_Data wave_data;
soundFile = fopen(path, "wb");
// check for RIFF and WAVE tag in memeory
riff_header.chunkID[0] = 'R';
riff_header.chunkID[1] = 'I';
riff_header.chunkID[2] = 'F';
riff_header.chunkID[3] = 'F';
riff_header.format[0] = 'W';
riff_header.format[1] = 'A';
riff_header.format[2] = 'V';
riff_header.format[3] = 'E';
// Read in the first chunk into the struct
fwrite(&riff_header, sizeof(struct RIFF_Header), 1, soundFile);
// check for fmt tag in memory
wave_format.subChunkID[0] = 'f';
wave_format.subChunkID[1] = 'm';
wave_format.subChunkID[2] = 't';
wave_format.subChunkID[3] = ' ';
wave_format.audioFormat = 1;
wave_format.sampleRate = sampleRate;
wave_format.numChannels = channels;
wave_format.bitsPerSample = sampleBits;
wave_format.byteRate = (sampleRate * sampleBits * channels) / 8;
wave_format.blockAlign = (sampleBits * channels) / 8;
wave_format.subChunkSize = 16;
// Read in the 2nd chunk for the wave info
fwrite(&wave_format, sizeof(struct WAVE_Format), 1, soundFile);
// Read in the the last byte of data before the sound file
// check for data tag in memory
wave_data.subChunkID[0] = 'd';
wave_data.subChunkID[1] = 'a';
wave_data.subChunkID[2] = 't';
wave_data.subChunkID[3] = 'a';
wave_data.subChunk2Size = size;
fwrite(&wave_data, sizeof(struct WAVE_Data), 1, soundFile);
// Read in the sound data into the soundData variable
fwrite(data, wave_data.subChunk2Size, 1, soundFile);
// clean up and return true if successful
fclose(soundFile);
return;
}
#endif
ALsizei FramesToBytes(ALsizei size, ALenum channels, ALenum type)
{
2022-07-28 13:59:04 +02:00
switch (channels)
{
case AL_MONO_SOFT:
size *= 1;
break;
case AL_STEREO_SOFT:
size *= 2;
break;
case AL_REAR_SOFT:
size *= 2;
break;
case AL_QUAD_SOFT:
size *= 4;
break;
case AL_5POINT1_SOFT:
size *= 6;
break;
case AL_6POINT1_SOFT:
size *= 7;
break;
case AL_7POINT1_SOFT:
size *= 8;
break;
}
2022-07-28 13:43:10 +02:00
2022-07-28 13:59:04 +02:00
switch (type)
{
case AL_BYTE_SOFT:
size *= sizeof(ALbyte);
break;
case AL_UNSIGNED_BYTE_SOFT:
size *= sizeof(ALubyte);
break;
case AL_SHORT_SOFT:
size *= sizeof(ALshort);
break;
case AL_UNSIGNED_SHORT_SOFT:
size *= sizeof(ALushort);
break;
case AL_INT_SOFT:
size *= sizeof(ALint);
break;
case AL_UNSIGNED_INT_SOFT:
size *= sizeof(ALuint);
break;
case AL_FLOAT_SOFT:
size *= sizeof(ALfloat);
break;
case AL_DOUBLE_SOFT:
size *= sizeof(ALdouble);
break;
}
return size;
}
2022-07-28 13:43:10 +02:00
static unsigned int bufferSampleSize(segaapiContext_t *context)
{ // printf("%s %d\n", __func__, __LINE__);
return context->channels * ((context->sampleFormat == HASF_SIGNED_16PCM) ? 2 : 1);
}
static void updateBufferLoop(segaapiContext_t *context)
{ // printf("%s %d\n", __func__, __LINE__);
if (context == NULL)
return;
unsigned int sampleSize = bufferSampleSize(context);
alSourcei(context->alSource, AL_BUFFER, AL_NONE);
// CHECK();
/*
FIXME: Re-enable, only crashed before - so fix this too..
ALint loopPoints[] = { buffer->startLoop / sampleSize, buffer->endLoop / sampleSize };
alBufferiv(buffer->alBuffer,AL_LOOP_POINTS_SOFT,loopPoints);
CHECK();
*/
return;
}
void AL_APIENTRY wrap_BufferSamples(ALuint buffer, ALuint samplerate,
ALenum internalformat, ALsizei samples,
ALenum channels, ALenum type,
const ALvoid *data)
{
alBufferData(buffer, internalformat, data,
FramesToBytes(samples, channels, type),
samplerate);
}
static void updateBufferData(segaapiContext_t *context, unsigned int offset, size_t length)
{
ALenum alFormat = -1;
ALenum alChannels = -1;
ALenum alType;
switch (context->sampleFormat)
{
case HASF_UNSIGNED_8PCM: /* Unsigned (offset 128) 8-bit PCM */
alType = AL_BYTE_SOFT;
switch (context->channels)
{
case 1:
alFormat = AL_MONO8_SOFT;
alChannels = AL_MONO_SOFT;
break;
case 2:
alFormat = AL_STEREO8_SOFT;
alChannels = AL_STEREO_SOFT;
break;
default:
break;
}
break;
case HASF_SIGNED_16PCM: /* Signed 16-bit PCM */
alType = AL_SHORT_SOFT;
switch (context->channels)
{
case 1:
alFormat = AL_MONO16_SOFT;
alChannels = AL_MONO_SOFT;
break;
case 2:
alFormat = AL_STEREO16_SOFT;
alChannels = AL_STEREO_SOFT;
break;
default:
break;
}
default:
break;
}
if (alFormat == -1)
{
dbgPrint("Unknown format! 0x%X with %u channels!\n", context->sampleFormat, context->channels);
}
ALint position;
alGetSourcei(context->alSource, AL_SAMPLE_OFFSET, &position); // TODO: Patch if looping is active
// CHECK();
ALint unsafe[2];
alGetSourceiv(context->alSource, AL_BYTE_RW_OFFSETS_SOFT, unsafe); // AL_BYTE_OFFSET
// CHECK();
if (offset != -1)
{
2022-07-28 13:59:04 +02:00
// printf("CANNOT DO IT\n");
// exit(1);
2022-07-28 13:43:10 +02:00
wrap_BufferSamples(context->alBuffer, context->sampleRate, alFormat, context->size / bufferSampleSize(context), alChannels, alType, &(context->data[offset]));
2022-07-28 13:59:04 +02:00
// alBufferSubSamplesSOFT(context->alBuffer, offset / bufferSampleSize(context), length / bufferSampleSize(context), alChannels, alType, &context->data[offset]);
// CHECK();
2022-07-28 13:43:10 +02:00
dbgPrint("Soft update in buffer %X at %u (%u bytes) - buffer playing at %u, unsafe region is %u to %u\n", (uintptr_t)context, offset, length, position, unsafe[0], unsafe[1]);
}
else
{
alSourcei(context->alSource, AL_BUFFER, AL_NONE);
// CHECK();
wrap_BufferSamples(context->alBuffer, context->sampleRate, alFormat, context->size / bufferSampleSize(context), alChannels, alType, context->data);
// CHECK();
alSourcei(context->alSource, AL_BUFFER, context->alBuffer);
// CHECK();
updateBufferLoop(context);
dbgPrint("Hard update in buffer %X (%u bytes) - buffer playing at %u, unsafe region is %u to %u\n", (uintptr_t)context, context->size, position, unsafe[0], unsafe[1]);
}
#ifdef DUMP_WAV
// This dumps the buffer
if (context->data != NULL)
{
uint8_t *nonZero = context->data;
while (*nonZero++ == 0x00)
;
if ((nonZero - (uint8_t *)context->data) < context->size)
{
printf("%s %d\n", __func__, __LINE__);
char buf[1000];
sprintf(buf, "SAMPLE-%X-%i-%04X-%u.wav", (uintptr_t)context, context->channels, context->sampleFormat, context->sampleRate);
dbgPrint("Writing: %s (%i)", buf, context->size);
void *tmp = malloc(context->size);
if (tmp != NULL)
{
alGetBufferSamplesSOFT(context->alBuffer, 0, context->size / bufferSampleSize(context), alChannels, alType, tmp);
// CHECK();
dumpWaveBuffer(buf, context->channels, context->sampleRate, (context->sampleFormat == HASF_SIGNED_16PCM) ? 16 : 8, tmp, context->size);
free(tmp);
}
}
}
#endif
return;
}
static void resetBuffer(segaapiContext_t *context)
{ // printf("%s %d\n", __func__, __LINE__);
// * - Send Routing
// * - for 1 channel buffer, channel is routed to Front-Left and Front-Right.
// * - for 2 channel buffer, channel 0 is routed Front-Left, channel 1 is routed Front-Right
// * - Send Levels are set to 0 (infinite attenuation)
// * - Channel Volume is set to 0xFFFFFFFF (no attenuation)
// * - No notification.
// * - StartLoopOffset is set to 0.
context->startLoop = 0;
// * - EndLoopOffset and EndOffset are set to pConfig->mapdata.dwSize.
context->endOffset = context->size;
context->endLoop = context->size;
// * - No loop.
context->loop = false;
context->paused = false;
context->playWithSetup = false;
tsf *res = (tsf *)TSF_MALLOC(sizeof(tsf));
TSF_MEMSET(res, 0, sizeof(tsf));
res->presetNum = 0;
res->outSampleRate = context->sampleRate;
context->synth = res;
struct tsf_region *region = malloc(sizeof(struct tsf_region));
memset(region, 0, sizeof(struct tsf_region));
tsf_region_clear(region, 0);
region->ampenv.delay = 0;
region->ampenv.hold = 300.0f;
region->ampenv.attack = 0;
region->ampenv.decay = 0;
region->ampenv.release = 0;
region->ampenv.sustain = 0;
context->region = region;
// * - Buffer is in the stop state.
// * - Play position is set to 0.
updateBufferData(context, -1, -1);
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_Play(void* hHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_Play() 0x%x", hHandle);
segaapiContext_t *context = hHandle;
if (context == NULL)
return SEGAERR_BAD_PARAM;
#ifdef DUMP_BUFFER
if (context->data != NULL)
{
if (context->synthesizer)
{
if (context->data)
{
char filename[1000];
sprintf(filename, "SYNTH-%X-%i-%04X-%u.bin", (uintptr_t)context, context->channels, context->sampleFormat, context->sampleRate);
dbgPrint("Writing: %s (%i)", filename, context->size);
dumpBuffer(filename, context->data, context->size);
}
}
}
#endif
alSourcei(context->alSource, AL_LOOPING, context->loop ? AL_TRUE : AL_FALSE);
alSourcei(context->alSource, AL_BUFFER, context->alBuffer);
alSourcePlay(context->alSource);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_Pause(void* hHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_Pause() 0x%x", hHandle);
segaapiContext_t *context = hHandle;
if (context == NULL)
return SEGAERR_BAD_PARAM;
alSourcePause(context->alSource);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_Stop(void* hHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_Stop() 0x%x", hHandle);
segaapiContext_t *context = hHandle;
if (context == NULL)
return SEGAERR_BAD_PARAM;
alSourceStop(context->alSource);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_PlayWithSetup(void* hHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_PlayWithSetup() 0x%x", hHandle);
segaapiContext_t *context = hHandle;
if (context == NULL)
return SEGAERR_BAD_PARAM;
alSourcei(context->alSource, AL_LOOPING, context->loop ? AL_TRUE : AL_FALSE);
alSourcei(context->alSource, AL_BUFFER, context->alBuffer);
alSourcePlay(context->alSource);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
PlaybackStatus SEGAAPI_GetPlaybackStatus(void* hHandle)
2022-07-28 13:43:10 +02:00
{
ALint state;
dbgPrint("SEGAAPI_GetPlaybackStatus() 0x%x", hHandle);
segaapiContext_t *context = hHandle;
if (context == NULL)
2022-07-28 13:59:04 +02:00
return PLAYBACK_STATUS_INVALID;
2022-07-28 13:43:10 +02:00
alGetSourcei(context->alSource, AL_SOURCE_STATE, &state);
switch (state)
{
case AL_PLAYING:
2022-07-28 13:59:04 +02:00
return PLAYBACK_STATUS_ACTIVE;
2022-07-28 13:43:10 +02:00
case AL_PAUSED:
2022-07-28 13:59:04 +02:00
return PLAYBACK_STATUS_PAUSE;
2022-07-28 13:43:10 +02:00
case AL_INITIAL:
case AL_STOPPED:
2022-07-28 13:59:04 +02:00
return PLAYBACK_STATUS_STOP;
2022-07-28 13:43:10 +02:00
default:
2022-07-28 13:59:04 +02:00
return PLAYBACK_STATUS_INVALID;
2022-07-28 13:43:10 +02:00
}
2022-07-28 13:59:04 +02:00
return PLAYBACK_STATUS_INVALID;
2022-07-28 13:43:10 +02:00
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetFormat(void* hHandle, HAWOSEFORMAT *pFormat)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetFormat() 0x%x", hHandle);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_GetFormat(void* hHandle, HAWOSEFORMAT *pFormat)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetFormat() 0x%x", hHandle);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetSampleRate(void* hHandle, unsigned int dwSampleRate)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetSampleRate() 0x%x 0x%x", hHandle, dwSampleRate);
if (hHandle == NULL)
{ // Not sure if this is correct here, but ABC currently tries to call this with a null pointer..
return SEGAERR_BAD_HANDLE;
}
segaapiContext_t *context = hHandle;
context->sampleRate = dwSampleRate;
updateBufferData(context, -1, -1);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
unsigned int SEGAAPI_GetSampleRate(void* hHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetSampleRate() 0x%x", hHandle);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetPriority(void* hHandle, unsigned int dwPriority)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetPriority() 0x%x 0x%x", hHandle, dwPriority);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
unsigned int SEGAAPI_GetPriority(void* hHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetPriority() 0x%x", hHandle);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetUserData(void* hHandle, void* hUserData)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetUserData() 0x%x 0x%x", hHandle, hUserData);
if (hHandle == NULL)
{ // Not sure if this is correct here, but ABC currently tries to call this with a null pointer..
dbgPrint("SEGAAPI_SetUserData() SEGAERR_BAD_HANDLE");
return SEGAERR_BAD_HANDLE;
}
segaapiContext_t *context = hHandle;
context->userData = hUserData;
dbgPrint("SEGAAPI_SetUserData() complete");
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
void* SEGAAPI_GetUserData(void* hHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetPriority() 0x%x", hHandle);
if (hHandle == NULL)
return NULL;
segaapiContext_t *context = hHandle;
return context->userData;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetSendRouting(void* hHandle, unsigned int dwChannel, unsigned int dwSend, HAROUTING dwDest)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetSendRouting() 0x%x 0x%x 0x%x 0x%x", hHandle, dwChannel, dwSend, dwDest);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
HAROUTING SEGAAPI_GetSendRouting(void* hHandle, unsigned int dwChannel, unsigned int dwSend)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetSendRouting() 0x%x 0x%x 0x%x", hHandle, dwChannel, dwSend);
return HA_UNUSED_PORT;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetSendLevel(void* hHandle, unsigned int dwChannel, unsigned int dwSend, unsigned int dwLevel)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetSendLevel() 0x%x 0x%x 0x%x 0x%x", hHandle, dwChannel, dwSend, dwLevel);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
unsigned int SEGAAPI_GetSendLevel(void* hHandle, unsigned int dwChannel, unsigned int dwSend)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetSendLevel() 0x%x 0x%x 0x%x", hHandle, dwChannel, dwSend);
return 0;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetChannelVolume(void* hHandle, unsigned int dwChannel, unsigned int dwVolume)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetChannelVolume() 0x%x 0x%x 0x%x", hHandle, dwChannel, dwVolume);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
unsigned int SEGAAPI_GetChannelVolume(void* hHandle, unsigned int dwChannel)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetChannelVolume() 0x%x 0x%x", hHandle, dwChannel);
return 0;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetPlaybackPosition(void* hHandle, unsigned int dwPlaybackPos)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetPlaybackPosition() 0x%x 0x%x", hHandle, dwPlaybackPos);
segaapiContext_t *context = hHandle;
alSourcei(context->alSource, AL_BYTE_OFFSET, dwPlaybackPos);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
unsigned int SEGAAPI_GetPlaybackPosition(void* hHandle)
2022-07-28 13:43:10 +02:00
{
ALint position;
dbgPrint("SEGAAPI_GetPlaybackPosition() 0x%x", hHandle);
segaapiContext_t *context = hHandle;
alGetSourcei(context->alSource, AL_BYTE_OFFSET, &position);
return position;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetNotificationFrequency(void* hHandle, unsigned int dwFrameCount)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetNotificationFrequency() 0x%x 0x%x", hHandle, dwFrameCount);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetNotificationPoint(void* hHandle, unsigned int dwBufferOffset)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetNotificationPoint() 0x%x 0x%x", hHandle, dwBufferOffset);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_ClearNotificationPoint(void* hHandle, unsigned int dwBufferOffset)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_ClearNotificationPoint() 0x%x 0x%x", hHandle, dwBufferOffset);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetStartLoopOffset(void* hHandle, unsigned int dwOffset)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetStartLoopOffset() 0x%x 0x%x", hHandle, dwOffset);
if (hHandle == NULL)
{ // Not sure if this is correct here, but ABC currently tries to call this with a null pointer..
return SEGAERR_BAD_HANDLE;
}
segaapiContext_t *context = hHandle;
context->startLoop = dwOffset;
updateBufferLoop(context);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
unsigned int SEGAAPI_GetStartLoopOffset(void* hHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetStartLoopOffset() 0x%x", hHandle);
return 0;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetEndLoopOffset(void* hHandle, unsigned int dwOffset)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetEndLoopOffset() 0x%x 0x%x", hHandle, dwOffset);
if (hHandle == NULL)
{ // Not sure if this is correct here, but ABC currently tries to call this with a null pointer..
return SEGAERR_BAD_HANDLE;
}
segaapiContext_t *context = hHandle;
context->endLoop = dwOffset;
updateBufferLoop(context);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
unsigned int SEGAAPI_GetEndLoopOffset(void* hHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetEndLoopOffset() 0x%x", hHandle);
return 0;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetEndOffset(void* hHandle, unsigned int dwOffset)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetEndOffset() 0x%x 0x%x", hHandle, dwOffset);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
unsigned int SEGAAPI_GetEndOffset(void* hHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetEndOffset() 0x%x", hHandle);
return 0;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetLoopState(void* hHandle, int bDoContinuousLooping)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetLoopState() 0x%x 0x%x", hHandle, bDoContinuousLooping);
segaapiContext_t *context = hHandle;
context->loop = bDoContinuousLooping;
alSourcei(context->alSource, AL_LOOPING, context->loop ? AL_TRUE : AL_FALSE);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_GetLoopState(void* hHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetLoopState() 0x%x", hHandle);
return 0;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_UpdateBuffer(void* hHandle, unsigned int dwStartOffset, unsigned int dwLength)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_UpdateBuffer() 0x%x 0x%x 0x%x", hHandle, dwStartOffset, dwLength);
if (hHandle == NULL)
{
dbgPrint("SEGAAPI_UpdateBuffer() SEGAERR_BAD_HANDLE");
return SEGAERR_BAD_HANDLE;
}
segaapiContext_t *context = hHandle;
updateBufferData(context, dwStartOffset, dwLength);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetSynthParam(void* hHandle, HASYNTHPARAMSEXT param, int lPARWValue)
2022-07-28 13:43:10 +02:00
{
float volume;
float semiTones;
float freqRatio;
dbgPrint("SEGAAPI_SetSynthParam() 0x%x 0x%x 0x%x", hHandle, param, lPARWValue);
segaapiContext_t *context = hHandle;
2022-07-28 13:59:04 +02:00
2022-07-28 13:43:10 +02:00
if (context == NULL)
return SEGAERR_BAD_PARAM;
enum
{
StartAddrsOffset,
EndAddrsOffset,
StartloopAddrsOffset,
EndloopAddrsOffset,
StartAddrsCoarseOffset,
ModLfoToPitch,
VibLfoToPitch,
ModEnvToPitch,
InitialFilterFc,
InitialFilterQ,
ModLfoToFilterFc,
ModEnvToFilterFc,
EndAddrsCoarseOffset,
ModLfoToVolume,
Unused1,
ChorusEffectsSend,
ReverbEffectsSend,
Pan,
Unused2,
Unused3,
Unused4,
DelayModLFO,
FreqModLFO,
DelayVibLFO,
FreqVibLFO,
DelayModEnv,
AttackModEnv,
HoldModEnv,
DecayModEnv,
SustainModEnv,
ReleaseModEnv,
KeynumToModEnvHold,
KeynumToModEnvDecay,
DelayVolEnv,
AttackVolEnv,
HoldVolEnv,
DecayVolEnv,
SustainVolEnv,
ReleaseVolEnv,
KeynumToVolEnvHold,
KeynumToVolEnvDecay,
Instrument,
Reserved1,
KeyRange,
VelRange,
StartloopAddrsCoarseOffset,
Keynum,
Velocity,
InitialAttenuation,
Reserved2,
EndloopAddrsCoarseOffset,
CoarseTune,
FineTune,
SampleID,
SampleModes,
Reserved3,
ScaleTuning,
ExclusiveClass,
OverridingRootKey,
Unused5,
EndOper
};
int mapping[26] = {
InitialAttenuation, ///< 0, 0x00, initialAttenuation
FineTune, ///< 1, 0x01, fineTune + coarseTune * 100
InitialFilterFc, ///< 2, 0x02, initialFilterFc
InitialFilterQ, ///< 3, 0x03, initialFilterQ
DelayVolEnv, ///< 4, 0x04, delayVolEnv
AttackVolEnv, ///< 5, 0x05, attackVolEnv
HoldVolEnv, ///< 6, 0x06, holdVolEnv
DecayVolEnv, ///< 7, 0x07, decayVolEnv
SustainVolEnv, ///< 8, 0x08, sustainVolEnv
ReleaseVolEnv, ///< 9, 0x09, releaseVolEnv
DelayModEnv, ///< 10, 0x0A, delayModEnv
AttackModEnv, ///< 11, 0x0B, attackModEnv
HoldModEnv, ///< 12, 0x0C, holdModEnv
DecayModEnv, ///< 13, 0x0D, decayModEnv
SustainModEnv, ///< 14, 0x0E, sustainModEnv
ReleaseModEnv, ///< 15, 0x0F, releaseModEnv
DelayModLFO, ///< 16, 0x10, delayModLFO
FreqModLFO, ///< 17, 0x11, freqModLFO
DelayVibLFO, ///< 18, 0x12, delayVibLFO
FreqVibLFO, ///< 19, 0x13, freqVibLFO
ModLfoToPitch, ///< 20, 0x14, modLfoToPitch
VibLfoToPitch, ///< 21, 0x15, vibLfoToPitch
ModLfoToFilterFc, ///< 22, 0x16, modLfoToFilterFc
ModLfoToVolume, ///< 23, 0x17, modLfoToVolume
ModEnvToPitch, ///< 24, 0x18, modEnvToPitch
ModEnvToFilterFc ///< 25, 0x19, modEnvToFilterFc
};
int realParam = mapping[param];
switch (param)
{
case HAVP_ATTENUATION:
volume = tsf_decibelsToGain(0.0f - lPARWValue / 10.0f);
alListenerf(AL_GAIN, volume);
// buffer->xaVoice->SetVolume(volume);
dbgPrint("SEGAAPI_SetSynthParam() HAVP_ATTENUATION gain: %f dB: %d", volume, lPARWValue);
break;
case HAVP_PITCH:
semiTones = lPARWValue / 100.0f;
// freqRatio = XAudio2SemitonesToFrequencyRatio(semiTones);
// http://www-personal.umich.edu/~bazald/l/api/_x_audio2_8h_source.html
freqRatio = powf(2.0f, semiTones / 12.0f);
// buffer->xaVoice->SetFrequencyRatio(freqRatio);
alSourcef(context->alSource, AL_PITCH, freqRatio);
dbgPrint("SEGAAPI_SetSynthParam() HAVP_PITCH hHandle: %08X semitones: %f freqRatio: %f", hHandle, semiTones, freqRatio);
break;
default:
dbgPrint("SEGAAPI_SetSynthParam() unsupported param: 0x%x", param);
}
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_GetSynthParam(void* hHandle, HASYNTHPARAMSEXT param)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetSynthParam() 0x%x 0x%x", hHandle, param);
return 0;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetSynthParamMultiple(void* hHandle, unsigned int dwNumParams, SynthParamSet *pSynthParams)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetSynthParamMultiple() 0x%x 0x%x 0x%x", hHandle, dwNumParams, pSynthParams);
segaapiContext_t *context = hHandle;
if (context == NULL)
return SEGAERR_BAD_PARAM;
for (int i = 0; i < dwNumParams; i++)
{
SEGAAPI_SetSynthParam(hHandle, pSynthParams[i].param, pSynthParams[i].lPARWValue);
}
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_GetSynthParamMultiple(void* hHandle, unsigned int dwNumParams, SynthParamSet *pSynthParams)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetSynthParamMultiple() 0x%x 0x%x 0x%x", hHandle, dwNumParams, pSynthParams);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetReleaseState(void* hHandle, int bSet)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetReleaseState() 0x%x 0x%x", hHandle, bSet);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_CreateBuffer(HAWOSEBUFFERCONFIG *pConfig, HAWOSEGABUFFERCALLBACK pCallback, unsigned int dwFlags, void* *phHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_CreateBuffer() 0x%x 0x%x 0x%x 0x%x", pConfig, pCallback, dwFlags, phHandle);
if ((phHandle == NULL) || (pConfig == NULL))
{
g_LastStatus = SEGAERR_BAD_POINTER;
dbgPrint("SEGAAPI_CreateBuffer() SEGAERR_BAD_POINTER");
return SEGAERR_BAD_POINTER;
}
segaapiContext_t *context = malloc(sizeof(segaapiContext_t));
if (context == NULL)
{
dbgPrint("SEGAAPI_CreateBuffer() SEGAERR_OUT_OF_MEMORY");
return SEGAERR_OUT_OF_MEMORY;
}
// dbgPrint("SEGAAPI_CreateBuffer() allocated %i bytes",sizeof(segaapiContext_t));
context->playing = false;
context->callback = pCallback;
context->synthesizer = dwFlags & HABUF_SYNTH_BUFFER;
context->sampleRate = pConfig->dwSampleRate;
context->sampleFormat = pConfig->dwSampleFormat;
context->channels = pConfig->byNumChans;
context->userData = pConfig->hUserData;
context->size = pConfig->mapData.dwSize;
pConfig->mapData.dwOffset = 0;
// can't have all 3 types at once - sanity check
if ((dwFlags & 0x06) == 0x06)
{
dbgPrint("SEGAAPI_CreateBuffer() SEGAERR_BAD_PARAM");
free(context);
return SEGAERR_BAD_PARAM;
}
// indiate that caller allocate memory
if (dwFlags & HABUF_ALLOC_USER_MEM)
{
context->data = pConfig->mapData.hBufferHdr;
dbgPrint("SEGAAPI_CreateBuffer() user memory 0x%x", context->data);
}
// reuse memory
else if (dwFlags & HABUF_USE_MAPPED_MEM)
{
context->data = pConfig->mapData.hBufferHdr;
if (context->data == NULL)
{
// null pointer, allocate memory
context->data = malloc(context->size);
if (context->data == NULL)
{
dbgPrint("SEGAAPI_CreateBuffer() SEGAERR_OUT_OF_MEMORY");
return SEGAERR_OUT_OF_MEMORY;
}
dbgPrint("SEGAAPI_CreateBuffer() bad pointer, allocated %i data bytes", context->size);
}
else
dbgPrint("SEGAAPI_CreateBuffer() reusing memory 0x%x", context->data);
}
// Allocate new buffer (caller will fill it later)
else
{
context->data = malloc(context->size);
if (context->data == NULL)
{
dbgPrint("SEGAAPI_CreateBuffer() SEGAERR_OUT_OF_MEMORY");
return SEGAERR_OUT_OF_MEMORY;
}
dbgPrint("SEGAAPI_CreateBuffer() allocated %i data bytes", context->size);
}
#ifdef DEBUG_SAMPLE
dbgPrint("SEGAAPI_CreateBuffer() dwPriority = 0x%08X; // The priority with which the voices should be allocated. This is used when voices need to be ripped off.", pConfig->dwPriority);
dbgPrint("SEGAAPI_CreateBuffer() dwSampleRate = %u; // The sample rate the voice desires", pConfig->dwSampleRate);
dbgPrint("SEGAAPI_CreateBuffer() dwSampleFormat = 0x%08X; // The sample format the voice will use", pConfig->dwSampleFormat);
dbgPrint("SEGAAPI_CreateBuffer() byNumChans = 0x%08X; // The number of samples in the sample frame. (1 = mono, 2 = stereo).", pConfig->byNumChans);
dbgPrint("SEGAAPI_CreateBuffer() dwReserved = 0x%08X; // Reserved field", pConfig->dwReserved);
dbgPrint("SEGAAPI_CreateBuffer() hUserData = 0x%08X; // User data", (uintptr_t)pConfig->hUserData);
dbgPrint("SEGAAPI_CreateBuffer() mapData { // The sample memory mapping for the buffer.");
dbgPrint("SEGAAPI_CreateBuffer() dwSize = 0x%08X; // Supply by caller. Size (in bytes) of the valid sample data", pConfig->mapData.dwSize);
dbgPrint("SEGAAPI_CreateBuffer() dwOffset = 0x%08X; // Return by driver. Offset of buffer where the the first valid sample should be written to", pConfig->mapData.dwOffset);
dbgPrint("SEGAAPI_CreateBuffer() hBufferHdr = 0x%08X; // Memory address that user-space application can access, or mapped memory handle.", (uintptr_t)pConfig->mapData.hBufferHdr);
dbgPrint("SEGAAPI_CreateBuffer() }");
#endif
pConfig->mapData.hBufferHdr = context->data;
alGenBuffers(1, &context->alBuffer);
alGenSources(1, &context->alSource);
/*
TODO:
* HABUF_ALLOC_USER_MEM bit when set indicates caller allocate sound data memory buffer.
* HABUF_USE_MAPPED_MEM
Can't be used at the same time!!!
*/
if (context->synthesizer)
{
dbgPrint("SEGAAPI_CreateBuffer() !!! Doesn't support synth buffers yet!");
// https://stackoverflow.com/questions/44157238/can-i-produce-a-synthetic-sound-using-openal
}
resetBuffer(context);
*phHandle = context;
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_DestroyBuffer(void* hHandle)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_DestroyBuffer() 0x%x", hHandle);
if (hHandle == NULL)
return SEGAERR_BAD_PARAM;
free(hHandle);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetGlobalEAXProperty(GUID *guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetGlobalEAXProperty() 0x%x 0x%x 0x%x 0x%x", guid, ulProperty, pData, ulDataSize);
return 0;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_GetGlobalEAXProperty(GUID *guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetGlobalEAXProperty() 0x%x 0x%x 0x%x 0x%x", guid, ulProperty, pData, ulDataSize);
return 0;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetSPDIFOutChannelStatus(unsigned int dwChannelStatus, unsigned int dwExtChannelStatus)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetSPDIFOutChannelStatus() 0x%x 0x%x", dwChannelStatus, dwExtChannelStatus);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_GetSPDIFOutChannelStatus(unsigned int *pdwChannelStatus, unsigned int *pdwExtChannelStatus)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetSPDIFOutChannelStatus() 0x%x 0x%x", pdwChannelStatus, pdwExtChannelStatus);
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetSPDIFOutSampleRate(HASPDIFOUTRATE dwSamplingRate)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetSPDIFOutSampleRate() 0x%x", dwSamplingRate);
return SEGAERR_UNSUPPORTED;
}
HASPDIFOUTRATE SEGAAPI_GetSPDIFOutSampleRate(void)
{
dbgPrint("SEGAAPI_GetSPDIFOutSampleRate()");
return HASPDIFOUT_48KHZ;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetSPDIFOutChannelRouting(unsigned int dwChannel, HAROUTING dwSource)
2022-07-28 13:43:10 +02:00
{
switch (dwChannel)
{
case 0: // left
dbgPrint("SEGAAPI_SetSPDIFOutChannelRouting() dwChannel = LEFT; dwSource = 0x%x", dwSource);
break;
case 1: // right
dbgPrint("SEGAAPI_SetSPDIFOutChannelRouting() dwChannel = RIGHT; dwSource = 0x%x", dwSource);
break;
default:
dbgPrint("SEGAAPI_SetSPDIFOutChannelRouting() dwChannel = UNKNOWN; dwSource = 0x%x", dwSource);
break;
}
return SEGAERR_UNSUPPORTED;
}
2022-07-28 19:54:00 +02:00
HAROUTING SEGAAPI_GetSPDIFOutChannelRouting(unsigned int dwChannel)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetSPDIFOutChannelRouting() 0x%x", dwChannel);
return HA_UNUSED_PORT;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_SetIOVolume(HAPHYSICALIO dwPhysIO, unsigned int dwVolume)
2022-07-28 13:43:10 +02:00
{
// float v = (dwVolume >> 16) & 0xffff;
dbgPrint("SEGAAPI_SetIOVolume() 0x%x 0x%x", dwPhysIO, dwVolume);
// alListenerf(AL_GAIN, v);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
unsigned int SEGAAPI_GetIOVolume(HAPHYSICALIO dwPhysIO)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetIOVolume() 0x%x", dwPhysIO);
return 0xffffffff;
}
2022-07-28 19:54:00 +02:00
void SEGAAPI_SetLastStatus(int LastStatus)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_SetLastStatus() 0x%x", LastStatus);
return;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_GetLastStatus(void)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_GetLastStatus()");
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_Reset(void)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_Reset()");
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_Init(void)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_Init()");
int res = alutInit(NULL, NULL);
if (res == AL_FALSE)
{
dbgPrint("SEGAAPI_Init() alutInit failed");
return SEGAERR_FAIL;
}
alBufferSamplesSOFT = alGetProcAddress("alBufferSamplesSOFT");
if (alBufferSamplesSOFT == NULL)
{
2022-07-28 13:59:04 +02:00
dbgPrint("Warning: Could not resolve AL extension!\n");
// exit(1);
2022-07-28 13:43:10 +02:00
}
alBufferSubSamplesSOFT = alGetProcAddress("alBufferSubSamplesSOFT");
if (alBufferSubSamplesSOFT == NULL)
{
2022-07-28 13:59:04 +02:00
dbgPrint("Warning: Could not resolve AL extension!\n");
// exit(1);
2022-07-28 13:43:10 +02:00
}
alGetBufferSamplesSOFT = alGetProcAddress("alGetBufferSamplesSOFT");
if (alGetBufferSamplesSOFT == NULL)
{
2022-07-28 13:59:04 +02:00
dbgPrint("Warning: Could not resolve AL extension!\n");
// exit(1);
2022-07-28 13:43:10 +02:00
}
SEGAAPI_SetGlobalEAXProperty((GUID *)&EAXPROPERTYID_EAX40_FXSlot2, 0, (void *)&EAX_NULL_GUID, 16);
SEGAAPI_SetSPDIFOutChannelRouting(0, 0);
SEGAAPI_SetSPDIFOutChannelRouting(1, 1);
SEGAAPI_SetSPDIFOutSampleRate(1);
return SEGA_SUCCESS;
}
2022-07-28 19:54:00 +02:00
int SEGAAPI_Exit(void)
2022-07-28 13:43:10 +02:00
{
dbgPrint("SEGAAPI_Exit()");
alutExit();
return SEGA_SUCCESS;
}