Skip to content

Commit

Permalink
Checkpoint: FAudioFX_Collector
Browse files Browse the repository at this point in the history
  • Loading branch information
kg committed Feb 7, 2025
1 parent af74e66 commit 7a375ad
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 1 deletion.
1 change: 1 addition & 0 deletions include/FAPO.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ typedef struct FAPOProcessBufferParameters
{
void* pBuffer;
FAPOBufferFlags BufferFlags;
// The number of per-channel frames (samples) in the buffer. The total size of the buffer in floats is (ValidFrameCount * ChannelCount).
uint32_t ValidFrameCount;
} FAPOProcessBufferParameters;

Expand Down
11 changes: 11 additions & 0 deletions include/FAudioFX.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,22 @@ typedef struct FAudioFXReverbI3DL2Parameters

/* Functions */

FAUDIOAPI uint32_t FAudioCreateCollector(FAPO** ppApo, uint32_t Flags, float* pBuffer, uint32_t bufferLength, void** pSampleCounter);
FAUDIOAPI uint32_t FAudioCreateVolumeMeter(FAPO** ppApo, uint32_t Flags);
FAUDIOAPI uint32_t FAudioCreateReverb(FAPO** ppApo, uint32_t Flags);
FAUDIOAPI uint32_t FAudioCreateReverb9(FAPO** ppApo, uint32_t Flags);

/* See "extensions/CustomAllocatorEXT.txt" for more information. */
FAUDIOAPI uint32_t FAudioCreateCollectorWithCustomAllocatorEXT(
FAPO** ppApo,
uint32_t Flags,
float* pBuffer,
uint32_t bufferLength,
void** pSampleCounter,
FAudioMallocFunc customMalloc,
FAudioFreeFunc customFree,
FAudioReallocFunc customRealloc
);
FAUDIOAPI uint32_t FAudioCreateVolumeMeterWithCustomAllocatorEXT(
FAPO** ppApo,
uint32_t Flags,
Expand Down
243 changes: 243 additions & 0 deletions src/FAudioFX_collector.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/* FAudio - XAudio Reimplementation for FNA
*
* Copyright (c) 2011-2025 Ethan Lee, Katelyn Gadd, and the FNA 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.
*
* Katelyn Gadd <[email protected]>
*
*/

#include "FAudioFX.h"
#include "FAudio_internal.h"
#include "SDL_atomic.h"

/* Sample Collector FAPO Implementation */

const FAudioGUID FAudioFX_CLSID_Collector = /* 2.7 */
{
0xCAC1105F,
0x619B,
0x4D04,
{
0x83,
0x1A,
0x44,
0xE1,
0xCB,
0xF1,
0x2D,
0x58
}
};

static FAPORegistrationProperties CollectorProperties =
{
/* .clsid = */ {0},
/* .FriendlyName = */
{
'C', 'o', 'l', 'l', 'e', 'c', 't', 'o', 'r', '\0'
},
/*.CopyrightInfo = */
{
'C', 'o', 'p', 'y', 'r', 'i', 'g', 'h', 't', ' ', '(', 'c', ')',
'K', 'a', 't', 'e', 'l', 'y', 'n', ' ', 'G', 'a', 'd', 'd', '\0'
},
/*.MajorVersion = */ 0,
/*.MinorVersion = */ 0,
/*.Flags = */(
FAPO_FLAG_CHANNELS_MUST_MATCH |
FAPO_FLAG_FRAMERATE_MUST_MATCH |
FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH |
FAPO_FLAG_BUFFERCOUNT_MUST_MATCH |
FAPO_FLAG_INPLACE_SUPPORTED |
FAPO_FLAG_INPLACE_REQUIRED
),
/*.MinInputBufferCount = */ 1,
/*.MaxInputBufferCount = */ 1,
/*.MinOutputBufferCount = */ 1,
/*.MaxOutputBufferCount =*/ 1
};

typedef struct FAudioFXCollector
{
FAPOBase base;
uint16_t channels;
float* pBuffer;
uint32_t bufferLength;
void** pSampleCounter;
} FAudioFXCollector;

uint32_t FAudioFXCollector_LockForProcess(
FAudioFXCollector*fapo,
uint32_t InputLockedParameterCount,
const FAPOLockForProcessBufferParameters *pInputLockedParameters,
uint32_t OutputLockedParameterCount,
const FAPOLockForProcessBufferParameters *pOutputLockedParameters
) {
/* Verify parameter counts... */
if ( InputLockedParameterCount < fapo->base.m_pRegistrationProperties->MinInputBufferCount ||
InputLockedParameterCount > fapo->base.m_pRegistrationProperties->MaxInputBufferCount ||
OutputLockedParameterCount < fapo->base.m_pRegistrationProperties->MinOutputBufferCount ||
OutputLockedParameterCount > fapo->base.m_pRegistrationProperties->MaxOutputBufferCount )
{
return FAUDIO_E_INVALID_ARG;
}


/* Validate input/output formats */
#define VERIFY_FORMAT_FLAG(flag, prop) \
if ( (fapo->base.m_pRegistrationProperties->Flags & flag) && \
(pInputLockedParameters->pFormat->prop != pOutputLockedParameters->pFormat->prop) ) \
{ \
return FAUDIO_E_INVALID_ARG; \
}
VERIFY_FORMAT_FLAG(FAPO_FLAG_CHANNELS_MUST_MATCH, nChannels)
VERIFY_FORMAT_FLAG(FAPO_FLAG_FRAMERATE_MUST_MATCH, nSamplesPerSec)
VERIFY_FORMAT_FLAG(FAPO_FLAG_BITSPERSAMPLE_MUST_MATCH, wBitsPerSample)
#undef VERIFY_FORMAT_FLAG
if ( (fapo->base.m_pRegistrationProperties->Flags & FAPO_FLAG_BUFFERCOUNT_MUST_MATCH) &&
(InputLockedParameterCount != OutputLockedParameterCount) )
{
return FAUDIO_E_INVALID_ARG;
}

fapo->channels = pInputLockedParameters->pFormat->nChannels;
fapo->base.m_fIsLocked = 1;
return 0;
}

void FAudioFXCollector_UnlockForProcess(FAudioFXCollector*fapo)
{
fapo->base.m_fIsLocked = 0;
}

void FAudioFXCollector_Process(
FAudioFXCollector* fapo,
uint32_t InputProcessParameterCount,
const FAPOProcessBufferParameters* pInputProcessParameters,
uint32_t OutputProcessParameterCount,
FAPOProcessBufferParameters* pOutputProcessParameters,
int32_t IsEnabled
) {
uint32_t channels = fapo->channels,
bufferLength = fapo->bufferLength,
sampleCount = pInputProcessParameters->ValidFrameCount;
// We're supposed to be the only one updating the value of the counter, but we're doing atomic writes so let's do atomic reads too.
uint64_t sampleCounter = (uint64_t)SDL_AtomicGetPtr(fapo->pSampleCounter);
// Wrap the initial write offset (we'll wrap it around when it hits the end too.)
uint32_t writeOffset = sampleCounter % bufferLength;
float* pBuffer = (float*)pInputProcessParameters->pBuffer;
float* pOutput = fapo->pBuffer;
float multiplier = 1.0f / channels;

for (uint32_t i = 0; i < sampleCount; i++) {
// Accumulate all channels and average them
float accumulator = 0;
for (uint32_t j = 0; j < channels; j++)
accumulator += pBuffer[j];
accumulator *= multiplier;

// Advance to next set of samples
pBuffer += channels;

// Write to output buffer
pOutput[writeOffset++] = accumulator;
// Wrap write offset
if (writeOffset >= bufferLength)
writeOffset = 0;
}

// Update the sample counter in one go. SDL's only atomic 64-bit write is AtomicSetPtr and it's pointer sized.
// We're the only code allowed to update this counter so we don't need to AtomicAdd or CAS, just atomic write to prevent tearing.
SDL_AtomicSetPtr(fapo->pSampleCounter, (void*)(sampleCounter + sampleCount));

FAPOBase_EndProcess(&fapo->base);
}

void FAudioFXCollector_Free(void* fapo)
{
FAudioFXCollector *collector = (FAudioFXCollector*) fapo;
collector->base.pFree(fapo);
}

/* Public API */

uint32_t FAudioCreateCollector(FAPO** ppApo, uint32_t Flags, float* pBuffer, uint32_t bufferLength, void** pSampleCounter)
{
return FAudioCreateCollectorWithCustomAllocatorEXT(
ppApo,
Flags,
pBuffer,
bufferLength,
pSampleCounter,
FAudio_malloc,
FAudio_free,
FAudio_realloc
);
}

FAUDIOAPI uint32_t FAudioCreateCollectorWithCustomAllocatorEXT(
FAPO** ppApo,
uint32_t Flags,
float* pBuffer,
uint32_t bufferLength,
void** pSampleCounter,
FAudioMallocFunc customMalloc,
FAudioFreeFunc customFree,
FAudioReallocFunc customRealloc
) {
/* Allocate... */
FAudioFXCollector *result = (FAudioFXCollector*) customMalloc(
sizeof(FAudioFXCollector)
);

/* Initialize... */
FAudio_memcpy(
&CollectorProperties.clsid,
&FAudioFX_CLSID_Collector,
sizeof(FAudioGUID)
);
CreateFAPOBaseWithCustomAllocatorEXT(
&result->base,
&CollectorProperties,
NULL,
0,
1,
customMalloc,
customFree,
customRealloc
);

/* Function table... */
result->base.base.LockForProcess = (LockForProcessFunc)
FAudioFXCollector_LockForProcess;
result->base.base.UnlockForProcess = (UnlockForProcessFunc)
FAudioFXCollector_UnlockForProcess;
result->base.base.Process = (ProcessFunc)
FAudioFXCollector_Process;
result->base.base.GetParameters = NULL;
result->base.Destructor = FAudioFXCollector_Free;

/* Finally. */
*ppApo = &result->base.base;
return 0;
}

/* vim: set noexpandtab shiftwidth=8 tabstop=8: */
3 changes: 2 additions & 1 deletion visualc/FAudio.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<ClCompile Include="..\src\FAudio_internal.c" />
<ClCompile Include="..\src\FAudio_internal_simd.c" />
<ClCompile Include="..\src\FAudio_operationset.c" />
<ClCompile Include="..\src\FAudioFX_collector.c" />
<ClCompile Include="..\src\FAudioFX_reverb.c" />
<ClCompile Include="..\src\FAudioFX_volumemeter.c" />
<ClCompile Include="..\src\FACT.c" />
Expand Down Expand Up @@ -105,4 +106,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

0 comments on commit 7a375ad

Please sign in to comment.