mirror of
https://github.com/lindbergh-loader/lindbergh-loader.git
synced 2026-10-14 01:49:12 +03:00
1160 lines
32 KiB
C
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;
|
|
}
|