2 * OpenAL cross platform audio library
3 * Copyright (C) 2018 by Raul Herraiz.
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.
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.
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
30 #include "alc/effects/base.h"
31 #include "alnumbers.h"
32 #include "alnumeric.h"
34 #include "core/ambidefs.h"
35 #include "core/bufferline.h"
36 #include "core/device.h"
37 #include "core/effects/base.h"
38 #include "core/effectslot.h"
39 #include "core/mixer.h"
40 #include "core/mixer/defs.h"
41 #include "intrusive_ptr.h"
50 using uint
= unsigned int;
51 using complex_f
= std::complex<float>;
53 constexpr size_t StftSize
{1024};
54 constexpr size_t StftHalfSize
{StftSize
>> 1};
55 constexpr size_t OversampleFactor
{8};
57 static_assert(StftSize
%OversampleFactor
== 0, "Factor must be a clean divisor of the size");
58 constexpr size_t StftStep
{StftSize
/ OversampleFactor
};
60 /* Define a Hann window, used to filter the STFT input and output. */
62 alignas(16) std::array
<float,StftSize
> mData
{};
66 /* Create lookup table of the Hann window for the desired size. */
67 for(size_t i
{0};i
< StftHalfSize
;i
++)
69 constexpr double scale
{al::numbers::pi
/ double{StftSize
}};
70 const double val
{std::sin((static_cast<double>(i
)+0.5) * scale
)};
71 mData
[i
] = mData
[StftSize
-1-i
] = static_cast<float>(val
* val
);
75 const Windower gWindow
{};
84 struct PshifterState final
: public EffectState
{
85 /* Effect parameters */
92 std::array
<float,StftSize
> mFIFO
{};
93 std::array
<float,StftHalfSize
+1> mLastPhase
{};
94 std::array
<float,StftHalfSize
+1> mSumPhase
{};
95 std::array
<float,StftSize
> mOutputAccum
{};
98 alignas(16) std::array
<float,StftSize
> mFftBuffer
{};
99 alignas(16) std::array
<float,StftSize
> mFftWorkBuffer
{};
101 std::array
<FrequencyBin
,StftHalfSize
+1> mAnalysisBuffer
{};
102 std::array
<FrequencyBin
,StftHalfSize
+1> mSynthesisBuffer
{};
104 alignas(16) FloatBufferLine mBufferOut
{};
106 /* Effect gains for each output channel */
107 std::array
<float,MaxAmbiChannels
> mCurrentGains
{};
108 std::array
<float,MaxAmbiChannels
> mTargetGains
{};
111 void deviceUpdate(const DeviceBase
*device
, const BufferStorage
*buffer
) override
;
112 void update(const ContextBase
*context
, const EffectSlot
*slot
, const EffectProps
*props
,
113 const EffectTarget target
) override
;
114 void process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
,
115 const al::span
<FloatBufferLine
> samplesOut
) override
;
118 void PshifterState::deviceUpdate(const DeviceBase
*, const BufferStorage
*)
120 /* (Re-)initializing parameters and clear the buffers. */
122 mPos
= StftSize
- StftStep
;
123 mPitchShiftI
= MixerFracOne
;
127 mLastPhase
.fill(0.0f
);
128 mSumPhase
.fill(0.0f
);
129 mOutputAccum
.fill(0.0f
);
130 mFftBuffer
.fill(0.0f
);
131 mAnalysisBuffer
.fill(FrequencyBin
{});
132 mSynthesisBuffer
.fill(FrequencyBin
{});
134 mCurrentGains
.fill(0.0f
);
135 mTargetGains
.fill(0.0f
);
138 mFft
= PFFFTSetup
{StftSize
, PFFFT_REAL
};
141 void PshifterState::update(const ContextBase
*, const EffectSlot
*slot
,
142 const EffectProps
*props_
, const EffectTarget target
)
144 auto &props
= std::get
<PshifterProps
>(*props_
);
145 const int tune
{props
.CoarseTune
*100 + props
.FineTune
};
146 const float pitch
{std::pow(2.0f
, static_cast<float>(tune
) / 1200.0f
)};
147 mPitchShiftI
= std::clamp(fastf2u(pitch
*MixerFracOne
), uint
{MixerFracHalf
},
148 uint
{MixerFracOne
}*2u);
149 mPitchShift
= static_cast<float>(mPitchShiftI
) * float{1.0f
/MixerFracOne
};
151 static constexpr auto coeffs
= CalcDirectionCoeffs(std::array
{0.0f
, 0.0f
, -1.0f
});
153 mOutTarget
= target
.Main
->Buffer
;
154 ComputePanGains(target
.Main
, coeffs
, slot
->Gain
, mTargetGains
);
157 void PshifterState::process(const size_t samplesToDo
,
158 const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
)
160 /* Pitch shifter engine based on the work of Stephan Bernsee.
161 * http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/
164 /* Cycle offset per update expected of each frequency bin (bin 0 is none,
165 * bin 1 is x1, bin 2 is x2, etc).
167 constexpr float expected_cycles
{al::numbers::pi_v
<float>*2.0f
/ OversampleFactor
};
169 for(size_t base
{0u};base
< samplesToDo
;)
171 const size_t todo
{std::min(StftStep
-mCount
, samplesToDo
-base
)};
173 /* Retrieve the output samples from the FIFO and fill in the new input
176 auto fifo_iter
= mFIFO
.begin()+mPos
+ mCount
;
177 std::copy_n(fifo_iter
, todo
, mBufferOut
.begin()+base
);
179 std::copy_n(samplesIn
[0].begin()+base
, todo
, fifo_iter
);
183 /* Check whether FIFO buffer is filled with new samples. */
184 if(mCount
< StftStep
) break;
186 mPos
= (mPos
+StftStep
) & (mFIFO
.size()-1);
188 /* Time-domain signal windowing, store in FftBuffer, and apply a
189 * forward FFT to get the frequency-domain signal.
191 for(size_t src
{mPos
}, k
{0u};src
< StftSize
;++src
,++k
)
192 mFftBuffer
[k
] = mFIFO
[src
] * gWindow
.mData
[k
];
193 for(size_t src
{0u}, k
{StftSize
-mPos
};src
< mPos
;++src
,++k
)
194 mFftBuffer
[k
] = mFIFO
[src
] * gWindow
.mData
[k
];
195 mFft
.transform_ordered(mFftBuffer
.data(), mFftBuffer
.data(), mFftWorkBuffer
.data(),
198 /* Analyze the obtained data. Since the real FFT is symmetric, only
199 * StftHalfSize+1 samples are needed.
201 for(size_t k
{0u};k
< StftHalfSize
+1;++k
)
203 const auto cplx
= (k
== 0) ? complex_f
{mFftBuffer
[0]} :
204 (k
== StftHalfSize
) ? complex_f
{mFftBuffer
[1]} :
205 complex_f
{mFftBuffer
[k
*2], mFftBuffer
[k
*2 + 1]};
206 const float magnitude
{std::abs(cplx
)};
207 const float phase
{std::arg(cplx
)};
209 /* Compute the phase difference from the last update and subtract
210 * the expected phase difference for this bin.
212 * When oversampling, the expected per-update offset increments by
213 * 1/OversampleFactor for every frequency bin. So, the offset wraps
214 * every 'OversampleFactor' bin.
216 const auto bin_offset
= static_cast<float>(k
% OversampleFactor
);
217 float tmp
{(phase
- mLastPhase
[k
]) - bin_offset
*expected_cycles
};
218 /* Store the actual phase for the next update. */
219 mLastPhase
[k
] = phase
;
221 /* Normalize from pi, and wrap the delta between -1 and +1. */
222 tmp
*= al::numbers::inv_pi_v
<float>;
223 int qpd
{float2int(tmp
)};
224 tmp
-= static_cast<float>(qpd
+ (qpd
%2));
226 /* Get deviation from bin frequency (-0.5 to +0.5), and account for
229 tmp
*= 0.5f
* OversampleFactor
;
231 /* Compute the k-th partials' frequency bin target and store the
232 * magnitude and frequency bin in the analysis buffer. We don't
233 * need the "true frequency" since it's a linear relationship with
236 mAnalysisBuffer
[k
].Magnitude
= magnitude
;
237 mAnalysisBuffer
[k
].FreqBin
= static_cast<float>(k
) + tmp
;
240 /* Shift the frequency bins according to the pitch adjustment,
241 * accumulating the magnitudes of overlapping frequency bins.
243 std::fill(mSynthesisBuffer
.begin(), mSynthesisBuffer
.end(), FrequencyBin
{});
245 static constexpr size_t bin_limit
{((StftHalfSize
+1)<<MixerFracBits
) - MixerFracHalf
- 1};
246 const size_t bin_count
{std::min(StftHalfSize
+1, bin_limit
/mPitchShiftI
+ 1)};
247 for(size_t k
{0u};k
< bin_count
;k
++)
249 const size_t j
{(k
*mPitchShiftI
+ MixerFracHalf
) >> MixerFracBits
};
251 /* If more than two bins end up together, use the target frequency
252 * bin for the one with the dominant magnitude. There might be a
253 * better way to handle this, but it's better than last-index-wins.
255 if(mAnalysisBuffer
[k
].Magnitude
> mSynthesisBuffer
[j
].Magnitude
)
256 mSynthesisBuffer
[j
].FreqBin
= mAnalysisBuffer
[k
].FreqBin
* mPitchShift
;
257 mSynthesisBuffer
[j
].Magnitude
+= mAnalysisBuffer
[k
].Magnitude
;
260 /* Reconstruct the frequency-domain signal from the adjusted frequency
263 for(size_t k
{0u};k
< StftHalfSize
+1;k
++)
265 /* Calculate the actual delta phase for this bin's target frequency
266 * bin, and accumulate it to get the actual bin phase.
268 float tmp
{mSumPhase
[k
] + mSynthesisBuffer
[k
].FreqBin
*expected_cycles
};
270 /* Wrap between -pi and +pi for the sum. If mSumPhase is left to
271 * grow indefinitely, it will lose precision and produce less exact
274 tmp
*= al::numbers::inv_pi_v
<float>;
275 int qpd
{float2int(tmp
)};
276 tmp
-= static_cast<float>(qpd
+ (qpd
%2));
277 mSumPhase
[k
] = tmp
* al::numbers::pi_v
<float>;
279 const complex_f cplx
{std::polar(mSynthesisBuffer
[k
].Magnitude
, mSumPhase
[k
])};
281 mFftBuffer
[0] = cplx
.real();
282 else if(k
== StftHalfSize
)
283 mFftBuffer
[1] = cplx
.real();
286 mFftBuffer
[k
*2 + 0] = cplx
.real();
287 mFftBuffer
[k
*2 + 1] = cplx
.imag();
291 /* Apply an inverse FFT to get the time-domain signal, and accumulate
292 * for the output with windowing.
294 mFft
.transform_ordered(mFftBuffer
.data(), mFftBuffer
.data(), mFftWorkBuffer
.data(),
297 static constexpr float scale
{3.0f
/ OversampleFactor
/ StftSize
};
298 for(size_t dst
{mPos
}, k
{0u};dst
< StftSize
;++dst
,++k
)
299 mOutputAccum
[dst
] += gWindow
.mData
[k
]*mFftBuffer
[k
] * scale
;
300 for(size_t dst
{0u}, k
{StftSize
-mPos
};dst
< mPos
;++dst
,++k
)
301 mOutputAccum
[dst
] += gWindow
.mData
[k
]*mFftBuffer
[k
] * scale
;
303 /* Copy out the accumulated result, then clear for the next iteration. */
304 std::copy_n(mOutputAccum
.begin() + mPos
, StftStep
, mFIFO
.begin() + mPos
);
305 std::fill_n(mOutputAccum
.begin() + mPos
, StftStep
, 0.0f
);
308 /* Now, mix the processed sound data to the output. */
309 MixSamples(al::span
{mBufferOut
}.first(samplesToDo
), samplesOut
, mCurrentGains
, mTargetGains
,
310 std::max(samplesToDo
, 512_uz
), 0);
314 struct PshifterStateFactory final
: public EffectStateFactory
{
315 al::intrusive_ptr
<EffectState
> create() override
316 { return al::intrusive_ptr
<EffectState
>{new PshifterState
{}}; }
321 EffectStateFactory
*PshifterStateFactory_getFactory()
323 static PshifterStateFactory PshifterFactory
{};
324 return &PshifterFactory
;