chorus.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. /**
  2. * OpenAL cross platform audio library
  3. * Copyright (C) 2013 by Mike Gorchak
  4. * This library is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU Library General Public
  6. * License as published by the Free Software Foundation; either
  7. * version 2 of the License, or (at your option) any later version.
  8. *
  9. * This library is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * Library General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Library General Public
  15. * License along with this library; if not, write to the
  16. * Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. * Or go to http://www.gnu.org/copyleft/lgpl.html
  19. */
  20. #include "config.h"
  21. #include <algorithm>
  22. #include <climits>
  23. #include <cmath>
  24. #include <cstdlib>
  25. #include <iterator>
  26. #include "AL/al.h"
  27. #include "AL/alc.h"
  28. #include "AL/efx.h"
  29. #include "al/auxeffectslot.h"
  30. #include "alcmain.h"
  31. #include "alcontext.h"
  32. #include "almalloc.h"
  33. #include "alnumeric.h"
  34. #include "alspan.h"
  35. #include "alu.h"
  36. #include "ambidefs.h"
  37. #include "effects/base.h"
  38. #include "math_defs.h"
  39. #include "opthelpers.h"
  40. #include "vector.h"
  41. namespace {
  42. static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch");
  43. static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch");
  44. enum class WaveForm {
  45. Sinusoid,
  46. Triangle
  47. };
  48. void GetTriangleDelays(ALuint *delays, const ALuint start_offset, const ALuint lfo_range,
  49. const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const size_t todo)
  50. {
  51. ASSUME(lfo_range > 0);
  52. ASSUME(todo > 0);
  53. ALuint offset{start_offset};
  54. auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALuint
  55. {
  56. offset = (offset+1)%lfo_range;
  57. const float offset_norm{static_cast<float>(offset) * lfo_scale};
  58. return static_cast<ALuint>(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay);
  59. };
  60. std::generate_n(delays, todo, gen_lfo);
  61. }
  62. void GetSinusoidDelays(ALuint *delays, const ALuint start_offset, const ALuint lfo_range,
  63. const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const size_t todo)
  64. {
  65. ASSUME(lfo_range > 0);
  66. ASSUME(todo > 0);
  67. ALuint offset{start_offset};
  68. auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALuint
  69. {
  70. offset = (offset+1)%lfo_range;
  71. const float offset_norm{static_cast<float>(offset) * lfo_scale};
  72. return static_cast<ALuint>(fastf2i(std::sin(offset_norm)*depth) + delay);
  73. };
  74. std::generate_n(delays, todo, gen_lfo);
  75. }
  76. struct ChorusState final : public EffectState {
  77. al::vector<ALfloat,16> mSampleBuffer;
  78. ALuint mOffset{0};
  79. ALuint mLfoOffset{0};
  80. ALuint mLfoRange{1};
  81. ALfloat mLfoScale{0.0f};
  82. ALuint mLfoDisp{0};
  83. /* Gains for left and right sides */
  84. struct {
  85. ALfloat Current[MAX_OUTPUT_CHANNELS]{};
  86. ALfloat Target[MAX_OUTPUT_CHANNELS]{};
  87. } mGains[2];
  88. /* effect parameters */
  89. WaveForm mWaveform{};
  90. ALint mDelay{0};
  91. ALfloat mDepth{0.0f};
  92. ALfloat mFeedback{0.0f};
  93. ALboolean deviceUpdate(const ALCdevice *device) override;
  94. void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
  95. void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
  96. DEF_NEWDEL(ChorusState)
  97. };
  98. ALboolean ChorusState::deviceUpdate(const ALCdevice *Device)
  99. {
  100. constexpr ALfloat max_delay{maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY)};
  101. const auto frequency = static_cast<float>(Device->Frequency);
  102. const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)};
  103. if(maxlen != mSampleBuffer.size())
  104. {
  105. mSampleBuffer.resize(maxlen);
  106. mSampleBuffer.shrink_to_fit();
  107. }
  108. std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
  109. for(auto &e : mGains)
  110. {
  111. std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
  112. std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
  113. }
  114. return AL_TRUE;
  115. }
  116. void ChorusState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target)
  117. {
  118. constexpr ALsizei mindelay{(MAX_RESAMPLER_PADDING>>1) << FRACTIONBITS};
  119. switch(props->Chorus.Waveform)
  120. {
  121. case AL_CHORUS_WAVEFORM_TRIANGLE:
  122. mWaveform = WaveForm::Triangle;
  123. break;
  124. case AL_CHORUS_WAVEFORM_SINUSOID:
  125. mWaveform = WaveForm::Sinusoid;
  126. break;
  127. }
  128. /* The LFO depth is scaled to be relative to the sample delay. Clamp the
  129. * delay and depth to allow enough padding for resampling.
  130. */
  131. const ALCdevice *device{Context->mDevice.get()};
  132. const auto frequency = static_cast<float>(device->Frequency);
  133. mDelay = maxi(float2int(props->Chorus.Delay*frequency*FRACTIONONE + 0.5f), mindelay);
  134. mDepth = minf(props->Chorus.Depth * static_cast<float>(mDelay),
  135. static_cast<float>(mDelay - mindelay));
  136. mFeedback = props->Chorus.Feedback;
  137. /* Gains for left and right sides */
  138. ALfloat coeffs[2][MAX_AMBI_CHANNELS];
  139. CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f, coeffs[0]);
  140. CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f, coeffs[1]);
  141. mOutTarget = target.Main->Buffer;
  142. ComputePanGains(target.Main, coeffs[0], Slot->Params.Gain, mGains[0].Target);
  143. ComputePanGains(target.Main, coeffs[1], Slot->Params.Gain, mGains[1].Target);
  144. ALfloat rate{props->Chorus.Rate};
  145. if(!(rate > 0.0f))
  146. {
  147. mLfoOffset = 0;
  148. mLfoRange = 1;
  149. mLfoScale = 0.0f;
  150. mLfoDisp = 0;
  151. }
  152. else
  153. {
  154. /* Calculate LFO coefficient (number of samples per cycle). Limit the
  155. * max range to avoid overflow when calculating the displacement.
  156. */
  157. ALuint lfo_range{float2uint(minf(frequency/rate + 0.5f, ALfloat{INT_MAX/360 - 180}))};
  158. mLfoOffset = mLfoOffset * lfo_range / mLfoRange;
  159. mLfoRange = lfo_range;
  160. switch(mWaveform)
  161. {
  162. case WaveForm::Triangle:
  163. mLfoScale = 4.0f / static_cast<float>(mLfoRange);
  164. break;
  165. case WaveForm::Sinusoid:
  166. mLfoScale = al::MathDefs<float>::Tau() / static_cast<float>(mLfoRange);
  167. break;
  168. }
  169. /* Calculate lfo phase displacement */
  170. ALint phase{props->Chorus.Phase};
  171. if(phase < 0) phase = 360 + phase;
  172. mLfoDisp = (mLfoRange*static_cast<ALuint>(phase) + 180) / 360;
  173. }
  174. }
  175. void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
  176. {
  177. const size_t bufmask{mSampleBuffer.size()-1};
  178. const ALfloat feedback{mFeedback};
  179. const ALuint avgdelay{(static_cast<ALuint>(mDelay) + (FRACTIONONE>>1)) >> FRACTIONBITS};
  180. ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
  181. ALuint offset{mOffset};
  182. for(size_t base{0u};base < samplesToDo;)
  183. {
  184. const size_t todo{minz(256, samplesToDo-base)};
  185. ALuint moddelays[2][256];
  186. if(mWaveform == WaveForm::Sinusoid)
  187. {
  188. GetSinusoidDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay,
  189. todo);
  190. GetSinusoidDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale,
  191. mDepth, mDelay, todo);
  192. }
  193. else /*if(mWaveform == WaveForm::Triangle)*/
  194. {
  195. GetTriangleDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay,
  196. todo);
  197. GetTriangleDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale,
  198. mDepth, mDelay, todo);
  199. }
  200. mLfoOffset = (mLfoOffset+static_cast<ALuint>(todo)) % mLfoRange;
  201. alignas(16) ALfloat temps[2][256];
  202. for(size_t i{0u};i < todo;i++)
  203. {
  204. // Feed the buffer's input first (necessary for delays < 1).
  205. delaybuf[offset&bufmask] = samplesIn[0][base+i];
  206. // Tap for the left output.
  207. ALuint delay{offset - (moddelays[0][i]>>FRACTIONBITS)};
  208. ALfloat mu{static_cast<float>(moddelays[0][i]&FRACTIONMASK) * (1.0f/FRACTIONONE)};
  209. temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
  210. delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
  211. // Tap for the right output.
  212. delay = offset - (moddelays[1][i]>>FRACTIONBITS);
  213. mu = static_cast<float>(moddelays[1][i]&FRACTIONMASK) * (1.0f/FRACTIONONE);
  214. temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
  215. delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
  216. // Accumulate feedback from the average delay of the taps.
  217. delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
  218. ++offset;
  219. }
  220. for(ALsizei c{0};c < 2;c++)
  221. MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target,
  222. samplesToDo-base, base);
  223. base += todo;
  224. }
  225. mOffset = offset;
  226. }
  227. void Chorus_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
  228. {
  229. switch(param)
  230. {
  231. case AL_CHORUS_WAVEFORM:
  232. if(!(val >= AL_CHORUS_MIN_WAVEFORM && val <= AL_CHORUS_MAX_WAVEFORM))
  233. SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid chorus waveform");
  234. props->Chorus.Waveform = val;
  235. break;
  236. case AL_CHORUS_PHASE:
  237. if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE))
  238. SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus phase out of range");
  239. props->Chorus.Phase = val;
  240. break;
  241. default:
  242. context->setError(AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
  243. }
  244. }
  245. void Chorus_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
  246. { Chorus_setParami(props, context, param, vals[0]); }
  247. void Chorus_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
  248. {
  249. switch(param)
  250. {
  251. case AL_CHORUS_RATE:
  252. if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE))
  253. SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus rate out of range");
  254. props->Chorus.Rate = val;
  255. break;
  256. case AL_CHORUS_DEPTH:
  257. if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH))
  258. SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus depth out of range");
  259. props->Chorus.Depth = val;
  260. break;
  261. case AL_CHORUS_FEEDBACK:
  262. if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK))
  263. SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus feedback out of range");
  264. props->Chorus.Feedback = val;
  265. break;
  266. case AL_CHORUS_DELAY:
  267. if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY))
  268. SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus delay out of range");
  269. props->Chorus.Delay = val;
  270. break;
  271. default:
  272. context->setError(AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
  273. }
  274. }
  275. void Chorus_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
  276. { Chorus_setParamf(props, context, param, vals[0]); }
  277. void Chorus_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
  278. {
  279. switch(param)
  280. {
  281. case AL_CHORUS_WAVEFORM:
  282. *val = props->Chorus.Waveform;
  283. break;
  284. case AL_CHORUS_PHASE:
  285. *val = props->Chorus.Phase;
  286. break;
  287. default:
  288. context->setError(AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
  289. }
  290. }
  291. void Chorus_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
  292. { Chorus_getParami(props, context, param, vals); }
  293. void Chorus_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
  294. {
  295. switch(param)
  296. {
  297. case AL_CHORUS_RATE:
  298. *val = props->Chorus.Rate;
  299. break;
  300. case AL_CHORUS_DEPTH:
  301. *val = props->Chorus.Depth;
  302. break;
  303. case AL_CHORUS_FEEDBACK:
  304. *val = props->Chorus.Feedback;
  305. break;
  306. case AL_CHORUS_DELAY:
  307. *val = props->Chorus.Delay;
  308. break;
  309. default:
  310. context->setError(AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
  311. }
  312. }
  313. void Chorus_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
  314. { Chorus_getParamf(props, context, param, vals); }
  315. DEFINE_ALEFFECT_VTABLE(Chorus);
  316. struct ChorusStateFactory final : public EffectStateFactory {
  317. EffectState *create() override { return new ChorusState{}; }
  318. EffectProps getDefaultProps() const noexcept override;
  319. const EffectVtable *getEffectVtable() const noexcept override { return &Chorus_vtable; }
  320. };
  321. EffectProps ChorusStateFactory::getDefaultProps() const noexcept
  322. {
  323. EffectProps props{};
  324. props.Chorus.Waveform = AL_CHORUS_DEFAULT_WAVEFORM;
  325. props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE;
  326. props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE;
  327. props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH;
  328. props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK;
  329. props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY;
  330. return props;
  331. }
  332. void Flanger_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
  333. {
  334. switch(param)
  335. {
  336. case AL_FLANGER_WAVEFORM:
  337. if(!(val >= AL_FLANGER_MIN_WAVEFORM && val <= AL_FLANGER_MAX_WAVEFORM))
  338. SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid flanger waveform");
  339. props->Chorus.Waveform = val;
  340. break;
  341. case AL_FLANGER_PHASE:
  342. if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE))
  343. SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger phase out of range");
  344. props->Chorus.Phase = val;
  345. break;
  346. default:
  347. context->setError(AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
  348. }
  349. }
  350. void Flanger_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
  351. { Flanger_setParami(props, context, param, vals[0]); }
  352. void Flanger_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
  353. {
  354. switch(param)
  355. {
  356. case AL_FLANGER_RATE:
  357. if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE))
  358. SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger rate out of range");
  359. props->Chorus.Rate = val;
  360. break;
  361. case AL_FLANGER_DEPTH:
  362. if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH))
  363. SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger depth out of range");
  364. props->Chorus.Depth = val;
  365. break;
  366. case AL_FLANGER_FEEDBACK:
  367. if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK))
  368. SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger feedback out of range");
  369. props->Chorus.Feedback = val;
  370. break;
  371. case AL_FLANGER_DELAY:
  372. if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY))
  373. SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger delay out of range");
  374. props->Chorus.Delay = val;
  375. break;
  376. default:
  377. context->setError(AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
  378. }
  379. }
  380. void Flanger_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
  381. { Flanger_setParamf(props, context, param, vals[0]); }
  382. void Flanger_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
  383. {
  384. switch(param)
  385. {
  386. case AL_FLANGER_WAVEFORM:
  387. *val = props->Chorus.Waveform;
  388. break;
  389. case AL_FLANGER_PHASE:
  390. *val = props->Chorus.Phase;
  391. break;
  392. default:
  393. context->setError(AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
  394. }
  395. }
  396. void Flanger_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
  397. { Flanger_getParami(props, context, param, vals); }
  398. void Flanger_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
  399. {
  400. switch(param)
  401. {
  402. case AL_FLANGER_RATE:
  403. *val = props->Chorus.Rate;
  404. break;
  405. case AL_FLANGER_DEPTH:
  406. *val = props->Chorus.Depth;
  407. break;
  408. case AL_FLANGER_FEEDBACK:
  409. *val = props->Chorus.Feedback;
  410. break;
  411. case AL_FLANGER_DELAY:
  412. *val = props->Chorus.Delay;
  413. break;
  414. default:
  415. context->setError(AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
  416. }
  417. }
  418. void Flanger_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
  419. { Flanger_getParamf(props, context, param, vals); }
  420. DEFINE_ALEFFECT_VTABLE(Flanger);
  421. /* Flanger is basically a chorus with a really short delay. They can both use
  422. * the same processing functions, so piggyback flanger on the chorus functions.
  423. */
  424. struct FlangerStateFactory final : public EffectStateFactory {
  425. EffectState *create() override { return new ChorusState{}; }
  426. EffectProps getDefaultProps() const noexcept override;
  427. const EffectVtable *getEffectVtable() const noexcept override { return &Flanger_vtable; }
  428. };
  429. EffectProps FlangerStateFactory::getDefaultProps() const noexcept
  430. {
  431. EffectProps props{};
  432. props.Chorus.Waveform = AL_FLANGER_DEFAULT_WAVEFORM;
  433. props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE;
  434. props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE;
  435. props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH;
  436. props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
  437. props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY;
  438. return props;
  439. }
  440. } // namespace
  441. EffectStateFactory *ChorusStateFactory_getFactory()
  442. {
  443. static ChorusStateFactory ChorusFactory{};
  444. return &ChorusFactory;
  445. }
  446. EffectStateFactory *FlangerStateFactory_getFactory()
  447. {
  448. static FlangerStateFactory FlangerFactory{};
  449. return &FlangerFactory;
  450. }