Show More
Commit Description:
Various UI improvements.
Commit Description:
Various UI improvements.
References:
File last commit:
Show/Diff file:
Action:
FNA/lib/FAudio/src/FACT_internal.c
3196 lines | 82.0 KiB | text/x-c | CLexer
3196 lines | 82.0 KiB | text/x-c | CLexer
r0 | /* FAudio - XAudio Reimplementation for FNA | |||
* | ||||
* Copyright (c) 2011-2020 Ethan Lee, Luigi Auriemma, and the MonoGame Team | ||||
* | ||||
* This software is provided 'as-is', without any express or implied warranty. | ||||
* In no event will the authors be held liable for any damages arising from | ||||
* the use of this software. | ||||
* | ||||
* Permission is granted to anyone to use this software for any purpose, | ||||
* including commercial applications, and to alter it and redistribute it | ||||
* freely, subject to the following restrictions: | ||||
* | ||||
* 1. The origin of this software must not be misrepresented; you must not | ||||
* claim that you wrote the original software. If you use this software in a | ||||
* product, an acknowledgment in the product documentation would be | ||||
* appreciated but is not required. | ||||
* | ||||
* 2. Altered source versions must be plainly marked as such, and must not be | ||||
* misrepresented as being the original software. | ||||
* | ||||
* 3. This notice may not be removed or altered from any source distribution. | ||||
* | ||||
* Ethan "flibitijibibo" Lee <flibitijibibo@flibitijibibo.com> | ||||
* | ||||
*/ | ||||
#include "FACT_internal.h" | ||||
#include "FAudioFX.h" | ||||
/* RNG */ | ||||
#define STB_EXTERN | ||||
#define STB_DEFINE | ||||
#include "stb.h" | ||||
#define FACT_INTERNAL_rng() ((float) stb_frand()) | ||||
/* XACT Versions */ | ||||
#define FACT_CONTENT_VERSION_3_4 45 | ||||
#define FACT_CONTENT_VERSION_3_1 44 | ||||
static inline int FACT_INTERNAL_SupportedContent(uint16_t version) | ||||
{ | ||||
return ( version == FACT_CONTENT_VERSION || | ||||
version == FACT_CONTENT_VERSION_3_4 || | ||||
version == FACT_CONTENT_VERSION_3_1 ); | ||||
} | ||||
#define WAVEBANK_HEADER_VERSION 44 | ||||
#define WAVEBANK_HEADER_VERSION_3_4 43 | ||||
#define WAVEBANK_HEADER_VERSION_3_1 42 | ||||
static inline int FACT_INTERNAL_SupportedWBContent(uint16_t version) | ||||
{ | ||||
return ( version == WAVEBANK_HEADER_VERSION || | ||||
version == WAVEBANK_HEADER_VERSION_3_4 || | ||||
version == WAVEBANK_HEADER_VERSION_3_1 ); | ||||
} | ||||
/* Helper Functions */ | ||||
#define SWAP_16(x) \ | ||||
((x >> 8) & 0x00FF) | \ | ||||
((x << 8) & 0xFF00) | ||||
#define DOSWAP_16(x) x = SWAP_16(x) | ||||
#define SWAP_32(x) \ | ||||
((x >> 24) & 0x000000FF) | \ | ||||
((x >> 8) & 0x0000FF00) | \ | ||||
((x << 8) & 0x00FF0000) | \ | ||||
((x << 24) & 0xFF000000) | ||||
#define DOSWAP_32(x) x = SWAP_32(x) | ||||
#define SWAP_64(x) \ | ||||
((x >> 32) & 0x00000000000000FF) | \ | ||||
((x >> 24) & 0x000000000000FF00) | \ | ||||
((x >> 16) & 0x0000000000FF0000) | \ | ||||
((x >> 8) & 0x00000000FF000000) | \ | ||||
((x << 8) & 0x000000FF00000000) | \ | ||||
((x << 16) & 0x0000FF0000000000) | \ | ||||
((x << 24) & 0x00FF000000000000) | \ | ||||
((x << 32) & 0xFF00000000000000) | ||||
#define DOSWAP_64(x) x = SWAP_32(x) | ||||
static inline float FACT_INTERNAL_CalculateAmplitudeRatio(float decibel) | ||||
{ | ||||
return (float) FAudio_pow(10.0, decibel / 2000.0); | ||||
} | ||||
static inline void FACT_INTERNAL_ReadFile( | ||||
FACTReadFileCallback pReadFile, | ||||
FACTGetOverlappedResultCallback pGetOverlappedResult, | ||||
void* io, | ||||
uint32_t offset, | ||||
uint32_t packetSize, | ||||
uint8_t **packetBuffer, | ||||
uint32_t *packetBufferLen, | ||||
FAudioReallocFunc pRealloc, | ||||
void* dst, | ||||
uint32_t len | ||||
) { | ||||
FACTOverlapped ovlp; | ||||
uint32_t realOffset, realLen, offPacket, lenPacket, result; | ||||
uint8_t usePacketBuffer; | ||||
void *buf; | ||||
ovlp.Internal = NULL; | ||||
ovlp.InternalHigh = NULL; | ||||
ovlp.OffsetHigh = 0; /* I sure hope so... */ | ||||
ovlp.hEvent = NULL; | ||||
/* We have to read data in multiples of the sector size, or else | ||||
* Win32 ReadFile returns ERROR_INVALID_PARAMETER | ||||
*/ | ||||
realOffset = offset; | ||||
realLen = len; | ||||
usePacketBuffer = 0; | ||||
if (packetSize > 0) | ||||
{ | ||||
offPacket = realOffset % packetSize; | ||||
if (offPacket > 0) | ||||
{ | ||||
usePacketBuffer = 1; | ||||
realOffset -= offPacket; | ||||
realLen += offPacket; | ||||
} | ||||
lenPacket = realLen % packetSize; | ||||
if (lenPacket > 0) | ||||
{ | ||||
usePacketBuffer = 1; | ||||
realLen += (packetSize - lenPacket); | ||||
} | ||||
} | ||||
/* If we're compensating for sector alignment, use a temp buffer and copy to | ||||
* the real destination after we're finished. | ||||
*/ | ||||
if (usePacketBuffer) | ||||
{ | ||||
if (*packetBufferLen < realLen) | ||||
{ | ||||
*packetBufferLen = realLen; | ||||
*packetBuffer = pRealloc(*packetBuffer, realLen); | ||||
} | ||||
buf = *packetBuffer; | ||||
} | ||||
else | ||||
{ | ||||
buf = dst; | ||||
} | ||||
/* Read, finally. */ | ||||
ovlp.Offset = realOffset; | ||||
if (!pReadFile(io, buf, realLen, NULL, &ovlp)) | ||||
{ | ||||
while (ovlp.Internal == (void*) 0x103) /* STATUS_PENDING */ | ||||
{ | ||||
/* Don't actually sleep, just yield the thread */ | ||||
FAudio_sleep(0); | ||||
} | ||||
} | ||||
pGetOverlappedResult(io, &ovlp, &result, 1); | ||||
/* Copy the subregion that we actually care about, if applicable */ | ||||
if (usePacketBuffer) | ||||
{ | ||||
FAudio_memcpy(dst, *packetBuffer + offPacket, len); | ||||
} | ||||
} | ||||
/* Internal Functions */ | ||||
void FACT_INTERNAL_GetNextWave( | ||||
FACTCue *cue, | ||||
FACTSound *sound, | ||||
FACTTrack *track, | ||||
FACTTrackInstance *trackInst, | ||||
FACTEvent *evt, | ||||
FACTEventInstance *evtInst | ||||
) { | ||||
FAudioSendDescriptor reverbDesc[2]; | ||||
FAudioVoiceSends reverbSends; | ||||
const char *wbName; | ||||
FACTWaveBank *wb = NULL; | ||||
LinkedList *list; | ||||
uint16_t wbTrack; | ||||
uint8_t wbIndex; | ||||
uint8_t loopCount = 0; | ||||
float max, next; | ||||
uint8_t noTrackVariation = 1; | ||||
uint32_t i; | ||||
/* Track Variation */ | ||||
if (evt->wave.isComplex) | ||||
{ | ||||
if ( trackInst->activeWave.wave == NULL || | ||||
!(evt->wave.complex.variation & 0x00F0) ) | ||||
{ | ||||
/* No-op, no variation on loop */ | ||||
} | ||||
/* Ordered, Ordered From Random */ | ||||
else if ( (evt->wave.complex.variation & 0xF) == 0 || | ||||
(evt->wave.complex.variation & 0xF) == 1 ) | ||||
{ | ||||
evtInst->valuei += 1; | ||||
if (evtInst->valuei >= evt->wave.complex.trackCount) | ||||
{ | ||||
evtInst->valuei = 0; | ||||
} | ||||
} | ||||
/* Random */ | ||||
else if ((evt->wave.complex.variation & 0xF) == 2) | ||||
{ | ||||
max = 0.0f; | ||||
for (i = 0; i < evt->wave.complex.trackCount; i += 1) | ||||
{ | ||||
max += evt->wave.complex.weights[i]; | ||||
} | ||||
next = FACT_INTERNAL_rng() * max; | ||||
for (i = evt->wave.complex.trackCount; i > 0; i -= 1) | ||||
{ | ||||
if (next > (max - evt->wave.complex.weights[i - 1])) | ||||
{ | ||||
evtInst->valuei = i - 1; | ||||
break; | ||||
} | ||||
max -= evt->wave.complex.weights[i - 1]; | ||||
} | ||||
} | ||||
/* Random (No Immediate Repeats), Shuffle */ | ||||
else if ( (evt->wave.complex.variation & 0xF) == 3 || | ||||
(evt->wave.complex.variation & 0xF) == 4 ) | ||||
{ | ||||
max = 0.0f; | ||||
for (i = 0; i < evt->wave.complex.trackCount; i += 1) | ||||
{ | ||||
if (i == evtInst->valuei) | ||||
{ | ||||
continue; | ||||
} | ||||
max += evt->wave.complex.weights[i]; | ||||
} | ||||
next = FACT_INTERNAL_rng() * max; | ||||
for (i = evt->wave.complex.trackCount; i > 0; i -= 1) | ||||
{ | ||||
if (i - 1 == evtInst->valuei) | ||||
{ | ||||
continue; | ||||
} | ||||
if (next > (max - evt->wave.complex.weights[i - 1])) | ||||
{ | ||||
evtInst->valuei = i - 1; | ||||
break; | ||||
} | ||||
max -= evt->wave.complex.weights[i - 1]; | ||||
} | ||||
} | ||||
if (evt->wave.complex.variation & 0x00F0) | ||||
{ | ||||
noTrackVariation = 0; | ||||
} | ||||
wbIndex = evt->wave.complex.wavebanks[evtInst->valuei]; | ||||
wbTrack = evt->wave.complex.tracks[evtInst->valuei]; | ||||
} | ||||
else | ||||
{ | ||||
wbIndex = evt->wave.simple.wavebank; | ||||
wbTrack = evt->wave.simple.track; | ||||
} | ||||
wbName = cue->parentBank->wavebankNames[wbIndex]; | ||||
list = cue->parentBank->parentEngine->wbList; | ||||
while (list != NULL) | ||||
{ | ||||
wb = (FACTWaveBank*) list->entry; | ||||
if (FAudio_strcmp(wbName, wb->name) == 0) | ||||
{ | ||||
break; | ||||
} | ||||
list = list->next; | ||||
} | ||||
FAudio_assert(wb != NULL); | ||||
/* Generate the Wave */ | ||||
if ( evtInst->loopCount == 255 && | ||||
noTrackVariation && | ||||
!(evt->wave.variationFlags & 0x0F00) ) | ||||
{ | ||||
/* For infinite loops with no variation, let Wave do the work */ | ||||
loopCount = 255; | ||||
} | ||||
FACTWaveBank_Prepare( | ||||
wb, | ||||
wbTrack, | ||||
evt->wave.flags, | ||||
0, | ||||
loopCount, | ||||
&trackInst->upcomingWave.wave | ||||
); | ||||
trackInst->upcomingWave.wave->parentCue = cue; | ||||
if (sound->dspCodeCount > 0) /* Never more than 1...? */ | ||||
{ | ||||
reverbDesc[0].Flags = 0; | ||||
reverbDesc[0].pOutputVoice = cue->parentBank->parentEngine->master; | ||||
reverbDesc[1].Flags = 0; | ||||
reverbDesc[1].pOutputVoice = cue->parentBank->parentEngine->reverbVoice; | ||||
reverbSends.SendCount = 2; | ||||
reverbSends.pSends = reverbDesc; | ||||
FAudioVoice_SetOutputVoices( | ||||
trackInst->upcomingWave.wave->voice, | ||||
&reverbSends | ||||
); | ||||
} | ||||
/* 3D Audio */ | ||||
if (cue->active3D) | ||||
{ | ||||
FACTWave_SetMatrixCoefficients( | ||||
trackInst->upcomingWave.wave, | ||||
cue->srcChannels, | ||||
cue->dstChannels, | ||||
cue->matrixCoefficients | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
/* TODO: Position/Angle/UseCenterSpeaker */ | ||||
} | ||||
/* Pitch Variation */ | ||||
if (evt->wave.variationFlags & 0x1000) | ||||
{ | ||||
const int16_t rngPitch = (int16_t) ( | ||||
FACT_INTERNAL_rng() * | ||||
(evt->wave.maxPitch - evt->wave.minPitch) | ||||
) + evt->wave.minPitch; | ||||
if (trackInst->activeWave.wave != NULL) | ||||
{ | ||||
/* Variation on Loop */ | ||||
if (evt->wave.variationFlags & 0x0100) | ||||
{ | ||||
/* Add/Replace */ | ||||
if (evt->wave.variationFlags & 0x0004) | ||||
{ | ||||
trackInst->upcomingWave.basePitch = | ||||
trackInst->activeWave.basePitch + rngPitch; | ||||
} | ||||
else | ||||
{ | ||||
trackInst->upcomingWave.basePitch = rngPitch + sound->pitch; | ||||
} | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
/* Initial Pitch Variation */ | ||||
trackInst->upcomingWave.basePitch = rngPitch + sound->pitch; | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
trackInst->upcomingWave.basePitch = sound->pitch; | ||||
} | ||||
/* Volume Variation */ | ||||
if (evt->wave.variationFlags & 0x2000) | ||||
{ | ||||
const float rngVolume = ( | ||||
FACT_INTERNAL_rng() * | ||||
(evt->wave.maxVolume - evt->wave.minVolume) | ||||
) + evt->wave.minVolume; | ||||
if (trackInst->activeWave.wave != NULL) | ||||
{ | ||||
/* Variation on Loop */ | ||||
if (evt->wave.variationFlags & 0x0200) | ||||
{ | ||||
/* Add/Replace */ | ||||
if (evt->wave.variationFlags & 0x0001) | ||||
{ | ||||
trackInst->upcomingWave.baseVolume = | ||||
trackInst->activeWave.baseVolume + rngVolume; | ||||
} | ||||
else | ||||
{ | ||||
trackInst->upcomingWave.baseVolume = ( | ||||
rngVolume + | ||||
sound->volume + | ||||
track->volume | ||||
); | ||||
} | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
/* Initial Volume Variation */ | ||||
trackInst->upcomingWave.baseVolume = ( | ||||
rngVolume + | ||||
sound->volume + | ||||
track->volume | ||||
); | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
trackInst->upcomingWave.baseVolume = sound->volume + track->volume; | ||||
} | ||||
/* Filter Variation, QFactor/Freq are always together */ | ||||
if (evt->wave.variationFlags & 0xC000) | ||||
{ | ||||
const float rngQFactor = 1.0f / ( | ||||
FACT_INTERNAL_rng() * | ||||
(evt->wave.maxQFactor - evt->wave.minQFactor) | ||||
); | ||||
const float rngFrequency = ( | ||||
FACT_INTERNAL_rng() * | ||||
(evt->wave.maxFrequency - evt->wave.minFrequency) | ||||
) / 20000.0f; | ||||
if (trackInst->activeWave.wave != NULL) | ||||
{ | ||||
/* Variation on Loop */ | ||||
if (evt->wave.variationFlags & 0x0C00) | ||||
{ | ||||
/* TODO: Add/Replace */ | ||||
/* FIXME: Which is QFactor/Freq? | ||||
if (evt->wave.variationFlags & 0x0010) | ||||
{ | ||||
} | ||||
else | ||||
{ | ||||
} | ||||
if (evt->wave.variationFlags & 0x0040) | ||||
{ | ||||
} | ||||
else | ||||
{ | ||||
} | ||||
*/ | ||||
trackInst->upcomingWave.baseQFactor = rngQFactor; | ||||
trackInst->upcomingWave.baseFrequency = rngFrequency; | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
/* Initial Filter Variation */ | ||||
trackInst->upcomingWave.baseQFactor = rngQFactor; | ||||
trackInst->upcomingWave.baseFrequency = rngFrequency; | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
trackInst->upcomingWave.baseQFactor = 1.0f / (float) track->qfactor; | ||||
trackInst->upcomingWave.baseFrequency = track->frequency / 20000.0f; | ||||
} | ||||
/* Try to change loop counter at the very end */ | ||||
if (loopCount == 255) | ||||
{ | ||||
/* For infinite loops with no variation, Wave does the work */ | ||||
evtInst->loopCount = 0; | ||||
} | ||||
else if (evtInst->loopCount > 0) | ||||
{ | ||||
evtInst->loopCount -= 1; | ||||
} | ||||
} | ||||
uint8_t FACT_INTERNAL_CreateSound(FACTCue *cue, uint16_t fadeInMS) | ||||
{ | ||||
int32_t i, j, k; | ||||
float max, next, weight; | ||||
const char *wbName; | ||||
FACTWaveBank *wb = NULL; | ||||
LinkedList *list; | ||||
FACTEvent *evt; | ||||
FACTEventInstance *evtInst; | ||||
FACTSound *baseSound = NULL; | ||||
FACTSoundInstance *newSound; | ||||
FACTRPC *rpc; | ||||
float lastX; | ||||
union | ||||
{ | ||||
float maxf; | ||||
uint8_t maxi; | ||||
} limitmax; | ||||
FACTCue *tmp, *wnr; | ||||
uint16_t categoryIndex; | ||||
FACTAudioCategory *category; | ||||
if (cue->data->flags & 0x04) | ||||
{ | ||||
/* Sound */ | ||||
baseSound = cue->sound; | ||||
} | ||||
else | ||||
{ | ||||
/* Variation */ | ||||
if (cue->variation->flags == 3) | ||||
{ | ||||
/* Interactive */ | ||||
if (cue->parentBank->parentEngine->variables[cue->variation->variable].accessibility & 0x04) | ||||
{ | ||||
FACTCue_GetVariable( | ||||
cue, | ||||
cue->variation->variable, | ||||
&next | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
FACTAudioEngine_GetGlobalVariable( | ||||
cue->parentBank->parentEngine, | ||||
cue->variation->variable, | ||||
&next | ||||
); | ||||
} | ||||
for (i = 0; i < cue->variation->entryCount; i += 1) | ||||
{ | ||||
if ( next <= cue->variation->entries[i].maxWeight && | ||||
next >= cue->variation->entries[i].minWeight ) | ||||
{ | ||||
break; | ||||
} | ||||
} | ||||
/* This should only happen when the user control | ||||
* variable is none of the sound probabilities, in | ||||
* which case we are just silent. But, we should still | ||||
* claim to be "playing" in the meantime. | ||||
*/ | ||||
if (i == cue->variation->entryCount) | ||||
{ | ||||
return 1; | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
/* Random */ | ||||
max = 0.0f; | ||||
for (i = 0; i < cue->variation->entryCount; i += 1) | ||||
{ | ||||
max += ( | ||||
cue->variation->entries[i].maxWeight - | ||||
cue->variation->entries[i].minWeight | ||||
); | ||||
} | ||||
next = FACT_INTERNAL_rng() * max; | ||||
/* Use > 0, not >= 0. If we hit 0, that's it! */ | ||||
for (i = cue->variation->entryCount - 1; i > 0; i -= 1) | ||||
{ | ||||
weight = ( | ||||
cue->variation->entries[i].maxWeight - | ||||
cue->variation->entries[i].minWeight | ||||
); | ||||
if (next > (max - weight)) | ||||
{ | ||||
break; | ||||
} | ||||
max -= weight; | ||||
} | ||||
} | ||||
if (cue->variation->isComplex) | ||||
{ | ||||
/* Grab the Sound via the code. FIXME: Do this at load time? */ | ||||
for (j = 0; j < cue->parentBank->soundCount; j += 1) | ||||
{ | ||||
if (cue->variation->entries[i].soundCode == cue->parentBank->soundCodes[j]) | ||||
{ | ||||
baseSound = &cue->parentBank->sounds[j]; | ||||
break; | ||||
} | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
/* Pull in the WaveBank... */ | ||||
wbName = cue->parentBank->wavebankNames[ | ||||
cue->variation->entries[i].simple.wavebank | ||||
]; | ||||
list = cue->parentBank->parentEngine->wbList; | ||||
while (list != NULL) | ||||
{ | ||||
wb = (FACTWaveBank*) list->entry; | ||||
if (FAudio_strcmp(wbName, wb->name) == 0) | ||||
{ | ||||
break; | ||||
} | ||||
list = list->next; | ||||
} | ||||
FAudio_assert(wb != NULL); | ||||
/* Generate the wave... */ | ||||
FACTWaveBank_Prepare( | ||||
wb, | ||||
cue->variation->entries[i].simple.track, | ||||
0, | ||||
0, | ||||
0, | ||||
&cue->simpleWave | ||||
); | ||||
cue->simpleWave->parentCue = cue; | ||||
} | ||||
} | ||||
/* Alloc SoundInstance variables */ | ||||
if (baseSound != NULL) | ||||
{ | ||||
/* Category Instance Limits */ | ||||
categoryIndex = baseSound->category; | ||||
if (categoryIndex != FACTCATEGORY_INVALID) | ||||
{ | ||||
category = &cue->parentBank->parentEngine->categories[categoryIndex]; | ||||
if (category->instanceCount >= category->instanceLimit) | ||||
{ | ||||
wnr = NULL; | ||||
tmp = cue->parentBank->cueList; | ||||
if (category->maxInstanceBehavior == 0) /* Fail */ | ||||
{ | ||||
cue->state |= FACT_STATE_STOPPED; | ||||
cue->state &= ~( | ||||
FACT_STATE_PLAYING | | ||||
FACT_STATE_STOPPING | | ||||
FACT_STATE_PAUSED | ||||
); | ||||
return 0; | ||||
} | ||||
else if (category->maxInstanceBehavior == 1) /* Queue */ | ||||
{ | ||||
/* FIXME: How is this different from Replace Oldest? */ | ||||
while (tmp != NULL) | ||||
{ | ||||
if ( tmp != cue && | ||||
tmp->playingSound != NULL && | ||||
tmp->playingSound->sound->category == categoryIndex && | ||||
!(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) ) | ||||
{ | ||||
wnr = tmp; | ||||
break; | ||||
} | ||||
tmp = tmp->next; | ||||
} | ||||
} | ||||
else if (category->maxInstanceBehavior == 2) /* Replace Oldest */ | ||||
{ | ||||
while (tmp != NULL) | ||||
{ | ||||
if ( tmp != cue && | ||||
tmp->playingSound != NULL && | ||||
tmp->playingSound->sound->category == categoryIndex && | ||||
!(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) ) | ||||
{ | ||||
wnr = tmp; | ||||
break; | ||||
} | ||||
tmp = tmp->next; | ||||
} | ||||
} | ||||
else if (category->maxInstanceBehavior == 3) /* Replace Quietest */ | ||||
{ | ||||
limitmax.maxf = FACTVOLUME_MAX; | ||||
while (tmp != NULL) | ||||
{ | ||||
if ( tmp != cue && | ||||
tmp->playingSound != NULL && | ||||
tmp->playingSound->sound->category == categoryIndex && | ||||
/*FIXME: tmp->playingSound->volume < limitmax.maxf &&*/ | ||||
!(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) ) | ||||
{ | ||||
wnr = tmp; | ||||
/* limitmax.maxf = tmp->playingSound->volume; */ | ||||
} | ||||
tmp = tmp->next; | ||||
} | ||||
} | ||||
else if (category->maxInstanceBehavior == 4) /* Replace Lowest Priority */ | ||||
{ | ||||
limitmax.maxi = 0xFF; | ||||
while (tmp != NULL) | ||||
{ | ||||
if ( tmp != cue && | ||||
tmp->playingSound != NULL && | ||||
tmp->playingSound->sound->category == categoryIndex && | ||||
tmp->playingSound->sound->priority < limitmax.maxi && | ||||
!(tmp->state & (FACT_STATE_STOPPING | FACT_STATE_STOPPED)) ) | ||||
{ | ||||
wnr = tmp; | ||||
limitmax.maxi = tmp->playingSound->sound->priority; | ||||
} | ||||
tmp = tmp->next; | ||||
} | ||||
} | ||||
if (wnr != NULL) | ||||
{ | ||||
fadeInMS = category->fadeInMS; | ||||
if (wnr->playingSound != NULL) | ||||
{ | ||||
FACT_INTERNAL_BeginFadeOut(wnr->playingSound, category->fadeOutMS); | ||||
} | ||||
else | ||||
{ | ||||
FACTCue_Stop(wnr, 0); | ||||
} | ||||
} | ||||
} | ||||
category->instanceCount += 1; | ||||
} | ||||
newSound = (FACTSoundInstance*) cue->parentBank->parentEngine->pMalloc( | ||||
sizeof(FACTSoundInstance) | ||||
); | ||||
newSound->parentCue = cue; | ||||
newSound->sound = baseSound; | ||||
newSound->rpcData.rpcVolume = 0.0f; | ||||
newSound->rpcData.rpcPitch = 0.0f; | ||||
newSound->rpcData.rpcReverbSend = 0.0f; | ||||
newSound->rpcData.rpcFilterQFactor = FAUDIO_DEFAULT_FILTER_ONEOVERQ; | ||||
newSound->rpcData.rpcFilterFreq = FAUDIO_DEFAULT_FILTER_FREQUENCY; | ||||
newSound->fadeType = (fadeInMS > 0); | ||||
if (newSound->fadeType) | ||||
{ | ||||
newSound->fadeStart = FAudio_timems(); | ||||
newSound->fadeTarget = fadeInMS; | ||||
} | ||||
else | ||||
{ | ||||
newSound->fadeStart = 0; | ||||
newSound->fadeTarget = 0; | ||||
} | ||||
newSound->tracks = (FACTTrackInstance*) cue->parentBank->parentEngine->pMalloc( | ||||
sizeof(FACTTrackInstance) * newSound->sound->trackCount | ||||
); | ||||
for (i = 0; i < newSound->sound->trackCount; i += 1) | ||||
{ | ||||
newSound->tracks[i].rpcData.rpcVolume = 0.0f; | ||||
newSound->tracks[i].rpcData.rpcPitch = 0.0f; | ||||
newSound->tracks[i].rpcData.rpcReverbSend = 0.0f; | ||||
newSound->tracks[i].rpcData.rpcFilterQFactor = FAUDIO_DEFAULT_FILTER_ONEOVERQ; | ||||
newSound->tracks[i].rpcData.rpcFilterFreq = FAUDIO_DEFAULT_FILTER_FREQUENCY; | ||||
newSound->tracks[i].evtVolume = 0.0f; | ||||
newSound->tracks[i].evtPitch = 0.0f; | ||||
newSound->tracks[i].activeWave.wave = NULL; | ||||
newSound->tracks[i].activeWave.baseVolume = 0.0f; | ||||
newSound->tracks[i].activeWave.basePitch = 0; | ||||
newSound->tracks[i].activeWave.baseQFactor = FAUDIO_DEFAULT_FILTER_ONEOVERQ; | ||||
newSound->tracks[i].activeWave.baseFrequency = FAUDIO_DEFAULT_FILTER_FREQUENCY; | ||||
newSound->tracks[i].upcomingWave.wave = NULL; | ||||
newSound->tracks[i].upcomingWave.baseVolume = 0.0f; | ||||
newSound->tracks[i].upcomingWave.basePitch = 0; | ||||
newSound->tracks[i].upcomingWave.baseQFactor = FAUDIO_DEFAULT_FILTER_ONEOVERQ; | ||||
newSound->tracks[i].upcomingWave.baseFrequency = FAUDIO_DEFAULT_FILTER_FREQUENCY; | ||||
newSound->tracks[i].events = (FACTEventInstance*) cue->parentBank->parentEngine->pMalloc( | ||||
sizeof(FACTEventInstance) * newSound->sound->tracks[i].eventCount | ||||
); | ||||
for (j = 0; j < newSound->sound->tracks[i].eventCount; j += 1) | ||||
{ | ||||
evt = &newSound->sound->tracks[i].events[j]; | ||||
newSound->tracks[i].events[j].timestamp = | ||||
newSound->sound->tracks[i].events[j].timestamp; | ||||
newSound->tracks[i].events[j].loopCount = 0; | ||||
newSound->tracks[i].events[j].finished = 0; | ||||
newSound->tracks[i].events[j].value = 0.0f; | ||||
if ( evt->type == FACTEVENT_PLAYWAVE || | ||||
evt->type == FACTEVENT_PLAYWAVETRACKVARIATION || | ||||
evt->type == FACTEVENT_PLAYWAVEEFFECTVARIATION || | ||||
evt->type == FACTEVENT_PLAYWAVETRACKEFFECTVARIATION ) | ||||
{ | ||||
newSound->tracks[i].events[j].loopCount = | ||||
newSound->sound->tracks[i].events[j].wave.loopCount; | ||||
evtInst = &newSound->tracks[i].events[j]; | ||||
if ( !evt->wave.isComplex || | ||||
(evt->wave.complex.variation & 0xF) == 0 ) | ||||
{ | ||||
evtInst->valuei = 0; | ||||
} | ||||
else | ||||
{ | ||||
max = 0.0f; | ||||
for (k = 0; k < evt->wave.complex.trackCount; k += 1) | ||||
{ | ||||
max += evt->wave.complex.weights[k]; | ||||
} | ||||
next = FACT_INTERNAL_rng() * max; | ||||
for (k = evt->wave.complex.trackCount - 1; k >= 0; k -= 1) | ||||
{ | ||||
if (next > (max - evt->wave.complex.weights[k])) | ||||
{ | ||||
evtInst->valuei = k; | ||||
break; | ||||
} | ||||
max -= evt->wave.complex.weights[k]; | ||||
} | ||||
} | ||||
FACT_INTERNAL_GetNextWave( | ||||
cue, | ||||
newSound->sound, | ||||
&newSound->sound->tracks[i], | ||||
&newSound->tracks[i], | ||||
evt, | ||||
evtInst | ||||
); | ||||
newSound->tracks[i].waveEvt = evt; | ||||
newSound->tracks[i].waveEvtInst = evtInst; | ||||
} | ||||
else if ( evt->type == FACTEVENT_PITCHREPEATING || | ||||
evt->type == FACTEVENT_VOLUMEREPEATING ) | ||||
{ | ||||
newSound->tracks[i].events[j].loopCount = | ||||
newSound->sound->tracks[i].events[j].value.repeats; | ||||
} | ||||
else if (evt->type == FACTEVENT_MARKERREPEATING) | ||||
{ | ||||
newSound->tracks[i].events[j].loopCount = | ||||
newSound->sound->tracks[i].events[j].marker.repeats; | ||||
} | ||||
} | ||||
} | ||||
/* Calculate Max RPC Release Time */ | ||||
cue->maxRpcReleaseTime = 0; | ||||
for (i = 0; i < newSound->sound->trackCount; i += 1) | ||||
{ | ||||
for (j = 0; j < newSound->sound->tracks[i].rpcCodeCount; j += 1) | ||||
{ | ||||
rpc = FACT_INTERNAL_GetRPC( | ||||
newSound->parentCue->parentBank->parentEngine, | ||||
newSound->sound->tracks[i].rpcCodes[j] | ||||
); | ||||
if ( rpc->parameter == RPC_PARAMETER_VOLUME && | ||||
cue->parentBank->parentEngine->variables[rpc->variable].accessibility & 0x04 ) | ||||
{ | ||||
if (FAudio_strcmp( | ||||
newSound->parentCue->parentBank->parentEngine->variableNames[rpc->variable], | ||||
"ReleaseTime" | ||||
) == 0) { | ||||
lastX = rpc->points[rpc->pointCount - 1].x; | ||||
if (lastX > cue->maxRpcReleaseTime) | ||||
{ | ||||
cue->maxRpcReleaseTime = (uint32_t) lastX /* bleh */; | ||||
} | ||||
} | ||||
} | ||||
} | ||||
} | ||||
cue->playingSound = newSound; | ||||
} | ||||
return 1; | ||||
} | ||||
void FACT_INTERNAL_DestroySound(FACTSoundInstance *sound) | ||||
{ | ||||
uint8_t i; | ||||
sound->parentCue->playingSound = NULL; | ||||
for (i = 0; i < sound->sound->trackCount; i += 1) | ||||
{ | ||||
if (sound->tracks[i].activeWave.wave != NULL) | ||||
{ | ||||
FACTWave_Destroy( | ||||
sound->tracks[i].activeWave.wave | ||||
); | ||||
} | ||||
if (sound->tracks[i].upcomingWave.wave != NULL) | ||||
{ | ||||
FACTWave_Destroy( | ||||
sound->tracks[i].upcomingWave.wave | ||||
); | ||||
} | ||||
sound->parentCue->parentBank->parentEngine->pFree( | ||||
sound->tracks[i].events | ||||
); | ||||
} | ||||
sound->parentCue->parentBank->parentEngine->pFree(sound->tracks); | ||||
if (sound->sound->category != FACTCATEGORY_INVALID) | ||||
{ | ||||
sound->parentCue->parentBank->parentEngine->categories[ | ||||
sound->sound->category | ||||
].instanceCount -= 1; | ||||
} | ||||
/* TODO: if (sound->parentCue->playingSounds == NULL) */ | ||||
{ | ||||
sound->parentCue->state |= FACT_STATE_STOPPED; | ||||
sound->parentCue->state &= ~(FACT_STATE_PLAYING | FACT_STATE_STOPPING); | ||||
sound->parentCue->data->instanceCount -= 1; | ||||
} | ||||
sound->parentCue->parentBank->parentEngine->pFree(sound); | ||||
} | ||||
void FACT_INTERNAL_BeginFadeOut(FACTSoundInstance *sound, uint16_t fadeOutMS) | ||||
{ | ||||
if (fadeOutMS == 0) | ||||
{ | ||||
/* No fade? Screw it, just delete us */ | ||||
FACT_INTERNAL_DestroySound(sound); | ||||
return; | ||||
} | ||||
sound->fadeType = 2; /* Out */ | ||||
sound->fadeStart = FAudio_timems(); | ||||
sound->fadeTarget = fadeOutMS; | ||||
} | ||||
void FACT_INTERNAL_BeginReleaseRPC(FACTSoundInstance *sound, uint16_t releaseMS) | ||||
{ | ||||
if (releaseMS == 0) | ||||
{ | ||||
/* No release RPC? Screw it, just delete us */ | ||||
FACT_INTERNAL_DestroySound(sound); | ||||
return; | ||||
} | ||||
sound->fadeType = 3; /* Release RPC */ | ||||
sound->fadeStart = FAudio_timems(); | ||||
sound->fadeTarget = releaseMS; | ||||
} | ||||
/* RPC Helper Functions */ | ||||
FACTRPC* FACT_INTERNAL_GetRPC( | ||||
FACTAudioEngine *engine, | ||||
uint32_t code | ||||
) { | ||||
uint16_t i; | ||||
for (i = 0; i < engine->rpcCount; i += 1) | ||||
{ | ||||
if (engine->rpcCodes[i] == code) | ||||
{ | ||||
return &engine->rpcs[i]; | ||||
} | ||||
} | ||||
FAudio_assert(0 && "RPC code not found!"); | ||||
return NULL; | ||||
} | ||||
float FACT_INTERNAL_CalculateRPC( | ||||
FACTRPC *rpc, | ||||
float var | ||||
) { | ||||
float result; | ||||
uint8_t i; | ||||
/* Min/Max */ | ||||
if (var <= rpc->points[0].x) | ||||
{ | ||||
/* Zero to first defined point */ | ||||
return rpc->points[0].y; | ||||
} | ||||
if (var >= rpc->points[rpc->pointCount - 1].x) | ||||
{ | ||||
/* Last defined point to infinity */ | ||||
return rpc->points[rpc->pointCount - 1].y; | ||||
} | ||||
/* Something between points... TODO: Non-linear curves */ | ||||
result = 0.0f; | ||||
for (i = 0; i < rpc->pointCount - 1; i += 1) | ||||
{ | ||||
/* y = b */ | ||||
result = rpc->points[i].y; | ||||
if (var >= rpc->points[i].x && var <= rpc->points[i + 1].x) | ||||
{ | ||||
/* y += mx */ | ||||
result += | ||||
((rpc->points[i + 1].y - rpc->points[i].y) / | ||||
(rpc->points[i + 1].x - rpc->points[i].x)) * | ||||
(var - rpc->points[i].x); | ||||
/* Pre-algebra, rockin'! */ | ||||
break; | ||||
} | ||||
} | ||||
return result; | ||||
} | ||||
void FACT_INTERNAL_UpdateRPCs( | ||||
FACTCue *cue, | ||||
uint8_t codeCount, | ||||
uint32_t *codes, | ||||
FACTInstanceRPCData *data, | ||||
uint32_t timestamp, | ||||
uint32_t elapsedTrack | ||||
) { | ||||
uint8_t i; | ||||
FACTRPC *rpc; | ||||
float rpcResult; | ||||
float variableValue; | ||||
FACTAudioEngine *engine = cue->parentBank->parentEngine; | ||||
if (codeCount > 0) | ||||
{ | ||||
/* Do NOT overwrite Frequency! */ | ||||
data->rpcVolume = 0.0f; | ||||
data->rpcPitch = 0.0f; | ||||
data->rpcReverbSend = 0.0f; | ||||
data->rpcFilterQFactor = FAUDIO_DEFAULT_FILTER_ONEOVERQ; | ||||
for (i = 0; i < codeCount; i += 1) | ||||
{ | ||||
rpc = FACT_INTERNAL_GetRPC( | ||||
engine, | ||||
codes[i] | ||||
); | ||||
if (engine->variables[rpc->variable].accessibility & 0x04) | ||||
{ | ||||
if (FAudio_strcmp( | ||||
engine->variableNames[rpc->variable], | ||||
"AttackTime" | ||||
) == 0) { | ||||
variableValue = (float) elapsedTrack; | ||||
} | ||||
else if (FAudio_strcmp( | ||||
engine->variableNames[rpc->variable], | ||||
"ReleaseTime" | ||||
) == 0) { | ||||
if (cue->playingSound->fadeType == 3) /* Release RPC */ | ||||
{ | ||||
variableValue = (float) (timestamp - cue->playingSound->fadeStart); | ||||
} | ||||
else | ||||
{ | ||||
variableValue = 0.0f; | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
variableValue = cue->variableValues[rpc->variable]; | ||||
} | ||||
rpcResult = FACT_INTERNAL_CalculateRPC( | ||||
rpc, | ||||
variableValue | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
rpcResult = FACT_INTERNAL_CalculateRPC( | ||||
rpc, | ||||
engine->globalVariableValues[rpc->variable] | ||||
); | ||||
} | ||||
if (rpc->parameter == RPC_PARAMETER_VOLUME) | ||||
{ | ||||
data->rpcVolume += rpcResult; | ||||
} | ||||
else if (rpc->parameter == RPC_PARAMETER_PITCH) | ||||
{ | ||||
data->rpcPitch += rpcResult; | ||||
} | ||||
else if (rpc->parameter == RPC_PARAMETER_REVERBSEND) | ||||
{ | ||||
data->rpcReverbSend += rpcResult; | ||||
} | ||||
else if (rpc->parameter == RPC_PARAMETER_FILTERFREQUENCY) | ||||
{ | ||||
/* Yes, just overwrite... */ | ||||
data->rpcFilterFreq = rpcResult / 20000.0f; | ||||
} | ||||
else if (rpc->parameter == RPC_PARAMETER_FILTERQFACTOR) | ||||
{ | ||||
/* TODO: How do we combine these? */ | ||||
data->rpcFilterQFactor += 1.0f / rpcResult; | ||||
} | ||||
else | ||||
{ | ||||
FAudio_assert(0 && "Unhandled RPC parameter type!"); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
/* Engine Update Function */ | ||||
void FACT_INTERNAL_UpdateEngine(FACTAudioEngine *engine) | ||||
{ | ||||
FAudioFXReverbParameters rvbPar; | ||||
uint16_t i, j, par; | ||||
float rpcResult; | ||||
for (i = 0; i < engine->rpcCount; i += 1) | ||||
{ | ||||
if (engine->rpcs[i].parameter >= RPC_PARAMETER_COUNT) | ||||
{ | ||||
/* FIXME: Why did I make this global vars only...? */ | ||||
if (!(engine->variables[engine->rpcs[i].variable].accessibility & 0x04)) | ||||
{ | ||||
for (j = 0; j < engine->dspPresetCount; j += 1) | ||||
{ | ||||
/* FIXME: This affects all DSP presets! | ||||
* What if there's more than one? | ||||
*/ | ||||
par = engine->rpcs[i].parameter - RPC_PARAMETER_COUNT; | ||||
rpcResult = FACT_INTERNAL_CalculateRPC( | ||||
&engine->rpcs[i], | ||||
engine->globalVariableValues[engine->rpcs[i].variable] | ||||
); | ||||
engine->dspPresets[j].parameters[par].value = FAudio_clamp( | ||||
rpcResult, | ||||
engine->dspPresets[j].parameters[par].minVal, | ||||
engine->dspPresets[j].parameters[par].maxVal | ||||
); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
/* Set Effect parameters from above RPC changes */ | ||||
if (engine->reverbVoice != NULL) | ||||
{ | ||||
rvbPar.WetDryMix = engine->dspPresets[0].parameters[21].value; | ||||
rvbPar.ReflectionsDelay = (uint32_t) engine->dspPresets[0].parameters[0].value; | ||||
rvbPar.ReverbDelay = (uint8_t) engine->dspPresets[0].parameters[1].value; | ||||
rvbPar.RearDelay = (uint8_t) engine->dspPresets[0].parameters[12].value; | ||||
rvbPar.PositionLeft = (uint8_t) engine->dspPresets[0].parameters[2].value; | ||||
rvbPar.PositionRight = (uint8_t) engine->dspPresets[0].parameters[3].value; | ||||
rvbPar.PositionMatrixLeft = (uint8_t) engine->dspPresets[0].parameters[4].value; | ||||
rvbPar.PositionMatrixRight = (uint8_t) engine->dspPresets[0].parameters[5].value; | ||||
rvbPar.HighEQGain = (uint8_t) engine->dspPresets[0].parameters[10].value; | ||||
rvbPar.LowEQCutoff = (uint8_t) engine->dspPresets[0].parameters[9].value; | ||||
rvbPar.LowEQGain = (uint8_t) engine->dspPresets[0].parameters[8].value; | ||||
rvbPar.LateDiffusion = (uint8_t) engine->dspPresets[0].parameters[7].value; | ||||
rvbPar.EarlyDiffusion = (uint8_t) engine->dspPresets[0].parameters[6].value; | ||||
rvbPar.HighEQCutoff = (uint8_t) engine->dspPresets[0].parameters[11].value; | ||||
rvbPar.RoomFilterMain = engine->dspPresets[0].parameters[14].value; | ||||
rvbPar.RoomFilterFreq = engine->dspPresets[0].parameters[13].value; | ||||
rvbPar.RoomFilterHF = engine->dspPresets[0].parameters[15].value; | ||||
rvbPar.ReflectionsGain = engine->dspPresets[0].parameters[16].value; | ||||
rvbPar.ReverbGain = engine->dspPresets[0].parameters[17].value; | ||||
rvbPar.DecayTime = engine->dspPresets[0].parameters[18].value; | ||||
rvbPar.Density = engine->dspPresets[0].parameters[19].value; | ||||
rvbPar.RoomSize = engine->dspPresets[0].parameters[20].value; | ||||
FAudioVoice_SetEffectParameters( | ||||
engine->reverbVoice, | ||||
0, | ||||
&rvbPar, | ||||
sizeof(FAudioFXReverbParameters), | ||||
0 | ||||
); | ||||
} | ||||
} | ||||
/* Cue Update Functions */ | ||||
static inline void FACT_INTERNAL_StopTrack( | ||||
FACTTrack *track, | ||||
FACTTrackInstance *trackInst, | ||||
uint8_t immediate | ||||
) { | ||||
uint8_t i; | ||||
/* Stop the wave (may as-authored or immedate */ | ||||
if (trackInst->activeWave.wave != NULL) | ||||
{ | ||||
FACTWave_Stop( | ||||
trackInst->activeWave.wave, | ||||
immediate | ||||
); | ||||
} | ||||
/* If there was another sound coming, it ain't now! */ | ||||
if (trackInst->upcomingWave.wave != NULL) | ||||
{ | ||||
FACTWave_Destroy(trackInst->upcomingWave.wave); | ||||
trackInst->upcomingWave.wave = NULL; | ||||
} | ||||
/* Kill the loop count too */ | ||||
for (i = 0; i < track->eventCount; i += 1) | ||||
{ | ||||
trackInst->events[i].loopCount = 0; | ||||
trackInst->events[i].finished = 1; | ||||
} | ||||
} | ||||
void FACT_INTERNAL_ActivateEvent( | ||||
FACTSoundInstance *sound, | ||||
FACTTrack *track, | ||||
FACTTrackInstance *trackInst, | ||||
FACTEvent *evt, | ||||
FACTEventInstance *evtInst, | ||||
uint32_t elapsed | ||||
) { | ||||
uint8_t i; | ||||
float svResult; | ||||
uint8_t skipLoopCheck = 0; | ||||
/* STOP */ | ||||
if (evt->type == FACTEVENT_STOP) | ||||
{ | ||||
/* Stop Cue */ | ||||
if (evt->stop.flags & 0x02) | ||||
{ | ||||
if ( evt->stop.flags & 0x01 || | ||||
( sound->parentCue->parentBank->cues[sound->parentCue->index].fadeOutMS == 0 && | ||||
sound->parentCue->maxRpcReleaseTime == 0 ) ) | ||||
{ | ||||
for (i = 0; i < sound->sound->trackCount; i += 1) | ||||
{ | ||||
FACT_INTERNAL_StopTrack( | ||||
&sound->sound->tracks[i], | ||||
&sound->tracks[i], | ||||
1 | ||||
); | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
if (sound->parentCue->parentBank->cues[sound->parentCue->index].fadeOutMS > 0) | ||||
{ | ||||
FACT_INTERNAL_BeginFadeOut( | ||||
sound, | ||||
sound->parentCue->parentBank->cues[sound->parentCue->index].fadeOutMS | ||||
); | ||||
} | ||||
else if (sound->parentCue->maxRpcReleaseTime > 0) | ||||
{ | ||||
FACT_INTERNAL_BeginReleaseRPC( | ||||
sound, | ||||
sound->parentCue->maxRpcReleaseTime | ||||
); | ||||
} | ||||
sound->parentCue->state |= FACT_STATE_STOPPING; | ||||
} | ||||
} | ||||
/* Stop track */ | ||||
else | ||||
{ | ||||
FACT_INTERNAL_StopTrack( | ||||
track, | ||||
trackInst, | ||||
evt->stop.flags & 0x01 | ||||
); | ||||
} | ||||
} | ||||
/* PLAYWAVE */ | ||||
else if ( evt->type == FACTEVENT_PLAYWAVE || | ||||
evt->type == FACTEVENT_PLAYWAVETRACKVARIATION || | ||||
evt->type == FACTEVENT_PLAYWAVEEFFECTVARIATION || | ||||
evt->type == FACTEVENT_PLAYWAVETRACKEFFECTVARIATION ) | ||||
{ | ||||
FAudio_assert(trackInst->activeWave.wave == NULL); | ||||
FAudio_assert(trackInst->upcomingWave.wave != NULL); | ||||
FAudio_memcpy( | ||||
&trackInst->activeWave, | ||||
&trackInst->upcomingWave, | ||||
sizeof(trackInst->activeWave) | ||||
); | ||||
trackInst->upcomingWave.wave = NULL; | ||||
FACTWave_Play(trackInst->activeWave.wave); | ||||
} | ||||
/* SETVALUE */ | ||||
else if ( evt->type == FACTEVENT_PITCH || | ||||
evt->type == FACTEVENT_PITCHREPEATING || | ||||
evt->type == FACTEVENT_VOLUME || | ||||
evt->type == FACTEVENT_VOLUMEREPEATING ) | ||||
{ | ||||
/* Ramp/Equation */ | ||||
if (evt->value.settings & 0x01) | ||||
{ | ||||
/* FIXME: Incorporate 2nd derivative into the interpolated pitch (slopeDelta) */ | ||||
skipLoopCheck = elapsed <= (evtInst->timestamp + evt->value.ramp.duration); | ||||
svResult = ( | ||||
evt->value.ramp.initialSlope * | ||||
evt->value.ramp.duration / 1000 * | ||||
10 /* "Slices" */ | ||||
) + evt->value.ramp.initialValue; | ||||
svResult = ( | ||||
(svResult - evt->value.ramp.initialValue) | ||||
) * FAudio_clamp( | ||||
(float) (elapsed - evtInst->timestamp) / evt->value.ramp.duration, | ||||
0.0f, | ||||
1.0f | ||||
) + evt->value.ramp.initialValue; | ||||
evtInst->value = svResult; | ||||
} | ||||
else | ||||
{ | ||||
/* Value/Random */ | ||||
if (evt->value.equation.flags & 0x04) | ||||
{ | ||||
svResult = evt->value.equation.value1; | ||||
} | ||||
else if (evt->value.equation.flags & 0x08) | ||||
{ | ||||
svResult = evt->value.equation.value1 + FACT_INTERNAL_rng() * ( | ||||
evt->value.equation.value2 - | ||||
evt->value.equation.value1 | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
svResult = 0.0f; | ||||
FAudio_assert(0 && "Equation flags?"); | ||||
} | ||||
/* Add/Replace */ | ||||
if (evt->value.equation.flags & 0x01) | ||||
{ | ||||
if( evt->type == FACTEVENT_PITCH || | ||||
evt->type == FACTEVENT_PITCHREPEATING ) | ||||
{ | ||||
evtInst->value = trackInst->evtPitch + svResult; | ||||
} | ||||
else | ||||
{ | ||||
evtInst->value = trackInst->evtVolume + svResult; | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
evtInst->value = svResult; | ||||
} | ||||
} | ||||
/* Set the result, finally. */ | ||||
if ( evt->type == FACTEVENT_PITCH || | ||||
evt->type == FACTEVENT_PITCHREPEATING ) | ||||
{ | ||||
trackInst->evtPitch = evtInst->value; | ||||
} | ||||
else | ||||
{ | ||||
trackInst->evtVolume = evtInst->value; | ||||
} | ||||
if (skipLoopCheck) | ||||
{ | ||||
return; | ||||
} | ||||
if (evtInst->loopCount > 0) | ||||
{ | ||||
if (evtInst->loopCount != 0xFF && evtInst->loopCount != 0xFFFF) | ||||
{ | ||||
evtInst->loopCount -= 1; | ||||
} | ||||
evtInst->timestamp += evt->value.frequency; | ||||
return; | ||||
} | ||||
} | ||||
/* MARKER */ | ||||
else if ( evt->type == FACTEVENT_MARKER || | ||||
evt->type == FACTEVENT_MARKERREPEATING ) | ||||
{ | ||||
/* TODO: FACT_INTERNAL_Marker(evt->marker*) */ | ||||
if (evtInst->loopCount > 0) | ||||
{ | ||||
if (evtInst->loopCount != 0xFF) | ||||
{ | ||||
evtInst->loopCount -= 1; | ||||
} | ||||
evtInst->timestamp += evt->marker.frequency; | ||||
return; | ||||
} | ||||
} | ||||
/* ??? */ | ||||
else | ||||
{ | ||||
FAudio_assert(0 && "Unknown event type!"); | ||||
} | ||||
/* If we made it here, we're done! */ | ||||
evtInst->finished = 1; | ||||
} | ||||
uint8_t FACT_INTERNAL_UpdateSound(FACTSoundInstance *sound, uint32_t timestamp) | ||||
{ | ||||
uint8_t i, j; | ||||
uint32_t waveState; | ||||
uint32_t elapsedCue; | ||||
FACTEventInstance *evtInst; | ||||
FAudioFilterParameters filterParams; | ||||
uint8_t finished = 1; | ||||
/* Instance limiting Fade in/out */ | ||||
float fadeVolume; | ||||
if (sound->fadeType == 1) /* Fade In */ | ||||
{ | ||||
if ((timestamp - sound->fadeStart) >= sound->fadeTarget) | ||||
{ | ||||
/* We've faded in! */ | ||||
fadeVolume = 1.0f; | ||||
sound->fadeStart = 0; | ||||
sound->fadeTarget = 0; | ||||
sound->fadeType = 0; | ||||
} | ||||
else | ||||
{ | ||||
fadeVolume = ( | ||||
(float) (timestamp - sound->fadeStart) / | ||||
(float) sound->fadeTarget | ||||
); | ||||
} | ||||
} | ||||
else if (sound->fadeType == 2) /* Fade Out */ | ||||
{ | ||||
if ((timestamp - sound->fadeStart) >= sound->fadeTarget) | ||||
{ | ||||
/* We've faded out! */ | ||||
return 1; | ||||
} | ||||
fadeVolume = 1.0f - ( | ||||
(float) (timestamp - sound->fadeStart) / | ||||
(float) sound->fadeTarget | ||||
); | ||||
} | ||||
else if (sound->fadeType == 3) /* Release RPC */ | ||||
{ | ||||
if ((timestamp - sound->fadeStart) >= sound->fadeTarget) | ||||
{ | ||||
/* We've faded out! */ | ||||
return 1; | ||||
} | ||||
fadeVolume = 1.0f; | ||||
} | ||||
else | ||||
{ | ||||
fadeVolume = 1.0f; | ||||
} | ||||
/* To get the time on a single Cue, subtract from the global time | ||||
* the latest start time minus the total time elapsed (minus pause time) | ||||
*/ | ||||
elapsedCue = timestamp - (sound->parentCue->start - sound->parentCue->elapsed); | ||||
/* RPC updates */ | ||||
sound->rpcData.rpcFilterFreq = -1.0f; | ||||
FACT_INTERNAL_UpdateRPCs( | ||||
sound->parentCue, | ||||
sound->sound->rpcCodeCount, | ||||
sound->sound->rpcCodes, | ||||
&sound->rpcData, | ||||
timestamp, | ||||
elapsedCue - sound->tracks[0].events[0].timestamp | ||||
); | ||||
for (i = 0; i < sound->sound->trackCount; i += 1) | ||||
{ | ||||
sound->tracks[i].rpcData.rpcFilterFreq = sound->rpcData.rpcFilterFreq; | ||||
FACT_INTERNAL_UpdateRPCs( | ||||
sound->parentCue, | ||||
sound->sound->tracks[i].rpcCodeCount, | ||||
sound->sound->tracks[i].rpcCodes, | ||||
&sound->tracks[i].rpcData, | ||||
timestamp, | ||||
elapsedCue - sound->sound->tracks[i].events[0].timestamp | ||||
); | ||||
} | ||||
/* Go through each event for each track */ | ||||
for (i = 0; i < sound->sound->trackCount; i += 1) | ||||
{ | ||||
/* Event updates */ | ||||
for (j = 0; j < sound->sound->tracks[i].eventCount; j += 1) | ||||
{ | ||||
evtInst = &sound->tracks[i].events[j]; | ||||
if (!evtInst->finished) | ||||
{ | ||||
/* Cue's not done yet...! */ | ||||
finished = 0; | ||||
/* Trigger events at the right time */ | ||||
if (elapsedCue >= evtInst->timestamp) | ||||
{ | ||||
FACT_INTERNAL_ActivateEvent( | ||||
sound, | ||||
&sound->sound->tracks[i], | ||||
&sound->tracks[i], | ||||
&sound->sound->tracks[i].events[j], | ||||
evtInst, | ||||
elapsedCue | ||||
); | ||||
} | ||||
} | ||||
} | ||||
/* Wave updates */ | ||||
if (sound->tracks[i].activeWave.wave == NULL) | ||||
{ | ||||
continue; | ||||
} | ||||
finished = 0; | ||||
/* Clear out Waves as they finish */ | ||||
FACTWave_GetState( | ||||
sound->tracks[i].activeWave.wave, | ||||
&waveState | ||||
); | ||||
if (waveState & FACT_STATE_STOPPED) | ||||
{ | ||||
FACTWave_Destroy(sound->tracks[i].activeWave.wave); | ||||
FAudio_memcpy( | ||||
&sound->tracks[i].activeWave, | ||||
&sound->tracks[i].upcomingWave, | ||||
sizeof(sound->tracks[i].activeWave) | ||||
); | ||||
sound->tracks[i].upcomingWave.wave = NULL; | ||||
if (sound->tracks[i].activeWave.wave == NULL) | ||||
{ | ||||
continue; | ||||
} | ||||
FACTWave_Play(sound->tracks[i].activeWave.wave); | ||||
} | ||||
FACTWave_SetVolume( | ||||
sound->tracks[i].activeWave.wave, | ||||
FACT_INTERNAL_CalculateAmplitudeRatio( | ||||
sound->tracks[i].activeWave.baseVolume + | ||||
sound->rpcData.rpcVolume + | ||||
sound->tracks[i].rpcData.rpcVolume + | ||||
sound->tracks[i].evtVolume | ||||
) * sound->parentCue->parentBank->parentEngine->categories[ | ||||
sound->sound->category | ||||
].currentVolume * | ||||
fadeVolume | ||||
); | ||||
FACTWave_SetPitch( | ||||
sound->tracks[i].activeWave.wave, | ||||
(int16_t) ( | ||||
sound->tracks[i].activeWave.basePitch + | ||||
sound->rpcData.rpcPitch + | ||||
sound->tracks[i].rpcData.rpcPitch + | ||||
sound->tracks[i].evtPitch | ||||
) | ||||
); | ||||
if (sound->sound->tracks[i].filter != 0xFF) | ||||
{ | ||||
/* FIXME: From what I can gather, filter parameters get | ||||
* overwritten by the RPC value if a filter RPC exists. | ||||
* Priority is Sound < Sound RPC < Track RPC, I think? | ||||
*/ | ||||
filterParams.Type = (FAudioFilterType) sound->sound->tracks[i].filter; | ||||
if (sound->tracks[i].rpcData.rpcFilterFreq >= 0.0f) | ||||
{ | ||||
filterParams.Frequency = sound->tracks[i].rpcData.rpcFilterFreq; | ||||
} | ||||
else | ||||
{ | ||||
filterParams.Frequency = sound->tracks[i].activeWave.baseFrequency; | ||||
} | ||||
filterParams.OneOverQ = ( | ||||
sound->tracks[i].activeWave.baseQFactor + | ||||
sound->rpcData.rpcFilterQFactor + | ||||
sound->tracks[i].rpcData.rpcFilterQFactor | ||||
) / 3.0f; /* FIXME: How do we combine QFactor params? */ | ||||
FAudioVoice_SetFilterParameters( | ||||
sound->tracks[i].activeWave.wave->voice, | ||||
&filterParams, | ||||
0 | ||||
); | ||||
} | ||||
/* TODO: Wave updates: | ||||
* - ReverbSend (SetOutputMatrix on index 1, submix voice) | ||||
*/ | ||||
} | ||||
return finished; | ||||
} | ||||
void FACT_INTERNAL_UpdateCue(FACTCue *cue) | ||||
{ | ||||
uint32_t i; | ||||
float next; | ||||
FACTSoundInstance *sound; | ||||
/* Interactive sound selection */ | ||||
if (!(cue->data->flags & 0x04) && cue->variation->flags == 3) | ||||
{ | ||||
/* Interactive */ | ||||
if (cue->parentBank->parentEngine->variables[cue->variation->variable].accessibility & 0x04) | ||||
{ | ||||
FACTCue_GetVariable( | ||||
cue, | ||||
cue->variation->variable, | ||||
&next | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
FACTAudioEngine_GetGlobalVariable( | ||||
cue->parentBank->parentEngine, | ||||
cue->variation->variable, | ||||
&next | ||||
); | ||||
} | ||||
if (next != cue->interactive) | ||||
{ | ||||
cue->interactive = next; | ||||
/* New sound, time for death! */ | ||||
if (cue->playingSound != NULL) | ||||
{ | ||||
/* Copy of DestroySound but does not set Cue to STOPPED */ | ||||
sound = cue->playingSound; | ||||
sound->parentCue->playingSound = NULL; | ||||
for (i = 0; i < sound->sound->trackCount; i += 1) | ||||
{ | ||||
if (sound->tracks[i].activeWave.wave != NULL) | ||||
{ | ||||
FACTWave_Destroy( | ||||
sound->tracks[i].activeWave.wave | ||||
); | ||||
} | ||||
if (sound->tracks[i].upcomingWave.wave != NULL) | ||||
{ | ||||
FACTWave_Destroy( | ||||
sound->tracks[i].upcomingWave.wave | ||||
); | ||||
} | ||||
cue->parentBank->parentEngine->pFree( | ||||
sound->tracks[i].events | ||||
); | ||||
} | ||||
cue->parentBank->parentEngine->pFree(sound->tracks); | ||||
if (sound->sound->category != FACTCATEGORY_INVALID) | ||||
{ | ||||
sound->parentCue->parentBank->parentEngine->categories[ | ||||
sound->sound->category | ||||
].instanceCount -= 1; | ||||
} | ||||
} | ||||
/* TODO: Reset cue times? Transition tables...? | ||||
cue->start = elapsed; | ||||
cue->elapsed = 0; | ||||
*/ | ||||
FACT_INTERNAL_CreateSound(cue, 0 /* fadeIn */); | ||||
} | ||||
} | ||||
} | ||||
/* FACT Thread */ | ||||
int32_t FACT_INTERNAL_APIThread(void* enginePtr) | ||||
{ | ||||
FACTAudioEngine *engine = (FACTAudioEngine*) enginePtr; | ||||
LinkedList *sbList; | ||||
FACTCue *cue, *cBackup; | ||||
uint32_t timestamp, updateTime; | ||||
/* Needs to match the audio thread priority, or else the scheduler will | ||||
* let this thread sit around with a lock while the audio thread spins | ||||
* infinitely! | ||||
*/ | ||||
FAudio_PlatformThreadPriority(FAUDIO_THREAD_PRIORITY_HIGH); | ||||
threadstart: | ||||
FAudio_PlatformLockMutex(engine->apiLock); | ||||
/* We want the timestamp to be uniform across all Cues. | ||||
* Oftentimes many Cues are played at once with the expectation | ||||
* that they will sync, so give them all the same timestamp | ||||
* so all the various actions will go together even if it takes | ||||
* an extra millisecond to get through the whole Cue list. | ||||
*/ | ||||
timestamp = FAudio_timems(); | ||||
FACT_INTERNAL_UpdateEngine(engine); | ||||
sbList = engine->sbList; | ||||
while (sbList != NULL) | ||||
{ | ||||
cue = ((FACTSoundBank*) sbList->entry)->cueList; | ||||
while (cue != NULL) | ||||
{ | ||||
FACT_INTERNAL_UpdateCue(cue); | ||||
if (cue->state & FACT_STATE_PAUSED) | ||||
{ | ||||
cue = cue->next; | ||||
continue; | ||||
} | ||||
if (cue->playingSound != NULL) | ||||
{ | ||||
if (FACT_INTERNAL_UpdateSound(cue->playingSound, timestamp)) | ||||
{ | ||||
FACT_INTERNAL_DestroySound(cue->playingSound); | ||||
} | ||||
} | ||||
/* Destroy if it's done and not user-handled. */ | ||||
if (cue->managed && (cue->state & FACT_STATE_STOPPED)) | ||||
{ | ||||
cBackup = cue->next; | ||||
FACTCue_Destroy(cue); | ||||
cue = cBackup; | ||||
} | ||||
else | ||||
{ | ||||
cue = cue->next; | ||||
} | ||||
} | ||||
sbList = sbList->next; | ||||
} | ||||
FAudio_PlatformUnlockMutex(engine->apiLock); | ||||
if (engine->initialized) | ||||
{ | ||||
/* FIXME: 10ms is based on the XAudio2 update time...? */ | ||||
updateTime = FAudio_timems() - timestamp; | ||||
if (updateTime < 10) | ||||
{ | ||||
FAudio_sleep(10 - updateTime); | ||||
} | ||||
goto threadstart; | ||||
} | ||||
return 0; | ||||
} | ||||
/* FAudio callbacks */ | ||||
void FACT_INTERNAL_OnBufferEnd(FAudioVoiceCallback *callback, void* pContext) | ||||
{ | ||||
FAudioBuffer buffer; | ||||
FAudioBufferWMA bufferWMA; | ||||
FACTWaveCallback *c = (FACTWaveCallback*) callback; | ||||
FACTWaveBankEntry *entry; | ||||
uint32_t end, left, length; | ||||
entry = &c->wave->parentBank->entries[c->wave->index]; | ||||
/* Calculate total bytes left in this wave iteration */ | ||||
if (c->wave->loopCount > 0 && entry->LoopRegion.dwTotalSamples > 0) | ||||
{ | ||||
length = entry->LoopRegion.dwStartSample + entry->LoopRegion.dwTotalSamples; | ||||
if (entry->Format.wFormatTag == 0x0) | ||||
{ | ||||
length = ( | ||||
length * | ||||
entry->Format.nChannels * | ||||
(1 << entry->Format.wBitsPerSample) | ||||
); | ||||
} | ||||
else if (entry->Format.wFormatTag == 0x2) | ||||
{ | ||||
length = ( | ||||
length / | ||||
/* wSamplesPerBlock */ | ||||
((entry->Format.wBlockAlign + 16) * 2) * | ||||
/* nBlockAlign */ | ||||
((entry->Format.wBlockAlign + 22) * entry->Format.nChannels) | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
length = entry->PlayRegion.dwLength; | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
length = entry->PlayRegion.dwLength; | ||||
} | ||||
end = entry->PlayRegion.dwOffset + length; | ||||
left = length - (c->wave->streamOffset - entry->PlayRegion.dwOffset); | ||||
/* Don't bother if we're EOS or the Wave has stopped */ | ||||
if ( (c->wave->streamOffset >= end) || | ||||
(c->wave->state & FACT_STATE_STOPPED) ) | ||||
{ | ||||
return; | ||||
} | ||||
/* Assign buffer memory */ | ||||
buffer.pAudioData = c->wave->streamCache; | ||||
buffer.AudioBytes = FAudio_min( | ||||
c->wave->streamSize, | ||||
left | ||||
); | ||||
/* Read! */ | ||||
FACT_INTERNAL_ReadFile( | ||||
c->wave->parentBank->parentEngine->pReadFile, | ||||
c->wave->parentBank->parentEngine->pGetOverlappedResult, | ||||
c->wave->parentBank->io, | ||||
c->wave->streamOffset, | ||||
c->wave->parentBank->packetSize, | ||||
&c->wave->parentBank->packetBuffer, | ||||
&c->wave->parentBank->packetBufferLen, | ||||
c->wave->parentBank->parentEngine->pRealloc, | ||||
c->wave->streamCache, | ||||
buffer.AudioBytes | ||||
); | ||||
c->wave->streamOffset += buffer.AudioBytes; | ||||
/* Last buffer in the stream? */ | ||||
buffer.Flags = 0; | ||||
if (c->wave->streamOffset >= end) | ||||
{ | ||||
/* Loop if applicable */ | ||||
if (c->wave->loopCount > 0) | ||||
{ | ||||
if (c->wave->loopCount != 255) | ||||
{ | ||||
c->wave->loopCount -= 1; | ||||
} | ||||
c->wave->streamOffset = entry->PlayRegion.dwOffset; | ||||
/* Loop start */ | ||||
if (entry->Format.wFormatTag == 0x0) | ||||
{ | ||||
c->wave->streamOffset += ( | ||||
entry->LoopRegion.dwStartSample * | ||||
entry->Format.nChannels * | ||||
(1 << entry->Format.wBitsPerSample) | ||||
); | ||||
} | ||||
else if (entry->Format.wFormatTag == 0x2) | ||||
{ | ||||
c->wave->streamOffset += ( | ||||
entry->LoopRegion.dwStartSample / | ||||
/* wSamplesPerBlock */ | ||||
((entry->Format.wBlockAlign + 16) * 2) * | ||||
/* nBlockAlign */ | ||||
((entry->Format.wBlockAlign + 22) * entry->Format.nChannels) | ||||
); | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
buffer.Flags = FAUDIO_END_OF_STREAM; | ||||
} | ||||
} | ||||
/* Unused properties */ | ||||
buffer.PlayBegin = 0; | ||||
buffer.PlayLength = 0; | ||||
buffer.LoopBegin = 0; | ||||
buffer.LoopLength = 0; | ||||
buffer.LoopCount = 0; | ||||
buffer.pContext = NULL; | ||||
/* Submit, finally. */ | ||||
if ( entry->Format.wFormatTag == 0x1 || | ||||
entry->Format.wFormatTag == 0x3 ) | ||||
{ | ||||
bufferWMA.pDecodedPacketCumulativeBytes = | ||||
c->wave->parentBank->seekTables[c->wave->index].entries; | ||||
bufferWMA.PacketCount = | ||||
c->wave->parentBank->seekTables[c->wave->index].entryCount; | ||||
FAudioSourceVoice_SubmitSourceBuffer( | ||||
c->wave->voice, | ||||
&buffer, | ||||
&bufferWMA | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
FAudioSourceVoice_SubmitSourceBuffer( | ||||
c->wave->voice, | ||||
&buffer, | ||||
NULL | ||||
); | ||||
} | ||||
} | ||||
void FACT_INTERNAL_OnStreamEnd(FAudioVoiceCallback *callback) | ||||
{ | ||||
FACTWaveCallback *c = (FACTWaveCallback*) callback; | ||||
c->wave->state = FACT_STATE_STOPPED; | ||||
if ( c->wave->parentCue != NULL && | ||||
c->wave->parentCue->simpleWave == c->wave ) | ||||
{ | ||||
c->wave->parentCue->state |= FACT_STATE_STOPPED; | ||||
c->wave->parentCue->state &= ~( | ||||
FACT_STATE_PLAYING | | ||||
FACT_STATE_STOPPING | ||||
); | ||||
c->wave->parentCue->data->instanceCount -= 1; | ||||
} | ||||
} | ||||
/* FAudioIOStream functions */ | ||||
int32_t FACTCALL FACT_INTERNAL_DefaultReadFile( | ||||
void *hFile, | ||||
void *buffer, | ||||
uint32_t nNumberOfBytesToRead, | ||||
uint32_t *lpNumberOfBytesRead, /* Not referenced! */ | ||||
FACTOverlapped *lpOverlapped | ||||
) { | ||||
FAudioIOStream *io = (FAudioIOStream*) hFile; | ||||
lpOverlapped->Internal = (void*) 0x00000103; /* STATUS_PENDING */ | ||||
FAudio_PlatformLockMutex((FAudioMutex) io->lock); | ||||
io->seek(io->data, (size_t) lpOverlapped->Pointer, FAUDIO_SEEK_SET); | ||||
lpOverlapped->InternalHigh = (void*) (size_t) (io->read( | ||||
io->data, | ||||
buffer, | ||||
nNumberOfBytesToRead, | ||||
1 | ||||
) * nNumberOfBytesToRead); | ||||
FAudio_PlatformUnlockMutex((FAudioMutex) io->lock); | ||||
lpOverlapped->Internal = 0; /* STATUS_SUCCESS */ | ||||
return 1; | ||||
} | ||||
int32_t FACTCALL FACT_INTERNAL_DefaultGetOverlappedResult( | ||||
void *hFile, | ||||
FACTOverlapped *lpOverlapped, | ||||
uint32_t *lpNumberOfBytesTransferred, | ||||
int32_t bWait | ||||
) { | ||||
*lpNumberOfBytesTransferred = (uint32_t) (size_t) lpOverlapped->InternalHigh; | ||||
return 1; | ||||
} | ||||
/* Parsing functions */ | ||||
#define READ_FUNC(type, size, suffix, swapped) \ | ||||
static inline type read_##suffix(uint8_t **ptr, const uint8_t swapendian) \ | ||||
{ \ | ||||
type result = *((type*) *ptr); \ | ||||
*ptr += size; \ | ||||
return swapendian ? (swapped) : result; \ | ||||
} | ||||
READ_FUNC(uint8_t, 1, u8, result) | ||||
READ_FUNC(uint16_t, 2, u16, SWAP_16(result)) | ||||
READ_FUNC(uint32_t, 4, u32, SWAP_32(result)) | ||||
READ_FUNC(int16_t, 2, s16, SWAP_16(result)) | ||||
READ_FUNC(int32_t, 4, s32, SWAP_32(result)) | ||||
READ_FUNC(float, 4, f32, result) | ||||
#undef READ_FUNC | ||||
static inline float read_volbyte(uint8_t **ptr, const uint8_t swapendian) | ||||
{ | ||||
/* FIXME: This magnificent beauty came from Mathematica! | ||||
* The byte values for all possible input dB values from the .xap are here: | ||||
* http://www.flibitijibibo.com/XACTVolume.txt | ||||
* Yes, this is actually what the XACT builder really does. | ||||
* | ||||
* Thanks to Kenny for plotting all that data. | ||||
* -flibit | ||||
*/ | ||||
return (float) ((3969.0 * FAudio_log10(read_u8(ptr, swapendian) / 28240.0)) + 8715.0); | ||||
} | ||||
uint32_t FACT_INTERNAL_ParseAudioEngine( | ||||
FACTAudioEngine *pEngine, | ||||
const FACTRuntimeParameters *pParams | ||||
) { | ||||
uint32_t categoryOffset, | ||||
variableOffset, | ||||
blob1Offset, | ||||
categoryNameIndexOffset, | ||||
blob2Offset, | ||||
variableNameIndexOffset, | ||||
categoryNameOffset, | ||||
variableNameOffset, | ||||
rpcOffset, | ||||
dspPresetOffset, | ||||
dspParameterOffset; | ||||
uint16_t blob1Count, blob2Count; | ||||
uint8_t version, tool; | ||||
size_t memsize; | ||||
uint16_t i, j; | ||||
uint8_t *ptr = (uint8_t*) pParams->pGlobalSettingsBuffer; | ||||
uint8_t *start = ptr; | ||||
uint8_t se = 0; /* Swap Endian */ | ||||
uint32_t magic = read_u32(&ptr, 0); | ||||
se = magic == 0x58475346; | ||||
if (magic != 0x46534758 && magic != 0x58475346) /* 'XGSF' */ | ||||
{ | ||||
return -1; /* TODO: NOT XACT FILE */ | ||||
} | ||||
if (!FACT_INTERNAL_SupportedContent(read_u16(&ptr, se))) | ||||
{ | ||||
return -2; | ||||
} | ||||
tool = read_u16(&ptr, se); /* Tool version */ | ||||
if (tool != 42) | ||||
{ | ||||
return -3; | ||||
} | ||||
ptr += 2; /* Unknown value */ | ||||
/* Last modified, unused */ | ||||
ptr += 8; | ||||
/* XACT Version (Windows == 3, Xbox == 7) */ | ||||
version = read_u8(&ptr, se); | ||||
if ( version != 3 && | ||||
version != 7 ) | ||||
{ | ||||
return -4; /* TODO: VERSION TOO OLD */ | ||||
} | ||||
/* Object counts */ | ||||
pEngine->categoryCount = read_u16(&ptr, se); | ||||
pEngine->variableCount = read_u16(&ptr, se); | ||||
blob1Count = read_u16(&ptr, se); | ||||
blob2Count = read_u16(&ptr, se); | ||||
pEngine->rpcCount = read_u16(&ptr, se); | ||||
pEngine->dspPresetCount = read_u16(&ptr, se); | ||||
pEngine->dspParameterCount = read_u16(&ptr, se); | ||||
/* Object offsets */ | ||||
categoryOffset = read_u32(&ptr, se); | ||||
variableOffset = read_u32(&ptr, se); | ||||
blob1Offset = read_u32(&ptr, se); | ||||
categoryNameIndexOffset = read_u32(&ptr, se); | ||||
blob2Offset = read_u32(&ptr, se); | ||||
variableNameIndexOffset = read_u32(&ptr, se); | ||||
categoryNameOffset = read_u32(&ptr, se); | ||||
variableNameOffset = read_u32(&ptr, se); | ||||
rpcOffset = read_u32(&ptr, se); | ||||
dspPresetOffset = read_u32(&ptr, se); | ||||
dspParameterOffset = read_u32(&ptr, se); | ||||
/* Category data */ | ||||
FAudio_assert((ptr - start) == categoryOffset); | ||||
pEngine->categories = (FACTAudioCategory*) pEngine->pMalloc( | ||||
sizeof(FACTAudioCategory) * pEngine->categoryCount | ||||
); | ||||
for (i = 0; i < pEngine->categoryCount; i += 1) | ||||
{ | ||||
pEngine->categories[i].instanceLimit = read_u8(&ptr, se); | ||||
pEngine->categories[i].fadeInMS = read_u16(&ptr, se); | ||||
pEngine->categories[i].fadeOutMS = read_u16(&ptr, se); | ||||
pEngine->categories[i].maxInstanceBehavior = read_u8(&ptr, se) >> 3; | ||||
pEngine->categories[i].parentCategory = read_u16(&ptr, se); | ||||
pEngine->categories[i].volume = FACT_INTERNAL_CalculateAmplitudeRatio( | ||||
read_volbyte(&ptr, se) | ||||
); | ||||
pEngine->categories[i].visibility = read_u8(&ptr, se); | ||||
pEngine->categories[i].instanceCount = 0; | ||||
pEngine->categories[i].currentVolume = 1.0f; | ||||
} | ||||
/* Variable data */ | ||||
FAudio_assert((ptr - start) == variableOffset); | ||||
pEngine->variables = (FACTVariable*) pEngine->pMalloc( | ||||
sizeof(FACTVariable) * pEngine->variableCount | ||||
); | ||||
for (i = 0; i < pEngine->variableCount; i += 1) | ||||
{ | ||||
pEngine->variables[i].accessibility = read_u8(&ptr, se); | ||||
pEngine->variables[i].initialValue = read_f32(&ptr, se); | ||||
pEngine->variables[i].minValue = read_f32(&ptr, se); | ||||
pEngine->variables[i].maxValue = read_f32(&ptr, se); | ||||
} | ||||
/* Global variable storage. Some unused data for non-global vars */ | ||||
pEngine->globalVariableValues = (float*) pEngine->pMalloc( | ||||
sizeof(float) * pEngine->variableCount | ||||
); | ||||
for (i = 0; i < pEngine->variableCount; i += 1) | ||||
{ | ||||
pEngine->globalVariableValues[i] = pEngine->variables[i].initialValue; | ||||
} | ||||
/* RPC data */ | ||||
if (pEngine->rpcCount > 0) | ||||
{ | ||||
FAudio_assert((ptr - start) == rpcOffset); | ||||
pEngine->rpcs = (FACTRPC*) pEngine->pMalloc( | ||||
sizeof(FACTRPC) * | ||||
pEngine->rpcCount | ||||
); | ||||
pEngine->rpcCodes = (uint32_t*) pEngine->pMalloc( | ||||
sizeof(uint32_t) * | ||||
pEngine->rpcCount | ||||
); | ||||
for (i = 0; i < pEngine->rpcCount; i += 1) | ||||
{ | ||||
pEngine->rpcCodes[i] = (uint32_t) (ptr - start); | ||||
pEngine->rpcs[i].variable = read_u16(&ptr, se); | ||||
pEngine->rpcs[i].pointCount = read_u8(&ptr, se); | ||||
pEngine->rpcs[i].parameter = read_u16(&ptr, se); | ||||
pEngine->rpcs[i].points = (FACTRPCPoint*) pEngine->pMalloc( | ||||
sizeof(FACTRPCPoint) * | ||||
pEngine->rpcs[i].pointCount | ||||
); | ||||
for (j = 0; j < pEngine->rpcs[i].pointCount; j += 1) | ||||
{ | ||||
pEngine->rpcs[i].points[j].x = read_f32(&ptr, se); | ||||
pEngine->rpcs[i].points[j].y = read_f32(&ptr, se); | ||||
pEngine->rpcs[i].points[j].type = read_u8(&ptr, se); | ||||
} | ||||
} | ||||
} | ||||
/* DSP Preset data */ | ||||
if (pEngine->dspPresetCount > 0) | ||||
{ | ||||
FAudio_assert((ptr - start) == dspPresetOffset); | ||||
pEngine->dspPresets = (FACTDSPPreset*) pEngine->pMalloc( | ||||
sizeof(FACTDSPPreset) * | ||||
pEngine->dspPresetCount | ||||
); | ||||
pEngine->dspPresetCodes = (uint32_t*) pEngine->pMalloc( | ||||
sizeof(uint32_t) * | ||||
pEngine->dspPresetCount | ||||
); | ||||
for (i = 0; i < pEngine->dspPresetCount; i += 1) | ||||
{ | ||||
pEngine->dspPresetCodes[i] = (uint32_t) (ptr - start); | ||||
pEngine->dspPresets[i].accessibility = read_u8(&ptr, se); | ||||
pEngine->dspPresets[i].parameterCount = read_u32(&ptr, se); | ||||
pEngine->dspPresets[i].parameters = (FACTDSPParameter*) pEngine->pMalloc( | ||||
sizeof(FACTDSPParameter) * | ||||
pEngine->dspPresets[i].parameterCount | ||||
); /* This will be filled in just a moment... */ | ||||
} | ||||
/* DSP Parameter data */ | ||||
FAudio_assert((ptr - start) == dspParameterOffset); | ||||
for (i = 0; i < pEngine->dspPresetCount; i += 1) | ||||
{ | ||||
for (j = 0; j < pEngine->dspPresets[i].parameterCount; j += 1) | ||||
{ | ||||
pEngine->dspPresets[i].parameters[j].type = read_u8(&ptr, se); | ||||
pEngine->dspPresets[i].parameters[j].value = read_f32(&ptr, se); | ||||
pEngine->dspPresets[i].parameters[j].minVal = read_f32(&ptr, se); | ||||
pEngine->dspPresets[i].parameters[j].maxVal = read_f32(&ptr, se); | ||||
pEngine->dspPresets[i].parameters[j].unknown = read_u16(&ptr, se); | ||||
} | ||||
} | ||||
} | ||||
/* Blob #1, no idea what this is... */ | ||||
FAudio_assert((ptr - start) == blob1Offset); | ||||
ptr += blob1Count * 2; | ||||
/* Category Name Index data */ | ||||
FAudio_assert((ptr - start) == categoryNameIndexOffset); | ||||
ptr += pEngine->categoryCount * 6; /* FIXME: index as assert value? */ | ||||
/* Category Name data */ | ||||
FAudio_assert((ptr - start) == categoryNameOffset); | ||||
pEngine->categoryNames = (char**) pEngine->pMalloc( | ||||
sizeof(char*) * | ||||
pEngine->categoryCount | ||||
); | ||||
for (i = 0; i < pEngine->categoryCount; i += 1) | ||||
{ | ||||
memsize = FAudio_strlen((char*) ptr) + 1; /* Dastardly! */ | ||||
pEngine->categoryNames[i] = (char*) pEngine->pMalloc(memsize); | ||||
FAudio_memcpy(pEngine->categoryNames[i], ptr, memsize); | ||||
ptr += memsize; | ||||
} | ||||
/* Blob #2, no idea what this is... */ | ||||
FAudio_assert((ptr - start) == blob2Offset); | ||||
ptr += blob2Count * 2; | ||||
/* Variable Name Index data */ | ||||
FAudio_assert((ptr - start) == variableNameIndexOffset); | ||||
ptr += pEngine->variableCount * 6; /* FIXME: index as assert value? */ | ||||
/* Variable Name data */ | ||||
FAudio_assert((ptr - start) == variableNameOffset); | ||||
pEngine->variableNames = (char**) pEngine->pMalloc( | ||||
sizeof(char*) * | ||||
pEngine->variableCount | ||||
); | ||||
for (i = 0; i < pEngine->variableCount; i += 1) | ||||
{ | ||||
memsize = FAudio_strlen((char*) ptr) + 1; /* Dastardly! */ | ||||
pEngine->variableNames[i] = (char*) pEngine->pMalloc(memsize); | ||||
FAudio_memcpy(pEngine->variableNames[i], ptr, memsize); | ||||
ptr += memsize; | ||||
} | ||||
/* Peristent Notifications */ | ||||
pEngine->notifications = 0; | ||||
pEngine->cue_context = NULL; | ||||
pEngine->sb_context = NULL; | ||||
pEngine->wb_context = NULL; | ||||
pEngine->wave_context = NULL; | ||||
/* Finally. */ | ||||
FAudio_assert((ptr - start) == pParams->globalSettingsBufferSize); | ||||
return 0; | ||||
} | ||||
void FACT_INTERNAL_ParseTrackEvents( | ||||
uint8_t **ptr, | ||||
uint8_t se, | ||||
FACTTrack *track, | ||||
FAudioMallocFunc pMalloc | ||||
) { | ||||
uint32_t evtInfo; | ||||
uint8_t minWeight, maxWeight, separator; | ||||
uint8_t i; | ||||
uint16_t j; | ||||
track->eventCount = read_u8(ptr, se); | ||||
track->events = (FACTEvent*) pMalloc( | ||||
sizeof(FACTEvent) * | ||||
track->eventCount | ||||
); | ||||
FAudio_zero(track->events, sizeof(FACTEvent) * track->eventCount); | ||||
for (i = 0; i < track->eventCount; i += 1) | ||||
{ | ||||
evtInfo = read_u32(ptr, se); | ||||
track->events[i].randomOffset = read_u16(ptr, se); | ||||
track->events[i].type = evtInfo & 0x001F; | ||||
track->events[i].timestamp = (evtInfo >> 5) & 0xFFFF; | ||||
separator = read_u8(ptr, se); | ||||
FAudio_assert(separator == 0xFF); /* Separator? */ | ||||
#define EVTTYPE(t) (track->events[i].type == t) | ||||
if (EVTTYPE(FACTEVENT_STOP)) | ||||
{ | ||||
track->events[i].stop.flags = read_u8(ptr, se); | ||||
} | ||||
else if (EVTTYPE(FACTEVENT_PLAYWAVE)) | ||||
{ | ||||
/* Basic Wave */ | ||||
track->events[i].wave.isComplex = 0; | ||||
track->events[i].wave.flags = read_u8(ptr, se); | ||||
track->events[i].wave.simple.track = read_u16(ptr, se); | ||||
track->events[i].wave.simple.wavebank = read_u8(ptr, se); | ||||
track->events[i].wave.loopCount = read_u8(ptr, se); | ||||
track->events[i].wave.position = read_u16(ptr, se); | ||||
track->events[i].wave.angle = read_u16(ptr, se); | ||||
/* No Effect Variation */ | ||||
track->events[i].wave.variationFlags = 0; | ||||
} | ||||
else if (EVTTYPE(FACTEVENT_PLAYWAVETRACKVARIATION)) | ||||
{ | ||||
/* Complex Wave */ | ||||
track->events[i].wave.isComplex = 1; | ||||
track->events[i].wave.flags = read_u8(ptr, se); | ||||
track->events[i].wave.loopCount = read_u8(ptr, se); | ||||
track->events[i].wave.position = read_u16(ptr, se); | ||||
track->events[i].wave.angle = read_u16(ptr, se); | ||||
/* Track Variation */ | ||||
track->events[i].wave.complex.trackCount = read_u16(ptr, se); | ||||
track->events[i].wave.complex.variation = read_u16(ptr, se); | ||||
*ptr += 4; /* Unknown values */ | ||||
track->events[i].wave.complex.tracks = (uint16_t*) pMalloc( | ||||
sizeof(uint16_t) * | ||||
track->events[i].wave.complex.trackCount | ||||
); | ||||
track->events[i].wave.complex.wavebanks = (uint8_t*) pMalloc( | ||||
sizeof(uint8_t) * | ||||
track->events[i].wave.complex.trackCount | ||||
); | ||||
track->events[i].wave.complex.weights = (uint8_t*) pMalloc( | ||||
sizeof(uint8_t) * | ||||
track->events[i].wave.complex.trackCount | ||||
); | ||||
for (j = 0; j < track->events[i].wave.complex.trackCount; j += 1) | ||||
{ | ||||
track->events[i].wave.complex.tracks[j] = read_u16(ptr, se); | ||||
track->events[i].wave.complex.wavebanks[j] = read_u8(ptr, se); | ||||
minWeight = read_u8(ptr, se); | ||||
maxWeight = read_u8(ptr, se); | ||||
track->events[i].wave.complex.weights[j] = ( | ||||
maxWeight - minWeight | ||||
); | ||||
} | ||||
/* No Effect Variation */ | ||||
track->events[i].wave.variationFlags = 0; | ||||
} | ||||
else if (EVTTYPE(FACTEVENT_PLAYWAVEEFFECTVARIATION)) | ||||
{ | ||||
/* Basic Wave */ | ||||
track->events[i].wave.isComplex = 0; | ||||
track->events[i].wave.flags = read_u8(ptr, se); | ||||
track->events[i].wave.simple.track = read_u16(ptr, se); | ||||
track->events[i].wave.simple.wavebank = read_u8(ptr, se); | ||||
track->events[i].wave.loopCount = read_u8(ptr, se); | ||||
track->events[i].wave.position = read_u16(ptr, se); | ||||
track->events[i].wave.angle = read_u16(ptr, se); | ||||
/* Effect Variation */ | ||||
track->events[i].wave.minPitch = read_s16(ptr, se); | ||||
track->events[i].wave.maxPitch = read_s16(ptr, se); | ||||
track->events[i].wave.minVolume = read_volbyte(ptr, se); | ||||
track->events[i].wave.maxVolume = read_volbyte(ptr, se); | ||||
track->events[i].wave.minFrequency = read_f32(ptr, se); | ||||
track->events[i].wave.maxFrequency = read_f32(ptr, se); | ||||
track->events[i].wave.minQFactor = read_f32(ptr, se); | ||||
track->events[i].wave.maxQFactor = read_f32(ptr, se); | ||||
track->events[i].wave.variationFlags = read_u16(ptr, se); | ||||
} | ||||
else if (EVTTYPE(FACTEVENT_PLAYWAVETRACKEFFECTVARIATION)) | ||||
{ | ||||
/* Complex Wave */ | ||||
track->events[i].wave.isComplex = 1; | ||||
track->events[i].wave.flags = read_u8(ptr, se); | ||||
track->events[i].wave.loopCount = read_u8(ptr, se); | ||||
track->events[i].wave.position = read_u16(ptr, se); | ||||
track->events[i].wave.angle = read_u16(ptr, se); | ||||
/* Effect Variation */ | ||||
track->events[i].wave.minPitch = read_s16(ptr, se); | ||||
track->events[i].wave.maxPitch = read_s16(ptr, se); | ||||
track->events[i].wave.minVolume = read_volbyte(ptr, se); | ||||
track->events[i].wave.maxVolume = read_volbyte(ptr, se); | ||||
track->events[i].wave.minFrequency = read_f32(ptr, se); | ||||
track->events[i].wave.maxFrequency = read_f32(ptr, se); | ||||
track->events[i].wave.minQFactor = read_f32(ptr, se); | ||||
track->events[i].wave.maxQFactor = read_f32(ptr, se); | ||||
track->events[i].wave.variationFlags = read_u16(ptr, se); | ||||
/* Track Variation */ | ||||
track->events[i].wave.complex.trackCount = read_u16(ptr, se); | ||||
track->events[i].wave.complex.variation = read_u16(ptr, se); | ||||
*ptr += 4; /* Unknown values */ | ||||
track->events[i].wave.complex.tracks = (uint16_t*) pMalloc( | ||||
sizeof(uint16_t) * | ||||
track->events[i].wave.complex.trackCount | ||||
); | ||||
track->events[i].wave.complex.wavebanks = (uint8_t*) pMalloc( | ||||
sizeof(uint8_t) * | ||||
track->events[i].wave.complex.trackCount | ||||
); | ||||
track->events[i].wave.complex.weights = (uint8_t*) pMalloc( | ||||
sizeof(uint8_t) * | ||||
track->events[i].wave.complex.trackCount | ||||
); | ||||
for (j = 0; j < track->events[i].wave.complex.trackCount; j += 1) | ||||
{ | ||||
track->events[i].wave.complex.tracks[j] = read_u16(ptr, se); | ||||
track->events[i].wave.complex.wavebanks[j] = read_u8(ptr, se); | ||||
minWeight = read_u8(ptr, se); | ||||
maxWeight = read_u8(ptr, se); | ||||
track->events[i].wave.complex.weights[j] = ( | ||||
maxWeight - minWeight | ||||
); | ||||
} | ||||
} | ||||
else if ( EVTTYPE(FACTEVENT_PITCH) || | ||||
EVTTYPE(FACTEVENT_VOLUME) || | ||||
EVTTYPE(FACTEVENT_PITCHREPEATING) || | ||||
EVTTYPE(FACTEVENT_VOLUMEREPEATING) ) | ||||
{ | ||||
track->events[i].value.settings = read_u8(ptr, se); | ||||
if (track->events[i].value.settings & 1) /* Ramp */ | ||||
{ | ||||
track->events[i].value.repeats = 0; | ||||
track->events[i].value.ramp.initialValue = read_f32(ptr, se); | ||||
track->events[i].value.ramp.initialSlope = read_f32(ptr, se) * 100; | ||||
track->events[i].value.ramp.slopeDelta = read_f32(ptr, se); | ||||
track->events[i].value.ramp.duration = read_u16(ptr, se); | ||||
} | ||||
else /* Equation */ | ||||
{ | ||||
track->events[i].value.equation.flags = read_u8(ptr, se); | ||||
/* SetValue, SetRandomValue, anything else? */ | ||||
FAudio_assert(track->events[i].value.equation.flags & 0x0C); | ||||
track->events[i].value.equation.value1 = read_f32(ptr, se); | ||||
track->events[i].value.equation.value2 = read_f32(ptr, se); | ||||
*ptr += 5; /* Unknown values */ | ||||
if ( EVTTYPE(FACTEVENT_PITCHREPEATING) || | ||||
EVTTYPE(FACTEVENT_VOLUMEREPEATING) ) | ||||
{ | ||||
track->events[i].value.repeats = read_u16(ptr, se); | ||||
track->events[i].value.frequency = read_u16(ptr, se); | ||||
} | ||||
else | ||||
{ | ||||
track->events[i].value.repeats = 0; | ||||
} | ||||
} | ||||
} | ||||
else if (EVTTYPE(FACTEVENT_MARKER)) | ||||
{ | ||||
track->events[i].marker.marker = read_u32(ptr, se); | ||||
track->events[i].marker.repeats = 0; | ||||
track->events[i].marker.frequency = 0; | ||||
} | ||||
else if (EVTTYPE(FACTEVENT_MARKERREPEATING)) | ||||
{ | ||||
track->events[i].marker.marker = read_u32(ptr, se); | ||||
track->events[i].marker.repeats = read_u16(ptr, se); | ||||
track->events[i].marker.frequency = read_u16(ptr, se); | ||||
} | ||||
else | ||||
{ | ||||
FAudio_assert(0 && "Unknown event type!"); | ||||
} | ||||
#undef EVTTYPE | ||||
} | ||||
} | ||||
uint32_t FACT_INTERNAL_ParseSoundBank( | ||||
FACTAudioEngine *pEngine, | ||||
const void *pvBuffer, | ||||
uint32_t dwSize, | ||||
FACTSoundBank **ppSoundBank | ||||
) { | ||||
FACTSoundBank *sb; | ||||
uint16_t cueSimpleCount, | ||||
cueComplexCount, | ||||
cueTotalAlign; | ||||
int32_t cueSimpleOffset, | ||||
cueComplexOffset, | ||||
cueNameOffset, | ||||
variationOffset, | ||||
transitionOffset, | ||||
wavebankNameOffset, | ||||
cueHashOffset, | ||||
cueNameIndexOffset, | ||||
soundOffset; | ||||
uint8_t platform; | ||||
size_t memsize; | ||||
uint16_t i, j, cur, tool; | ||||
uint8_t *ptrBookmark; | ||||
uint8_t *ptr = (uint8_t*) pvBuffer; | ||||
uint8_t *start = ptr; | ||||
uint32_t magic = read_u32(&ptr, 0); | ||||
uint8_t se = magic == 0x5344424B; /* Swap Endian */ | ||||
if (magic != 0x4B424453 && magic != 0x5344424B) /* 'SDBK' */ | ||||
{ | ||||
return -1; /* TODO: NOT XACT FILE */ | ||||
} | ||||
if (!FACT_INTERNAL_SupportedContent(read_u16(&ptr, se))) | ||||
{ | ||||
return -2; | ||||
} | ||||
tool = read_u16(&ptr, se); /* Tool version */ | ||||
if (tool != 43) | ||||
{ | ||||
return -3; | ||||
} | ||||
/* CRC, unused */ | ||||
ptr += 2; | ||||
/* Last modified, unused */ | ||||
ptr += 8; | ||||
/* Windows == 1, Xbox == 3 */ | ||||
platform = read_u8(&ptr, se); | ||||
if ( platform != 1 && | ||||
platform != 3 ) | ||||
{ | ||||
return -1; /* TODO: WRONG PLATFORM */ | ||||
} | ||||
sb = (FACTSoundBank*) pEngine->pMalloc(sizeof(FACTSoundBank)); | ||||
sb->parentEngine = pEngine; | ||||
sb->cueList = NULL; | ||||
sb->notifyOnDestroy = 0; | ||||
sb->usercontext = NULL; | ||||
cueSimpleCount = read_u16(&ptr, se); | ||||
cueComplexCount = read_u16(&ptr, se); | ||||
ptr += 2; /* Unknown value */ | ||||
cueTotalAlign = read_u16(&ptr, se); /* FIXME: Why? */ | ||||
sb->cueCount = cueSimpleCount + cueComplexCount; | ||||
sb->wavebankCount = read_u8(&ptr, se); | ||||
sb->soundCount = read_u16(&ptr, se); | ||||
/* Cue name length, unused */ | ||||
ptr += 2; | ||||
ptr += 2; /* Unknown value */ | ||||
cueSimpleOffset = read_s32(&ptr, se); | ||||
cueComplexOffset = read_s32(&ptr, se); | ||||
cueNameOffset = read_s32(&ptr, se); | ||||
ptr += 4; /* Unknown value */ | ||||
variationOffset = read_s32(&ptr, se); | ||||
transitionOffset = read_s32(&ptr, se); | ||||
wavebankNameOffset = read_s32(&ptr, se); | ||||
cueHashOffset = read_s32(&ptr, se); | ||||
cueNameIndexOffset = read_s32(&ptr, se); | ||||
soundOffset = read_s32(&ptr, se); | ||||
/* SoundBank Name */ | ||||
memsize = FAudio_strlen((char*) ptr) + 1; /* Dastardly! */ | ||||
sb->name = (char*) pEngine->pMalloc(memsize); | ||||
FAudio_memcpy(sb->name, ptr, memsize); | ||||
ptr += 64; | ||||
/* WaveBank Name data */ | ||||
FAudio_assert((ptr - start) == wavebankNameOffset); | ||||
sb->wavebankNames = (char**) pEngine->pMalloc( | ||||
sizeof(char*) * | ||||
sb->wavebankCount | ||||
); | ||||
for (i = 0; i < sb->wavebankCount; i += 1) | ||||
{ | ||||
memsize = FAudio_strlen((char*) ptr) + 1; | ||||
sb->wavebankNames[i] = (char*) pEngine->pMalloc(memsize); | ||||
FAudio_memcpy(sb->wavebankNames[i], ptr, memsize); | ||||
ptr += 64; | ||||
} | ||||
/* Sound data */ | ||||
FAudio_assert((ptr - start) == soundOffset); | ||||
sb->sounds = (FACTSound*) pEngine->pMalloc( | ||||
sizeof(FACTSound) * | ||||
sb->soundCount | ||||
); | ||||
sb->soundCodes = (uint32_t*) pEngine->pMalloc( | ||||
sizeof(uint32_t) * | ||||
sb->soundCount | ||||
); | ||||
for (i = 0; i < sb->soundCount; i += 1) | ||||
{ | ||||
sb->soundCodes[i] = (uint32_t) (ptr - start); | ||||
sb->sounds[i].flags = read_u8(&ptr, se); | ||||
sb->sounds[i].category = read_u16(&ptr, se); | ||||
sb->sounds[i].volume = read_volbyte(&ptr, se); | ||||
sb->sounds[i].pitch = read_s16(&ptr, se); | ||||
sb->sounds[i].priority = read_u8(&ptr, se); | ||||
/* Length of sound entry, unused */ | ||||
ptr += 2; | ||||
/* Simple/Complex Track data */ | ||||
if (sb->sounds[i].flags & 0x01) | ||||
{ | ||||
sb->sounds[i].trackCount = read_u8(&ptr, se); | ||||
memsize = sizeof(FACTTrack) * sb->sounds[i].trackCount; | ||||
sb->sounds[i].tracks = (FACTTrack*) pEngine->pMalloc(memsize); | ||||
FAudio_zero(sb->sounds[i].tracks, memsize); | ||||
} | ||||
else | ||||
{ | ||||
sb->sounds[i].trackCount = 1; | ||||
memsize = sizeof(FACTTrack) * sb->sounds[i].trackCount; | ||||
sb->sounds[i].tracks = (FACTTrack*) pEngine->pMalloc(memsize); | ||||
FAudio_zero(sb->sounds[i].tracks, memsize); | ||||
sb->sounds[i].tracks[0].volume = 0.0f; | ||||
sb->sounds[i].tracks[0].filter = 0xFF; | ||||
sb->sounds[i].tracks[0].eventCount = 1; | ||||
sb->sounds[i].tracks[0].events = (FACTEvent*) pEngine->pMalloc( | ||||
sizeof(FACTEvent) | ||||
); | ||||
FAudio_zero( | ||||
sb->sounds[i].tracks[0].events, | ||||
sizeof(FACTEvent) | ||||
); | ||||
sb->sounds[i].tracks[0].events[0].type = FACTEVENT_PLAYWAVE; | ||||
sb->sounds[i].tracks[0].events[0].wave.position = 0; /* FIXME */ | ||||
sb->sounds[i].tracks[0].events[0].wave.angle = 0; /* FIXME */ | ||||
sb->sounds[i].tracks[0].events[0].wave.simple.track = read_u16(&ptr, se); | ||||
sb->sounds[i].tracks[0].events[0].wave.simple.wavebank = read_u8(&ptr, se); | ||||
} | ||||
/* RPC Code data */ | ||||
if (sb->sounds[i].flags & 0x0E) | ||||
{ | ||||
const uint16_t rpcDataLength = read_u16(&ptr, se); | ||||
ptrBookmark = ptr - 2; | ||||
#define COPYRPCBLOCK(loc) \ | ||||
loc.rpcCodeCount = read_u8(&ptr, se); \ | ||||
memsize = sizeof(uint32_t) * loc.rpcCodeCount; \ | ||||
loc.rpcCodes = (uint32_t*) pEngine->pMalloc(memsize); \ | ||||
FAudio_memcpy(loc.rpcCodes, ptr, memsize); \ | ||||
ptr += memsize; | ||||
/* Sound has attached RPCs */ | ||||
if (sb->sounds[i].flags & 0x02) | ||||
{ | ||||
COPYRPCBLOCK(sb->sounds[i]) | ||||
} | ||||
else | ||||
{ | ||||
sb->sounds[i].rpcCodeCount = 0; | ||||
sb->sounds[i].rpcCodes = NULL; | ||||
} | ||||
/* Tracks have attached RPCs */ | ||||
if (sb->sounds[i].flags & 0x04) | ||||
{ | ||||
for (j = 0; j < sb->sounds[i].trackCount; j += 1) | ||||
{ | ||||
COPYRPCBLOCK(sb->sounds[i].tracks[j]) | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
for (j = 0; j < sb->sounds[i].trackCount; j += 1) | ||||
{ | ||||
sb->sounds[i].tracks[j].rpcCodeCount = 0; | ||||
sb->sounds[i].tracks[j].rpcCodes = NULL; | ||||
} | ||||
} | ||||
#undef COPYRPCBLOCK | ||||
/* FIXME: Does 0x08 mean something for RPCs...? */ | ||||
FAudio_assert((ptr - ptrBookmark) == rpcDataLength); | ||||
} | ||||
else | ||||
{ | ||||
sb->sounds[i].rpcCodeCount = 0; | ||||
sb->sounds[i].rpcCodes = NULL; | ||||
for (j = 0; j < sb->sounds[i].trackCount; j += 1) | ||||
{ | ||||
sb->sounds[i].tracks[j].rpcCodeCount = 0; | ||||
sb->sounds[i].tracks[j].rpcCodes = NULL; | ||||
} | ||||
} | ||||
/* DSP Preset Code data */ | ||||
if (sb->sounds[i].flags & 0x10) | ||||
{ | ||||
/* DSP presets length, unused */ | ||||
ptr += 2; | ||||
sb->sounds[i].dspCodeCount = read_u8(&ptr, se); | ||||
memsize = sizeof(uint32_t) * sb->sounds[i].dspCodeCount; | ||||
sb->sounds[i].dspCodes = (uint32_t*) pEngine->pMalloc(memsize); | ||||
FAudio_memcpy(sb->sounds[i].dspCodes, ptr, memsize); | ||||
ptr += memsize; | ||||
} | ||||
else | ||||
{ | ||||
sb->sounds[i].dspCodeCount = 0; | ||||
sb->sounds[i].dspCodes = NULL; | ||||
} | ||||
/* Track data */ | ||||
if (sb->sounds[i].flags & 0x01) | ||||
{ | ||||
for (j = 0; j < sb->sounds[i].trackCount; j += 1) | ||||
{ | ||||
sb->sounds[i].tracks[j].volume = read_volbyte(&ptr, se); | ||||
sb->sounds[i].tracks[j].code = read_u32(&ptr, se); | ||||
sb->sounds[i].tracks[j].filter = read_u8(&ptr, se); | ||||
if (sb->sounds[i].tracks[j].filter & 0x01) | ||||
{ | ||||
sb->sounds[i].tracks[j].filter = | ||||
(sb->sounds[i].tracks[j].filter >> 1) & 0x02; | ||||
} | ||||
else | ||||
{ | ||||
/* Huh...? */ | ||||
sb->sounds[i].tracks[j].filter = 0xFF; | ||||
} | ||||
sb->sounds[i].tracks[j].qfactor = read_u8(&ptr, se); | ||||
sb->sounds[i].tracks[j].frequency = read_u16(&ptr, se); | ||||
} | ||||
/* All Track events are stored at the end of the block */ | ||||
for (j = 0; j < sb->sounds[i].trackCount; j += 1) | ||||
{ | ||||
FAudio_assert((ptr - start) == sb->sounds[i].tracks[j].code); | ||||
FACT_INTERNAL_ParseTrackEvents( | ||||
&ptr, | ||||
se, | ||||
&sb->sounds[i].tracks[j], | ||||
pEngine->pMalloc | ||||
); | ||||
} | ||||
} | ||||
} | ||||
/* All Cue data */ | ||||
sb->variationCount = 0; | ||||
sb->transitionCount = 0; | ||||
sb->cues = (FACTCueData*) pEngine->pMalloc( | ||||
sizeof(FACTCueData) * | ||||
sb->cueCount | ||||
); | ||||
cur = 0; | ||||
/* Simple Cue data */ | ||||
FAudio_assert(cueSimpleCount == 0 || (ptr - start) == cueSimpleOffset); | ||||
for (i = 0; i < cueSimpleCount; i += 1, cur += 1) | ||||
{ | ||||
sb->cues[cur].flags = read_u8(&ptr, se); | ||||
sb->cues[cur].sbCode = read_u32(&ptr, se); | ||||
sb->cues[cur].transitionOffset = 0; | ||||
sb->cues[cur].instanceLimit = 0xFF; | ||||
sb->cues[cur].fadeInMS = 0; | ||||
sb->cues[cur].fadeOutMS = 0; | ||||
sb->cues[cur].maxInstanceBehavior = 0; | ||||
sb->cues[cur].instanceCount = 0; | ||||
} | ||||
/* Complex Cue data */ | ||||
FAudio_assert(cueComplexCount == 0 || (ptr - start) == cueComplexOffset); | ||||
for (i = 0; i < cueComplexCount; i += 1, cur += 1) | ||||
{ | ||||
sb->cues[cur].flags = read_u8(&ptr, se); | ||||
sb->cues[cur].sbCode = read_u32(&ptr, se); | ||||
sb->cues[cur].transitionOffset = read_u32(&ptr, se); | ||||
if (sb->cues[cur].transitionOffset == 0xFFFFFFFF) | ||||
{ | ||||
/* FIXME: Why */ | ||||
sb->cues[cur].transitionOffset = 0; | ||||
} | ||||
sb->cues[cur].instanceLimit = read_u8(&ptr, se); | ||||
sb->cues[cur].fadeInMS = read_u16(&ptr, se); | ||||
sb->cues[cur].fadeOutMS = read_u16(&ptr, se); | ||||
sb->cues[cur].maxInstanceBehavior = read_u8(&ptr, se) >> 3; | ||||
sb->cues[cur].instanceCount = 0; | ||||
if (!(sb->cues[cur].flags & 0x04)) | ||||
{ | ||||
/* FIXME: Is this the only way to get this...? */ | ||||
sb->variationCount += 1; | ||||
} | ||||
if (sb->cues[cur].transitionOffset > 0) | ||||
{ | ||||
/* FIXME: Is this the only way to get this...? */ | ||||
sb->transitionCount += 1; | ||||
} | ||||
} | ||||
/* Variation data */ | ||||
if (sb->variationCount > 0) | ||||
{ | ||||
FAudio_assert((ptr - start) == variationOffset); | ||||
sb->variations = (FACTVariationTable*) pEngine->pMalloc( | ||||
sizeof(FACTVariationTable) * | ||||
sb->variationCount | ||||
); | ||||
sb->variationCodes = (uint32_t*) pEngine->pMalloc( | ||||
sizeof(uint32_t) * | ||||
sb->variationCount | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
sb->variations = NULL; | ||||
sb->variationCodes = NULL; | ||||
} | ||||
for (i = 0; i < sb->variationCount; i += 1) | ||||
{ | ||||
sb->variationCodes[i] = (uint32_t) (ptr - start); | ||||
sb->variations[i].entryCount = read_u16(&ptr, se); | ||||
sb->variations[i].flags = (read_u16(&ptr, se) >> 3) & 0x07; | ||||
ptr += 2; /* Unknown value */ | ||||
sb->variations[i].variable = read_s16(&ptr, se); | ||||
memsize = sizeof(FACTVariation) * sb->variations[i].entryCount; | ||||
sb->variations[i].entries = (FACTVariation*) pEngine->pMalloc( | ||||
memsize | ||||
); | ||||
FAudio_zero(sb->variations[i].entries, memsize); | ||||
if (sb->variations[i].flags == 0) | ||||
{ | ||||
/* Wave with byte min/max */ | ||||
sb->variations[i].isComplex = 0; | ||||
for (j = 0; j < sb->variations[i].entryCount; j += 1) | ||||
{ | ||||
sb->variations[i].entries[j].simple.track = read_u16(&ptr, se); | ||||
sb->variations[i].entries[j].simple.wavebank = read_u8(&ptr, se); | ||||
sb->variations[i].entries[j].minWeight = read_u8(&ptr, se) / 255.0f; | ||||
sb->variations[i].entries[j].maxWeight = read_u8(&ptr, se) / 255.0f; | ||||
} | ||||
} | ||||
else if (sb->variations[i].flags == 1) | ||||
{ | ||||
/* Complex with byte min/max */ | ||||
sb->variations[i].isComplex = 1; | ||||
for (j = 0; j < sb->variations[i].entryCount; j += 1) | ||||
{ | ||||
sb->variations[i].entries[j].soundCode = read_u32(&ptr, se); | ||||
sb->variations[i].entries[j].minWeight = read_u8(&ptr, se) / 255.0f; | ||||
sb->variations[i].entries[j].maxWeight = read_u8(&ptr, se) / 255.0f; | ||||
} | ||||
} | ||||
else if (sb->variations[i].flags == 3) | ||||
{ | ||||
/* Complex Interactive Variation with float min/max */ | ||||
sb->variations[i].isComplex = 1; | ||||
for (j = 0; j < sb->variations[i].entryCount; j += 1) | ||||
{ | ||||
sb->variations[i].entries[j].soundCode = read_u32(&ptr, se); | ||||
sb->variations[i].entries[j].minWeight = read_f32(&ptr, se); | ||||
sb->variations[i].entries[j].maxWeight = read_f32(&ptr, se); | ||||
sb->variations[i].entries[j].linger = read_u32(&ptr, se); | ||||
} | ||||
} | ||||
else if (sb->variations[i].flags == 4) | ||||
{ | ||||
/* Compact Wave */ | ||||
sb->variations[i].isComplex = 0; | ||||
for (j = 0; j < sb->variations[i].entryCount; j += 1) | ||||
{ | ||||
sb->variations[i].entries[j].simple.track = read_u16(&ptr, se); | ||||
sb->variations[i].entries[j].simple.wavebank = read_u8(&ptr, se); | ||||
sb->variations[i].entries[j].minWeight = 0.0f; | ||||
sb->variations[i].entries[j].maxWeight = 1.0f; | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
FAudio_assert(0 && "Unknown variation type!"); | ||||
} | ||||
} | ||||
/* Transition data */ | ||||
if (sb->transitionCount > 0) | ||||
{ | ||||
FAudio_assert((ptr - start) == transitionOffset); | ||||
sb->transitions = (FACTTransitionTable*) pEngine->pMalloc( | ||||
sizeof(FACTTransitionTable) * | ||||
sb->transitionCount | ||||
); | ||||
sb->transitionCodes = (uint32_t*) pEngine->pMalloc( | ||||
sizeof(uint32_t) * | ||||
sb->transitionCount | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
sb->transitions = NULL; | ||||
sb->transitionCodes = NULL; | ||||
} | ||||
for (i = 0; i < sb->transitionCount; i += 1) | ||||
{ | ||||
sb->transitionCodes[i] = (uint32_t) (ptr - start); | ||||
sb->transitions[i].entryCount = read_u32(&ptr, se); | ||||
memsize = sizeof(FACTTransition) * sb->transitions[i].entryCount; | ||||
sb->transitions[i].entries = (FACTTransition*) pEngine->pMalloc( | ||||
memsize | ||||
); | ||||
FAudio_zero(sb->transitions[i].entries, memsize); | ||||
for (j = 0; j < sb->transitions[i].entryCount; j += 1) | ||||
{ | ||||
sb->transitions[i].entries[j].soundCode = read_s32(&ptr, se); | ||||
sb->transitions[i].entries[j].srcMarkerMin = read_u32(&ptr, se); | ||||
sb->transitions[i].entries[j].srcMarkerMax = read_u32(&ptr, se); | ||||
sb->transitions[i].entries[j].dstMarkerMin = read_u32(&ptr, se); | ||||
sb->transitions[i].entries[j].dstMarkerMax = read_u32(&ptr, se); | ||||
sb->transitions[i].entries[j].fadeIn = read_u16(&ptr, se); | ||||
sb->transitions[i].entries[j].fadeOut = read_u16(&ptr, se); | ||||
sb->transitions[i].entries[j].flags = read_u16(&ptr, se); | ||||
} | ||||
} | ||||
/* Cue Hash data? No idea what this is... */ | ||||
FAudio_assert((ptr - start) == cueHashOffset); | ||||
ptr += 2 * cueTotalAlign; | ||||
/* Cue Name Index data */ | ||||
FAudio_assert((ptr - start) == cueNameIndexOffset); | ||||
ptr += 6 * sb->cueCount; /* FIXME: index as assert value? */ | ||||
/* Cue Name data */ | ||||
FAudio_assert((ptr - start) == cueNameOffset); | ||||
sb->cueNames = (char**) pEngine->pMalloc( | ||||
sizeof(char*) * | ||||
sb->cueCount | ||||
); | ||||
for (i = 0; i < sb->cueCount; i += 1) | ||||
{ | ||||
memsize = FAudio_strlen((char*) ptr) + 1; | ||||
sb->cueNames[i] = (char*) pEngine->pMalloc(memsize); | ||||
FAudio_memcpy(sb->cueNames[i], ptr, memsize); | ||||
ptr += memsize; | ||||
} | ||||
/* Add to the Engine SoundBank list */ | ||||
LinkedList_AddEntry( | ||||
&pEngine->sbList, | ||||
sb, | ||||
pEngine->sbLock, | ||||
pEngine->pMalloc | ||||
); | ||||
/* Finally. */ | ||||
FAudio_assert((ptr - start) == dwSize); | ||||
*ppSoundBank = sb; | ||||
return 0; | ||||
} | ||||
/* This parser is based on the unxwb project, written by Luigi Auriemma. | ||||
* | ||||
* While the unxwb project was released under the GPL, Luigi has given us | ||||
* permission to use the unxwb sources under the zlib license. | ||||
* | ||||
* The unxwb website can be found here: | ||||
* | ||||
* http://aluigi.altervista.org/papers.htm#xbox | ||||
*/ | ||||
uint32_t FACT_INTERNAL_ParseWaveBank( | ||||
FACTAudioEngine *pEngine, | ||||
void* io, | ||||
uint32_t offset, | ||||
uint32_t packetSize, | ||||
FACTReadFileCallback pRead, | ||||
FACTGetOverlappedResultCallback pOverlap, | ||||
uint16_t isStreaming, | ||||
FACTWaveBank **ppWaveBank | ||||
) { | ||||
uint8_t se = 0; /* Swap Endian */ | ||||
FACTWaveBank *wb; | ||||
size_t memsize; | ||||
uint32_t i, j; | ||||
FACTWaveBankHeader header; | ||||
FACTWaveBankData wbinfo; | ||||
uint32_t compactEntry; | ||||
int32_t seekTableOffset; | ||||
uint32_t fileOffset; | ||||
uint8_t *packetBuffer = NULL; | ||||
uint32_t packetBufferLen = 0; | ||||
#define SEEKSET(loc) \ | ||||
fileOffset = offset + loc; | ||||
#define SEEKCUR(loc) \ | ||||
fileOffset += loc; | ||||
#define READ(dst, size) \ | ||||
FACT_INTERNAL_ReadFile( \ | ||||
pRead, \ | ||||
pOverlap, \ | ||||
io, \ | ||||
fileOffset, \ | ||||
packetSize, \ | ||||
&packetBuffer, \ | ||||
&packetBufferLen, \ | ||||
pEngine->pRealloc, \ | ||||
dst, \ | ||||
size \ | ||||
); \ | ||||
SEEKCUR(size) | ||||
fileOffset = offset; | ||||
READ(&header, sizeof(header)) | ||||
se = header.dwSignature == 0x57424E44; | ||||
if (se) | ||||
{ | ||||
DOSWAP_32(header.dwSignature); | ||||
DOSWAP_32(header.dwVersion); | ||||
DOSWAP_32(header.dwHeaderVersion); | ||||
for (i = 0; i < FACT_WAVEBANK_SEGIDX_COUNT; i += 1) | ||||
{ | ||||
DOSWAP_32(header.Segments[i].dwOffset); | ||||
DOSWAP_32(header.Segments[i].dwLength); | ||||
} | ||||
} | ||||
if (header.dwSignature != 0x444E4257) | ||||
{ | ||||
return -1; /* TODO: NOT XACT FILE */ | ||||
} | ||||
if (!FACT_INTERNAL_SupportedContent(header.dwVersion)) | ||||
{ | ||||
return -2; | ||||
} | ||||
if (!FACT_INTERNAL_SupportedWBContent(header.dwHeaderVersion)) | ||||
{ | ||||
return -3; | ||||
} | ||||
wb = (FACTWaveBank*) pEngine->pMalloc(sizeof(FACTWaveBank)); | ||||
wb->parentEngine = pEngine; | ||||
wb->waveList = NULL; | ||||
wb->waveLock = FAudio_PlatformCreateMutex(); | ||||
wb->packetSize = packetSize; | ||||
wb->io = io; | ||||
wb->notifyOnDestroy = 0; | ||||
wb->usercontext = NULL; | ||||
/* WaveBank Data */ | ||||
SEEKSET(header.Segments[FACT_WAVEBANK_SEGIDX_BANKDATA].dwOffset) | ||||
READ(&wbinfo, sizeof(wbinfo)) | ||||
if (se) | ||||
{ | ||||
DOSWAP_32(wbinfo.dwFlags); | ||||
DOSWAP_32(wbinfo.dwEntryCount); | ||||
DOSWAP_32(wbinfo.dwEntryMetaDataElementSize); | ||||
DOSWAP_32(wbinfo.dwEntryNameElementSize); | ||||
DOSWAP_32(wbinfo.dwAlignment); | ||||
DOSWAP_32(wbinfo.CompactFormat.dwValue); | ||||
DOSWAP_64(wbinfo.BuildTime); | ||||
} | ||||
wb->streaming = (wbinfo.dwFlags & FACT_WAVEBANK_TYPE_STREAMING); | ||||
wb->entryCount = wbinfo.dwEntryCount; | ||||
memsize = FAudio_strlen(wbinfo.szBankName) + 1; | ||||
wb->name = (char*) pEngine->pMalloc(memsize); | ||||
FAudio_memcpy(wb->name, wbinfo.szBankName, memsize); | ||||
memsize = sizeof(FACTWaveBankEntry) * wbinfo.dwEntryCount; | ||||
wb->entries = (FACTWaveBankEntry*) pEngine->pMalloc(memsize); | ||||
FAudio_zero(wb->entries, memsize); | ||||
memsize = sizeof(uint32_t) * wbinfo.dwEntryCount; | ||||
wb->entryRefs = (uint32_t*) pEngine->pMalloc(memsize); | ||||
FAudio_zero(wb->entryRefs, memsize); | ||||
/* FIXME: How much do we care about this? */ | ||||
FAudio_assert(wb->streaming == isStreaming); | ||||
wb->streaming = isStreaming; | ||||
/* WaveBank Entry Metadata */ | ||||
SEEKSET(header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYMETADATA].dwOffset) | ||||
if (wbinfo.dwFlags & FACT_WAVEBANK_FLAGS_COMPACT) | ||||
{ | ||||
for (i = 0; i < wbinfo.dwEntryCount - 1; i += 1) | ||||
{ | ||||
READ(&compactEntry, sizeof(compactEntry)) | ||||
if (se) | ||||
{ | ||||
DOSWAP_32(compactEntry); | ||||
} | ||||
wb->entries[i].PlayRegion.dwOffset = ( | ||||
(compactEntry & ((1 << 21) - 1)) * | ||||
wbinfo.dwAlignment | ||||
); | ||||
wb->entries[i].PlayRegion.dwLength = ( | ||||
(compactEntry >> 21) & ((1 << 11) - 1) | ||||
); | ||||
/* TODO: Deviation table */ | ||||
SEEKCUR(wbinfo.dwEntryMetaDataElementSize) | ||||
wb->entries[i].PlayRegion.dwLength = ( | ||||
(compactEntry & ((1 << 21) - 1)) * | ||||
wbinfo.dwAlignment | ||||
) - wb->entries[i].PlayRegion.dwOffset; | ||||
wb->entries[i].PlayRegion.dwOffset += | ||||
header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYWAVEDATA].dwOffset; | ||||
} | ||||
READ(&compactEntry, sizeof(compactEntry)) | ||||
if (se) | ||||
{ | ||||
DOSWAP_32(compactEntry); | ||||
} | ||||
wb->entries[i].PlayRegion.dwOffset = ( | ||||
(compactEntry & ((1 << 21) - 1)) * | ||||
wbinfo.dwAlignment | ||||
); | ||||
/* TODO: Deviation table */ | ||||
SEEKCUR(wbinfo.dwEntryMetaDataElementSize) | ||||
wb->entries[i].PlayRegion.dwLength = ( | ||||
header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYWAVEDATA].dwLength - | ||||
wb->entries[i].PlayRegion.dwOffset | ||||
); | ||||
wb->entries[i].PlayRegion.dwOffset += | ||||
header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYWAVEDATA].dwOffset; | ||||
} | ||||
else | ||||
{ | ||||
for (i = 0; i < wbinfo.dwEntryCount; i += 1) | ||||
{ | ||||
READ(&wb->entries[i], wbinfo.dwEntryMetaDataElementSize) | ||||
if (se) | ||||
{ | ||||
DOSWAP_32(wb->entries[i].dwFlagsAndDuration); | ||||
DOSWAP_32(wb->entries[i].Format.dwValue); | ||||
DOSWAP_32(wb->entries[i].PlayRegion.dwOffset); | ||||
DOSWAP_32(wb->entries[i].PlayRegion.dwLength); | ||||
DOSWAP_32(wb->entries[i].LoopRegion.dwStartSample); | ||||
DOSWAP_32(wb->entries[i].LoopRegion.dwTotalSamples); | ||||
} | ||||
wb->entries[i].PlayRegion.dwOffset += | ||||
header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYWAVEDATA].dwOffset; | ||||
} | ||||
/* FIXME: This is a bit hacky. */ | ||||
if (wbinfo.dwEntryMetaDataElementSize < 24) | ||||
{ | ||||
for (i = 0; i < wbinfo.dwEntryCount; i += 1) | ||||
{ | ||||
wb->entries[i].PlayRegion.dwLength = | ||||
header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYWAVEDATA].dwLength; | ||||
} | ||||
} | ||||
} | ||||
/* WaveBank Seek Tables */ | ||||
if ( wbinfo.dwFlags & FACT_WAVEBANK_FLAGS_SEEKTABLES && | ||||
header.Segments[FACT_WAVEBANK_SEGIDX_SEEKTABLES].dwLength > 0 ) | ||||
{ | ||||
/* The seek table data layout is an absolute disaster! */ | ||||
wb->seekTables = (FACTSeekTable*) pEngine->pMalloc( | ||||
wbinfo.dwEntryCount * sizeof(FACTSeekTable) | ||||
); | ||||
for (i = 0; i < wbinfo.dwEntryCount; i += 1) | ||||
{ | ||||
/* Get the table offset... */ | ||||
SEEKSET( | ||||
header.Segments[FACT_WAVEBANK_SEGIDX_SEEKTABLES].dwOffset + | ||||
i * sizeof(uint32_t) | ||||
) | ||||
READ(&seekTableOffset, sizeof(int32_t)) | ||||
if (se) | ||||
{ | ||||
DOSWAP_32(seekTableOffset); | ||||
} | ||||
/* If the offset is -1, this wave needs no table */ | ||||
if (seekTableOffset == -1) | ||||
{ | ||||
wb->seekTables[i].entryCount = 0; | ||||
wb->seekTables[i].entries = NULL; | ||||
continue; | ||||
} | ||||
/* Go to the table offset, after the offset table... */ | ||||
SEEKSET( | ||||
header.Segments[FACT_WAVEBANK_SEGIDX_SEEKTABLES].dwOffset + | ||||
(wbinfo.dwEntryCount * sizeof(uint32_t)) + | ||||
seekTableOffset | ||||
) | ||||
/* Read the table, finally. */ | ||||
READ(&wb->seekTables[i].entryCount, sizeof(uint32_t)) | ||||
if (se) | ||||
{ | ||||
DOSWAP_32(wb->seekTables[i].entryCount); | ||||
} | ||||
wb->seekTables[i].entries = (uint32_t*) pEngine->pMalloc( | ||||
wb->seekTables[i].entryCount * sizeof(uint32_t) | ||||
); | ||||
READ( | ||||
wb->seekTables[i].entries, | ||||
wb->seekTables[i].entryCount * sizeof(uint32_t) | ||||
) | ||||
if (se) | ||||
{ | ||||
for (j = 0; j < wb->seekTables[i].entryCount; j += 1) | ||||
{ | ||||
DOSWAP_32(wb->seekTables[i].entries[j]); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
wb->seekTables = NULL; | ||||
} | ||||
/* TODO: WaveBank Entry Names | ||||
if (wbinfo.dwFlags & FACT_WAVEBANK_FLAGS_ENTRYNAMES) | ||||
{ | ||||
SEEKSET(header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYNAMES].dwOffset) | ||||
} | ||||
*/ | ||||
/* Add to the Engine WaveBank list */ | ||||
LinkedList_AddEntry( | ||||
&pEngine->wbList, | ||||
wb, | ||||
pEngine->wbLock, | ||||
pEngine->pMalloc | ||||
); | ||||
/* Finally. */ | ||||
wb->packetBuffer = packetBuffer; | ||||
wb->packetBufferLen = packetBufferLen; | ||||
*ppWaveBank = wb; | ||||
return 0; | ||||
} | ||||
/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ | ||||