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 "alcomplex.h"
33 #include "alnumbers.h"
34 #include "alnumeric.h"
36 #include "core/bufferline.h"
37 #include "core/devformat.h"
38 #include "core/device.h"
39 #include "core/effectslot.h"
40 #include "core/mixer.h"
41 #include "core/mixer/defs.h"
42 #include "intrusive_ptr.h"
49 using uint
= unsigned int;
50 using complex_d
= std::complex<double>;
52 constexpr size_t StftSize
{1024};
53 constexpr size_t StftHalfSize
{StftSize
>> 1};
54 constexpr size_t OversampleFactor
{4};
56 static_assert(StftSize
%OversampleFactor
== 0, "Factor must be a clean divisor of the size");
57 constexpr size_t StftStep
{StftSize
/ OversampleFactor
};
59 /* Define a Hann window, used to filter the STFT input and output. */
60 std::array
<double,StftSize
> InitHannWindow()
62 std::array
<double,StftSize
> ret
;
63 /* Create lookup table of the Hann window for the desired size. */
64 for(size_t i
{0};i
< StftHalfSize
;i
++)
66 constexpr double scale
{al::numbers::pi
/ double{StftSize
}};
67 const double val
{std::sin((static_cast<double>(i
)+0.5) * scale
)};
68 ret
[i
] = ret
[StftSize
-1-i
] = val
* val
;
72 alignas(16) const std::array
<double,StftSize
> HannWindow
= InitHannWindow();
81 struct PshifterState final
: public EffectState
{
82 /* Effect parameters */
89 std::array
<double,StftSize
> mFIFO
;
90 std::array
<double,StftHalfSize
+1> mLastPhase
;
91 std::array
<double,StftHalfSize
+1> mSumPhase
;
92 std::array
<double,StftSize
> mOutputAccum
;
94 std::array
<complex_d
,StftSize
> mFftBuffer
;
96 std::array
<FrequencyBin
,StftHalfSize
+1> mAnalysisBuffer
;
97 std::array
<FrequencyBin
,StftHalfSize
+1> mSynthesisBuffer
;
99 alignas(16) FloatBufferLine mBufferOut
;
101 /* Effect gains for each output channel */
102 float mCurrentGains
[MaxAmbiChannels
];
103 float mTargetGains
[MaxAmbiChannels
];
106 void deviceUpdate(const DeviceBase
*device
, const Buffer
&buffer
) override
;
107 void update(const ContextBase
*context
, const EffectSlot
*slot
, const EffectProps
*props
,
108 const EffectTarget target
) override
;
109 void process(const size_t samplesToDo
, const al::span
<const FloatBufferLine
> samplesIn
,
110 const al::span
<FloatBufferLine
> samplesOut
) override
;
112 DEF_NEWDEL(PshifterState
)
115 void PshifterState::deviceUpdate(const DeviceBase
*, const Buffer
&)
117 /* (Re-)initializing parameters and clear the buffers. */
119 mPos
= StftSize
- StftStep
;
120 mPitchShiftI
= MixerFracOne
;
123 std::fill(mFIFO
.begin(), mFIFO
.end(), 0.0);
124 std::fill(mLastPhase
.begin(), mLastPhase
.end(), 0.0);
125 std::fill(mSumPhase
.begin(), mSumPhase
.end(), 0.0);
126 std::fill(mOutputAccum
.begin(), mOutputAccum
.end(), 0.0);
127 std::fill(mFftBuffer
.begin(), mFftBuffer
.end(), complex_d
{});
128 std::fill(mAnalysisBuffer
.begin(), mAnalysisBuffer
.end(), FrequencyBin
{});
129 std::fill(mSynthesisBuffer
.begin(), mSynthesisBuffer
.end(), FrequencyBin
{});
131 std::fill(std::begin(mCurrentGains
), std::end(mCurrentGains
), 0.0f
);
132 std::fill(std::begin(mTargetGains
), std::end(mTargetGains
), 0.0f
);
135 void PshifterState::update(const ContextBase
*, const EffectSlot
*slot
,
136 const EffectProps
*props
, const EffectTarget target
)
138 const int tune
{props
->Pshifter
.CoarseTune
*100 + props
->Pshifter
.FineTune
};
139 const float pitch
{std::pow(2.0f
, static_cast<float>(tune
) / 1200.0f
)};
140 mPitchShiftI
= fastf2u(pitch
*MixerFracOne
);
141 mPitchShift
= mPitchShiftI
* double{1.0/MixerFracOne
};
143 static constexpr auto coeffs
= CalcDirectionCoeffs({0.0f
, 0.0f
, -1.0f
});
145 mOutTarget
= target
.Main
->Buffer
;
146 ComputePanGains(target
.Main
, coeffs
.data(), slot
->Gain
, mTargetGains
);
149 void PshifterState::process(const size_t samplesToDo
,
150 const al::span
<const FloatBufferLine
> samplesIn
, const al::span
<FloatBufferLine
> samplesOut
)
152 /* Pitch shifter engine based on the work of Stephan Bernsee.
153 * http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/
156 /* Cycle offset per update expected of each frequency bin (bin 0 is none,
157 * bin 1 is x1, bin 2 is x2, etc).
159 constexpr double expected_cycles
{al::numbers::pi
*2.0 / OversampleFactor
};
161 for(size_t base
{0u};base
< samplesToDo
;)
163 const size_t todo
{minz(StftStep
-mCount
, samplesToDo
-base
)};
165 /* Retrieve the output samples from the FIFO and fill in the new input
168 auto fifo_iter
= mFIFO
.begin()+mPos
+ mCount
;
169 std::transform(fifo_iter
, fifo_iter
+todo
, mBufferOut
.begin()+base
,
170 [](double d
) noexcept
-> float { return static_cast<float>(d
); });
172 std::copy_n(samplesIn
[0].begin()+base
, todo
, fifo_iter
);
176 /* Check whether FIFO buffer is filled with new samples. */
177 if(mCount
< StftStep
) break;
179 mPos
= (mPos
+StftStep
) & (mFIFO
.size()-1);
181 /* Time-domain signal windowing, store in FftBuffer, and apply a
182 * forward FFT to get the frequency-domain signal.
184 for(size_t src
{mPos
}, k
{0u};src
< StftSize
;++src
,++k
)
185 mFftBuffer
[k
] = mFIFO
[src
] * HannWindow
[k
];
186 for(size_t src
{0u}, k
{StftSize
-mPos
};src
< mPos
;++src
,++k
)
187 mFftBuffer
[k
] = mFIFO
[src
] * HannWindow
[k
];
188 forward_fft(al::as_span(mFftBuffer
));
190 /* Analyze the obtained data. Since the real FFT is symmetric, only
191 * StftHalfSize+1 samples are needed.
193 for(size_t k
{0u};k
< StftHalfSize
+1;k
++)
195 const double magnitude
{std::abs(mFftBuffer
[k
])};
196 const double phase
{std::arg(mFftBuffer
[k
])};
198 /* Compute the phase difference from the last update and subtract
199 * the expected phase difference for this bin.
201 * When oversampling, the expected per-update offset increments by
202 * 1/OversampleFactor for every frequency bin. So, the offset wraps
203 * every 'OversampleFactor' bin.
205 const auto bin_offset
= static_cast<double>(k
% OversampleFactor
);
206 double tmp
{(phase
- mLastPhase
[k
]) - bin_offset
*expected_cycles
};
207 /* Store the actual phase for the next update. */
208 mLastPhase
[k
] = phase
;
210 /* Normalize from pi, and wrap the delta between -1 and +1. */
211 tmp
*= al::numbers::inv_pi
;
212 int qpd
{double2int(tmp
)};
213 tmp
-= qpd
+ (qpd
%2);
215 /* Get deviation from bin frequency (-0.5 to +0.5), and account for
218 tmp
*= 0.5 * OversampleFactor
;
220 /* Compute the k-th partials' frequency bin target and store the
221 * magnitude and frequency bin in the analysis buffer. We don't
222 * need the "true frequency" since it's a linear relationship with
225 mAnalysisBuffer
[k
].Magnitude
= magnitude
;
226 mAnalysisBuffer
[k
].FreqBin
= static_cast<double>(k
) + tmp
;
229 /* Shift the frequency bins according to the pitch adjustment,
230 * accumulating the magnitudes of overlapping frequency bins.
232 std::fill(mSynthesisBuffer
.begin(), mSynthesisBuffer
.end(), FrequencyBin
{});
234 constexpr size_t bin_limit
{((StftHalfSize
+1)<<MixerFracBits
) - MixerFracHalf
- 1};
235 const size_t bin_count
{minz(StftHalfSize
+1, bin_limit
/mPitchShiftI
+ 1)};
236 for(size_t k
{0u};k
< bin_count
;k
++)
238 const size_t j
{(k
*mPitchShiftI
+ MixerFracHalf
) >> MixerFracBits
};
240 /* If more than two bins end up together, use the target frequency
241 * bin for the one with the dominant magnitude. There might be a
242 * better way to handle this, but it's better than last-index-wins.
244 if(mAnalysisBuffer
[k
].Magnitude
> mSynthesisBuffer
[j
].Magnitude
)
245 mSynthesisBuffer
[j
].FreqBin
= mAnalysisBuffer
[k
].FreqBin
* mPitchShift
;
246 mSynthesisBuffer
[j
].Magnitude
+= mAnalysisBuffer
[k
].Magnitude
;
249 /* Reconstruct the frequency-domain signal from the adjusted frequency
252 for(size_t k
{0u};k
< StftHalfSize
+1;k
++)
254 /* Calculate the actual delta phase for this bin's target frequency
255 * bin, and accumulate it to get the actual bin phase.
257 double tmp
{mSumPhase
[k
] + mSynthesisBuffer
[k
].FreqBin
*expected_cycles
};
259 /* Wrap between -pi and +pi for the sum. If mSumPhase is left to
260 * grow indefinitely, it will lose precision and produce less exact
263 int qpd
{double2int(tmp
* al::numbers::inv_pi
)};
264 tmp
-= al::numbers::pi
* (qpd
+ (qpd
%2));
267 mFftBuffer
[k
] = std::polar(mSynthesisBuffer
[k
].Magnitude
, mSumPhase
[k
]);
269 for(size_t k
{StftHalfSize
+1};k
< StftSize
;++k
)
270 mFftBuffer
[k
] = std::conj(mFftBuffer
[StftSize
-k
]);
272 /* Apply an inverse FFT to get the time-domain signal, and accumulate
273 * for the output with windowing.
275 inverse_fft(al::as_span(mFftBuffer
));
277 static constexpr double scale
{4.0 / OversampleFactor
/ StftSize
};
278 for(size_t dst
{mPos
}, k
{0u};dst
< StftSize
;++dst
,++k
)
279 mOutputAccum
[dst
] += HannWindow
[k
]*mFftBuffer
[k
].real() * scale
;
280 for(size_t dst
{0u}, k
{StftSize
-mPos
};dst
< mPos
;++dst
,++k
)
281 mOutputAccum
[dst
] += HannWindow
[k
]*mFftBuffer
[k
].real() * scale
;
283 /* Copy out the accumulated result, then clear for the next iteration. */
284 std::copy_n(mOutputAccum
.begin() + mPos
, StftStep
, mFIFO
.begin() + mPos
);
285 std::fill_n(mOutputAccum
.begin() + mPos
, StftStep
, 0.0);
288 /* Now, mix the processed sound data to the output. */
289 MixSamples({mBufferOut
.data(), samplesToDo
}, samplesOut
, mCurrentGains
, mTargetGains
,
290 maxz(samplesToDo
, 512), 0);
294 struct PshifterStateFactory final
: public EffectStateFactory
{
295 al::intrusive_ptr
<EffectState
> create() override
296 { return al::intrusive_ptr
<EffectState
>{new PshifterState
{}}; }
301 EffectStateFactory
*PshifterStateFactory_getFactory()
303 static PshifterStateFactory PshifterFactory
{};
304 return &PshifterFactory
;