4 #ifdef HAVE_SSE_INTRINSICS
11 #include "al/auxeffectslot.h"
13 #include "alcomplex.h"
14 #include "alcontext.h"
18 #include "bformatdec.h"
19 #include "buffer_storage.h"
20 #include "effects/base.h"
21 #include "filters/splitter.h"
22 #include "fmt_traits.h"
24 #include "polyphase_resampler.h"
29 /* Convolution reverb is implemented using a segmented overlap-add method. The
30 * impulse response is broken up into multiple segments of 512 samples, and
31 * each segment has an FFT applied with a 1024-sample buffer (the latter half
32 * left silent) to get its frequency-domain response. The resulting response
33 * has its positive/non-mirrored frequencies saved (513 bins) in each segment.
35 * Input samples are similarly broken up into 512-sample segments, with an FFT
36 * applied to each new incoming segment to get its 513 bins. A history of FFT'd
37 * input segments is maintained, equal to the length of the impulse response.
39 * To apply the reverberation, each impulse response segment is convolved with
40 * its paired input segment (using complex multiplies, far cheaper than FIRs),
41 * accumulating into a 1024-bin FFT buffer. The input history is then shifted
42 * to align with later impulse response segments for next time.
44 * An inverse FFT is then applied to the accumulated FFT buffer to get a 1024-
45 * sample time-domain response for output, which is split in two halves. The
46 * first half is the 512-sample output, and the second half is a 512-sample
47 * (really, 511) delayed extension, which gets added to the output next time.
48 * Convolving two time-domain responses of lengths N and M results in a time-
49 * domain signal of length N+M-1, and this holds true regardless of the
50 * convolution being applied in the frequency domain, so these "overflow"
51 * samples need to be accounted for.
53 * To avoid a delay with gathering enough input samples to apply an FFT with,
54 * the first segment is applied directly in the time-domain as the samples come
55 * in. Once enough have been retrieved, the FFT is applied on the input and
56 * it's paired with the remaining (FFT'd) filter segments for processing.
60 void LoadSamples(double *RESTRICT dst
, const al::byte
*src
, const size_t srcstep
, FmtType srctype
,
61 const size_t samples
) noexcept
63 #define HANDLE_FMT(T) case T: al::LoadSampleArray<T>(dst, src, srcstep, samples); break
69 HANDLE_FMT(FmtDouble
);
77 auto GetAmbiScales(AmbiScaling scaletype
) noexcept
-> const std::array
<float,MAX_AMBI_CHANNELS
>&
79 if(scaletype
== AmbiScaling::FuMa
) return AmbiScale::FromFuMa
;
80 if(scaletype
== AmbiScaling::SN3D
) return AmbiScale::FromSN3D
;
81 return AmbiScale::FromN3D
;
84 auto GetAmbiLayout(AmbiLayout layouttype
) noexcept
-> const std::array
<uint8_t,MAX_AMBI_CHANNELS
>&
86 if(layouttype
== AmbiLayout::FuMa
) return AmbiIndex::FromFuMa
;
87 return AmbiIndex::FromACN
;
90 auto GetAmbi2DLayout(AmbiLayout layouttype
) noexcept
-> const std::array
<uint8_t,MAX_AMBI2D_CHANNELS
>&
92 if(layouttype
== AmbiLayout::FuMa
) return AmbiIndex::FromFuMa2D
;
93 return AmbiIndex::From2D
;
103 using complex_d
= std::complex<double>;
105 constexpr size_t ConvolveUpdateSize
{1024};
106 constexpr size_t ConvolveUpdateSamples
{ConvolveUpdateSize
/ 2};
109 void apply_fir(al::span
<float> dst
, const float *RESTRICT src
, const float *RESTRICT filter
)
111 #ifdef HAVE_SSE_INTRINSICS
112 for(float &output
: dst
)
114 __m128 r4
{_mm_setzero_ps()};
115 for(size_t j
{0};j
< ConvolveUpdateSamples
;j
+=4)
117 const __m128 coeffs
{_mm_load_ps(&filter
[j
])};
118 const __m128 s
{_mm_loadu_ps(&src
[j
])};
120 r4
= _mm_add_ps(r4
, _mm_mul_ps(s
, coeffs
));
122 r4
= _mm_add_ps(r4
, _mm_shuffle_ps(r4
, r4
, _MM_SHUFFLE(0, 1, 2, 3)));
123 r4
= _mm_add_ps(r4
, _mm_movehl_ps(r4
, r4
));
124 output
= _mm_cvtss_f32(r4
);
131 for(float &output
: dst
)
134 for(size_t j
{0};j
< ConvolveUpdateSamples
;++j
)
135 ret
+= src
[j
] * filter
[j
];
142 struct ConvolutionState final
: public EffectState
{
143 FmtChannels mChannels
{};
144 AmbiLayout mAmbiLayout
{};
145 AmbiScaling mAmbiScaling
{};
149 std::array
<float,ConvolveUpdateSamples
*2> mInput
{};
150 al::vector
<std::array
<float,ConvolveUpdateSamples
>,16> mFilter
;
151 al::vector
<std::array
<float,ConvolveUpdateSamples
*2>,16> mOutput
;
153 alignas(16) std::array
<complex_d
,ConvolveUpdateSize
> mFftBuffer
{};
155 size_t mCurrentSegment
{0};
156 size_t mNumConvolveSegs
{0};
159 alignas(16) FloatBufferLine mBuffer
{};
161 BandSplitter mFilter
{};
162 float Current
[MAX_OUTPUT_CHANNELS
]{};
163 float Target
[MAX_OUTPUT_CHANNELS
]{};
165 using ChannelDataArray
= al::FlexArray
<ChannelData
>;
166 std::unique_ptr
<ChannelDataArray
> mChans
;
167 std::unique_ptr
<complex_d
[]> mComplexData
;
170 ConvolutionState() = default;
171 ~ConvolutionState() override
= default;
173 void NormalMix(const al::span
<FloatBufferLine
> samplesOut
, const size_t samplesToDo
);
174 void UpsampleMix(const al::span
<FloatBufferLine
> samplesOut
, const size_t samplesToDo
);
175 void (ConvolutionState::*mMix
)(const al::span
<FloatBufferLine
>,const size_t)
176 {&ConvolutionState::NormalMix
};
178 void deviceUpdate(const ALCdevice
*device
) override
;
179 void setBuffer(const ALCdevice
*device
, const BufferStorage
*buffer
) override
;
180 void update(const ALCcontext
*context
, const ALeffectslot
*slot
, const EffectProps
*props
, const EffectTarget target
) override
;
181 void process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
) override
;
183 DEF_NEWDEL(ConvolutionState
)
186 void ConvolutionState::NormalMix(const al::span
<FloatBufferLine
> samplesOut
,
187 const size_t samplesToDo
)
189 for(auto &chan
: *mChans
)
190 MixSamples({chan
.mBuffer
.data(), samplesToDo
}, samplesOut
, chan
.Current
, chan
.Target
,
194 void ConvolutionState::UpsampleMix(const al::span
<FloatBufferLine
> samplesOut
,
195 const size_t samplesToDo
)
197 for(auto &chan
: *mChans
)
199 const al::span
<float> src
{chan
.mBuffer
.data(), samplesToDo
};
200 chan
.mFilter
.processHfScale(src
, chan
.mHfScale
);
201 MixSamples(src
, samplesOut
, chan
.Current
, chan
.Target
, samplesToDo
, 0);
206 void ConvolutionState::deviceUpdate(const ALCdevice
* /*device*/)
210 void ConvolutionState::setBuffer(const ALCdevice
*device
, const BufferStorage
*buffer
)
212 constexpr ALuint MaxConvolveAmbiOrder
{1u};
216 decltype(mFilter
){}.swap(mFilter
);
217 decltype(mOutput
){}.swap(mOutput
);
218 mFftBuffer
.fill(complex_d
{});
221 mNumConvolveSegs
= 0;
224 mComplexData
= nullptr;
226 /* An empty buffer doesn't need a convolution filter. */
227 if(!buffer
|| buffer
->mSampleLen
< 1) return;
229 constexpr size_t m
{ConvolveUpdateSize
/2 + 1};
230 auto bytesPerSample
= BytesFromFmt(buffer
->mType
);
231 auto realChannels
= ChannelsFromFmt(buffer
->mChannels
, buffer
->mAmbiOrder
);
232 auto numChannels
= ChannelsFromFmt(buffer
->mChannels
,
233 minu(buffer
->mAmbiOrder
, MaxConvolveAmbiOrder
));
235 mChans
= ChannelDataArray::Create(numChannels
);
237 /* The impulse response needs to have the same sample rate as the input and
238 * output. The bsinc24 resampler is decent, but there is high-frequency
239 * attenation that some people may be able to pick up on. Since this is
240 * called very infrequently, go ahead and use the polyphase resampler.
242 PPhaseResampler resampler
;
243 if(device
->Frequency
!= buffer
->mSampleRate
)
244 resampler
.init(buffer
->mSampleRate
, device
->Frequency
);
245 const auto resampledCount
= static_cast<ALuint
>(
246 (uint64_t{buffer
->mSampleLen
}*device
->Frequency
+ (buffer
->mSampleRate
-1)) /
247 buffer
->mSampleRate
);
249 const BandSplitter splitter
{400.0f
/ static_cast<float>(device
->Frequency
)};
250 for(auto &e
: *mChans
)
251 e
.mFilter
= splitter
;
253 mFilter
.resize(numChannels
, {});
254 mOutput
.resize(numChannels
, {});
256 /* Calculate the number of segments needed to hold the impulse response and
257 * the input history (rounded up), and allocate them. Exclude one segment
258 * which gets applied as a time-domain FIR filter. Make sure at least one
259 * segment is allocated to simplify handling.
261 mNumConvolveSegs
= (resampledCount
+(ConvolveUpdateSamples
-1)) / ConvolveUpdateSamples
;
262 mNumConvolveSegs
= maxz(mNumConvolveSegs
, 2) - 1;
264 const size_t complex_length
{mNumConvolveSegs
* m
* (numChannels
+1)};
265 mComplexData
= std::make_unique
<complex_d
[]>(complex_length
);
266 std::fill_n(mComplexData
.get(), complex_length
, complex_d
{});
268 mChannels
= buffer
->mChannels
;
269 mAmbiLayout
= buffer
->mAmbiLayout
;
270 mAmbiScaling
= buffer
->mAmbiScaling
;
271 mAmbiOrder
= minu(buffer
->mAmbiOrder
, MaxConvolveAmbiOrder
);
273 auto srcsamples
= std::make_unique
<double[]>(maxz(buffer
->mSampleLen
, resampledCount
));
274 complex_d
*filteriter
= mComplexData
.get() + mNumConvolveSegs
*m
;
275 for(size_t c
{0};c
< numChannels
;++c
)
277 /* Load the samples from the buffer, and resample to match the device. */
278 LoadSamples(srcsamples
.get(), buffer
->mData
.data() + bytesPerSample
*c
, realChannels
,
279 buffer
->mType
, buffer
->mSampleLen
);
280 if(device
->Frequency
!= buffer
->mSampleRate
)
281 resampler
.process(buffer
->mSampleLen
, srcsamples
.get(), resampledCount
,
284 /* Store the first segment's samples in reverse in the time-domain, to
285 * apply as a FIR filter.
287 const size_t first_size
{minz(resampledCount
, ConvolveUpdateSamples
)};
288 std::transform(srcsamples
.get(), srcsamples
.get()+first_size
, mFilter
[c
].rbegin(),
289 [](const double d
) noexcept
-> float { return static_cast<float>(d
); });
291 size_t done
{first_size
};
292 for(size_t s
{0};s
< mNumConvolveSegs
;++s
)
294 const size_t todo
{minz(resampledCount
-done
, ConvolveUpdateSamples
)};
296 auto iter
= std::copy_n(&srcsamples
[done
], todo
, mFftBuffer
.begin());
298 std::fill(iter
, mFftBuffer
.end(), complex_d
{});
300 forward_fft(mFftBuffer
);
301 filteriter
= std::copy_n(mFftBuffer
.cbegin(), m
, filteriter
);
307 void ConvolutionState::update(const ALCcontext
*context
, const ALeffectslot
*slot
,
308 const EffectProps
* /*props*/, const EffectTarget target
)
310 /* NOTE: Stereo and Rear are slightly different from normal mixing (as
311 * defined in alu.cpp). These are 45 degrees from center, rather than the
312 * 30 degrees used there.
314 * TODO: LFE is not mixed to output. This will require each buffer channel
315 * to have its own output target since the main mixing buffer won't have an
316 * LFE channel (due to being B-Format).
318 static const ChanMap MonoMap
[1]{
319 { FrontCenter
, 0.0f
, 0.0f
}
321 { FrontLeft
, Deg2Rad(-45.0f
), Deg2Rad(0.0f
) },
322 { FrontRight
, Deg2Rad( 45.0f
), Deg2Rad(0.0f
) }
324 { BackLeft
, Deg2Rad(-135.0f
), Deg2Rad(0.0f
) },
325 { BackRight
, Deg2Rad( 135.0f
), Deg2Rad(0.0f
) }
327 { FrontLeft
, Deg2Rad( -45.0f
), Deg2Rad(0.0f
) },
328 { FrontRight
, Deg2Rad( 45.0f
), Deg2Rad(0.0f
) },
329 { BackLeft
, Deg2Rad(-135.0f
), Deg2Rad(0.0f
) },
330 { BackRight
, Deg2Rad( 135.0f
), Deg2Rad(0.0f
) }
332 { FrontLeft
, Deg2Rad( -30.0f
), Deg2Rad(0.0f
) },
333 { FrontRight
, Deg2Rad( 30.0f
), Deg2Rad(0.0f
) },
334 { FrontCenter
, Deg2Rad( 0.0f
), Deg2Rad(0.0f
) },
336 { SideLeft
, Deg2Rad(-110.0f
), Deg2Rad(0.0f
) },
337 { SideRight
, Deg2Rad( 110.0f
), Deg2Rad(0.0f
) }
339 { FrontLeft
, Deg2Rad(-30.0f
), Deg2Rad(0.0f
) },
340 { FrontRight
, Deg2Rad( 30.0f
), Deg2Rad(0.0f
) },
341 { FrontCenter
, Deg2Rad( 0.0f
), Deg2Rad(0.0f
) },
343 { BackCenter
, Deg2Rad(180.0f
), Deg2Rad(0.0f
) },
344 { SideLeft
, Deg2Rad(-90.0f
), Deg2Rad(0.0f
) },
345 { SideRight
, Deg2Rad( 90.0f
), Deg2Rad(0.0f
) }
347 { FrontLeft
, Deg2Rad( -30.0f
), Deg2Rad(0.0f
) },
348 { FrontRight
, Deg2Rad( 30.0f
), Deg2Rad(0.0f
) },
349 { FrontCenter
, Deg2Rad( 0.0f
), Deg2Rad(0.0f
) },
351 { BackLeft
, Deg2Rad(-150.0f
), Deg2Rad(0.0f
) },
352 { BackRight
, Deg2Rad( 150.0f
), Deg2Rad(0.0f
) },
353 { SideLeft
, Deg2Rad( -90.0f
), Deg2Rad(0.0f
) },
354 { SideRight
, Deg2Rad( 90.0f
), Deg2Rad(0.0f
) }
357 if(mNumConvolveSegs
< 1)
360 mMix
= &ConvolutionState::NormalMix
;
362 for(auto &chan
: *mChans
)
363 std::fill(std::begin(chan
.Target
), std::end(chan
.Target
), 0.0f
);
364 const float gain
{slot
->Params
.Gain
};
365 if(mChannels
== FmtBFormat3D
|| mChannels
== FmtBFormat2D
)
367 ALCdevice
*device
{context
->mDevice
.get()};
368 if(device
->mAmbiOrder
> mAmbiOrder
)
370 mMix
= &ConvolutionState::UpsampleMix
;
371 const auto scales
= BFormatDec::GetHFOrderScales(mAmbiOrder
, device
->mAmbiOrder
);
372 (*mChans
)[0].mHfScale
= scales
[0];
373 for(size_t i
{1};i
< mChans
->size();++i
)
374 (*mChans
)[i
].mHfScale
= scales
[1];
376 mOutTarget
= target
.Main
->Buffer
;
378 const auto &scales
= GetAmbiScales(mAmbiScaling
);
379 const uint8_t *index_map
{(mChannels
== FmtBFormat2D
) ?
380 GetAmbi2DLayout(mAmbiLayout
).data() :
381 GetAmbiLayout(mAmbiLayout
).data()};
383 std::array
<float,MAX_AMBI_CHANNELS
> coeffs
{};
384 for(size_t c
{0u};c
< mChans
->size();++c
)
386 const size_t acn
{index_map
[c
]};
387 coeffs
[acn
] = scales
[acn
];
388 ComputePanGains(target
.Main
, coeffs
.data(), gain
, (*mChans
)[c
].Target
);
394 ALCdevice
*device
{context
->mDevice
.get()};
395 al::span
<const ChanMap
> chanmap
{};
398 case FmtMono
: chanmap
= MonoMap
; break;
399 case FmtStereo
: chanmap
= StereoMap
; break;
400 case FmtRear
: chanmap
= RearMap
; break;
401 case FmtQuad
: chanmap
= QuadMap
; break;
402 case FmtX51
: chanmap
= X51Map
; break;
403 case FmtX61
: chanmap
= X61Map
; break;
404 case FmtX71
: chanmap
= X71Map
; break;
410 mOutTarget
= target
.Main
->Buffer
;
411 if(device
->mRenderMode
== RenderMode::Pairwise
)
413 auto ScaleAzimuthFront
= [](float azimuth
, float scale
) -> float
415 const float abs_azi
{std::fabs(azimuth
)};
416 if(!(abs_azi
>= al::MathDefs
<float>::Pi()*0.5f
))
417 return std::copysign(minf(abs_azi
*scale
, al::MathDefs
<float>::Pi()*0.5f
), azimuth
);
421 for(size_t i
{0};i
< chanmap
.size();++i
)
423 if(chanmap
[i
].channel
== LFE
) continue;
424 const auto coeffs
= CalcAngleCoeffs(ScaleAzimuthFront(chanmap
[i
].angle
, 2.0f
),
425 chanmap
[i
].elevation
, 0.0f
);
426 ComputePanGains(target
.Main
, coeffs
.data(), gain
, (*mChans
)[i
].Target
);
429 else for(size_t i
{0};i
< chanmap
.size();++i
)
431 if(chanmap
[i
].channel
== LFE
) continue;
432 const auto coeffs
= CalcAngleCoeffs(chanmap
[i
].angle
, chanmap
[i
].elevation
, 0.0f
);
433 ComputePanGains(target
.Main
, coeffs
.data(), gain
, (*mChans
)[i
].Target
);
438 void ConvolutionState::process(const size_t samplesToDo
,
439 const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
)
441 if(mNumConvolveSegs
< 1)
444 constexpr size_t m
{ConvolveUpdateSize
/2 + 1};
445 size_t curseg
{mCurrentSegment
};
446 auto &chans
= *mChans
;
448 for(size_t base
{0u};base
< samplesToDo
;)
450 const size_t todo
{minz(ConvolveUpdateSamples
-mFifoPos
, samplesToDo
-base
)};
452 std::copy_n(samplesIn
[0].begin() + base
, todo
,
453 mInput
.begin()+ConvolveUpdateSamples
+mFifoPos
);
455 /* Apply the FIR for the newly retrieved input samples, and combine it
456 * with the inverse FFT'd output samples.
458 for(size_t c
{0};c
< chans
.size();++c
)
460 auto buf_iter
= chans
[c
].mBuffer
.begin() + base
;
461 apply_fir({std::addressof(*buf_iter
), todo
}, mInput
.data()+1 + mFifoPos
,
464 auto fifo_iter
= mOutput
[c
].begin() + mFifoPos
;
465 std::transform(fifo_iter
, fifo_iter
+todo
, buf_iter
, buf_iter
, std::plus
<>{});
471 /* Check whether the input buffer is filled with new samples. */
472 if(mFifoPos
< ConvolveUpdateSamples
) break;
475 /* Move the newest input to the front for the next iteration's history. */
476 std::copy(mInput
.cbegin()+ConvolveUpdateSamples
, mInput
.cend(), mInput
.begin());
478 /* Calculate the frequency domain response and add the relevant
479 * frequency bins to the FFT history.
481 auto fftiter
= std::copy_n(mInput
.cbegin(), ConvolveUpdateSamples
, mFftBuffer
.begin());
482 std::fill(fftiter
, mFftBuffer
.end(), complex_d
{});
483 forward_fft(mFftBuffer
);
485 std::copy_n(mFftBuffer
.cbegin(), m
, &mComplexData
[curseg
*m
]);
487 const complex_d
*RESTRICT filter
{mComplexData
.get() + mNumConvolveSegs
*m
};
488 for(size_t c
{0};c
< chans
.size();++c
)
490 std::fill_n(mFftBuffer
.begin(), m
, complex_d
{});
492 /* Convolve each input segment with its IR filter counterpart
495 const complex_d
*RESTRICT input
{&mComplexData
[curseg
*m
]};
496 for(size_t s
{curseg
};s
< mNumConvolveSegs
;++s
)
498 for(size_t i
{0};i
< m
;++i
,++input
,++filter
)
499 mFftBuffer
[i
] += *input
* *filter
;
501 input
= mComplexData
.get();
502 for(size_t s
{0};s
< curseg
;++s
)
504 for(size_t i
{0};i
< m
;++i
,++input
,++filter
)
505 mFftBuffer
[i
] += *input
* *filter
;
508 /* Reconstruct the mirrored/negative frequencies to do a proper
511 for(size_t i
{m
};i
< ConvolveUpdateSize
;++i
)
512 mFftBuffer
[i
] = std::conj(mFftBuffer
[ConvolveUpdateSize
-i
]);
514 /* Apply iFFT to get the 1024 (really 1023) samples for output. The
515 * 512 output samples are combined with the last output's 511
516 * second-half samples (and this output's second half is
517 * subsequently saved for next time).
519 inverse_fft(mFftBuffer
);
521 /* The iFFT'd response is scaled up by the number of bins, so apply
522 * the inverse to normalize the output.
524 for(size_t i
{0};i
< ConvolveUpdateSamples
;++i
)
526 static_cast<float>(mFftBuffer
[i
].real() * (1.0/double{ConvolveUpdateSize
})) +
527 mOutput
[c
][ConvolveUpdateSamples
+i
];
528 for(size_t i
{0};i
< ConvolveUpdateSamples
;++i
)
529 mOutput
[c
][ConvolveUpdateSamples
+i
] =
530 static_cast<float>(mFftBuffer
[ConvolveUpdateSamples
+i
].real() *
531 (1.0/double{ConvolveUpdateSize
}));
534 /* Shift the input history. */
535 curseg
= curseg
? (curseg
-1) : (mNumConvolveSegs
-1);
537 mCurrentSegment
= curseg
;
539 /* Finally, mix to the output. */
540 (this->*mMix
)(samplesOut
, samplesToDo
);
544 void ConvolutionEffect_setParami(EffectProps
* /*props*/, ALenum param
, int /*val*/)
549 throw effect_exception
{AL_INVALID_ENUM
, "Invalid null effect integer property 0x%04x",
553 void ConvolutionEffect_setParamiv(EffectProps
*props
, ALenum param
, const int *vals
)
558 ConvolutionEffect_setParami(props
, param
, vals
[0]);
561 void ConvolutionEffect_setParamf(EffectProps
* /*props*/, ALenum param
, float /*val*/)
566 throw effect_exception
{AL_INVALID_ENUM
, "Invalid null effect float property 0x%04x",
570 void ConvolutionEffect_setParamfv(EffectProps
*props
, ALenum param
, const float *vals
)
575 ConvolutionEffect_setParamf(props
, param
, vals
[0]);
579 void ConvolutionEffect_getParami(const EffectProps
* /*props*/, ALenum param
, int* /*val*/)
584 throw effect_exception
{AL_INVALID_ENUM
, "Invalid null effect integer property 0x%04x",
588 void ConvolutionEffect_getParamiv(const EffectProps
*props
, ALenum param
, int *vals
)
593 ConvolutionEffect_getParami(props
, param
, vals
);
596 void ConvolutionEffect_getParamf(const EffectProps
* /*props*/, ALenum param
, float* /*val*/)
601 throw effect_exception
{AL_INVALID_ENUM
, "Invalid null effect float property 0x%04x",
605 void ConvolutionEffect_getParamfv(const EffectProps
*props
, ALenum param
, float *vals
)
610 ConvolutionEffect_getParamf(props
, param
, vals
);
614 DEFINE_ALEFFECT_VTABLE(ConvolutionEffect
);
617 struct ConvolutionStateFactory final
: public EffectStateFactory
{
618 EffectState
*create() override
;
619 EffectProps
getDefaultProps() const noexcept override
;
620 const EffectVtable
*getEffectVtable() const noexcept override
;
623 /* Creates EffectState objects of the appropriate type. */
624 EffectState
*ConvolutionStateFactory::create()
625 { return new ConvolutionState
{}; }
627 /* Returns an ALeffectProps initialized with this effect type's default
630 EffectProps
ConvolutionStateFactory::getDefaultProps() const noexcept
636 /* Returns a pointer to this effect type's global set/get vtable. */
637 const EffectVtable
*ConvolutionStateFactory::getEffectVtable() const noexcept
638 { return &ConvolutionEffect_vtable
; }
642 EffectStateFactory
*ConvolutionStateFactory_getFactory()
644 static ConvolutionStateFactory ConvolutionFactory
{};
645 return &ConvolutionFactory
;