Workaround a GCC 5 issue
[openal-soft.git] / alc / effects / convolution.cpp
blobffd2f0c758b687bdd01574b084ab01fa76213b63
2 #include "config.h"
4 #ifdef HAVE_SSE_INTRINSICS
5 #include <xmmintrin.h>
6 #endif
8 #include "AL/al.h"
9 #include "AL/alc.h"
11 #include "al/auxeffectslot.h"
12 #include "alcmain.h"
13 #include "alcomplex.h"
14 #include "alcontext.h"
15 #include "almalloc.h"
16 #include "alspan.h"
17 #include "ambidefs.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"
23 #include "logging.h"
24 #include "polyphase_resampler.h"
27 namespace {
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
64 switch(srctype)
66 HANDLE_FMT(FmtUByte);
67 HANDLE_FMT(FmtShort);
68 HANDLE_FMT(FmtFloat);
69 HANDLE_FMT(FmtDouble);
70 HANDLE_FMT(FmtMulaw);
71 HANDLE_FMT(FmtAlaw);
73 #undef HANDLE_FMT
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;
97 struct ChanMap {
98 Channel channel;
99 float angle;
100 float elevation;
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);
126 ++src;
129 #else
131 for(float &output : dst)
133 float ret{0.0f};
134 for(size_t j{0};j < ConvolveUpdateSamples;++j)
135 ret += src[j] * filter[j];
136 output = ret;
137 ++src;
139 #endif
142 struct ConvolutionState final : public EffectState {
143 FmtChannels mChannels{};
144 AmbiLayout mAmbiLayout{};
145 AmbiScaling mAmbiScaling{};
146 ALuint mAmbiOrder{};
148 size_t mFifoPos{0};
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};
158 struct ChannelData {
159 alignas(16) FloatBufferLine mBuffer{};
160 float mHfScale{};
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,
191 samplesToDo, 0);
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};
214 mFifoPos = 0;
215 mInput.fill(0.0f);
216 decltype(mFilter){}.swap(mFilter);
217 decltype(mOutput){}.swap(mOutput);
218 mFftBuffer.fill(complex_d{});
220 mCurrentSegment = 0;
221 mNumConvolveSegs = 0;
223 mChans = nullptr;
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,
282 srcsamples.get());
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());
297 done += todo;
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 }
320 }, StereoMap[2]{
321 { FrontLeft, Deg2Rad(-45.0f), Deg2Rad(0.0f) },
322 { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) }
323 }, RearMap[2]{
324 { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
325 { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
326 }, QuadMap[4]{
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) }
331 }, X51Map[6]{
332 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
333 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
334 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
335 { LFE, 0.0f, 0.0f },
336 { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) },
337 { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) }
338 }, X61Map[7]{
339 { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
340 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
341 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
342 { LFE, 0.0f, 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) }
346 }, X71Map[8]{
347 { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
348 { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
349 { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
350 { LFE, 0.0f, 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)
358 return;
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);
389 coeffs[acn] = 0.0f;
392 else
394 ALCdevice *device{context->mDevice.get()};
395 al::span<const ChanMap> chanmap{};
396 switch(mChannels)
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;
405 case FmtBFormat2D:
406 case FmtBFormat3D:
407 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);
418 return 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)
442 return;
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,
462 mFilter[c].data());
464 auto fifo_iter = mOutput[c].begin() + mFifoPos;
465 std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{});
468 mFifoPos += todo;
469 base += todo;
471 /* Check whether the input buffer is filled with new samples. */
472 if(mFifoPos < ConvolveUpdateSamples) break;
473 mFifoPos = 0;
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
493 * (aligned in time).
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
509 * inverse FFT.
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)
525 mOutput[c][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*/)
546 switch(param)
548 default:
549 throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
550 param};
553 void ConvolutionEffect_setParamiv(EffectProps *props, ALenum param, const int *vals)
555 switch(param)
557 default:
558 ConvolutionEffect_setParami(props, param, vals[0]);
561 void ConvolutionEffect_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/)
563 switch(param)
565 default:
566 throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
567 param};
570 void ConvolutionEffect_setParamfv(EffectProps *props, ALenum param, const float *vals)
572 switch(param)
574 default:
575 ConvolutionEffect_setParamf(props, param, vals[0]);
579 void ConvolutionEffect_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/)
581 switch(param)
583 default:
584 throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
585 param};
588 void ConvolutionEffect_getParamiv(const EffectProps *props, ALenum param, int *vals)
590 switch(param)
592 default:
593 ConvolutionEffect_getParami(props, param, vals);
596 void ConvolutionEffect_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/)
598 switch(param)
600 default:
601 throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
602 param};
605 void ConvolutionEffect_getParamfv(const EffectProps *props, ALenum param, float *vals)
607 switch(param)
609 default:
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
628 * property values.
630 EffectProps ConvolutionStateFactory::getDefaultProps() const noexcept
632 EffectProps props{};
633 return props;
636 /* Returns a pointer to this effect type's global set/get vtable. */
637 const EffectVtable *ConvolutionStateFactory::getEffectVtable() const noexcept
638 { return &ConvolutionEffect_vtable; }
640 } // namespace
642 EffectStateFactory *ConvolutionStateFactory_getFactory()
644 static ConvolutionStateFactory ConvolutionFactory{};
645 return &ConvolutionFactory;