Create() rework; comments tweaks; +hrtfapoapi

This commit is contained in:
Vincent Billet
2025-05-26 21:17:44 +02:00
parent ee132b39d4
commit b775b4a1f1
6 changed files with 301 additions and 235 deletions
+60 -97
View File
@@ -1,75 +1,45 @@
#+build windows
/* NOTES:
1. Definition of terms:
DSP: Digital Signal Processing.
DSP: Digital Signal Processing.
CBR: Constant BitRate -- DSP that consumes a constant number of input samples to produce an output sample.
input samples to produce an output sample.
For example, a 22kHz to 44kHz resampler is CBR DSP.
Even though the number of input to output samples differ,
the ratio between input to output rate remains constant.
All user-defined XAPOs are assumed to be CBR as
XAudio2 only allows CBR DSP to be added to an effect chain.
For example, a 22kHz to 44kHz resampler is CBR DSP. Even though the number of input to output samples differ, the ratio between input to output rate remains constant.
All user-defined XAPOs are assumed to be CBR as XAudio2 only allows CBR DSP to be added to an effect chain.
XAPO: Cross-platform Audio Processing Object --
a thin wrapper that manages DSP code, allowing it to be easily plugged into an XAudio2 effect chain.
to be easily plugged into an XAudio2 effect chain.
Frame: A block of samples, one per channel, to be played simultaneously.
to be played simultaneously.
E.g. a mono stream has one sample per frame.
E.g. a mono stream has one sample per frame.
In-Place: Processing such that the input buffer equals the output buffer (i.e. input data modified directly).
output buffer (i.e. input data modified directly).
This form of processing is generally more efficient
than using separate memory for input and output.
However, an XAPO may not perform format conversion
when processing in-place.
This form of processing is generally more efficient than using separate memory for input and output.
However, an XAPO may not perform format conversion when processing in-place.
2. XAPO member variables are divided into three classifications:
Immutable: Set once via IXAPO.Initialize and remain
constant during the lifespan of the XAPO.
Immutable: Set once via IXAPO.Initialize and remain constant during the lifespan of the XAPO.
Locked: May change before the XAPO is locked via
IXAPO.LockForProcess but remain constant until IXAPO.UnlockForProcess is called.
until IXAPO.UnlockForProcess is called.
Dynamic: May change from one processing pass to the next, usually via IXAPOParameters.SetParameters.
usually via IXAPOParameters.SetParameters.
XAPOs should assign reasonable defaults to their dynamic
variables during IXAPO.Initialize/LockForProcess so
that calling IXAPOParameters.SetParameters is not
required before processing begins.
XAPOs should assign reasonable defaults to their dynamic variables during IXAPO.Initialize/LockForProcess so that calling IXAPOParameters.SetParameters is not required before processing begins.
When implementing an XAPO, determine the type of each variable and initialize them in the appropriate method. Immutable variables are generally preferable over locked which are preferable over dynamic.
initialize them in the appropriate method. Immutable variables are
generally preferable over locked which are preferable over dynamic.
That is, one should strive to minimize XAPO state changes for
best performance, maintainability, and ease of use.
That is, one should strive to minimize XAPO state changes for best performance, maintainability, and ease of use.
3. To minimize glitches, the realtime audio processing thread must not block. XAPO methods called by the realtime thread are commented as non-blocking and therefore should not use blocking synchronization, allocate memory, access the disk, etc.
not block. XAPO methods called by the realtime thread are commented
as non-blocking and therefore should not use blocking synchronization,
allocate memory, access the disk, etc. The XAPO interfaces were
designed to allow an effect implementer to move such operations
into other methods called on an application controlled thread.
The XAPO interfaces were designed to allow an effect implementer to move such operations into other methods called on an application controlled thread.
4. Extending functionality is accomplished through the addition of new COM interfaces. For example, if a new member is added to a parameter structure, a new interface using the new structure should be added, leaving the original interface unchanged.
COM interfaces. For example, if a new member is added to a parameter
structure, a new interface using the new structure should be added,
leaving the original interface unchanged.
This ensures consistent communication between future versions of
XAudio2 and various versions of XAPOs that may exist in an application.
This ensures consistent communication between future versions of XAudio2 and various versions of XAPOs that may exist in an application.
5. All audio data is interleaved in XAudio2.
The default audio format for an effect chain is WAVE_FORMAT_IEEE_FLOAT.
The default audio format for an effect chain is WAVE_FORMAT_IEEE_FLOAT.
6. User-defined XAPOs should assume all input and output buffers are 16-byte aligned. */
16-byte aligned.
7. See XAPOBase.odin for an XAPO base class which provides a default
implementation for most of the interface methods defined below. */
package windows_xaudio2
import win "core:sys/windows"
@@ -77,8 +47,7 @@ import win "core:sys/windows"
//--------------<D-E-F-I-N-I-T-I-O-N-S>-------------------------------------//
FORMAT_UNSUPPORTED := win.MAKE_HRESULT(win.SEVERITY.ERROR, win.FACILITY.XAPO, 0x01) // requested audio format unsupported
FORMAT_UNSUPPORTED := win.MAKE_HRESULT(win.SEVERITY.ERROR, 0x897, 0x01) // requested audio format unsupported
// supported number of channels (samples per frame) range
XAPO_MIN_CHANNELS :: 1
XAPO_MAX_CHANNELS :: 64
@@ -111,11 +80,11 @@ XAPO_FLAG :: enum u32 {
// XAPO must be run in-place. Use this flag only if your DSP implementation cannot process separate input and output buffers.
// If set, the following flags must also be set:
// CHANNELS_MUST_MATCH
// XAPO_FLAG_FRAMERATE_MUST_MATCH
// XAPO_FLAG_BITSPERSAMPLE_MUST_MATCH
// XAPO_FLAG_BUFFERCOUNT_MUST_MATCH
// XAPO_FLAG_INPLACE_SUPPORTED
// Multiple input and output buffers may be used with in-place XAPOs, though the input buffer count must equal the output buffer count.
// FRAMERATE_MUST_MATCH
// BITSPERSAMPLE_MUST_MATCH
// BUFFERCOUNT_MUST_MATCH
// INPLACE_SUPPORTED
// Multiple input and output buffers may be used with in-place XAPOs, though the input buffer count must equal the output buffer count.
// When multiple input/output buffers are used, the XAPO may assume input buffer [N] equals output buffer [N] for in-place processing.
INPLACE_REQUIRED = 5,
@@ -139,18 +108,18 @@ XAPO_REGISTRATION_PROPERTIES :: struct #packed {
MinInputBufferCount: u32, // minimum number of input buffers required for processing, can be 0
MaxInputBufferCount: u32, // maximum number of input buffers supported for processing, must be >= MinInputBufferCount
MinOutputBufferCount: u32, // minimum number of output buffers required for processing, can be 0, must match MinInputBufferCount when XAPO_FLAG.BUFFERCOUNT_MUST_MATCH used
MaxOutputBufferCount: u32, // maximum number of output buffers supported for processing, must be >= MinOutputBufferCount, must match MaxInputBufferCount when XAPO_FLAG_BUFFERCOUNT_MUST_MATCH used
}
MaxOutputBufferCount: u32, // maximum number of output buffers supported for processing, must be >= MinOutputBufferCount, must match MaxInputBufferCount when XAPO_FLAG.BUFFERCOUNT_MUST_MATCH used
}
// LockForProcess buffer parameters:
// Defines buffer parameters that remain constant while an XAPO is locked.
// Used with IXAPO.LockForProcess.
// For CBR XAPOs, MaxFrameCount is the only number of frames
// For CBR XAPOs, MaxFrameCount is the only number of frames
// IXAPO.Process would have to handle for the respective buffer.
XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS :: struct #packed {
XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS :: struct #packed {
pFormat: ^WAVEFORMATEX, // buffer audio format
MaxFrameCount: u32, // maximum number of frames in respective buffer that IXAPO.Process would have to handle, irrespective of dynamic variable settings, can be 0
}
}
// Buffer flags:
// Describes assumed content of the respective buffer.
@@ -161,31 +130,25 @@ XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS :: struct #packed {
// The flags represent what should be assumed is in the respective buffer. The flags may not reflect what is actually stored in memory.
XAPO_BUFFER_FLAGS :: enum i32 {
SILENT, // silent data should be assumed, respective memory may be uninitialized
XAPO_BUFFER_VALID, // arbitrary data should be assumed (may or may not be silent frames), respective memory initialized
}
VALID, // arbitrary data should be assumed (may or may not be silent frames), respective memory initialized
}
// Process buffer parameters:
// Defines buffer parameters that may change from one processing pass to the next. Used with IXAPO.Process.
// processing pass to the next. Used with IXAPO::Process.
//
// Note the byte size of the respective buffer must be at least:
// Note the byte size of the respective buffer must be at least:
// XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount * XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.pFormat.nBlockAlign
//
// Although the audio format and maximum size of the respective
// buffer is locked (defined by XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS),
// the actual memory address of the buffer given is permitted to change
// from one processing pass to the next.
//
// For CBR XAPOs, ValidFrameCount is constant while locked and equals
// the respective XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount.
XAPO_PROCESS_BUFFER_PARAMETERS :: struct #packed {
// Although the audio format and maximum size of the respective buffer is locked (defined by XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS), the actual memory address of the buffer given is permitted to change from one processing pass to the next.
// For CBR XAPOs, ValidFrameCount is constant while locked and equals the respective XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount.
XAPO_PROCESS_BUFFER_PARAMETERS :: struct #packed {
pBuffer: rawptr, // audio data buffer, must be non-nil
BufferFlags: XAPO_BUFFER_FLAGS, // describes assumed content of pBuffer, does not affect ValidFrameCount
BufferFlags: XAPO_BUFFER_FLAGS, // describes assumed content of pBuffer, does not affect ValidFrameCount
ValidFrameCount: u32, // number of frames of valid data, must be within respective [0, XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount], always XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS.MaxFrameCount for CBR/user-defined XAPOs, does not affect BufferFlags
}
// Used by IXAPO methods that must allocate arbitrary sized structures such as WAVEFORMATEX that are subsequently returned to the application.
XAPOAlloc :: win.CoTaskMemAlloc
XAPOFree :: win.CoTaskMemFree
IXAPO_UUID_STRING :: "A410B984-9839-4819-A0BE-2856AE6B3ADB"
IXAPO_UUID := &win.IID{0xA410B984, 0x9839, 0x4819, {0xA0, 0xBE, 0x28, 0x56, 0xAE, 0x6B, 0x3A, 0xDB}}
IXAPO :: struct #raw_union {
@@ -214,11 +177,11 @@ IXAPO_VTable :: struct {
// pOutputFormat - [in] output format known to be supported
// pRequestedInputFormat - [in] input format to examine
// ppSupportedInputFormat - [out] receives pointer to nearest input format supported if not nil and input/output configuration unsupported, use XAPOFree to free structure, left untouched on any failure except FORMAT_UNSUPPORTED
// RETURN VALUE:
// RETURN VALUE:
// COM error code, including:
// S_OK - input/output configuration supported, ppSupportedInputFormat left untouched
// FORMAT_UNSUPPORTED - input/output configuration unsupported, ppSupportedInputFormat receives pointer to nearest input format supported if not nil
// E_INVALIDARG - either audio format invalid, ppSupportedInputFormat left untouched
// E_INVALIDARG - either audio format invalid, ppSupportedInputFormat left untouched
IsInputFormatSupported: proc "system" (this: ^IXAPO, pOutputFormat: ^WAVEFORMATEX, pRequestedInputFormat: ^WAVEFORMATEX, ppSupportedInputFormat: ^^WAVEFORMATEX) -> HRESULT,
// DESCRIPTION:
@@ -232,11 +195,11 @@ IXAPO_VTable :: struct {
// pInputFormat - [in] input format known to be supported
// pRequestedOutputFormat - [in] output format to examine
// ppSupportedOutputFormat - [out] receives pointer to nearest output format supported if not nil and input/output configuration unsupported, use XAPOFree to free structure, left untouched on any failure except FORMAT_UNSUPPORTED
// RETURN VALUE:
// RETURN VALUE:
// COM error code, including:
// S_OK - input/output configuration supported, ppSupportedOutputFormat left untouched
// FORMAT_UNSUPPORTED - input/output configuration unsupported, ppSupportedOutputFormat receives pointer to nearest output format supported if not nil
// E_INVALIDARG - either audio format invalid, ppSupportedOutputFormat left untouched
// E_INVALIDARG - either audio format invalid, ppSupportedOutputFormat left untouched
IsOutputFormatSupported: proc "system" (this: ^IXAPO, pInputFormat: ^WAVEFORMATEX, pRequestedOutputFormat: ^WAVEFORMATEX, ppSupportedOutputFormat: ^^WAVEFORMATEX) -> HRESULT,
// DESCRIPTION:
@@ -246,10 +209,10 @@ IXAPO_VTable :: struct {
// Immutable variables (constant during the lifespan of the XAPO) should be set once via this method.
// Once initialized, an XAPO cannot be initialized again.
// An XAPO should be initialized before passing it to XAudio2 as part of an effect chain. XAudio2 will not call this method; it exists for future content-driven initialization.
// PARAMETERS:
// PARAMETERS:
// pData - [in] effect-specific initialization parameters, may be nil if DataByteSize == 0
// DataByteSize - [in] size of pData in bytes, may be 0 if pData is NULL
// RETURN VALUE:
// DataByteSize - [in] size of pData in bytes, may be 0 if pData is nil
// RETURN VALUE:
// COM error code
Initialize: proc "system" (this: ^IXAPO, pData: rawptr, DataByteSize: u32) -> HRESULT,
@@ -279,9 +242,9 @@ IXAPO_VTable :: struct {
// PARAMETERS:
// InputLockedParameterCount - [in] number of input buffers, must be within [XAPO_REGISTRATION_PROPERTIES.MinInputBufferCount, XAPO_REGISTRATION_PROPERTIES.MaxInputBufferCount]
// pInputLockedParameters - [in] array of input locked buffer parameter structures, may be nil if InputLockedParameterCount == 0, otherwise must have InputLockedParameterCount elements
// OutputLockedParameterCount - [in] number of output buffers, must be within [XAPO_REGISTRATION_PROPERTIES.MinOutputBufferCount, XAPO_REGISTRATION_PROPERTIES.MaxOutputBufferCount], must match InputLockedParameterCount when XAPO_FLAG_BUFFERCOUNT_MUST_MATCH used
// pOutputLockedParameters - [in] array of output locked buffer parameter structures, may be NULL if OutputLockedParameterCount == 0, otherwise must have OutputLockedParameterCount elements
// RETURN VALUE:
// OutputLockedParameterCount - [in] number of output buffers, must be within [XAPO_REGISTRATION_PROPERTIES.MinOutputBufferCount, XAPO_REGISTRATION_PROPERTIES.MaxOutputBufferCount], must match InputLockedParameterCount when XAPO_FLAG.BUFFERCOUNT_MUST_MATCH used
// pOutputLockedParameters - [in] array of output locked buffer parameter structures, may be nil if OutputLockedParameterCount == 0, otherwise must have OutputLockedParameterCount elements
// RETURN VALUE:
// COM error code
LockForProcess: proc "system" (this: ^IXAPO, InputLockedParameterCount: u32, pInputLockedParameters: [^]XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS, OutputLockedParameterCount: u32, pOutputLockedParameters: [^]XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS) -> HRESULT,
@@ -303,16 +266,16 @@ IXAPO_VTable :: struct {
// Multiple input/output buffers may be used with in-place XAPOs, though the input buffer count must equal the output buffer count.
// When multiple input/output buffers are used with in-place XAPOs, the XAPO may assume input buffer [N] equals output buffer [N].
// When IsEnabled is false, the XAPO should process thru. Thru processing means an XAPO should not apply its normal processing to the given input/output buffers during Process.
// It should instead pass data from input to output with as little modification possible. Effects that perform format conversion should continue to do so.
// The effect must ensure transitions between normal and thru processing do not introduce discontinuities into the signal.
// It should instead pass data from input to output with as little modification possible. Effects that perform format conversion should continue to do so.
// The effect must ensure transitions between normal and thru processing do not introduce discontinuities into the signal.
// XAudio2 calls this method only if the XAPO is locked. This method should not block as it is called from the realtime thread.
// PARAMETERS:
// InputProcessParameterCount - [in] number of input buffers, matches respective InputLockedParameterCount parameter given to LockForProcess
// pInputProcessParameters - [in] array of input process buffer parameter structures, may be nil if InputProcessParameterCount == 0, otherwise must have InputProcessParameterCount elements
// OutputProcessParameterCount - [in] number of output buffers, matches respective OutputLockedParameterCount parameter given to LockForProcess
// OutputProcessParameterCount - [in] number of output buffers, matches respective OutputLockedParameterCount parameter given to LockForProcess
// pOutputProcessParameters - [in/out] array of output process buffer parameter structures, may be nil if OutputProcessParameterCount == 0, otherwise must have OutputProcessParameterCount elements
// IsEnabled - [in] TRUE to process normally, FALSE to process thru
// RETURN VALUE:
// IsEnabled - [in] true to process normally, false to process thru
// RETURN VALUE:
// void
Process: proc "system" (this: ^IXAPO, InputProcessParameterCount: u32, pInputProcessParameters: [^]XAPO_PROCESS_BUFFER_PARAMETERS, OutputProcessParameterCount: u32, pOutputProcessParameters: [^]XAPO_PROCESS_BUFFER_PARAMETERS, IsEnabled: b32),
@@ -358,7 +321,7 @@ IXAPOParameters_VTable :: struct {
// This method should not block as it is called from the realtime thread.
// PARAMETERS:
// pParameters - [in] effect-specific parameter block, must be != nil
// ParameterByteSize - [in] size of pParameters in bytes, must be > 0
// ParameterByteSize - [in] size of pParameters in bytes, must be > 0
// RETURN VALUE:
// void
SetParameters: proc "system" (this: ^IXAPOParameters, pParameters: rawptr, ParameterByteSize: u32),
@@ -369,7 +332,7 @@ IXAPOParameters_VTable :: struct {
// Unlike SetParameters, XAudio2 does not call this method on the realtime thread. Thus, the XAPO must protect variables shared with SetParameters/Process using appropriate synchronization.
// PARAMETERS:
// pParameters - [out] receives effect-specific parameter block, must be != nil
// ParameterByteSize - [in] size of pParameters in bytes, must be > 0
// ParameterByteSize - [in] size of pParameters in bytes, must be > 0
// RETURN VALUE:
// void
GetParameters: proc "system" (this: ^IXAPOParameters, pParameters: rawptr, ParameterByteSize: u32),