/* 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 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 */ #include #include #include #include #include #include #include #include #include #include #include "segadef.h" #include "segaerr.h" #include "segaeax.h" #include "segaapi.h" #define TSF_IMPLEMENTATION #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); \ } \ } SEGASTATUS g_LastStatus = SEGA_SUCCESS; // 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) { 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; } 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; } 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) { // printf("CANNOT DO IT\n"); // exit(1); wrap_BufferSamples(context->alBuffer, context->sampleRate, alFormat, context->size / bufferSampleSize(context), alChannels, alType, &(context->data[offset])); // alBufferSubSamplesSOFT(context->alBuffer, offset / bufferSampleSize(context), length / bufferSampleSize(context), alChannels, alType, &context->data[offset]); // CHECK(); 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); } SEGASTATUS SEGAAPI_Play(CTHANDLE hHandle) { 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; } SEGASTATUS SEGAAPI_Pause(CTHANDLE hHandle) { dbgPrint("SEGAAPI_Pause() 0x%x", hHandle); segaapiContext_t *context = hHandle; if (context == NULL) return SEGAERR_BAD_PARAM; alSourcePause(context->alSource); return SEGA_SUCCESS; } SEGASTATUS SEGAAPI_Stop(CTHANDLE hHandle) { dbgPrint("SEGAAPI_Stop() 0x%x", hHandle); segaapiContext_t *context = hHandle; if (context == NULL) return SEGAERR_BAD_PARAM; alSourceStop(context->alSource); return SEGA_SUCCESS; } SEGASTATUS SEGAAPI_PlayWithSetup(CTHANDLE hHandle) { 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; } PlaybackStatus SEGAAPI_GetPlaybackStatus(CTHANDLE hHandle) { ALint state; dbgPrint("SEGAAPI_GetPlaybackStatus() 0x%x", hHandle); segaapiContext_t *context = hHandle; if (context == NULL) return PLAYBACK_STATUS_INVALID; alGetSourcei(context->alSource, AL_SOURCE_STATE, &state); switch (state) { case AL_PLAYING: return PLAYBACK_STATUS_ACTIVE; case AL_PAUSED: return PLAYBACK_STATUS_PAUSE; case AL_INITIAL: case AL_STOPPED: return PLAYBACK_STATUS_STOP; default: return PLAYBACK_STATUS_INVALID; } return PLAYBACK_STATUS_INVALID; } SEGASTATUS SEGAAPI_SetFormat(CTHANDLE hHandle, HAWOSEFORMAT *pFormat) { dbgPrint("SEGAAPI_SetFormat() 0x%x", hHandle); return SEGAERR_UNSUPPORTED; } SEGASTATUS SEGAAPI_GetFormat(CTHANDLE hHandle, HAWOSEFORMAT *pFormat) { dbgPrint("SEGAAPI_GetFormat() 0x%x", hHandle); return SEGAERR_UNSUPPORTED; } SEGASTATUS SEGAAPI_SetSampleRate(CTHANDLE hHandle, CTDWORD dwSampleRate) { 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; } CTDWORD SEGAAPI_GetSampleRate(CTHANDLE hHandle) { dbgPrint("SEGAAPI_GetSampleRate() 0x%x", hHandle); return SEGAERR_UNSUPPORTED; } SEGASTATUS SEGAAPI_SetPriority(CTHANDLE hHandle, CTDWORD dwPriority) { dbgPrint("SEGAAPI_SetPriority() 0x%x 0x%x", hHandle, dwPriority); return SEGAERR_UNSUPPORTED; } CTDWORD SEGAAPI_GetPriority(CTHANDLE hHandle) { dbgPrint("SEGAAPI_GetPriority() 0x%x", hHandle); return SEGAERR_UNSUPPORTED; } SEGASTATUS SEGAAPI_SetUserData(CTHANDLE hHandle, CTHANDLE hUserData) { 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; } CTHANDLE SEGAAPI_GetUserData(CTHANDLE hHandle) { dbgPrint("SEGAAPI_GetPriority() 0x%x", hHandle); if (hHandle == NULL) return NULL; segaapiContext_t *context = hHandle; return context->userData; } SEGASTATUS SEGAAPI_SetSendRouting(CTHANDLE hHandle, CTDWORD dwChannel, CTDWORD dwSend, HAROUTING dwDest) { dbgPrint("SEGAAPI_SetSendRouting() 0x%x 0x%x 0x%x 0x%x", hHandle, dwChannel, dwSend, dwDest); return SEGA_SUCCESS; } HAROUTING SEGAAPI_GetSendRouting(CTHANDLE hHandle, CTDWORD dwChannel, CTDWORD dwSend) { dbgPrint("SEGAAPI_GetSendRouting() 0x%x 0x%x 0x%x", hHandle, dwChannel, dwSend); return HA_UNUSED_PORT; } SEGASTATUS SEGAAPI_SetSendLevel(CTHANDLE hHandle, CTDWORD dwChannel, CTDWORD dwSend, CTDWORD dwLevel) { dbgPrint("SEGAAPI_SetSendLevel() 0x%x 0x%x 0x%x 0x%x", hHandle, dwChannel, dwSend, dwLevel); return SEGA_SUCCESS; } CTDWORD SEGAAPI_GetSendLevel(CTHANDLE hHandle, CTDWORD dwChannel, CTDWORD dwSend) { dbgPrint("SEGAAPI_GetSendLevel() 0x%x 0x%x 0x%x", hHandle, dwChannel, dwSend); return 0; } SEGASTATUS SEGAAPI_SetChannelVolume(CTHANDLE hHandle, CTDWORD dwChannel, CTDWORD dwVolume) { dbgPrint("SEGAAPI_SetChannelVolume() 0x%x 0x%x 0x%x", hHandle, dwChannel, dwVolume); return SEGAERR_UNSUPPORTED; } CTDWORD SEGAAPI_GetChannelVolume(CTHANDLE hHandle, CTDWORD dwChannel) { dbgPrint("SEGAAPI_GetChannelVolume() 0x%x 0x%x", hHandle, dwChannel); return 0; } SEGASTATUS SEGAAPI_SetPlaybackPosition(CTHANDLE hHandle, CTDWORD dwPlaybackPos) { dbgPrint("SEGAAPI_SetPlaybackPosition() 0x%x 0x%x", hHandle, dwPlaybackPos); segaapiContext_t *context = hHandle; alSourcei(context->alSource, AL_BYTE_OFFSET, dwPlaybackPos); return SEGA_SUCCESS; } CTDWORD SEGAAPI_GetPlaybackPosition(CTHANDLE hHandle) { ALint position; dbgPrint("SEGAAPI_GetPlaybackPosition() 0x%x", hHandle); segaapiContext_t *context = hHandle; alGetSourcei(context->alSource, AL_BYTE_OFFSET, &position); return position; } SEGASTATUS SEGAAPI_SetNotificationFrequency(CTHANDLE hHandle, CTDWORD dwFrameCount) { dbgPrint("SEGAAPI_SetNotificationFrequency() 0x%x 0x%x", hHandle, dwFrameCount); return SEGAERR_UNSUPPORTED; } SEGASTATUS SEGAAPI_SetNotificationPoint(CTHANDLE hHandle, CTDWORD dwBufferOffset) { dbgPrint("SEGAAPI_SetNotificationPoint() 0x%x 0x%x", hHandle, dwBufferOffset); return SEGAERR_UNSUPPORTED; } SEGASTATUS SEGAAPI_ClearNotificationPoint(CTHANDLE hHandle, CTDWORD dwBufferOffset) { dbgPrint("SEGAAPI_ClearNotificationPoint() 0x%x 0x%x", hHandle, dwBufferOffset); return SEGAERR_UNSUPPORTED; } SEGASTATUS SEGAAPI_SetStartLoopOffset(CTHANDLE hHandle, CTDWORD dwOffset) { 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; } CTDWORD SEGAAPI_GetStartLoopOffset(CTHANDLE hHandle) { dbgPrint("SEGAAPI_GetStartLoopOffset() 0x%x", hHandle); return 0; } SEGASTATUS SEGAAPI_SetEndLoopOffset(CTHANDLE hHandle, CTDWORD dwOffset) { 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; } CTDWORD SEGAAPI_GetEndLoopOffset(CTHANDLE hHandle) { dbgPrint("SEGAAPI_GetEndLoopOffset() 0x%x", hHandle); return 0; } SEGASTATUS SEGAAPI_SetEndOffset(CTHANDLE hHandle, CTDWORD dwOffset) { dbgPrint("SEGAAPI_SetEndOffset() 0x%x 0x%x", hHandle, dwOffset); return SEGAERR_UNSUPPORTED; } CTDWORD SEGAAPI_GetEndOffset(CTHANDLE hHandle) { dbgPrint("SEGAAPI_GetEndOffset() 0x%x", hHandle); return 0; } SEGASTATUS SEGAAPI_SetLoopState(CTHANDLE hHandle, CTBOOL bDoContinuousLooping) { 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; } CTBOOL SEGAAPI_GetLoopState(CTHANDLE hHandle) { dbgPrint("SEGAAPI_GetLoopState() 0x%x", hHandle); return 0; } SEGASTATUS SEGAAPI_UpdateBuffer(CTHANDLE hHandle, CTDWORD dwStartOffset, CTDWORD dwLength) { 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; } SEGASTATUS SEGAAPI_SetSynthParam(CTHANDLE hHandle, HASYNTHPARAMSEXT param, CTLONG lPARWValue) { float volume; float semiTones; float freqRatio; dbgPrint("SEGAAPI_SetSynthParam() 0x%x 0x%x 0x%x", hHandle, param, lPARWValue); segaapiContext_t *context = hHandle; 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; } CTLONG SEGAAPI_GetSynthParam(CTHANDLE hHandle, HASYNTHPARAMSEXT param) { dbgPrint("SEGAAPI_GetSynthParam() 0x%x 0x%x", hHandle, param); return 0; } SEGASTATUS SEGAAPI_SetSynthParamMultiple(CTHANDLE hHandle, CTDWORD dwNumParams, SynthParamSet *pSynthParams) { 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; } SEGASTATUS SEGAAPI_GetSynthParamMultiple(CTHANDLE hHandle, CTDWORD dwNumParams, SynthParamSet *pSynthParams) { dbgPrint("SEGAAPI_GetSynthParamMultiple() 0x%x 0x%x 0x%x", hHandle, dwNumParams, pSynthParams); return SEGAERR_UNSUPPORTED; } SEGASTATUS SEGAAPI_SetReleaseState(CTHANDLE hHandle, CTBOOL bSet) { dbgPrint("SEGAAPI_SetReleaseState() 0x%x 0x%x", hHandle, bSet); return SEGAERR_UNSUPPORTED; } SEGASTATUS SEGAAPI_CreateBuffer(HAWOSEBUFFERCONFIG *pConfig, HAWOSEGABUFFERCALLBACK pCallback, CTDWORD dwFlags, CTHANDLE *phHandle) { 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; } SEGASTATUS SEGAAPI_DestroyBuffer(CTHANDLE hHandle) { dbgPrint("SEGAAPI_DestroyBuffer() 0x%x", hHandle); if (hHandle == NULL) return SEGAERR_BAD_PARAM; free(hHandle); return SEGA_SUCCESS; } CTBOOL SEGAAPI_SetGlobalEAXProperty(GUID *guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize) { dbgPrint("SEGAAPI_SetGlobalEAXProperty() 0x%x 0x%x 0x%x 0x%x", guid, ulProperty, pData, ulDataSize); return 0; } CTBOOL SEGAAPI_GetGlobalEAXProperty(GUID *guid, unsigned long ulProperty, void *pData, unsigned long ulDataSize) { dbgPrint("SEGAAPI_GetGlobalEAXProperty() 0x%x 0x%x 0x%x 0x%x", guid, ulProperty, pData, ulDataSize); return 0; } SEGASTATUS SEGAAPI_SetSPDIFOutChannelStatus(CTDWORD dwChannelStatus, CTDWORD dwExtChannelStatus) { dbgPrint("SEGAAPI_SetSPDIFOutChannelStatus() 0x%x 0x%x", dwChannelStatus, dwExtChannelStatus); return SEGAERR_UNSUPPORTED; } SEGASTATUS SEGAAPI_GetSPDIFOutChannelStatus(CTDWORD *pdwChannelStatus, CTDWORD *pdwExtChannelStatus) { dbgPrint("SEGAAPI_GetSPDIFOutChannelStatus() 0x%x 0x%x", pdwChannelStatus, pdwExtChannelStatus); return SEGAERR_UNSUPPORTED; } SEGASTATUS SEGAAPI_SetSPDIFOutSampleRate(HASPDIFOUTRATE dwSamplingRate) { dbgPrint("SEGAAPI_SetSPDIFOutSampleRate() 0x%x", dwSamplingRate); return SEGAERR_UNSUPPORTED; } HASPDIFOUTRATE SEGAAPI_GetSPDIFOutSampleRate(void) { dbgPrint("SEGAAPI_GetSPDIFOutSampleRate()"); return HASPDIFOUT_48KHZ; } SEGASTATUS SEGAAPI_SetSPDIFOutChannelRouting(CTDWORD dwChannel, HAROUTING dwSource) { 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; } HAROUTING SEGAAPI_GetSPDIFOutChannelRouting(CTDWORD dwChannel) { dbgPrint("SEGAAPI_GetSPDIFOutChannelRouting() 0x%x", dwChannel); return HA_UNUSED_PORT; } SEGASTATUS SEGAAPI_SetIOVolume(HAPHYSICALIO dwPhysIO, CTDWORD dwVolume) { // float v = (dwVolume >> 16) & 0xffff; dbgPrint("SEGAAPI_SetIOVolume() 0x%x 0x%x", dwPhysIO, dwVolume); // alListenerf(AL_GAIN, v); return SEGA_SUCCESS; } CTDWORD SEGAAPI_GetIOVolume(HAPHYSICALIO dwPhysIO) { dbgPrint("SEGAAPI_GetIOVolume() 0x%x", dwPhysIO); return 0xffffffff; } void SEGAAPI_SetLastStatus(SEGASTATUS LastStatus) { dbgPrint("SEGAAPI_SetLastStatus() 0x%x", LastStatus); return; } SEGASTATUS SEGAAPI_GetLastStatus(void) { dbgPrint("SEGAAPI_GetLastStatus()"); return SEGA_SUCCESS; } SEGASTATUS SEGAAPI_Reset(void) { dbgPrint("SEGAAPI_Reset()"); return SEGA_SUCCESS; } SEGASTATUS SEGAAPI_Init(void) { 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) { dbgPrint("Warning: Could not resolve AL extension!\n"); // exit(1); } alBufferSubSamplesSOFT = alGetProcAddress("alBufferSubSamplesSOFT"); if (alBufferSubSamplesSOFT == NULL) { dbgPrint("Warning: Could not resolve AL extension!\n"); // exit(1); } alGetBufferSamplesSOFT = alGetProcAddress("alGetBufferSamplesSOFT"); if (alGetBufferSamplesSOFT == NULL) { dbgPrint("Warning: Could not resolve AL extension!\n"); // exit(1); } 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; } SEGASTATUS SEGAAPI_Exit(void) { dbgPrint("SEGAAPI_Exit()"); alutExit(); return SEGA_SUCCESS; }