Files
lindbergh-loader_lindbergh-…/src/libsegaapi/libsegaapi.c
T

1160 lines
32 KiB
C

#include "libsegaapi.h"
#include <FAudio.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <stdarg.h>
#include <pthread.h>
#define HANDLE_CHECK(handle, returnValue) \
if (handle == NULL) \
{ \
debug(1, "Error: Failed a handle check\n"); \
return returnValue; \
}
#define DEBUG_LEVEL -1
#define OUTPUT_CHANNELS 6 // FL, FR, BL, BR, C, SW
#define MAX_CHANNELS 6
#define MAX_SENDS 13
FAudio *fAudio;
pthread_mutex_t fAudioMutex;
FAudioMasteringVoice *fAudioMasteringVoice;
FAudioSubmixVoice *fAudioSubmixVoices[MAX_SENDS];
unsigned int lastStatus;
unsigned int maxInputChannels;
typedef struct FAudioVoiceCallbackWithBuffer FAudioVoiceCallbackWithBuffer;
typedef struct Buffer
{
FAudioWaveFormatEx fAudioFormat;
FAudioBuffer fAudioBuffer;
FAudioSourceVoice *fAudioSourceVoice;
FAudioVoiceCallbackWithBuffer *fAudioVoiceCallback;
uint8_t *data;
size_t size;
void *userData;
BufferCallback callback;
int bDoContinuousLooping;
PlaybackStatus playbackStatus;
unsigned int startLoop;
unsigned int endLoop;
unsigned int endOffset;
OutputFormat outputFormat;
int updateOutputFormat;
Routing routing[MAX_CHANNELS][MAX_SENDS];
unsigned int sendLevels[MAX_CHANNELS][MAX_SENDS];
unsigned int channelVolumes[MAX_CHANNELS];
SynthParams synthParams[MOD_ENV_TO_FILTER_CUTOFF + 1];
int lastStatus;
} Buffer;
typedef struct FAudioVoiceCallbackWithBuffer
{
OnBufferEndFunc OnBufferEnd;
OnBufferStartFunc OnBufferStart;
OnLoopEndFunc OnLoopEnd;
OnStreamEndFunc OnStreamEnd;
OnVoiceErrorFunc OnVoiceError;
OnVoiceProcessingPassEndFunc OnVoiceProcessingPassEnd;
OnVoiceProcessingPassStartFunc OnVoiceProcessingPassStart;
Buffer *buffer;
} FAudioVoiceCallbackWithBuffer;
void StaticOnBufferEnd(FAudioVoiceCallback *callback, void *pBufferContext)
{
Buffer *buffer = ((FAudioVoiceCallbackWithBuffer *)callback)->buffer;
if (!buffer->updateOutputFormat)
return;
FAudioVoiceState fAudioVoiceState;
FAudioSourceVoice_GetState(buffer->fAudioSourceVoice, &fAudioVoiceState, 0);
if (!fAudioVoiceState.BuffersQueued)
{
FAudioSourceVoice_SetSourceSampleRate(buffer->fAudioSourceVoice, buffer->outputFormat.dwSampleRate);
buffer->updateOutputFormat = 0;
return;
}
FAudioSourceVoice_FlushSourceBuffers(buffer->fAudioSourceVoice);
}
void StaticOnVoiceProcessingPassStart(FAudioVoiceCallback *callback, uint32_t bytesRequired)
{
}
void StaticOnVoiceProcessingPassEnd(FAudioVoiceCallback *callback)
{
}
void StaticOnStreamEnd(FAudioVoiceCallback *callback)
{
}
void StaticOnBufferStart(FAudioVoiceCallback *callback, void *pBufferContext)
{
}
void StaticOnLoopEnd(FAudioVoiceCallback *callback, void *pBufferContext)
{
}
void StaticOnVoiceError(FAudioVoiceCallback *callback, void *pBufferContext, uint32_t error)
{
}
void debug(int level, const char *format, ...)
{
if (DEBUG_LEVEL < level)
return;
va_list args;
va_start(args, format);
vprintf(format, args);
fflush(stdout);
va_end(args);
}
static unsigned int bytesToSamples(Buffer *buffer, int value)
{
switch (buffer->outputFormat.dwSampleFormat)
{
case SIGNED_16PCM:
return value / (buffer->outputFormat.byNumChans * 2);
break;
case UNSIGNED_8PCM:
default:
return value / buffer->outputFormat.byNumChans;
break;
}
}
static unsigned int samplesToBytes(Buffer *buffer, int value)
{
switch (buffer->outputFormat.dwSampleFormat)
{
case SIGNED_16PCM:
return value * buffer->outputFormat.byNumChans * 2;
break;
case UNSIGNED_8PCM:
default:
return value * buffer->outputFormat.byNumChans;
break;
}
}
char *getRoutingString(Routing dwDest)
{
char *dest = "Unknown";
switch (dwDest)
{
case UNUSED_PORT:
dest = "UNUSED_PORT";
break;
case FRONT_LEFT_PORT:
dest = "FRONT_LEFT_PORT";
break;
case FRONT_RIGHT_PORT:
dest = "FRONT_RIGHT_PORT";
break;
case FRONT_CENTER_PORT:
dest = "FRONT_CENTER_PORT";
break;
case LFE_PORT:
dest = "LFE_PORT";
break;
case REAR_LEFT_PORT:
dest = "REAR_LEFT_PORT";
break;
case REAR_RIGHT_PORT:
dest = "REAR_RIGHT_PORT";
break;
case FXSLOT0_PORT:
dest = "FXSLOT0_PORT";
break;
case FXSLOT1_PORT:
dest = "FXSLOT1_PORT";
break;
case FXSLOT2_PORT:
dest = "FXSLOT2_PORT";
break;
case FXSLOT3_PORT:
dest = "FXSLOT3_PORT";
break;
default:
break;
}
return dest;
}
static void processSends(Buffer *buffer)
{
float matrix[MAX_SENDS][MAX_CHANNELS];
FAudioSubmixVoice *voices[MAX_CHANNELS * MAX_SENDS];
Routing routes[MAX_CHANNELS * MAX_SENDS];
int voiceIndex = 0;
for (int i = 0; i < buffer->outputFormat.byNumChans; i++)
{
for (int j = 0; j < MAX_SENDS; j++)
{
matrix[j][i] = 0;
debug(1, "Entry channel %d send %d to %s with level %X and channel level %X\n", i, j,
getRoutingString(buffer->routing[i][j]), buffer->sendLevels[i][j], buffer->channelVolumes[i]);
if (buffer->routing[i][j] != UNUSED_PORT && buffer->routing[i][j] < OUTPUT_CHANNELS)
{
voices[voiceIndex] = fAudioSubmixVoices[buffer->routing[i][j]];
routes[voiceIndex] = buffer->routing[i][j];
matrix[voiceIndex][i] =
(buffer->sendLevels[i][j] / (float)VOL_MAX) * (buffer->channelVolumes[i] / (float)VOL_MAX);
voiceIndex++;
}
}
}
debug(1, "-- matrix --\n");
for (int j = 0; j < buffer->outputFormat.byNumChans; j++)
{
debug(1, "CHANNEL_%d ", j + 1);
}
debug(1, "\n");
for (int i = 0; i < voiceIndex; i++)
{
debug(1, "%s ", getRoutingString(routes[i]));
for (int j = 0; j < buffer->outputFormat.byNumChans; j++)
{
debug(1, "%f ", matrix[i][j]);
}
debug(1, "\n");
}
debug(1, "--\n");
if (voiceIndex == 0)
return;
FAudioSendDescriptor fAudioSendDescriptor[MAX_CHANNELS * MAX_SENDS];
for (int i = 0; i < voiceIndex; i++)
{
fAudioSendDescriptor[i].Flags = 0;
fAudioSendDescriptor[i].pOutputVoice = voices[i];
}
FAudioVoiceSends fAudioVoiceSends = {.SendCount = voiceIndex, .pSends = fAudioSendDescriptor};
FAudioVoice_SetOutputVoices(buffer->fAudioSourceVoice, &fAudioVoiceSends);
for (int i = 0; i < voiceIndex; i++)
FAudioVoice_SetOutputMatrix(buffer->fAudioSourceVoice, voices[i], buffer->outputFormat.byNumChans, 1,
&matrix[i][0], FAUDIO_COMMIT_NOW);
}
int SEGAAPI_Play(void *hHandle)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
processSends(buffer);
SEGAAPI_UpdateBuffer(buffer, -1, -1);
buffer->playbackStatus = PLAYBACK_STATUS_ACTIVE;
FAudioSourceVoice_Start(buffer->fAudioSourceVoice, 0, FAUDIO_COMMIT_NOW);
return SEGA_SUCCESS;
}
int SEGAAPI_Pause(void *hHandle)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
buffer->playbackStatus = PLAYBACK_STATUS_PAUSE;
FAudioSourceVoice_Stop(buffer->fAudioSourceVoice, 0, FAUDIO_COMMIT_NOW);
return SEGA_SUCCESS;
}
int SEGAAPI_Stop(void *hHandle)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
buffer->playbackStatus = PLAYBACK_STATUS_STOP;
FAudioSourceVoice_Stop(buffer->fAudioSourceVoice, 0, FAUDIO_COMMIT_NOW);
FAudioSourceVoice_FlushSourceBuffers(buffer->fAudioSourceVoice);
return SEGA_SUCCESS;
}
int SEGAAPI_PlayWithSetup(void *hHandle, unsigned int dwNumSendRouteParams, SendRouteParamSet *pSendRouteParams,
unsigned int dwNumSendLevelParams, SendLevelParamSet *pSendLevelParams,
unsigned int dwNumVoiceParams, VoiceParamSet *pVoiceParams, unsigned int dwNumSynthParams,
SynthParamSet *pSynthParams)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
for (int i = 0; i < dwNumSendRouteParams; i++)
SEGAAPI_SetSendRouting(hHandle, pSendRouteParams[i].dwChannel, pSendRouteParams[i].dwSend,
pSendRouteParams[i].dwDest);
for (int i = 0; i < dwNumSendLevelParams; i++)
SEGAAPI_SetSendLevel(hHandle, pSendLevelParams[i].dwChannel, pSendLevelParams[i].dwSend,
pSendLevelParams[i].dwLevel);
for (int i = 0; i < dwNumVoiceParams; i++)
{
switch (pVoiceParams[i].VoiceIoctl)
{
case SET_START_LOOP_OFFSET:
SEGAAPI_SetStartLoopOffset(hHandle, pVoiceParams[i].dwParam1);
break;
case SET_END_LOOP_OFFSET:
SEGAAPI_SetEndLoopOffset(hHandle, pVoiceParams[i].dwParam1);
break;
case SET_END_OFFSET:
SEGAAPI_SetEndOffset(hHandle, pVoiceParams[i].dwParam1);
break;
case SET_PLAY_POSITION:
SEGAAPI_SetPlaybackPosition(hHandle, pVoiceParams[i].dwParam1);
break;
case SET_LOOP_STATE:
SEGAAPI_SetLoopState(hHandle, pVoiceParams[i].dwParam1);
break;
case SET_NOTIFICATION_POINT:
SEGAAPI_SetNotificationPoint(hHandle, pVoiceParams[i].dwParam1);
break;
case CLEAR_NOTIFICATION_POINT:
SEGAAPI_ClearNotificationPoint(hHandle, pVoiceParams[i].dwParam1);
break;
case SET_NOTIFICATION_FREQUENCY:
SEGAAPI_SetNotificationFrequency(hHandle, pVoiceParams[i].dwParam1);
break;
default:
break;
}
}
for (int i = 0; i < dwNumSynthParams; i++)
SEGAAPI_SetSynthParam(hHandle, pSynthParams[i].param, pSynthParams[i].lPARWValue);
return SEGAAPI_Play(hHandle);
}
PlaybackStatus SEGAAPI_GetPlaybackStatus(void *hHandle)
{
HANDLE_CHECK(hHandle, PLAYBACK_STATUS_INVALID);
Buffer *buffer = (Buffer *)hHandle;
if (buffer->playbackStatus == PLAYBACK_STATUS_PAUSE)
return PLAYBACK_STATUS_PAUSE;
FAudioVoiceState fAudioVoiceState;
FAudioSourceVoice_GetState(buffer->fAudioSourceVoice, &fAudioVoiceState, 0);
if (fAudioVoiceState.BuffersQueued == 0)
{
return PLAYBACK_STATUS_STOP;
}
if (!buffer->bDoContinuousLooping &&
fAudioVoiceState.SamplesPlayed >=
bytesToSamples(buffer, buffer->size < buffer->endOffset ? buffer->size : buffer->endOffset))
{
return PLAYBACK_STATUS_STOP;
}
return buffer->playbackStatus;
}
int SEGAAPI_SetFormat(void *hHandle, OutputFormat *pFormat)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
debug(1, "Warning: This game used SetFormat\n");
buffer->outputFormat.byNumChans = pFormat->byNumChans;
buffer->outputFormat.dwSampleFormat = pFormat->dwSampleFormat;
buffer->outputFormat.dwSampleRate = pFormat->dwSampleRate;
return SEGA_SUCCESS;
}
int SEGAAPI_GetFormat(void *hHandle, OutputFormat *pFormat)
{
HANDLE_CHECK(hHandle, 0);
Buffer *buffer = (Buffer *)hHandle;
debug(1, "Warning: This game used GetFormat\n");
pFormat->byNumChans = buffer->outputFormat.byNumChans;
pFormat->dwSampleFormat = buffer->outputFormat.dwSampleFormat;
pFormat->dwSampleRate = buffer->outputFormat.dwSampleRate;
return SEGA_SUCCESS;
}
int SEGAAPI_SetSampleRate(void *hHandle, unsigned int dwSampleRate)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
buffer->outputFormat.dwSampleRate = dwSampleRate;
FAudioVoiceState fAudioVoiceState;
FAudioSourceVoice_GetState(buffer->fAudioSourceVoice, &fAudioVoiceState, 0);
// If there are no buffers playing, set the sample rate immidiately.
if (!fAudioVoiceState.BuffersQueued)
{
FAudioSourceVoice_SetSourceSampleRate(buffer->fAudioSourceVoice, dwSampleRate);
return SEGA_SUCCESS;
}
// Flush the buffers and set the sample rate on next update
buffer->updateOutputFormat = 1;
FAudioSourceVoice_FlushSourceBuffers(buffer->fAudioSourceVoice);
return SEGA_SUCCESS;
}
unsigned int SEGAAPI_GetSampleRate(void *hHandle)
{
HANDLE_CHECK(hHandle, 0);
Buffer *buffer = (Buffer *)hHandle;
return buffer->outputFormat.dwSampleRate;
}
int SEGAAPI_SetPriority(void *hHandle, unsigned int dwPriority)
{
// We have an unlimited number of buffers, so we can ignore this function.
return SEGA_SUCCESS;
}
unsigned int SEGAAPI_GetPriority(void *hHandle)
{
// Assume everything has maximum priority and won't get cut.
return P_MAXIMUM;
}
int SEGAAPI_SetUserData(void *hHandle, void *hUserData)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
buffer->userData = hUserData;
return SEGA_SUCCESS;
}
void *SEGAAPI_GetUserData(void *hHandle)
{
HANDLE_CHECK(hHandle, NULL);
Buffer *buffer = (Buffer *)hHandle;
return buffer->userData;
}
int SEGAAPI_SetSendRouting(void *hHandle, unsigned int dwChannel, unsigned int dwSend, Routing dwDest)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
if (dwChannel >= MAX_CHANNELS)
return SEGA_ERROR_INVALID_CHANNEL;
if (dwSend >= MAX_SENDS)
return SEGA_ERROR_INVALID_SEND;
debug(1, "Info: Set send routing called for buffer %p channel %d send %d to destination %s\n", hHandle, dwChannel,
dwSend, getRoutingString(dwDest));
buffer->routing[dwChannel][dwSend] = dwDest;
processSends(buffer);
return SEGA_SUCCESS;
}
Routing SEGAAPI_GetSendRouting(void *hHandle, unsigned int dwChannel, unsigned int dwSend)
{
HANDLE_CHECK(hHandle, UNUSED_PORT);
Buffer *buffer = (Buffer *)hHandle;
if (dwChannel >= MAX_CHANNELS)
return SEGA_ERROR_INVALID_CHANNEL;
if (dwSend >= MAX_SENDS)
return SEGA_ERROR_INVALID_SEND;
return buffer->routing[dwChannel][dwSend];
}
int SEGAAPI_SetSendLevel(void *hHandle, unsigned int dwChannel, unsigned int dwSend, unsigned int dwLevel)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
if (dwChannel >= MAX_CHANNELS)
return SEGA_ERROR_INVALID_CHANNEL;
if (dwSend >= MAX_SENDS)
return SEGA_ERROR_INVALID_SEND;
buffer->sendLevels[dwChannel][dwSend] = dwLevel;
processSends(buffer);
return SEGA_SUCCESS;
}
unsigned int SEGAAPI_GetSendLevel(void *hHandle, unsigned int dwChannel, unsigned int dwSend)
{
HANDLE_CHECK(hHandle, 0);
Buffer *buffer = (Buffer *)hHandle;
if (dwChannel >= MAX_CHANNELS)
return 0;
if (dwSend >= MAX_SENDS)
return 0;
return buffer->sendLevels[dwChannel][dwSend];
}
int SEGAAPI_SetChannelVolume(void *hHandle, unsigned int dwChannel, unsigned int dwVolume)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
if (dwChannel >= MAX_CHANNELS)
return SEGA_ERROR_INVALID_CHANNEL;
buffer->channelVolumes[dwChannel] = dwVolume;
processSends(buffer);
return SEGA_SUCCESS;
}
unsigned int SEGAAPI_GetChannelVolume(void *hHandle, unsigned int dwChannel)
{
HANDLE_CHECK(hHandle, 0);
Buffer *buffer = (Buffer *)hHandle;
if (dwChannel >= MAX_CHANNELS)
return 0;
return buffer->channelVolumes[dwChannel];
}
int SEGAAPI_SetPlaybackPosition(void *hHandle, unsigned int dwPlaybackPos)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
if (dwPlaybackPos != 0)
{
debug(1, "Error: This game is trying to set the playback position which is not yet supported set to %d.\n",
dwPlaybackPos);
return SEGA_SUCCESS;
}
int status = SEGAAPI_GetPlaybackStatus(hHandle);
char *statusString = "UNKNOWN";
switch (status)
{
case PLAYBACK_STATUS_STOP:
statusString = "PLAYBACK_STATUS_STOP";
break;
case PLAYBACK_STATUS_PAUSE:
statusString = "PLAYBACK_STATUS_PAUSE";
break;
case PLAYBACK_STATUS_ACTIVE:
statusString = "PLAYBACK_STATUS_ACTIVE";
break;
case PLAYBACK_STATUS_INVALID:
statusString = "PLAYBACK_STATUS_INVALID";
break;
default:
break;
}
// Retrofan Test
if (buffer->bDoContinuousLooping && status == PLAYBACK_STATUS_STOP && buffer->fAudioFormat.nChannels == 1)
{
// printf("RETURNED SEGAAPI_SetPlaybackPosition(%p, %d) Looping %d Status %s\n", hHandle, dwPlaybackPos,
// buffer->bDoContinuousLooping, statusString);
// printf("Buffer Size: %d bytes, Channels: %d, Sample Rate: %d\n", buffer->fAudioFormat.nAvgBytesPerSec,
// buffer->fAudioFormat.nChannels, buffer->fAudioFormat.nSamplesPerSec);
return SEGA_SUCCESS;
}
// printf("Buffer Size: %d bytes, Channels: %d, Sample Rate: %d\n", buffer->fAudioFormat.nAvgBytesPerSec,
// buffer->fAudioFormat.nChannels, buffer->fAudioFormat.nSamplesPerSec);
// Retrofan Test
// printf("SEGAAPI_SetPlaybackPosition(%p, %d) Looping %d Status %s\n", hHandle, dwPlaybackPos,
// buffer->bDoContinuousLooping, statusString);
FAudioVoice_DestroyVoice(buffer->fAudioSourceVoice);
buffer->fAudioFormat.wFormatTag = 1;
buffer->fAudioFormat.nChannels = buffer->outputFormat.byNumChans;
buffer->fAudioFormat.nSamplesPerSec = buffer->outputFormat.dwSampleRate;
buffer->fAudioFormat.nAvgBytesPerSec = samplesToBytes(buffer, buffer->outputFormat.dwSampleRate);
buffer->fAudioFormat.nBlockAlign = samplesToBytes(buffer, 1);
buffer->fAudioFormat.wBitsPerSample = buffer->outputFormat.dwSampleFormat == SIGNED_16PCM ? 16 : 8;
buffer->fAudioFormat.cbSize = sizeof(FAudioWaveFormatEx);
FAudio_CreateSourceVoice(fAudio, &buffer->fAudioSourceVoice, &buffer->fAudioFormat, 0, FAUDIO_MAX_FREQ_RATIO,
(FAudioVoiceCallback *)buffer->fAudioVoiceCallback, NULL, NULL);
if (status == 1)
{
SEGAAPI_Play(hHandle);
}
// Should we flush the buffers or stop here?
return SEGA_SUCCESS;
}
unsigned int SEGAAPI_GetPlaybackPosition(void *hHandle)
{
HANDLE_CHECK(hHandle, 0);
Buffer *buffer = (Buffer *)hHandle;
FAudioVoiceState fAudioVoiceState;
FAudioSourceVoice_GetState(buffer->fAudioSourceVoice, &fAudioVoiceState, 0);
unsigned int bytesPlayed =
fAudioVoiceState.SamplesPlayed * buffer->fAudioFormat.nChannels * (buffer->fAudioFormat.wBitsPerSample / 8);
return bytesPlayed % buffer->size;
}
int SEGAAPI_SetNotificationFrequency(void *hHandle, unsigned int dwFrameCount)
{
debug(1, "Warning: This game is trying to use the notification frequency API\n");
return SEGA_SUCCESS;
}
int SEGAAPI_SetNotificationPoint(void *hHandle, unsigned int dwBufferOffset)
{
debug(1, "Warning: This game is trying to use the notification frequency API\n");
return SEGA_SUCCESS;
}
int SEGAAPI_ClearNotificationPoint(void *hHandle, unsigned int dwBufferOffset)
{
debug(1, "Warning: This game is trying to use the notification frequency API\n");
return SEGA_SUCCESS;
}
int SEGAAPI_SetStartLoopOffset(void *hHandle, unsigned int dwOffset)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
buffer->startLoop = dwOffset;
SEGAAPI_UpdateBuffer(hHandle, -1, -1);
return SEGA_SUCCESS;
}
unsigned int SEGAAPI_GetStartLoopOffset(void *hHandle)
{
HANDLE_CHECK(hHandle, 0);
Buffer *buffer = (Buffer *)hHandle;
return buffer->startLoop;
}
int SEGAAPI_SetEndLoopOffset(void *hHandle, unsigned int dwOffset)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
buffer->endLoop = dwOffset;
SEGAAPI_UpdateBuffer(hHandle, -1, -1);
return SEGA_SUCCESS;
}
unsigned int SEGAAPI_GetEndLoopOffset(void *hHandle)
{
HANDLE_CHECK(hHandle, 0);
Buffer *buffer = (Buffer *)hHandle;
return buffer->endLoop;
}
int SEGAAPI_SetEndOffset(void *hHandle, unsigned int dwOffset)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
buffer->endOffset = dwOffset;
SEGAAPI_UpdateBuffer(hHandle, -1, -1);
return SEGA_SUCCESS;
}
unsigned int SEGAAPI_GetEndOffset(void *hHandle)
{
HANDLE_CHECK(hHandle, 0);
Buffer *buffer = (Buffer *)hHandle;
return buffer->endOffset;
}
int SEGAAPI_SetLoopState(void *hHandle, int bDoContinuousLooping)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
buffer->bDoContinuousLooping = bDoContinuousLooping;
SEGAAPI_UpdateBuffer(hHandle, -1, -1);
return SEGA_SUCCESS;
}
int SEGAAPI_GetLoopState(void *hHandle)
{
HANDLE_CHECK(hHandle, 0);
Buffer *buffer = (Buffer *)hHandle;
return buffer->bDoContinuousLooping;
}
int SEGAAPI_UpdateBuffer(void *hHandle, unsigned int dwStartOffset, unsigned int dwLength)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
// Ignore the call if we're waiting to update the output format
if (buffer->updateOutputFormat)
return SEGA_SUCCESS;
FAudioSourceVoice_FlushSourceBuffers(buffer->fAudioSourceVoice);
// memset(buffer->fAudioBuffer, 0, sizeof(FAudioBuffer));
buffer->fAudioBuffer.Flags = 0;
buffer->fAudioBuffer.AudioBytes = buffer->size;
buffer->fAudioBuffer.pAudioData = buffer->data;
buffer->fAudioBuffer.PlayBegin = bytesToSamples(buffer, buffer->startLoop);
buffer->fAudioBuffer.PlayLength = bytesToSamples(buffer, buffer->endOffset - buffer->startLoop);
buffer->fAudioBuffer.LoopBegin = 0;
buffer->fAudioBuffer.LoopLength = 0;
buffer->fAudioBuffer.LoopCount = 0;
buffer->fAudioBuffer.pContext = NULL;
if (buffer->bDoContinuousLooping)
{
buffer->fAudioBuffer.LoopBegin = bytesToSamples(buffer, buffer->startLoop);
buffer->fAudioBuffer.LoopLength = bytesToSamples(buffer, buffer->endLoop - buffer->startLoop);
buffer->fAudioBuffer.LoopCount = FAUDIO_LOOP_INFINITE;
}
FAudioSourceVoice_SubmitSourceBuffer(buffer->fAudioSourceVoice, &buffer->fAudioBuffer, NULL);
return SEGA_SUCCESS;
}
int SEGAAPI_SetSynthParam(void *hHandle, SynthParams param, int lPARWValue)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
buffer->synthParams[param] = lPARWValue;
switch (param)
{
case ATTENUATION: // I think this is probably wrong and needs a fix
{
float volumeInDecibels = 0.0f - lPARWValue / 10.0f;
float volumeInGain = volumeInDecibels > -100.f ? powf(10.0f, volumeInDecibels * 0.05f) : 0;
FAudioVoice_SetVolume(buffer->fAudioSourceVoice, volumeInGain, FAUDIO_COMMIT_NOW);
}
break;
case PITCH:
{
float semiTones = lPARWValue / 100.0f;
float freqRatio = powf(2.0f, semiTones / 12.0f);
FAudioSourceVoice_SetFrequencyRatio(buffer->fAudioSourceVoice, freqRatio, FAUDIO_COMMIT_NOW);
}
break;
default:
break;
}
return SEGA_SUCCESS;
}
int SEGAAPI_GetSynthParam(void *hHandle, SynthParams param)
{
HANDLE_CHECK(hHandle, 0);
Buffer *buffer = (Buffer *)hHandle;
return buffer->synthParams[param];
}
int SEGAAPI_SetSynthParamMultiple(void *hHandle, unsigned int dwNumParams, SynthParamSet *pSynthParams)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
for (int i = 0; i < dwNumParams; i++)
{
SEGAAPI_SetSynthParam(hHandle, pSynthParams[i].param, pSynthParams[i].lPARWValue);
}
return SEGA_SUCCESS;
}
int SEGAAPI_GetSynthParamMultiple(void *hHandle, unsigned int dwNumParams, SynthParamSet *pSynthParams)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
for (int i = 0; i < dwNumParams; i++)
{
pSynthParams[i].lPARWValue = buffer->synthParams[pSynthParams[i].param];
}
return SEGA_SUCCESS;
}
int SEGAAPI_SetReleaseState(void *hHandle, int bSet)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
if (!bSet)
return SEGA_SUCCESS;
FAudioSourceVoice_FlushSourceBuffers(buffer->fAudioSourceVoice);
return SEGAAPI_Stop(hHandle);
}
int SEGAAPI_CreateBuffer(BufferConfig *pConfig, BufferCallback pCallback, unsigned int dwFlags, void **phHandle)
{
HANDLE_CHECK(pConfig, SEGA_ERROR_BAD_CONFIG);
HANDLE_CHECK(phHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)malloc(sizeof(Buffer));
buffer->playbackStatus = PLAYBACK_STATUS_STOP;
buffer->callback = pCallback;
buffer->outputFormat.dwSampleRate = pConfig->dwSampleRate;
buffer->outputFormat.dwSampleFormat = pConfig->dwSampleFormat;
buffer->outputFormat.byNumChans = pConfig->byNumChans;
buffer->updateOutputFormat = 0;
buffer->userData = pConfig->hUserData;
buffer->size = pConfig->mapData.dwSize;
pConfig->mapData.dwOffset = 0;
debug(1, "Info: Buffer created with %d channels\n", pConfig->byNumChans);
buffer->fAudioVoiceCallback = (FAudioVoiceCallbackWithBuffer *)malloc(sizeof(FAudioVoiceCallbackWithBuffer));
buffer->fAudioVoiceCallback->OnBufferStart = StaticOnBufferStart;
buffer->fAudioVoiceCallback->OnBufferEnd = StaticOnBufferEnd;
buffer->fAudioVoiceCallback->OnBufferStart = StaticOnBufferStart;
buffer->fAudioVoiceCallback->OnLoopEnd = StaticOnLoopEnd;
buffer->fAudioVoiceCallback->OnStreamEnd = StaticOnStreamEnd;
buffer->fAudioVoiceCallback->OnVoiceError = StaticOnVoiceError;
buffer->fAudioVoiceCallback->OnVoiceProcessingPassEnd = StaticOnVoiceProcessingPassEnd;
buffer->fAudioVoiceCallback->OnVoiceProcessingPassStart = StaticOnVoiceProcessingPassStart;
buffer->fAudioVoiceCallback->buffer = buffer;
// Use buffer supplied by caller
if (dwFlags & ALLOC_USER_MEM)
{
buffer->data = (uint8_t *)pConfig->mapData.hBufferHdr;
}
// Reuse buffer
else if (dwFlags & USE_MAPPED_MEM)
{
buffer->data = (uint8_t *)pConfig->mapData.hBufferHdr;
}
// Allocate new buffer (caller will fill it later)
else
{
buffer->data = (uint8_t *)malloc(buffer->size);
memset(buffer->data, 0, buffer->size);
}
pConfig->mapData.hBufferHdr = buffer->data;
buffer->fAudioFormat.wFormatTag = 1;
buffer->fAudioFormat.nChannels = buffer->outputFormat.byNumChans;
buffer->fAudioFormat.nSamplesPerSec = buffer->outputFormat.dwSampleRate;
buffer->fAudioFormat.nAvgBytesPerSec = samplesToBytes(buffer, buffer->outputFormat.dwSampleRate);
buffer->fAudioFormat.nBlockAlign = samplesToBytes(buffer, 1);
buffer->fAudioFormat.wBitsPerSample = pConfig->dwSampleFormat == SIGNED_16PCM ? 16 : 8;
buffer->fAudioFormat.cbSize = sizeof(FAudioWaveFormatEx);
pthread_mutex_lock(&fAudioMutex);
FAudio_CreateSourceVoice(fAudio, &buffer->fAudioSourceVoice, &buffer->fAudioFormat, 0, FAUDIO_MAX_FREQ_RATIO,
(FAudioVoiceCallback *)buffer->fAudioVoiceCallback, NULL, NULL);
pthread_mutex_unlock(&fAudioMutex);
memset(&buffer->fAudioBuffer, 0, sizeof(FAudioBuffer));
buffer->startLoop = 0;
buffer->endOffset = buffer->size;
buffer->endLoop = buffer->size;
buffer->bDoContinuousLooping = 0;
for (int channel = 0; channel < MAX_CHANNELS; channel++)
{
buffer->channelVolumes[channel] = VOL_MAX;
for (int send = 0; send < MAX_SENDS; send++)
{
buffer->sendLevels[channel][send] = 0;
buffer->routing[channel][send] = UNUSED_PORT;
}
}
switch (pConfig->byNumChans)
{
case 1:
buffer->routing[0][0] = FRONT_LEFT_PORT;
buffer->routing[0][1] = FRONT_RIGHT_PORT;
break;
case 2:
buffer->routing[0][0] = FRONT_LEFT_PORT;
buffer->routing[1][1] = FRONT_RIGHT_PORT;
break;
default:
break;
}
*phHandle = buffer;
return SEGA_SUCCESS;
}
int SEGAAPI_DestroyBuffer(void *hHandle)
{
HANDLE_CHECK(hHandle, SEGA_ERROR_BAD_HANDLE);
Buffer *buffer = (Buffer *)hHandle;
FAudioVoice_DestroyVoice(buffer->fAudioSourceVoice);
// TODO: hHandle = NULL?
debug(1, "SEGAAPI_DestroyBuffer called\n");
return SEGA_SUCCESS;
}
int SEGAAPI_SetGlobalEAXProperty(GUID *guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize)
{
// debug(1, "Warning: This game tried to use SEGAAPI_SetGlobalEAXProperty\n");
return SEGA_SUCCESS;
}
int SEGAAPI_GetGlobalEAXProperty(GUID *guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize)
{
debug(1, "Warning: This game tried to use SEGAAPI_GetGlobalEAXProperty\n");
return SEGA_SUCCESS;
}
int SEGAAPI_SetSPDIFOutChannelStatus(unsigned int dwChannelStatus, unsigned int dwExtChannelStatus)
{
debug(1, "Warning: This game tried to use SEGAAPI_SetSPDIFOutChannelStatus\n");
return SEGA_SUCCESS;
}
int SEGAAPI_GetSPDIFOutChannelStatus(unsigned int *pdwChannelStatus, unsigned int *pdwExtChannelStatus)
{
debug(1, "Warning: This game tried to use SEGAAPI_GetSPDIFOutChannelStatus\n");
return SEGA_SUCCESS;
}
int SEGAAPI_SetSPDIFOutSampleRate(SPDIFOutputSampleRate dwSamplingRate)
{
debug(1, "Warning: This game tried to use SEGAAPI_SetSPDIFOutSampleRate\n");
return SEGA_SUCCESS;
}
SPDIFOutputSampleRate SEGAAPI_GetSPDIFOutSampleRate(void)
{
debug(1, "Warning: This game tried to use SEGAAPI_GetSPDIFOutSampleRate\n");
return SPDIFOUT_44_1KHZ;
}
int SEGAAPI_SetSPDIFOutChannelRouting(unsigned int dwChannel, Routing dwSource)
{
debug(1, "Warning: This game tried to use SEGAAPI_SetSPDIFOutChannelRouting\n");
return SEGA_SUCCESS;
}
Routing SEGAAPI_GetSPDIFOutChannelRouting(unsigned int dwChannel)
{
debug(1, "Warning: This game tried to use SEGAAPI_GetSPDIFOutChannelRouting\n");
return UNUSED_PORT;
}
int SEGAAPI_SetIOVolume(SoundBoardIO dwPhysIO, unsigned int dwVolume)
{
FAudioVoice_SetVolume(fAudioMasteringVoice, dwVolume / (float)VOL_MAX, FAUDIO_COMMIT_NOW);
return SEGA_SUCCESS;
}
unsigned int SEGAAPI_GetIOVolume(SoundBoardIO dwPhysIO)
{
float volume;
FAudioVoice_GetVolume(fAudioMasteringVoice, &volume);
return (int)(volume * VOL_MAX);
}
void SEGAAPI_SetLastStatus(int LastStatus)
{
lastStatus = LastStatus;
}
int SEGAAPI_GetLastStatus(void)
{
return lastStatus;
}
int SEGAAPI_Reset(void)
{
return SEGA_SUCCESS;
}
int SEGAAPI_Init(void)
{
pthread_mutex_init(&fAudioMutex, NULL);
pthread_mutex_lock(&fAudioMutex);
FAudioCreate(&fAudio, 0, FAUDIO_DEFAULT_PROCESSOR);
FAudio_CreateMasteringVoice(fAudio, &fAudioMasteringVoice, FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE, 0, 0,
NULL);
FAudioVoiceDetails fAudioVoiceDetails;
FAudioVoice_GetVoiceDetails(fAudioMasteringVoice, &fAudioVoiceDetails);
maxInputChannels = fAudioVoiceDetails.InputChannels;
for (int i = 0; i < OUTPUT_CHANNELS; i++)
{
FAudio_CreateSubmixVoice(fAudio, &fAudioSubmixVoices[i], 1, fAudioVoiceDetails.InputSampleRate, 0, 0, NULL,
NULL);
float matrix[OUTPUT_CHANNELS] = {0};
if (fAudioVoiceDetails.InputChannels == 6)
{
matrix[i] = 1.0f;
}
else if (fAudioVoiceDetails.InputChannels == 2)
{
// Downmix, L, R, C, S, BL, BR
if (i < 2)
{
matrix[i] = 1.0f;
}
else if (i == 2)
{
matrix[0] = 1.0f;
matrix[1] = 1.0f;
}
else if (i == 3)
{
matrix[0] = 0.5f;
matrix[1] = 0.5f;
}
else
{
matrix[i - 4] = 0.5f;
}
}
FAudioVoice_SetOutputMatrix(fAudioSubmixVoices[i], fAudioMasteringVoice, 1, fAudioVoiceDetails.InputChannels,
matrix, FAUDIO_COMMIT_NOW);
}
pthread_mutex_unlock(&fAudioMutex);
return SEGA_SUCCESS;
}
int SEGAAPI_Exit(void)
{
debug(1, "SEGAAPI_Exit called\n");
return SEGA_SUCCESS;
}