diff options
Diffstat (limited to 'Alc/mixer.c')
-rw-r--r-- | Alc/mixer.c | 462 |
1 files changed, 326 insertions, 136 deletions
diff --git a/Alc/mixer.c b/Alc/mixer.c index 6a4abfc6..712075f1 100644 --- a/Alc/mixer.c +++ b/Alc/mixer.c @@ -13,8 +13,8 @@ * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * Or go to http://www.gnu.org/copyleft/lgpl.html */ @@ -34,11 +34,255 @@ #include "alListener.h" #include "alAuxEffectSlot.h" #include "alu.h" -#include "bs2b.h" +#include "mixer_defs.h" + + +static_assert((INT_MAX>>FRACTIONBITS)/MAX_PITCH > BUFFERSIZE, + "MAX_PITCH and/or BUFFERSIZE are too large for FRACTIONBITS!"); extern inline void InitiatePositionArrays(ALuint frac, ALuint increment, ALuint *frac_arr, ALuint *pos_arr, ALuint size); +alignas(16) union ResamplerCoeffs ResampleCoeffs; + + +enum Resampler { + PointResampler, + LinearResampler, + FIR4Resampler, + FIR8Resampler, + BSincResampler, + + ResamplerDefault = LinearResampler +}; + +/* FIR8 requires 3 extra samples before the current position, and 4 after. */ +static_assert(MAX_PRE_SAMPLES >= 3, "MAX_PRE_SAMPLES must be at least 3!"); +static_assert(MAX_POST_SAMPLES >= 4, "MAX_POST_SAMPLES must be at least 4!"); + + +static HrtfMixerFunc MixHrtfSamples = MixHrtf_C; +static MixerFunc MixSamples = Mix_C; +static ResamplerFunc ResampleSamples = Resample_point32_C; + +static inline HrtfMixerFunc SelectHrtfMixer(void) +{ +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return MixHrtf_SSE; +#endif +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return MixHrtf_Neon; +#endif + + return MixHrtf_C; +} + +static inline MixerFunc SelectMixer(void) +{ +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return Mix_SSE; +#endif +#ifdef HAVE_NEON + if((CPUCapFlags&CPU_CAP_NEON)) + return Mix_Neon; +#endif + + return Mix_C; +} + +static inline ResamplerFunc SelectResampler(enum Resampler resampler) +{ + switch(resampler) + { + case PointResampler: + return Resample_point32_C; + case LinearResampler: +#ifdef HAVE_SSE4_1 + if((CPUCapFlags&CPU_CAP_SSE4_1)) + return Resample_lerp32_SSE41; +#endif +#ifdef HAVE_SSE2 + if((CPUCapFlags&CPU_CAP_SSE2)) + return Resample_lerp32_SSE2; +#endif + return Resample_lerp32_C; + case FIR4Resampler: +#ifdef HAVE_SSE4_1 + if((CPUCapFlags&CPU_CAP_SSE4_1)) + return Resample_fir4_32_SSE41; +#endif +#ifdef HAVE_SSE3 + if((CPUCapFlags&CPU_CAP_SSE3)) + return Resample_fir4_32_SSE3; +#endif + return Resample_fir4_32_C; + case FIR8Resampler: +#ifdef HAVE_SSE4_1 + if((CPUCapFlags&CPU_CAP_SSE4_1)) + return Resample_fir8_32_SSE41; +#endif +#ifdef HAVE_SSE3 + if((CPUCapFlags&CPU_CAP_SSE3)) + return Resample_fir8_32_SSE3; +#endif + return Resample_fir8_32_C; + case BSincResampler: +#ifdef HAVE_SSE + if((CPUCapFlags&CPU_CAP_SSE)) + return Resample_bsinc32_SSE; +#endif + return Resample_bsinc32_C; + } + + return Resample_point32_C; +} + + +/* The sinc resampler makes use of a Kaiser window to limit the needed sample + * points to 4 and 8, respectively. + */ + +#ifndef M_PI +#define M_PI (3.14159265358979323846) +#endif +static inline double Sinc(double x) +{ + if(x == 0.0) return 1.0; + return sin(x*M_PI) / (x*M_PI); +} + +/* The zero-order modified Bessel function of the first kind, used for the + * Kaiser window. + * + * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k) + * = sum_{k=0}^inf ((x / 2)^k / k!)^2 + */ +static double BesselI_0(double x) +{ + double term, sum, x2, y, last_sum; + int k; + + /* Start at k=1 since k=0 is trivial. */ + term = 1.0; + sum = 1.0; + x2 = x / 2.0; + k = 1; + + /* Let the integration converge until the term of the sum is no longer + * significant. + */ + do { + y = x2 / k; + k ++; + last_sum = sum; + term *= y * y; + sum += term; + } while(sum != last_sum); + return sum; +} + +/* Calculate a Kaiser window from the given beta value and a normalized k + * [-1, 1]. + * + * w(k) = { I_0(B sqrt(1 - k^2)) / I_0(B), -1 <= k <= 1 + * { 0, elsewhere. + * + * Where k can be calculated as: + * + * k = i / l, where -l <= i <= l. + * + * or: + * + * k = 2 i / M - 1, where 0 <= i <= M. + */ +static inline double Kaiser(double b, double k) +{ + if(k <= -1.0 || k >= 1.0) return 0.0; + return BesselI_0(b * sqrt(1.0 - (k*k))) / BesselI_0(b); +} + +static inline double CalcKaiserBeta(double rejection) +{ + if(rejection > 50.0) + return 0.1102 * (rejection - 8.7); + if(rejection >= 21.0) + return (0.5842 * pow(rejection - 21.0, 0.4)) + + (0.07886 * (rejection - 21.0)); + return 0.0; +} + +static float SincKaiser(double r, double x) +{ + /* Limit rippling to -60dB. */ + return (float)(Kaiser(CalcKaiserBeta(60.0), x / r) * Sinc(x)); +} + + +void aluInitMixer(void) +{ + enum Resampler resampler = ResamplerDefault; + const char *str; + ALuint i; + + if(ConfigValueStr(NULL, NULL, "resampler", &str)) + { + if(strcasecmp(str, "point") == 0 || strcasecmp(str, "none") == 0) + resampler = PointResampler; + else if(strcasecmp(str, "linear") == 0) + resampler = LinearResampler; + else if(strcasecmp(str, "sinc4") == 0) + resampler = FIR4Resampler; + else if(strcasecmp(str, "sinc8") == 0) + resampler = FIR8Resampler; + else if(strcasecmp(str, "bsinc") == 0) + resampler = BSincResampler; + else if(strcasecmp(str, "cubic") == 0) + { + WARN("Resampler option \"cubic\" is deprecated, using sinc4\n"); + resampler = FIR4Resampler; + } + else + { + char *end; + long n = strtol(str, &end, 0); + if(*end == '\0' && (n == PointResampler || n == LinearResampler || n == FIR4Resampler)) + resampler = n; + else + WARN("Invalid resampler: %s\n", str); + } + } + + if(resampler == FIR8Resampler) + for(i = 0;i < FRACTIONONE;i++) + { + ALdouble mu = (ALdouble)i / FRACTIONONE; + ResampleCoeffs.FIR8[i][0] = SincKaiser(4.0, mu - -3.0); + ResampleCoeffs.FIR8[i][1] = SincKaiser(4.0, mu - -2.0); + ResampleCoeffs.FIR8[i][2] = SincKaiser(4.0, mu - -1.0); + ResampleCoeffs.FIR8[i][3] = SincKaiser(4.0, mu - 0.0); + ResampleCoeffs.FIR8[i][4] = SincKaiser(4.0, mu - 1.0); + ResampleCoeffs.FIR8[i][5] = SincKaiser(4.0, mu - 2.0); + ResampleCoeffs.FIR8[i][6] = SincKaiser(4.0, mu - 3.0); + ResampleCoeffs.FIR8[i][7] = SincKaiser(4.0, mu - 4.0); + } + else if(resampler == FIR4Resampler) + for(i = 0;i < FRACTIONONE;i++) + { + ALdouble mu = (ALdouble)i / FRACTIONONE; + ResampleCoeffs.FIR4[i][0] = SincKaiser(2.0, mu - -1.0); + ResampleCoeffs.FIR4[i][1] = SincKaiser(2.0, mu - 0.0); + ResampleCoeffs.FIR4[i][2] = SincKaiser(2.0, mu - 1.0); + ResampleCoeffs.FIR4[i][3] = SincKaiser(2.0, mu - 2.0); + } + + MixHrtfSamples = SelectHrtfMixer(); + MixSamples = SelectMixer(); + ResampleSamples = SelectResampler(resampler); +} + static inline ALfloat Sample_ALbyte(ALbyte val) { return val * (1.0f/127.0f); } @@ -50,7 +294,7 @@ static inline ALfloat Sample_ALfloat(ALfloat val) { return val; } #define DECL_TEMPLATE(T) \ -static void Load_##T(ALfloat *dst, const T *src, ALuint srcstep, ALuint samples)\ +static inline void Load_##T(ALfloat *dst, const T *src, ALuint srcstep, ALuint samples)\ { \ ALuint i; \ for(i = 0;i < samples;i++) \ @@ -63,7 +307,7 @@ DECL_TEMPLATE(ALfloat) #undef DECL_TEMPLATE -static void LoadData(ALfloat *dst, const ALvoid *src, ALuint srcstep, enum FmtType srctype, ALuint samples) +static void LoadSamples(ALfloat *dst, const ALvoid *src, ALuint srcstep, enum FmtType srctype, ALuint samples) { switch(srctype) { @@ -79,7 +323,7 @@ static void LoadData(ALfloat *dst, const ALvoid *src, ALuint srcstep, enum FmtTy } } -static void SilenceData(ALfloat *dst, ALuint samples) +static inline void SilenceSamples(ALfloat *dst, ALuint samples) { ALuint i; for(i = 0;i < samples;i++) @@ -95,20 +339,24 @@ static const ALfloat *DoFilters(ALfilterState *lpfilter, ALfilterState *hpfilter switch(type) { case AF_None: + ALfilterState_processPassthru(lpfilter, src, numsamples); + ALfilterState_processPassthru(hpfilter, src, numsamples); break; case AF_LowPass: ALfilterState_process(lpfilter, dst, src, numsamples); + ALfilterState_processPassthru(hpfilter, dst, numsamples); return dst; case AF_HighPass: + ALfilterState_processPassthru(lpfilter, src, numsamples); ALfilterState_process(hpfilter, dst, src, numsamples); return dst; case AF_BandPass: for(i = 0;i < numsamples;) { - ALfloat temp[64]; - ALuint todo = minu(64, numsamples-i); + ALfloat temp[256]; + ALuint todo = minu(256, numsamples-i); ALfilterState_process(lpfilter, temp, src+i, todo); ALfilterState_process(hpfilter, dst+i, temp, todo); @@ -120,38 +368,38 @@ static const ALfloat *DoFilters(ALfilterState *lpfilter, ALfilterState *hpfilter } -ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) +ALvoid MixSource(ALvoice *voice, ALsource *Source, ALCdevice *Device, ALuint SamplesToDo) { - ALsource *Source = src->Source; + ResamplerFunc Resample; ALbufferlistitem *BufferListItem; ALuint DataPosInt, DataPosFrac; ALboolean Looping; ALuint increment; - enum Resampler Resampler; ALenum State; ALuint OutPos; ALuint NumChannels; ALuint SampleSize; ALint64 DataSize64; + ALuint IrSize; ALuint chan, j; /* Get source info */ State = Source->state; - BufferListItem = Source->current_buffer; + BufferListItem = ATOMIC_LOAD(&Source->current_buffer); DataPosInt = Source->position; DataPosFrac = Source->position_fraction; Looping = Source->Looping; - increment = src->Step; - Resampler = (increment==FRACTIONONE) ? PointResampler : Source->Resampler; NumChannels = Source->NumChannels; SampleSize = Source->SampleSize; + increment = voice->Step; - /* Get current buffer queue item */ + IrSize = (Device->Hrtf ? GetHrtfIrSize(Device->Hrtf) : 0); + + Resample = ((increment == FRACTIONONE && DataPosFrac == 0) ? + Resample_copy32_C : ResampleSamples); OutPos = 0; do { - const ALuint BufferPrePadding = ResamplerPrePadding[Resampler]; - const ALuint BufferPadding = ResamplerPadding[Resampler]; ALuint SrcBufferSize, DstBufferSize; /* Figure out how many buffer samples will be needed */ @@ -159,13 +407,13 @@ ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) DataSize64 *= increment; DataSize64 += DataPosFrac+FRACTIONMASK; DataSize64 >>= FRACTIONBITS; - DataSize64 += BufferPadding+BufferPrePadding; + DataSize64 += MAX_POST_SAMPLES+MAX_PRE_SAMPLES; SrcBufferSize = (ALuint)mini64(DataSize64, BUFFERSIZE); /* Figure out how many samples we can actually mix from this. */ DataSize64 = SrcBufferSize; - DataSize64 -= BufferPadding+BufferPrePadding; + DataSize64 -= MAX_POST_SAMPLES+MAX_PRE_SAMPLES; DataSize64 <<= FRACTIONBITS; DataSize64 -= DataPosFrac; @@ -181,7 +429,11 @@ ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) { const ALfloat *ResampledData; ALfloat *SrcData = Device->SourceData; - ALuint SrcDataSize = 0; + ALuint SrcDataSize; + + /* Load the previous samples into the source data first. */ + memcpy(SrcData, voice->PrevSamples[chan], MAX_PRE_SAMPLES*sizeof(ALfloat)); + SrcDataSize = MAX_PRE_SAMPLES; if(Source->SourceType == AL_STATIC) { @@ -190,33 +442,24 @@ ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) ALuint DataSize; ALuint pos; + /* Offset buffer data to current channel */ + Data += chan*SampleSize; + /* If current pos is beyond the loop range, do not loop */ if(Looping == AL_FALSE || DataPosInt >= (ALuint)ALBuffer->LoopEnd) { Looping = AL_FALSE; - if(DataPosInt >= BufferPrePadding) - pos = DataPosInt - BufferPrePadding; - else - { - DataSize = BufferPrePadding - DataPosInt; - DataSize = minu(SrcBufferSize - SrcDataSize, DataSize); - - SilenceData(&SrcData[SrcDataSize], DataSize); - SrcDataSize += DataSize; - - pos = 0; - } - - /* Copy what's left to play in the source buffer, and clear the - * rest of the temp buffer */ + /* Load what's left to play from the source buffer, and + * clear the rest of the temp buffer */ + pos = DataPosInt; DataSize = minu(SrcBufferSize - SrcDataSize, ALBuffer->SampleLen - pos); - LoadData(&SrcData[SrcDataSize], &Data[(pos*NumChannels + chan)*SampleSize], - NumChannels, ALBuffer->FmtType, DataSize); + LoadSamples(&SrcData[SrcDataSize], &Data[pos * NumChannels*SampleSize], + NumChannels, ALBuffer->FmtType, DataSize); SrcDataSize += DataSize; - SilenceData(&SrcData[SrcDataSize], SrcBufferSize - SrcDataSize); + SilenceSamples(&SrcData[SrcDataSize], SrcBufferSize - SrcDataSize); SrcDataSize += SrcBufferSize - SrcDataSize; } else @@ -224,34 +467,14 @@ ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) ALuint LoopStart = ALBuffer->LoopStart; ALuint LoopEnd = ALBuffer->LoopEnd; - if(DataPosInt >= LoopStart) - { - pos = DataPosInt-LoopStart; - while(pos < BufferPrePadding) - pos += LoopEnd-LoopStart; - pos -= BufferPrePadding; - pos += LoopStart; - } - else if(DataPosInt >= BufferPrePadding) - pos = DataPosInt - BufferPrePadding; - else - { - DataSize = BufferPrePadding - DataPosInt; - DataSize = minu(SrcBufferSize - SrcDataSize, DataSize); - - SilenceData(&SrcData[SrcDataSize], DataSize); - SrcDataSize += DataSize; - - pos = 0; - } - - /* Copy what's left of this loop iteration, then copy repeats - * of the loop section */ + /* Load what's left of this loop iteration, then load + * repeats of the loop section */ + pos = DataPosInt; DataSize = LoopEnd - pos; DataSize = minu(SrcBufferSize - SrcDataSize, DataSize); - LoadData(&SrcData[SrcDataSize], &Data[(pos*NumChannels + chan)*SampleSize], - NumChannels, ALBuffer->FmtType, DataSize); + LoadSamples(&SrcData[SrcDataSize], &Data[pos * NumChannels*SampleSize], + NumChannels, ALBuffer->FmtType, DataSize); SrcDataSize += DataSize; DataSize = LoopEnd-LoopStart; @@ -259,8 +482,8 @@ ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) { DataSize = minu(SrcBufferSize - SrcDataSize, DataSize); - LoadData(&SrcData[SrcDataSize], &Data[(LoopStart*NumChannels + chan)*SampleSize], - NumChannels, ALBuffer->FmtType, DataSize); + LoadSamples(&SrcData[SrcDataSize], &Data[LoopStart * NumChannels*SampleSize], + NumChannels, ALBuffer->FmtType, DataSize); SrcDataSize += DataSize; } } @@ -269,45 +492,7 @@ ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) { /* Crawl the buffer queue to fill in the temp buffer */ ALbufferlistitem *tmpiter = BufferListItem; - ALuint pos; - - if(DataPosInt >= BufferPrePadding) - pos = DataPosInt - BufferPrePadding; - else - { - pos = BufferPrePadding - DataPosInt; - while(pos > 0) - { - ALbufferlistitem *prev; - if((prev=tmpiter->prev) != NULL) - tmpiter = prev; - else if(Looping) - { - while(tmpiter->next) - tmpiter = tmpiter->next; - } - else - { - ALuint DataSize = minu(SrcBufferSize - SrcDataSize, pos); - - SilenceData(&SrcData[SrcDataSize], DataSize); - SrcDataSize += DataSize; - - pos = 0; - break; - } - - if(tmpiter->buffer) - { - if((ALuint)tmpiter->buffer->SampleLen > pos) - { - pos = tmpiter->buffer->SampleLen - pos; - break; - } - pos -= tmpiter->buffer->SampleLen; - } - } - } + ALuint pos = DataPosInt; while(tmpiter && SrcBufferSize > SrcDataSize) { @@ -327,29 +512,35 @@ ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) pos -= pos; DataSize = minu(SrcBufferSize - SrcDataSize, DataSize); - LoadData(&SrcData[SrcDataSize], Data, NumChannels, - ALBuffer->FmtType, DataSize); + LoadSamples(&SrcData[SrcDataSize], Data, NumChannels, + ALBuffer->FmtType, DataSize); SrcDataSize += DataSize; } } tmpiter = tmpiter->next; if(!tmpiter && Looping) - tmpiter = Source->queue; + tmpiter = ATOMIC_LOAD(&Source->queue); else if(!tmpiter) { - SilenceData(&SrcData[SrcDataSize], SrcBufferSize - SrcDataSize); + SilenceSamples(&SrcData[SrcDataSize], SrcBufferSize - SrcDataSize); SrcDataSize += SrcBufferSize - SrcDataSize; } } } + /* Store the last source samples used for next time. */ + memcpy(voice->PrevSamples[chan], + &SrcData[(increment*DstBufferSize + DataPosFrac)>>FRACTIONBITS], + MAX_PRE_SAMPLES*sizeof(ALfloat) + ); + /* Now resample, then filter and mix to the appropriate outputs. */ - ResampledData = src->Resample( - &SrcData[BufferPrePadding], DataPosFrac, increment, + ResampledData = Resample(&voice->SincState, + &SrcData[MAX_PRE_SAMPLES], DataPosFrac, increment, Device->ResampledData, DstBufferSize ); { - DirectParams *parms = &src->Direct; + DirectParams *parms = &voice->Direct; const ALfloat *samples; samples = DoFilters( @@ -357,20 +548,18 @@ ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) Device->FilteredData, ResampledData, DstBufferSize, parms->Filters[chan].ActiveType ); - if(!src->IsHrtf) - src->Dry.Mix(parms->OutBuffer, samples, &parms->Mix.Gains[chan], - parms->Counter, OutPos, DstBufferSize); + if(!voice->IsHrtf) + MixSamples(samples, parms->OutChannels, parms->OutBuffer, parms->Gains[chan], + parms->Counter, OutPos, DstBufferSize); else - src->Dry.HrtfMix( - parms->OutBuffer, samples, parms->Counter, src->Offset, - OutPos, parms->Mix.Hrtf.IrSize, &parms->Mix.Hrtf.Params[chan], - &parms->Mix.Hrtf.State[chan], DstBufferSize - ); + MixHrtfSamples(parms->OutBuffer, samples, parms->Counter, voice->Offset, + OutPos, IrSize, &parms->Hrtf[chan].Params, + &parms->Hrtf[chan].State, DstBufferSize); } for(j = 0;j < Device->NumAuxSends;j++) { - SendParams *parms = &src->Send[j]; + SendParams *parms = &voice->Send[j]; const ALfloat *samples; if(!parms->OutBuffer) @@ -381,8 +570,8 @@ ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) Device->FilteredData, ResampledData, DstBufferSize, parms->Filters[chan].ActiveType ); - src->WetMix(parms->OutBuffer, samples, &parms->Gain, - parms->Counter, OutPos, DstBufferSize); + MixSamples(samples, 1, parms->OutBuffer, &parms->Gains[chan], + parms->Counter, OutPos, DstBufferSize); } } /* Update positions */ @@ -391,10 +580,10 @@ ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) DataPosFrac &= FRACTIONMASK; OutPos += DstBufferSize; - src->Offset += DstBufferSize; - src->Direct.Counter = maxu(src->Direct.Counter, DstBufferSize) - DstBufferSize; + voice->Offset += DstBufferSize; + voice->Direct.Counter = maxu(voice->Direct.Counter, DstBufferSize) - DstBufferSize; for(j = 0;j < Device->NumAuxSends;j++) - src->Send[j].Counter = maxu(src->Send[j].Counter, DstBufferSize) - DstBufferSize; + voice->Send[j].Counter = maxu(voice->Send[j].Counter, DstBufferSize) - DstBufferSize; /* Handle looping sources */ while(1) @@ -423,17 +612,18 @@ ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) if(DataSize > DataPosInt) break; - if(BufferListItem->next) - BufferListItem = BufferListItem->next; - else if(Looping) - BufferListItem = Source->queue; - else + if(!(BufferListItem=BufferListItem->next)) { - State = AL_STOPPED; - BufferListItem = NULL; - DataPosInt = 0; - DataPosFrac = 0; - break; + if(Looping) + BufferListItem = ATOMIC_LOAD(&Source->queue); + else + { + State = AL_STOPPED; + BufferListItem = NULL; + DataPosInt = 0; + DataPosFrac = 0; + break; + } } DataPosInt -= DataSize; @@ -442,7 +632,7 @@ ALvoid MixSource(ALactivesource *src, ALCdevice *Device, ALuint SamplesToDo) /* Update source info */ Source->state = State; - Source->current_buffer = BufferListItem; + ATOMIC_STORE(&Source->current_buffer, BufferListItem); Source->position = DataPosInt; Source->position_fraction = DataPosFrac; } |