vmorpher.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. /**
  2. * This file is part of the OpenAL Soft cross platform audio library
  3. *
  4. * Copyright (C) 2019 by Anis A. Hireche
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions are met:
  8. *
  9. * * Redistributions of source code must retain the above copyright notice,
  10. * this list of conditions and the following disclaimer.
  11. *
  12. * * Redistributions in binary form must reproduce the above copyright notice,
  13. * this list of conditions and the following disclaimer in the documentation
  14. * and/or other materials provided with the distribution.
  15. *
  16. * * Neither the name of Spherical-Harmonic-Transform nor the names of its
  17. * contributors may be used to endorse or promote products derived from
  18. * this software without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  21. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  23. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  24. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  25. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  26. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  27. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  28. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  29. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30. * POSSIBILITY OF SUCH DAMAGE.
  31. */
  32. #include "config.h"
  33. #include <algorithm>
  34. #include <array>
  35. #include <cmath>
  36. #include <cstdlib>
  37. #include <functional>
  38. #include <variant>
  39. #include "alc/effects/base.h"
  40. #include "alnumbers.h"
  41. #include "alnumeric.h"
  42. #include "alspan.h"
  43. #include "core/ambidefs.h"
  44. #include "core/bufferline.h"
  45. #include "core/context.h"
  46. #include "core/device.h"
  47. #include "core/effects/base.h"
  48. #include "core/effectslot.h"
  49. #include "core/mixer.h"
  50. #include "intrusive_ptr.h"
  51. struct BufferStorage;
  52. namespace {
  53. using uint = unsigned int;
  54. constexpr size_t MaxUpdateSamples{256};
  55. constexpr size_t NumFormants{4};
  56. constexpr float RcpQFactor{1.0f / 5.0f};
  57. enum : size_t {
  58. VowelAIndex,
  59. VowelBIndex,
  60. NumFilters
  61. };
  62. constexpr size_t WaveformFracBits{24};
  63. constexpr size_t WaveformFracOne{1<<WaveformFracBits};
  64. constexpr size_t WaveformFracMask{WaveformFracOne-1};
  65. inline float Sin(uint index)
  66. {
  67. constexpr float scale{al::numbers::pi_v<float>*2.0f / float{WaveformFracOne}};
  68. return std::sin(static_cast<float>(index) * scale)*0.5f + 0.5f;
  69. }
  70. inline float Saw(uint index)
  71. { return static_cast<float>(index) / float{WaveformFracOne}; }
  72. inline float Triangle(uint index)
  73. { return std::fabs(static_cast<float>(index)*(2.0f/WaveformFracOne) - 1.0f); }
  74. inline float Half(uint) { return 0.5f; }
  75. template<float (&func)(uint)>
  76. void Oscillate(const al::span<float> dst, uint index, const uint step)
  77. {
  78. std::generate(dst.begin(), dst.end(), [&index,step]
  79. {
  80. index += step;
  81. index &= WaveformFracMask;
  82. return func(index);
  83. });
  84. }
  85. struct FormantFilter {
  86. float mCoeff{0.0f};
  87. float mGain{1.0f};
  88. float mS1{0.0f};
  89. float mS2{0.0f};
  90. FormantFilter() = default;
  91. FormantFilter(float f0norm, float gain)
  92. : mCoeff{std::tan(al::numbers::pi_v<float> * f0norm)}, mGain{gain}
  93. { }
  94. void process(const float *samplesIn, float *samplesOut, const size_t numInput) noexcept
  95. {
  96. /* A state variable filter from a topology-preserving transform.
  97. * Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg
  98. */
  99. const float g{mCoeff};
  100. const float gain{mGain};
  101. const float h{1.0f / (1.0f + (g*RcpQFactor) + (g*g))};
  102. const float coeff{RcpQFactor + g};
  103. float s1{mS1};
  104. float s2{mS2};
  105. const auto input = al::span{samplesIn, numInput};
  106. const auto output = al::span{samplesOut, numInput};
  107. std::transform(input.cbegin(), input.cend(), output.cbegin(), output.begin(),
  108. [g,gain,h,coeff,&s1,&s2](const float in, const float out) noexcept -> float
  109. {
  110. const float H{(in - coeff*s1 - s2)*h};
  111. const float B{g*H + s1};
  112. const float L{g*B + s2};
  113. s1 = g*H + B;
  114. s2 = g*B + L;
  115. // Apply peak and accumulate samples.
  116. return out + B*gain;
  117. });
  118. mS1 = s1;
  119. mS2 = s2;
  120. }
  121. void clear() noexcept
  122. {
  123. mS1 = 0.0f;
  124. mS2 = 0.0f;
  125. }
  126. };
  127. struct VmorpherState final : public EffectState {
  128. struct OutParams {
  129. uint mTargetChannel{InvalidChannelIndex};
  130. /* Effect parameters */
  131. std::array<std::array<FormantFilter,NumFormants>,NumFilters> mFormants;
  132. /* Effect gains for each channel */
  133. float mCurrentGain{};
  134. float mTargetGain{};
  135. };
  136. std::array<OutParams,MaxAmbiChannels> mChans;
  137. void (*mGetSamples)(const al::span<float> dst, uint index, const uint step){};
  138. uint mIndex{0};
  139. uint mStep{1};
  140. /* Effects buffers */
  141. alignas(16) std::array<float,MaxUpdateSamples> mSampleBufferA{};
  142. alignas(16) std::array<float,MaxUpdateSamples> mSampleBufferB{};
  143. alignas(16) std::array<float,MaxUpdateSamples> mLfo{};
  144. void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
  145. void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
  146. const EffectTarget target) override;
  147. void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
  148. const al::span<FloatBufferLine> samplesOut) override;
  149. static std::array<FormantFilter,NumFormants> getFiltersByPhoneme(VMorpherPhenome phoneme,
  150. float frequency, float pitch) noexcept;
  151. };
  152. std::array<FormantFilter,NumFormants> VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme,
  153. float frequency, float pitch) noexcept
  154. {
  155. /* Using soprano formant set of values to
  156. * better match mid-range frequency space.
  157. *
  158. * See: https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html
  159. */
  160. switch(phoneme)
  161. {
  162. case VMorpherPhenome::A:
  163. return {{
  164. {( 800 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
  165. {(1150 * pitch) / frequency, 0.501187f}, /* std::pow(10.0f, -6 / 20.0f); */
  166. {(2900 * pitch) / frequency, 0.025118f}, /* std::pow(10.0f, -32 / 20.0f); */
  167. {(3900 * pitch) / frequency, 0.100000f} /* std::pow(10.0f, -20 / 20.0f); */
  168. }};
  169. case VMorpherPhenome::E:
  170. return {{
  171. {( 350 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
  172. {(2000 * pitch) / frequency, 0.100000f}, /* std::pow(10.0f, -20 / 20.0f); */
  173. {(2800 * pitch) / frequency, 0.177827f}, /* std::pow(10.0f, -15 / 20.0f); */
  174. {(3600 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
  175. }};
  176. case VMorpherPhenome::I:
  177. return {{
  178. {( 270 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
  179. {(2140 * pitch) / frequency, 0.251188f}, /* std::pow(10.0f, -12 / 20.0f); */
  180. {(2950 * pitch) / frequency, 0.050118f}, /* std::pow(10.0f, -26 / 20.0f); */
  181. {(3900 * pitch) / frequency, 0.050118f} /* std::pow(10.0f, -26 / 20.0f); */
  182. }};
  183. case VMorpherPhenome::O:
  184. return {{
  185. {( 450 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
  186. {( 800 * pitch) / frequency, 0.281838f}, /* std::pow(10.0f, -11 / 20.0f); */
  187. {(2830 * pitch) / frequency, 0.079432f}, /* std::pow(10.0f, -22 / 20.0f); */
  188. {(3800 * pitch) / frequency, 0.079432f} /* std::pow(10.0f, -22 / 20.0f); */
  189. }};
  190. case VMorpherPhenome::U:
  191. return {{
  192. {( 325 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
  193. {( 700 * pitch) / frequency, 0.158489f}, /* std::pow(10.0f, -16 / 20.0f); */
  194. {(2700 * pitch) / frequency, 0.017782f}, /* std::pow(10.0f, -35 / 20.0f); */
  195. {(3800 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
  196. }};
  197. default:
  198. break;
  199. }
  200. return {};
  201. }
  202. void VmorpherState::deviceUpdate(const DeviceBase*, const BufferStorage*)
  203. {
  204. for(auto &e : mChans)
  205. {
  206. e.mTargetChannel = InvalidChannelIndex;
  207. std::for_each(e.mFormants[VowelAIndex].begin(), e.mFormants[VowelAIndex].end(),
  208. std::mem_fn(&FormantFilter::clear));
  209. std::for_each(e.mFormants[VowelBIndex].begin(), e.mFormants[VowelBIndex].end(),
  210. std::mem_fn(&FormantFilter::clear));
  211. e.mCurrentGain = 0.0f;
  212. }
  213. }
  214. void VmorpherState::update(const ContextBase *context, const EffectSlot *slot,
  215. const EffectProps *props_, const EffectTarget target)
  216. {
  217. auto &props = std::get<VmorpherProps>(*props_);
  218. const DeviceBase *device{context->mDevice};
  219. const float frequency{static_cast<float>(device->Frequency)};
  220. const float step{props.Rate / frequency};
  221. mStep = fastf2u(std::clamp(step*WaveformFracOne, 0.0f, WaveformFracOne-1.0f));
  222. if(mStep == 0)
  223. mGetSamples = Oscillate<Half>;
  224. else if(props.Waveform == VMorpherWaveform::Sinusoid)
  225. mGetSamples = Oscillate<Sin>;
  226. else if(props.Waveform == VMorpherWaveform::Triangle)
  227. mGetSamples = Oscillate<Triangle>;
  228. else /*if(props.Waveform == VMorpherWaveform::Sawtooth)*/
  229. mGetSamples = Oscillate<Saw>;
  230. const float pitchA{std::pow(2.0f, static_cast<float>(props.PhonemeACoarseTuning) / 12.0f)};
  231. const float pitchB{std::pow(2.0f, static_cast<float>(props.PhonemeBCoarseTuning) / 12.0f)};
  232. auto vowelA = getFiltersByPhoneme(props.PhonemeA, frequency, pitchA);
  233. auto vowelB = getFiltersByPhoneme(props.PhonemeB, frequency, pitchB);
  234. /* Copy the filter coefficients to the input channels. */
  235. for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
  236. {
  237. std::copy(vowelA.begin(), vowelA.end(), mChans[i].mFormants[VowelAIndex].begin());
  238. std::copy(vowelB.begin(), vowelB.end(), mChans[i].mFormants[VowelBIndex].begin());
  239. }
  240. mOutTarget = target.Main->Buffer;
  241. auto set_channel = [this](size_t idx, uint outchan, float outgain)
  242. {
  243. mChans[idx].mTargetChannel = outchan;
  244. mChans[idx].mTargetGain = outgain;
  245. };
  246. target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel);
  247. }
  248. void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
  249. {
  250. alignas(16) std::array<float,MaxUpdateSamples> blended{};
  251. /* Following the EFX specification for a conformant implementation which describes
  252. * the effect as a pair of 4-band formant filters blended together using an LFO.
  253. */
  254. for(size_t base{0u};base < samplesToDo;)
  255. {
  256. const size_t td{std::min(MaxUpdateSamples, samplesToDo-base)};
  257. mGetSamples(al::span{mLfo}.first(td), mIndex, mStep);
  258. mIndex += static_cast<uint>(mStep * td);
  259. mIndex &= WaveformFracMask;
  260. auto chandata = mChans.begin();
  261. for(const auto &input : samplesIn)
  262. {
  263. const size_t outidx{chandata->mTargetChannel};
  264. if(outidx == InvalidChannelIndex)
  265. {
  266. ++chandata;
  267. continue;
  268. }
  269. const auto vowelA = al::span{chandata->mFormants[VowelAIndex]};
  270. const auto vowelB = al::span{chandata->mFormants[VowelBIndex]};
  271. /* Process first vowel. */
  272. std::fill_n(mSampleBufferA.begin(), td, 0.0f);
  273. vowelA[0].process(&input[base], mSampleBufferA.data(), td);
  274. vowelA[1].process(&input[base], mSampleBufferA.data(), td);
  275. vowelA[2].process(&input[base], mSampleBufferA.data(), td);
  276. vowelA[3].process(&input[base], mSampleBufferA.data(), td);
  277. /* Process second vowel. */
  278. std::fill_n(mSampleBufferB.begin(), td, 0.0f);
  279. vowelB[0].process(&input[base], mSampleBufferB.data(), td);
  280. vowelB[1].process(&input[base], mSampleBufferB.data(), td);
  281. vowelB[2].process(&input[base], mSampleBufferB.data(), td);
  282. vowelB[3].process(&input[base], mSampleBufferB.data(), td);
  283. for(size_t i{0u};i < td;i++)
  284. blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]);
  285. /* Now, mix the processed sound data to the output. */
  286. MixSamples(al::span{blended}.first(td), al::span{samplesOut[outidx]}.subspan(base),
  287. chandata->mCurrentGain, chandata->mTargetGain, samplesToDo-base);
  288. ++chandata;
  289. }
  290. base += td;
  291. }
  292. }
  293. struct VmorpherStateFactory final : public EffectStateFactory {
  294. al::intrusive_ptr<EffectState> create() override
  295. { return al::intrusive_ptr<EffectState>{new VmorpherState{}}; }
  296. };
  297. } // namespace
  298. EffectStateFactory *VmorpherStateFactory_getFactory()
  299. {
  300. static VmorpherStateFactory VmorpherFactory{};
  301. return &VmorpherFactory;
  302. }