2 * This file is part of the OpenAL Soft cross platform audio library
4 * Copyright (C) 2019 by Anis A. Hireche
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * * Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
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.
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.
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.
42 #include "alc/effects/base.h"
43 #include "alnumbers.h"
44 #include "alnumeric.h"
46 #include "core/ambidefs.h"
47 #include "core/bufferline.h"
48 #include "core/context.h"
49 #include "core/device.h"
50 #include "core/effects/base.h"
51 #include "core/effectslot.h"
52 #include "core/mixer.h"
53 #include "intrusive_ptr.h"
59 using uint
= unsigned int;
61 constexpr size_t MaxUpdateSamples
{256};
62 constexpr size_t NumFormants
{4};
63 constexpr float RcpQFactor
{1.0f
/ 5.0f
};
70 constexpr size_t WaveformFracBits
{24};
71 constexpr size_t WaveformFracOne
{1<<WaveformFracBits
};
72 constexpr size_t WaveformFracMask
{WaveformFracOne
-1};
74 inline float Sin(uint index
)
76 constexpr float scale
{al::numbers::pi_v
<float>*2.0f
/ float{WaveformFracOne
}};
77 return std::sin(static_cast<float>(index
) * scale
)*0.5f
+ 0.5f
;
80 inline float Saw(uint index
)
81 { return static_cast<float>(index
) / float{WaveformFracOne
}; }
83 inline float Triangle(uint index
)
84 { return std::fabs(static_cast<float>(index
)*(2.0f
/WaveformFracOne
) - 1.0f
); }
86 inline float Half(uint
) { return 0.5f
; }
88 template<float (&func
)(uint
)>
89 void Oscillate(const al::span
<float> dst
, uint index
, const uint step
)
91 std::generate(dst
.begin(), dst
.end(), [&index
,step
]
94 index
&= WaveformFracMask
;
99 struct FormantFilter
{
105 FormantFilter() = default;
106 FormantFilter(float f0norm
, float gain
)
107 : mCoeff
{std::tan(al::numbers::pi_v
<float> * f0norm
)}, mGain
{gain
}
110 void process(const float *samplesIn
, float *samplesOut
, const size_t numInput
) noexcept
112 /* A state variable filter from a topology-preserving transform.
113 * Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg
115 const float g
{mCoeff
};
116 const float gain
{mGain
};
117 const float h
{1.0f
/ (1.0f
+ (g
*RcpQFactor
) + (g
*g
))};
118 const float coeff
{RcpQFactor
+ g
};
122 const auto input
= al::span
{samplesIn
, numInput
};
123 const auto output
= al::span
{samplesOut
, numInput
};
124 std::transform(input
.cbegin(), input
.cend(), output
.cbegin(), output
.begin(),
125 [g
,gain
,h
,coeff
,&s1
,&s2
](const float in
, const float out
) noexcept
-> float
127 const float H
{(in
- coeff
*s1
- s2
)*h
};
128 const float B
{g
*H
+ s1
};
129 const float L
{g
*B
+ s2
};
134 // Apply peak and accumulate samples.
141 void clear() noexcept
149 struct VmorpherState final
: public EffectState
{
151 uint mTargetChannel
{InvalidChannelIndex
};
153 /* Effect parameters */
154 std::array
<std::array
<FormantFilter
,NumFormants
>,NumFilters
> mFormants
;
156 /* Effect gains for each channel */
157 float mCurrentGain
{};
160 std::array
<OutParams
,MaxAmbiChannels
> mChans
;
162 void (*mGetSamples
)(const al::span
<float> dst
, uint index
, const uint step
){};
167 /* Effects buffers */
168 alignas(16) std::array
<float,MaxUpdateSamples
> mSampleBufferA
{};
169 alignas(16) std::array
<float,MaxUpdateSamples
> mSampleBufferB
{};
170 alignas(16) std::array
<float,MaxUpdateSamples
> mLfo
{};
172 void deviceUpdate(const DeviceBase
*device
, const BufferStorage
*buffer
) override
;
173 void update(const ContextBase
*context
, const EffectSlot
*slot
, const EffectProps
*props
,
174 const EffectTarget target
) override
;
175 void process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
,
176 const al::span
<FloatBufferLine
> samplesOut
) override
;
178 static std::array
<FormantFilter
,NumFormants
> getFiltersByPhoneme(VMorpherPhenome phoneme
,
179 float frequency
, float pitch
) noexcept
;
182 std::array
<FormantFilter
,NumFormants
> VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme
,
183 float frequency
, float pitch
) noexcept
185 /* Using soprano formant set of values to
186 * better match mid-range frequency space.
188 * See: https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html
192 case VMorpherPhenome::A
:
194 {( 800 * pitch
) / frequency
, 1.000000f
}, /* std::pow(10.0f, 0 / 20.0f); */
195 {(1150 * pitch
) / frequency
, 0.501187f
}, /* std::pow(10.0f, -6 / 20.0f); */
196 {(2900 * pitch
) / frequency
, 0.025118f
}, /* std::pow(10.0f, -32 / 20.0f); */
197 {(3900 * pitch
) / frequency
, 0.100000f
} /* std::pow(10.0f, -20 / 20.0f); */
199 case VMorpherPhenome::E
:
201 {( 350 * pitch
) / frequency
, 1.000000f
}, /* std::pow(10.0f, 0 / 20.0f); */
202 {(2000 * pitch
) / frequency
, 0.100000f
}, /* std::pow(10.0f, -20 / 20.0f); */
203 {(2800 * pitch
) / frequency
, 0.177827f
}, /* std::pow(10.0f, -15 / 20.0f); */
204 {(3600 * pitch
) / frequency
, 0.009999f
} /* std::pow(10.0f, -40 / 20.0f); */
206 case VMorpherPhenome::I
:
208 {( 270 * pitch
) / frequency
, 1.000000f
}, /* std::pow(10.0f, 0 / 20.0f); */
209 {(2140 * pitch
) / frequency
, 0.251188f
}, /* std::pow(10.0f, -12 / 20.0f); */
210 {(2950 * pitch
) / frequency
, 0.050118f
}, /* std::pow(10.0f, -26 / 20.0f); */
211 {(3900 * pitch
) / frequency
, 0.050118f
} /* std::pow(10.0f, -26 / 20.0f); */
213 case VMorpherPhenome::O
:
215 {( 450 * pitch
) / frequency
, 1.000000f
}, /* std::pow(10.0f, 0 / 20.0f); */
216 {( 800 * pitch
) / frequency
, 0.281838f
}, /* std::pow(10.0f, -11 / 20.0f); */
217 {(2830 * pitch
) / frequency
, 0.079432f
}, /* std::pow(10.0f, -22 / 20.0f); */
218 {(3800 * pitch
) / frequency
, 0.079432f
} /* std::pow(10.0f, -22 / 20.0f); */
220 case VMorpherPhenome::U
:
222 {( 325 * pitch
) / frequency
, 1.000000f
}, /* std::pow(10.0f, 0 / 20.0f); */
223 {( 700 * pitch
) / frequency
, 0.158489f
}, /* std::pow(10.0f, -16 / 20.0f); */
224 {(2700 * pitch
) / frequency
, 0.017782f
}, /* std::pow(10.0f, -35 / 20.0f); */
225 {(3800 * pitch
) / frequency
, 0.009999f
} /* std::pow(10.0f, -40 / 20.0f); */
234 void VmorpherState::deviceUpdate(const DeviceBase
*, const BufferStorage
*)
236 for(auto &e
: mChans
)
238 e
.mTargetChannel
= InvalidChannelIndex
;
239 std::for_each(e
.mFormants
[VowelAIndex
].begin(), e
.mFormants
[VowelAIndex
].end(),
240 std::mem_fn(&FormantFilter::clear
));
241 std::for_each(e
.mFormants
[VowelBIndex
].begin(), e
.mFormants
[VowelBIndex
].end(),
242 std::mem_fn(&FormantFilter::clear
));
243 e
.mCurrentGain
= 0.0f
;
247 void VmorpherState::update(const ContextBase
*context
, const EffectSlot
*slot
,
248 const EffectProps
*props_
, const EffectTarget target
)
250 auto &props
= std::get
<VmorpherProps
>(*props_
);
251 const DeviceBase
*device
{context
->mDevice
};
252 const float frequency
{static_cast<float>(device
->Frequency
)};
253 const float step
{props
.Rate
/ frequency
};
254 mStep
= fastf2u(std::clamp(step
*WaveformFracOne
, 0.0f
, WaveformFracOne
-1.0f
));
257 mGetSamples
= Oscillate
<Half
>;
258 else if(props
.Waveform
== VMorpherWaveform::Sinusoid
)
259 mGetSamples
= Oscillate
<Sin
>;
260 else if(props
.Waveform
== VMorpherWaveform::Triangle
)
261 mGetSamples
= Oscillate
<Triangle
>;
262 else /*if(props.Waveform == VMorpherWaveform::Sawtooth)*/
263 mGetSamples
= Oscillate
<Saw
>;
265 const float pitchA
{std::pow(2.0f
, static_cast<float>(props
.PhonemeACoarseTuning
) / 12.0f
)};
266 const float pitchB
{std::pow(2.0f
, static_cast<float>(props
.PhonemeBCoarseTuning
) / 12.0f
)};
268 auto vowelA
= getFiltersByPhoneme(props
.PhonemeA
, frequency
, pitchA
);
269 auto vowelB
= getFiltersByPhoneme(props
.PhonemeB
, frequency
, pitchB
);
271 /* Copy the filter coefficients to the input channels. */
272 for(size_t i
{0u};i
< slot
->Wet
.Buffer
.size();++i
)
274 std::copy(vowelA
.begin(), vowelA
.end(), mChans
[i
].mFormants
[VowelAIndex
].begin());
275 std::copy(vowelB
.begin(), vowelB
.end(), mChans
[i
].mFormants
[VowelBIndex
].begin());
278 mOutTarget
= target
.Main
->Buffer
;
279 auto set_channel
= [this](size_t idx
, uint outchan
, float outgain
)
281 mChans
[idx
].mTargetChannel
= outchan
;
282 mChans
[idx
].mTargetGain
= outgain
;
284 target
.Main
->setAmbiMixParams(slot
->Wet
, slot
->Gain
, set_channel
);
287 void VmorpherState::process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
)
289 alignas(16) std::array
<float,MaxUpdateSamples
> blended
{};
291 /* Following the EFX specification for a conformant implementation which describes
292 * the effect as a pair of 4-band formant filters blended together using an LFO.
294 for(size_t base
{0u};base
< samplesToDo
;)
296 const size_t td
{std::min(MaxUpdateSamples
, samplesToDo
-base
)};
298 mGetSamples(al::span
{mLfo
}.first(td
), mIndex
, mStep
);
299 mIndex
+= static_cast<uint
>(mStep
* td
);
300 mIndex
&= WaveformFracMask
;
302 auto chandata
= mChans
.begin();
303 for(const auto &input
: samplesIn
)
305 const size_t outidx
{chandata
->mTargetChannel
};
306 if(outidx
== InvalidChannelIndex
)
312 const auto vowelA
= al::span
{chandata
->mFormants
[VowelAIndex
]};
313 const auto vowelB
= al::span
{chandata
->mFormants
[VowelBIndex
]};
315 /* Process first vowel. */
316 std::fill_n(mSampleBufferA
.begin(), td
, 0.0f
);
317 vowelA
[0].process(&input
[base
], mSampleBufferA
.data(), td
);
318 vowelA
[1].process(&input
[base
], mSampleBufferA
.data(), td
);
319 vowelA
[2].process(&input
[base
], mSampleBufferA
.data(), td
);
320 vowelA
[3].process(&input
[base
], mSampleBufferA
.data(), td
);
322 /* Process second vowel. */
323 std::fill_n(mSampleBufferB
.begin(), td
, 0.0f
);
324 vowelB
[0].process(&input
[base
], mSampleBufferB
.data(), td
);
325 vowelB
[1].process(&input
[base
], mSampleBufferB
.data(), td
);
326 vowelB
[2].process(&input
[base
], mSampleBufferB
.data(), td
);
327 vowelB
[3].process(&input
[base
], mSampleBufferB
.data(), td
);
329 for(size_t i
{0u};i
< td
;i
++)
330 blended
[i
] = lerpf(mSampleBufferA
[i
], mSampleBufferB
[i
], mLfo
[i
]);
332 /* Now, mix the processed sound data to the output. */
333 MixSamples(al::span
{blended
}.first(td
), al::span
{samplesOut
[outidx
]}.subspan(base
),
334 chandata
->mCurrentGain
, chandata
->mTargetGain
, samplesToDo
-base
);
343 struct VmorpherStateFactory final
: public EffectStateFactory
{
344 al::intrusive_ptr
<EffectState
> create() override
345 { return al::intrusive_ptr
<EffectState
>{new VmorpherState
{}}; }
350 EffectStateFactory
*VmorpherStateFactory_getFactory()
352 static VmorpherStateFactory VmorpherFactory
{};
353 return &VmorpherFactory
;