/* 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 * */ #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: */