diff --git a/src/libprojectM/Audio/BeatDetect.cpp b/src/libprojectM/Audio/BeatDetect.cpp deleted file mode 100755 index 96eca68dc..000000000 --- a/src/libprojectM/Audio/BeatDetect.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/** - * projectM -- Milkdrop-esque visualisation SDK - * Copyright (C)2003-2004 projectM Team - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * See 'LICENSE.txt' included within this release - * - */ -/** - * Takes sound data from wherever and returns beat detection values - * Uses statistical Energy-Based methods. Very simple - * - * Some stuff was taken from Frederic Patin's beat-detection article, - * you'll find it online - */ - -#include "BeatDetect.hpp" -#include "PCM.hpp" - -#include -#include - -namespace libprojectM { -namespace Audio { - -BeatDetect::BeatDetect(PCM& _pcm) - : pcm(_pcm) -{ -} - - -auto BeatDetect::Reset() noexcept -> void -{ - this->treb = 0; - this->mid = 0; - this->bass = 0; - this->trebAtt = 0; - this->midAtt = 0; - this->bassAtt = 0; - this->volAtt = 0; - this->volOld = 0; -} - - -auto BeatDetect::GetPCMScale() const noexcept -> float -{ - return beatSensitivity; -} - - -auto BeatDetect::GetFrameAudioData() const -> FrameAudioData -{ - FrameAudioData data{}; - - pcm.GetPcm(data.waveformLeft.data(), CHANNEL_L, WaveformSamples); - pcm.GetPcm(data.waveformRight.data(), CHANNEL_R, WaveformSamples); - pcm.GetSpectrum(data.spectrumLeft.data(), CHANNEL_L, SpectrumSamples); - pcm.GetSpectrum(data.spectrumRight.data(), CHANNEL_R, SpectrumSamples); - - data.vol = vol; - data.volAtt = volAtt; - - data.bass = bass; - data.bassAtt = bassAtt; - data.mid = mid; - data.midAtt = midAtt; - data.treb = treb; - data.trebAtt = trebAtt; - - return data; -} - -auto BeatDetect::CalculateBeatStatistics() -> void -{ - volOld = vol; - - std::array const freqL = - [this]() { - std::array freq{}; - pcm.GetSpectrum(freq.data(), CHANNEL_L, fftLength); - return freq; - }(); - std::array const freqR = - [this]() { - std::array freq{}; - pcm.GetSpectrum(freq.data(), CHANNEL_R, fftLength); - return freq; - }(); - - auto const intensityBetween = [&freqL, &freqR](size_t const from, size_t const to) { - return std::accumulate(&freqL[from], &freqL[to], 0.f) + - std::accumulate(&freqR[from], &freqR[to], 0.f); - }; - - auto const& updateBand = - [](float& band, float& bandAtt, LowPassFilter& bandSmoothed, float const bandIntensity) { - bandSmoothed.Update(bandIntensity); - band = bandIntensity / std::max(0.0001f, bandSmoothed.Get()); - bandAtt = .6f * bandAtt + .4f * band; - bandAtt = std::min(bandAtt, 100.f); - band = std::min(band, 100.f); - }; - - static_assert(fftLength >= 256, "fftLength too small"); - std::array constexpr ranges{0, 3, 23, 255}; - - float const bassIntensity = intensityBetween(ranges[0], ranges[1]); - updateBand(bass, bassAtt, bassSmoothed, bassIntensity); - - float const midIntensity = intensityBetween(ranges[1], ranges[2]); - updateBand(mid, midAtt, midSmoothed, midIntensity); - - float const trebIntensity = intensityBetween(ranges[2], ranges[3]); - updateBand(treb, trebAtt, trebSmoothed, trebIntensity); - - float const volIntensity = bassIntensity + midIntensity + trebIntensity; - updateBand(vol, volAtt, volSmoothed, volIntensity); -} - - - -auto BeatDetect::LowPassFilter::Update(float nextValue) noexcept -> void -{ - m_current -= m_buffer[m_bufferPos] / bufferLength; - m_current += nextValue / bufferLength; - m_buffer[m_bufferPos] = nextValue; - - ++m_bufferPos; - m_bufferPos %= bufferLength; -} - - -auto BeatDetect::LowPassFilter::Get() const noexcept -> float -{ - return m_current; -} - -} // namespace Audio -} // namespace libprojectM diff --git a/src/libprojectM/Audio/BeatDetect.hpp b/src/libprojectM/Audio/BeatDetect.hpp deleted file mode 100755 index c0a8bef61..000000000 --- a/src/libprojectM/Audio/BeatDetect.hpp +++ /dev/null @@ -1,100 +0,0 @@ -/** - * @file BeatDetect.cpp - * @brief Beat detection class. Takes decompressed sound buffers and returns various characteristics. - * - * projectM -- Milkdrop-esque visualisation SDK - * Copyright (C)2003-2007 projectM Team - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * See 'LICENSE.txt' included within this release - * - */ -#pragma once - -#include "FrameAudioData.hpp" -#include "PCM.hpp" - -#include - -namespace libprojectM { -namespace Audio { - -class BeatDetect -{ -public: - // We should probably remove pcm from the constructor, - // and just pass it as an argument to CalculateBeatStatistics. - explicit BeatDetect(PCM& pcm); - - auto Reset() noexcept -> void; - - /** - * Calculates and updates information about the beat - */ - auto CalculateBeatStatistics() -> void; - - // getPCMScale() was added to address https://github.com/projectM-visualizer/projectm/issues/161 - // Returning 1.0 results in using the raw PCM data, which can make the presets look pretty unresponsive - // if the application volume is low. - [[nodiscard]] - auto GetPCMScale() const noexcept -> float; - - /** - * @brief Returns a filled FrameAudioData structure for the current frame. - * @return A FrameAudioData structure with waveform, spectrum beat detection data. - */ - [[nodiscard]] - auto GetFrameAudioData() const -> FrameAudioData; - - float beatSensitivity{1.f}; - - float treb{0.f}; - float mid{0.f}; - float bass{0.f}; - float volOld{0.f}; - - float trebAtt{0.f}; - float midAtt{0.f}; - float bassAtt{0.f}; - float vol{0.f}; - float volAtt{0.f}; - - PCM& pcm; - -private: - class LowPassFilter - { - public: - auto - Update(float nextValue) noexcept -> void; - - [[nodiscard]] auto - Get() const noexcept -> float; - - private: - static size_t constexpr bufferLength{80}; - size_t m_bufferPos{0}; - std::array m_buffer{0.f}; - float m_current{0.f}; - }; - - LowPassFilter bassSmoothed; - LowPassFilter midSmoothed; - LowPassFilter trebSmoothed; - LowPassFilter volSmoothed; -}; - -} // namespace Audio -} // namespace libprojectM diff --git a/src/libprojectM/Audio/CMakeLists.txt b/src/libprojectM/Audio/CMakeLists.txt index 3f34fbcbd..dfe5108a6 100644 --- a/src/libprojectM/Audio/CMakeLists.txt +++ b/src/libprojectM/Audio/CMakeLists.txt @@ -1,14 +1,14 @@ add_library(Audio OBJECT AudioConstants.hpp - BeatDetect.cpp - BeatDetect.hpp MilkdropFFT.cpp MilkdropFFT.hpp FrameAudioData.cpp FrameAudioData.hpp PCM.cpp PCM.hpp + Loudness.cpp + Loudness.hpp ) target_include_directories(Audio diff --git a/src/libprojectM/Audio/Loudness.cpp b/src/libprojectM/Audio/Loudness.cpp new file mode 100644 index 000000000..4403efbd0 --- /dev/null +++ b/src/libprojectM/Audio/Loudness.cpp @@ -0,0 +1,62 @@ +#include "Loudness.hpp" + +#include + +namespace libprojectM { +namespace Audio { + +Loudness::Loudness(Loudness::Band band) + : m_band(band) +{ +} + +void Loudness::Update(const std::array& spectrumSamples, double secondsSinceLastFrame, uint32_t frame) +{ + SumBand(spectrumSamples); + UpdateBandAverage(secondsSinceLastFrame, frame); +} + +auto Loudness::CurrentRelative() const -> float +{ + return m_currentRelative; +} + +auto Loudness::AverageRelative() const -> float +{ + return m_averageRelative; +} + +void Loudness::SumBand(const std::array& spectrumSamples) +{ + int start = SpectrumSamples * static_cast(m_band) / 6; + int end = SpectrumSamples * static_cast(m_band) / 6; + + m_current = 0.0f; + for (int sample = start; sample < end; sample++) + { + m_current += spectrumSamples[sample]; + } +} + +void Loudness::UpdateBandAverage(double secondsSinceLastFrame, uint32_t frame) +{ + float rate = AdjustRateToFps(m_current > m_average ? 0.2f : 0.5f, secondsSinceLastFrame); + m_average = m_average * rate + m_current * (1.0f - rate); + + rate = AdjustRateToFps(frame < 50 ? 0.9f : 0.992f, secondsSinceLastFrame); + m_longAverage = m_longAverage * rate + m_current * (1.0f - rate); + + m_currentRelative = std::fabs(m_longAverage) < 0.001f ? 1.0f : m_current / m_longAverage; + m_averageRelative = std::fabs(m_longAverage) < 0.001f ? 1.0f : m_average / m_longAverage; +} + +auto Loudness::AdjustRateToFps(float rate, double secondsSinceLastFrame) -> float +{ + float const perSecondDecayRateAtFps1 = std::pow(rate, 30.0f); + float const perFrameDecayRateAtFps2 = std::pow(perSecondDecayRateAtFps1, static_cast(secondsSinceLastFrame)); + + return perFrameDecayRateAtFps2; +} + +} // namespace Audio +} // namespace libprojectM diff --git a/src/libprojectM/Audio/Loudness.hpp b/src/libprojectM/Audio/Loudness.hpp new file mode 100644 index 000000000..55f3baf8c --- /dev/null +++ b/src/libprojectM/Audio/Loudness.hpp @@ -0,0 +1,97 @@ +/** + * @file Loudness.hpp + * @brief Calculates loudness values in relation to previous frames. +**/ + +#pragma once + +#include "AudioConstants.hpp" + +#include +#include + +namespace libprojectM { +namespace Audio { + +/** + * @brief Calculates beat-detection loudness relative to the previous frame(s). + */ +class Loudness +{ +public: + /** + * @brief Frequency bands. + * Only the first half of the spectrum is used for these bands, each using one third of this half. + */ + enum class Band : int + { + Bass = 0, //!< Bass band (first sixth of the spectrum) + Middles = 1, //!< Middles band (second sixth of the spectrum) + Treble = 2 //!< Treble band (third sixth of the spectrum) + }; + + /** + * @brief Constructor. + * @param band The band to use for this loudness instance. + */ + explicit Loudness(Band band); + + /** + * @brief Updates the beat detection values and averages. + * Must only be called once per frame. + * @param spectrumSamples The current frame's spectrum analyzer samples to use for the update. + * @param secondsSinceLastFrame (Fractional) seconds passed since the last frame. + * @param frame The number of frames already rendered since start. For the first few frames, a different dampening factor + * will be used to avoid "jumpy" behaviour of the values. + */ + void Update(const std::array& spectrumSamples, double secondsSinceLastFrame, uint32_t frame); + + /** + * @brief Returns the current frame's unattenuated loudness relative to the previous frame. + * This value will revolve around 1.0, with <0.7 being very silent and >1.3 very loud audio. + * @return The current frame's unattenuated loudness relative to the previous frame. + */ + auto CurrentRelative() const -> float; + + /** + * @brief Returns the attenuated loudness averaged over the previous frames. + * This value will revolve around 1.0, with <0.7 being very silent and >1.3 very loud audio. + * Does not change as much as the value returned by CurrentRelative(). + * @return The attenuated loudness averaged over the previous frames. + */ + auto AverageRelative() const -> float; + +private: + /** + * @brief Sums up the spectrum samples for the configured band. + * @param spectrumSamples The spectrum analyzer samples to use. + */ + void SumBand(const std::array& spectrumSamples); + + /** + * @brief Updates the short- and long-term averages and relative values. + * @param secondsSinceLastFrame (Fractional) seconds passed since the last frame. + * @param frame The number of frames already rendered since start. + */ + void UpdateBandAverage(double secondsSinceLastFrame, uint32_t frame); + + /** + * @brief Adjusts the dampening rate according the the current FPS. + * @param rate The rate to be dampened. + * @param secondsSinceLastFrame (Fractional) seconds passed since the last frame. + * @return The dampened rate value. + */ + static auto AdjustRateToFps(float rate, double secondsSinceLastFrame) -> float; + + Band m_band{Band::Bass}; //!< The frequency band to use for this instance. + + float m_current{}; //!< The current frame's sum of all frequency strengths in the current band. + float m_average{}; //!< The short-term averaged value of m_current. + float m_longAverage{}; //!< The long-term averaged value of m_current. + + float m_currentRelative{1.0f}; //!< The relative loudness value to the previous frame. + float m_averageRelative{1.0f}; //!< The attenuated relative loudness value. +}; + +} // namespace Audio +} // namespace libprojectM diff --git a/src/libprojectM/Audio/MilkdropFFT.cpp b/src/libprojectM/Audio/MilkdropFFT.cpp index 2c03bed5d..83dfe128d 100644 --- a/src/libprojectM/Audio/MilkdropFFT.cpp +++ b/src/libprojectM/Audio/MilkdropFFT.cpp @@ -34,11 +34,10 @@ namespace Audio { constexpr auto PI = 3.141592653589793238462643383279502884197169399f; -void MilkdropFFT::Init(size_t samplesIn, size_t samplesOut, bool equalize, float envelopePower) +MilkdropFFT::MilkdropFFT(size_t samplesIn, size_t samplesOut, bool equalize, float envelopePower) + : m_samplesIn(samplesIn) + , m_numFrequencies(samplesOut * 2) { - m_samplesIn = samplesIn; - m_numFrequencies = samplesOut * 2; - InitBitRevTable(); InitCosSinTable(); InitEnvelopeTable(envelopePower); diff --git a/src/libprojectM/Audio/MilkdropFFT.hpp b/src/libprojectM/Audio/MilkdropFFT.hpp index 4e359843e..27f4dab1e 100644 --- a/src/libprojectM/Audio/MilkdropFFT.hpp +++ b/src/libprojectM/Audio/MilkdropFFT.hpp @@ -52,7 +52,7 @@ public: * @param envelopePower Specifies the envelope power. Set to any negative value to disable the envelope. * See InitEnvelopeTable for more info. */ - void Init(size_t samplesIn, size_t samplesOut, bool equalize = true, float envelopePower = 1.0f); + MilkdropFFT(size_t samplesIn, size_t samplesOut, bool equalize = true, float envelopePower = 1.0f); /** * @breif Converts time-domain samples into frequency-domain samples. diff --git a/src/libprojectM/Audio/PCM.cpp b/src/libprojectM/Audio/PCM.cpp index b204d990f..e18894447 100755 --- a/src/libprojectM/Audio/PCM.cpp +++ b/src/libprojectM/Audio/PCM.cpp @@ -1,239 +1,130 @@ -/** - * @file PCM.cpp - * @brief Takes sound data from wherever and hands it back out. - * - * Returns PCM Data or spectrum data, or the derivative of the PCM data - * - * projectM -- Milkdrop-esque visualisation SDK - * Copyright (C)2003-2004 projectM Team - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * See 'LICENSE.txt' included within this release - * - */ - #include "PCM.hpp" -#include "MilkdropFFT.hpp" -#include "AudioConstants.hpp" - #include -#include namespace libprojectM { namespace Audio { -/* - * Here is where we try to do auto volume setting. Doing this here - * means that none of the code downstream (waveforms, beatdetect, etc) needs - * to worry about it. - * - * 1) Don't overreact to level changes within a song - * 2) Ignore silence/gaps - * - * I don't know if it's necessary to have both sum and max, but that makes - * it easier to experiment... - */ -auto PCM::AutoLevel::UpdateLevel( - size_t const samples, - double const sum, - double const max) -> double -{ - - // This is an arbitrary number that helps control - // a) how quickly the level can change and - // b) keeps code from being affected by how the caller provides data (lot of short buffers, or fewer long buffers) - size_t constexpr autolevelSegment = 4096; - - if (sum / static_cast(samples) < 0.00001) - { - return m_level; - } - m_levelSum += sum; - m_levelax = std::max(m_levelax, max * 1.02); - m_levelSamples += samples; - if (m_levelSamples >= autolevelSegment || m_l0 <= 0) - { - double const maxRecent = std::max(std::max(m_l0, m_l1), std::max(m_l2, m_levelax)); - m_l0 = m_l1; - m_l1 = m_l2; - m_l2 = m_levelax; - m_levelax *= 0.95; - m_levelSamples = 0; - m_levelSum = 0; - m_level = (m_l0 <= 0) ? maxRecent : m_level * 0.96 + maxRecent * 0.04; - m_level = std::max(m_level, 0.0001); - } - return m_level; -} - - template< - size_t lOffset, - size_t rOffset, - size_t stride, int signalAmplitude, int signalOffset, - class SampleType> -void PCM::AddPcm( + typename SampleType> +void PCM::AddToBuffer( SampleType const* const samples, - size_t const count) + uint32_t channels, + size_t const sampleCount) { - float sum = 0; - float max = 0; - for (size_t i = 0; i < count; i++) + if (channels == 0 || sampleCount == 0) { - size_t const j = (m_start + i) % maxSamples; - m_pcmL[j] = (samples[lOffset + i * stride] - signalOffset) / float(signalAmplitude); - m_pcmR[j] = (samples[rOffset + i * stride] - signalOffset) / float(signalAmplitude); - sum += std::abs(m_pcmL[j]) + std::abs(m_pcmR[j]); - max = std::max(std::max(max, std::abs(m_pcmL[j])), std::abs(m_pcmR[j])); + return; } - m_start = (m_start + count) % maxSamples; - m_level = m_leveler.UpdateLevel(count, sum / 2, max); -} - -void PCM::AddMono(float const* const samples, size_t const count) -{ - AddPcm<0, 0, 1, 1, 0>(samples, count); -} -void PCM::AddMono(uint8_t const* const samples, size_t const count) -{ - AddPcm<0, 0, 1, 128, 0>(samples, count); -} -void PCM::AddMono(int16_t const* const samples, size_t const count) -{ - AddPcm<0, 0, 1, 32768, 0>(samples, count); -} - - -void PCM::AddStereo(float const* const samples, size_t const count) -{ - AddPcm<0, 1, 2, 1, 0>(samples, count); -} -void PCM::AddStereo(uint8_t const* const samples, size_t const count) -{ - AddPcm<0, 1, 2, 128, 0>(samples, count); -} -void PCM::AddStereo(int16_t const* const samples, size_t const count) -{ - AddPcm<0, 1, 2, 32768, 0>(samples, count); -} - - -// puts sound data requested at provided pointer -// -// samples is number of PCM samples to return -// returned values are normalized from -1 to 1 - -void PCM::GetPcm( - float* const data, - CHANNEL const channel, - size_t const samples) const -{ - assert(channel == 0 || channel == 1); - - CopyPcm(data, channel, samples); -} - - -void PCM::GetSpectrum( - float* const data, - CHANNEL const channel, - size_t const samples) -{ - assert(channel == 0 || channel == 1); - UpdateFftChannel(channel); - - auto const& spectrum = channel == 0 ? m_spectrumL : m_spectrumR; - size_t const count = samples <= fftLength ? samples : fftLength; - for (size_t i = 0; i < count; i++) + for (size_t i = 0; i < sampleCount; i++) { - data[i] = spectrum[i]; - } - for (size_t i = count; i < samples; i++) - { - data[0] = 0; + size_t const bufferOffset = (m_start + i) % WaveformSamples; + m_inputBufferL[bufferOffset] = 128.0f * (static_cast(samples[0 + i * channels]) - float(signalOffset)) / float(signalAmplitude); + if (channels > 1) + { + m_inputBufferR[bufferOffset] = 128.0f * (static_cast(samples[1 + i * channels]) - float(signalOffset)) / float(signalAmplitude); + } + else + { + m_inputBufferR[bufferOffset] = m_inputBufferL[bufferOffset]; + } } + m_start = (m_start + sampleCount) % WaveformSamples; } -void PCM::ResetAutoLevel() +void PCM::Add(float const* const samples, uint32_t channels, size_t const count) { - m_leveler = AutoLevel(); - m_level = 1.0f; + AddToBuffer<1, 0>(samples, channels, count); +} +void PCM::Add(uint8_t const* const samples, uint32_t channels, size_t const count) +{ + AddToBuffer<128, 128>(samples, channels, count); +} +void PCM::Add(int16_t const* const samples, uint32_t channels, size_t const count) +{ + AddToBuffer<0, 32768>(samples, channels, count); +} + +void PCM::UpdateFrameAudioData(double secondsSinceLastFrame, uint32_t frame) +{ + // 1. Copy audio data from input buffer + CopyNewWaveformData(); + + // 2. Align waveforms + + // 3. Update spectrum analyzer data for both channels + UpdateFftChannel(0); + UpdateFftChannel(1); + + // 4. Update beat detection values + m_bass.Update(m_spectrumL, secondsSinceLastFrame, frame); + m_middles.Update(m_spectrumL, secondsSinceLastFrame, frame); + m_treble.Update(m_spectrumL, secondsSinceLastFrame, frame); +} + +auto PCM::GetFrameAudioData() const -> FrameAudioData +{ + FrameAudioData data{}; + + std::copy(m_waveformL.begin(), m_waveformL.end(), data.waveformLeft.begin()); + std::copy(m_waveformR.begin(), m_waveformR.end(), data.waveformRight.begin()); + std::copy(m_spectrumL.begin(), m_spectrumL.end(), data.spectrumLeft.begin()); + std::copy(m_spectrumR.begin(), m_spectrumR.end(), data.spectrumRight.begin()); + + data.bass = m_bass.CurrentRelative(); + data.mid = m_middles.CurrentRelative(); + data.treb = m_treble.CurrentRelative(); + + data.bassAtt = m_bass.AverageRelative(); + data.midAtt = m_middles.AverageRelative(); + data.trebAtt = m_treble.AverageRelative(); + + data.vol = (data.bass + data.mid + data.treb) * 0.333f; + data.volAtt = (data.bassAtt + data.midAtt + data.trebAtt) * 0.333f; + + return data; } void PCM::UpdateFftChannel(size_t const channel) { assert(channel == 0 || channel == 1); - // ToDo: Add as member, init only once. - MilkdropFFT fft; - fft.Init(WaveformSamples, fftLength, true); - std::vector waveformSamples(WaveformSamples); std::vector spectrumValues; - // Get waveform data from ring buffer - auto const& from = channel == 0 ? m_pcmL : m_pcmR; - for (size_t i = 0, pos = m_start; i < WaveformSamples; i++) + auto const& from = channel == 0 ? m_waveformL : m_waveformR; + auto& spectrum = channel == 0 ? m_spectrumL : m_spectrumR; + + size_t oldI{0}; + for (size_t i = 0; i < WaveformSamples; i++) { - if (pos == 0) - { - pos = maxSamples; - } - waveformSamples[i] = static_cast(from[--pos]); + // Damp the input into the FFT a bit, to reduce high-frequency noise: + waveformSamples[i] = 0.5f * (from[i] + from[oldI]); + oldI = i; } - fft.TimeToFrequencyDomain(waveformSamples, spectrumValues); + m_fft.TimeToFrequencyDomain(waveformSamples, spectrumValues); - auto& spectrum = channel == 0 ? m_spectrumL : m_spectrumR; std::copy(spectrumValues.begin(), spectrumValues.end(), spectrum.begin()); } -// pull data from circular buffer -void PCM::CopyPcm(float* const to, size_t const channel, size_t const count) const +void PCM::CopyNewWaveformData() { - assert(channel == 0 || channel == 1); - assert(count < maxSamples); - auto const& from = channel == 0 ? m_pcmL : m_pcmR; - const double volume = 1.0 / m_level; - for (size_t i = 0, pos = m_start; i < count; i++) + const auto& copyChannel = + [](size_t start, const std::array& inputSamples, std::array& outputSamples) { - if (pos == 0) + for (size_t i = 0; i < WaveformSamples; i++) { - pos = maxSamples; + outputSamples[i] = inputSamples[(start + i) % WaveformSamples]; } - to[i] = static_cast(from[--pos] * volume); - } -} + }; -void PCM::CopyPcm(double* const to, size_t const channel, size_t const count) const -{ - assert(channel == 0 || channel == 1); - auto const& from = channel == 0 ? m_pcmL : m_pcmR; - double const volume = 1.0 / m_level; - for (size_t i = 0, pos = m_start; i < count; i++) - { - if (pos == 0) - { - pos = maxSamples; - } - to[i] = from[--pos] * volume; - } + auto const bufferStartIndex = m_start.load(); + copyChannel(bufferStartIndex, m_inputBufferL, m_waveformL); + copyChannel(bufferStartIndex, m_inputBufferR, m_waveformR); } } // namespace Audio diff --git a/src/libprojectM/Audio/PCM.hpp b/src/libprojectM/Audio/PCM.hpp index bf46f632b..459bea36f 100755 --- a/src/libprojectM/Audio/PCM.hpp +++ b/src/libprojectM/Audio/PCM.hpp @@ -1,36 +1,22 @@ /** - * projectM -- Milkdrop-esque visualisation SDK - * Copyright (C)2003-2007 projectM Team + * @file PCM.hpp + * @brief Takes sound data from the outside, analyzes it and hands it back out. * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * See 'LICENSE.txt' included within this release - * - */ -/** - * $Id$ - * - * Encapsulation of raw sound buffer. Used in beat detection - * - * $Log$ - */ + * Returns waveform and spectrum data, as well as relative beat detection values. + **/ #pragma once -#include "projectM-4/projectM_export.h" +#include "AudioConstants.hpp" + +#include "FrameAudioData.hpp" +#include "Loudness.hpp" +#include "MilkdropFFT.hpp" + +#include #include +#include #include #include @@ -38,124 +24,89 @@ namespace libprojectM { namespace Audio { -// FFT_LENGTH is number of magnitude values available from getSpectrum(). -// Internally this is generated using 2xFFT_LENGTH samples per channel. -size_t constexpr fftLength = 512; - -enum CHANNEL -{ - CHANNEL_L = 0, - CHANNEL_0 = 0, - CHANNEL_R = 1, - CHANNEL_1 = 1 -}; - class PROJECTM_EXPORT PCM { public: - /* maximum number of sound samples that are actually stored. */ - static constexpr size_t maxSamples = 2048; - /** - * @brief Adds a mono pcm buffer to the storage + * @brief Adds new interleaved floating-point PCM data to the buffer. + * Left channel is expected at offset 0, right channel at offset 1. Other channels are ignored. * @param samples The buffer to be added + * @param channels The number of channels in the input data. * @param count The amount of samples in the buffer */ - void AddMono(const float* samples, size_t count); - void AddMono(const uint8_t* samples, size_t count); - void AddMono(const int16_t* samples, size_t count); + void Add(const float* samples, uint32_t channels, size_t count); /** - * @brief Adds a stereo pcm buffer to the storage - * @param samples The buffer to be added. - * The channels are expected to be interleaved, LRLR. - * @param count The amount of samples in each channel (not total samples) + * @brief Adds new mono unsigned 8-bit PCM data to the storage + * Left channel is expected at offset 0, right channel at offset 1. Other channels are ignored. + * @param samples The buffer to be added + * @param channels The number of channels in the input data. + * @param count The amount of samples in the buffer */ - void AddStereo(const float* samples, size_t count); - void AddStereo(const uint8_t* samples, size_t count); - void AddStereo(const int16_t* samples, size_t count); + void Add(const uint8_t* samples, uint32_t channels, size_t count); /** - * PCM data - * The returned data will 'wrap' if more than maxsamples are requested. + * @brief Adds new mono signed 16-bit PCM data to the storage + * Left channel is expected at offset 0, right channel at offset 1. Other channels are ignored. + * @param samples The buffer to be added + * @param channels The number of channels in the input data. + * @param count The amount of samples in the buffer */ - void GetPcm(float* data, CHANNEL channel, size_t samples) const; - - /** Spectrum data - * The returned data will be zero padded if more than FFT_LENGTH values are requested - */ - void GetSpectrum(float* data, CHANNEL channel, size_t samples); + void Add(const int16_t* samples, uint32_t channels, size_t count); /** - * @brief Resets the auto leveler state. - * Makes sense after pausing/resuming audio or other interruptions. + * @brief Updates the internal audio data values for rendering the next frame. + * This method must only be called once per frame, as it does some temporal blending + * and alignments using the previous frame(s) as reference. This function will perform: + * - Passing the waveform data into the spectrum analyzer + * - Aligning waveforms to a best-fit match to the previous frame to produce a calmer waveform shape. + * - Calculating the bass/mid/treb values and their attenuated (time-smoothed) versions. + * + * @param secondsSinceLastFrame Time passed since rendering the last frame. Basically 1.0/FPS. + * @param frame Frames rendered since projectM was started. */ - void ResetAutoLevel(); + void UpdateFrameAudioData(double secondsSinceLastFrame, uint32_t frame); -protected: - // CPP20: Could use a struct for the first 5 params to clarify on call site - // together with designated initializers + /** + * @brief Returns a class holding a copy of the current frame audio data. + * @return A FrameAudioData class with waveform, spectrum and other derived values. + */ + auto GetFrameAudioData() const -> FrameAudioData; + +private: template< - size_t lOffset, - size_t rOffset, - size_t stride, int signalAmplitude, int signalOffset, - class SampleType> - void AddPcm(const SampleType* samples, size_t count); - - // copy data out of the circular PCM buffer - void CopyPcm(float* to, size_t channel, size_t count) const; - void CopyPcm(double* to, size_t channel, size_t count) const; + typename SampleType> + void AddToBuffer(const SampleType* samples, uint32_t channel, size_t sampleCount); // Updates FFT data void UpdateFftChannel(size_t channel); -private: - // mem-usage: - // pcmd 2x2048*4b = 16K - // vdata 2x512x2*8b = 16K - // spectrum 2x512*4b = 4k - // w = 512*8b = 4k + /** + * Copies data out of the circular input buffer into the per-frame waveform buffer. + */ + void CopyNewWaveformData(); - // circular PCM buffer - // adjust "volume" of PCM data as we go, this simplifies everything downstream... - // normalize to range [-1.0,1.0] - std::array m_pcmL{0.f}; - std::array m_pcmR{0.f}; - size_t m_start{0}; - size_t m_newSamples{0}; + // External input buffer + std::array m_inputBufferL{0.f}; //!< Circular buffer for left-channel PCM data. + std::array m_inputBufferR{0.f}; //!< Circular buffer for right-channel PCM data. + std::atomic m_start{0}; //!< Circular buffer start index. - // raw FFT data - std::array m_freqL{0.0}; - std::array m_freqR{0.0}; - // magnitude data - std::array m_spectrumL{0.f}; - std::array m_spectrumR{0.f}; + // Frame waveform data + std::array m_waveformL{0.f}; //!< Left-channel waveform data, aligned. + std::array m_waveformR{0.f}; //!< Right-channel waveform data, aligned. - std::array m_w{0.0}; - std::array m_ip{0}; + // Frame spectrum data + std::array m_spectrumL{0.f}; //!< Left-channel spectrum data. + std::array m_spectrumR{0.f}; //!< Right-channel spectrum data. - // see https://github.com/projectM-visualizer/projectm/issues/161 - class PROJECTM_EXPORT AutoLevel - { - public: - auto UpdateLevel(size_t samples, double sum, double max) -> double; + MilkdropFFT m_fft{WaveformSamples, SpectrumSamples, true}; //!< Spectrum analyzer instance. - private: - double m_level{0.01}; - // accumulate sample data - size_t m_levelSamples{0}; - double m_levelSum{0.0}; - double m_levelax{0.0}; - double m_l0{-1.0}; - double m_l1{-1.0}; - double m_l2{-1.0}; - }; - - // state for tracking audio level - double m_level{1.f}; - AutoLevel m_leveler{}; + // Frame beat detection values + Loudness m_bass{Loudness::Band::Bass}; //!< Beat detection/volume for the "bass" band. + Loudness m_middles{Loudness::Band::Middles}; //!< Beat detection/volume for the "middles" band. + Loudness m_treble{Loudness::Band::Treble}; //!< Beat detection/volume for the "treble" band. }; } // namespace Audio diff --git a/src/libprojectM/MilkdropPreset/CustomShape.cpp b/src/libprojectM/MilkdropPreset/CustomShape.cpp index 5d40b2664..7a3405f89 100644 --- a/src/libprojectM/MilkdropPreset/CustomShape.cpp +++ b/src/libprojectM/MilkdropPreset/CustomShape.cpp @@ -2,7 +2,6 @@ #include "PresetFileParser.hpp" -#include "Audio/BeatDetect.hpp" #include "Renderer/TextureManager.hpp" #include diff --git a/src/libprojectM/MilkdropPreset/CustomWaveform.cpp b/src/libprojectM/MilkdropPreset/CustomWaveform.cpp index c34d09638..867f7baf3 100644 --- a/src/libprojectM/MilkdropPreset/CustomWaveform.cpp +++ b/src/libprojectM/MilkdropPreset/CustomWaveform.cpp @@ -93,6 +93,7 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) InitPerPointEvaluationVariables(); int sampleCount = std::min(WaveformMaxPoints, static_cast(*m_perFrameContext.samples)); + sampleCount -= m_sep; // If there aren't enough samples to draw a single line or dot, skip drawing the waveform. if ((m_useDots && sampleCount < 1) || sampleCount < 2) @@ -107,8 +108,8 @@ void CustomWaveform::Draw(const PerFrameContext& presetPerFrameContext) ? m_presetState.audioData.spectrumRight.data() : m_presetState.audioData.waveformRight.data(); - //const float mult = m_scaling * m_presetState.waveScale * (m_spectrum ? 0.15f : 0.004f); - const float mult = m_scaling * m_presetState.waveScale * (m_spectrum ? 0.05f : 1.0f); + const float mult = m_scaling * m_presetState.waveScale * (m_spectrum ? 0.15f : 0.004f); + //const float mult = m_scaling * m_presetState.waveScale * (m_spectrum ? 0.05f : 1.0f); // PCM data smoothing const int offset1 = m_spectrum ? 0 : (maxSampleCount - sampleCount) / 2 - m_sep / 2; diff --git a/src/libprojectM/MilkdropPreset/Waveform.cpp b/src/libprojectM/MilkdropPreset/Waveform.cpp index 963672f5b..4b24bddc3 100644 --- a/src/libprojectM/MilkdropPreset/Waveform.cpp +++ b/src/libprojectM/MilkdropPreset/Waveform.cpp @@ -3,7 +3,6 @@ #include "PerFrameContext.hpp" #include "PresetState.hpp" -#include "Audio/BeatDetect.hpp" #include "projectM-opengl.h" #include @@ -284,15 +283,16 @@ void Waveform::WaveformMath(const PerFrameContext& presetPerFrameContext) // Tie size of waveform to beatSensitivity // ToDo: Beat sensitivity was probably not the correct value here? - const float volumeScale = m_presetState.audioData.vol; - if (volumeScale != 1.0) + //const float volumeScale = m_presetState.audioData.vol; + //if (volumeScale != 1.0) + //{ + // Scale waveform data to -1 .. 1 range + for (size_t i = 0; i < pcmDataL.size(); ++i) { - for (size_t i = 0; i < pcmDataL.size(); ++i) - { - pcmDataL[i] *= volumeScale; - pcmDataR[i] *= volumeScale; - } + pcmDataL[i] /= 128.0f; + pcmDataR[i] /= 128.0f; } + //} // Aspect multipliers float aspectX{1.0f}; diff --git a/src/libprojectM/ProjectM.cpp b/src/libprojectM/ProjectM.cpp index 9400cc73f..799a99639 100644 --- a/src/libprojectM/ProjectM.cpp +++ b/src/libprojectM/ProjectM.cpp @@ -25,7 +25,6 @@ #include "PresetFactoryManager.hpp" #include "TimeKeeper.hpp" -#include "Audio/BeatDetect.hpp" #include "Audio/PCM.hpp" //Sound data handler (buffering, FFT, etc.) #include "Renderer/CopyTexture.hpp" @@ -143,13 +142,16 @@ void ProjectM::RenderFrame() return; } - m_timeKeeper->UpdateTimers(); - m_beatDetect->CalculateBeatStatistics(); - #if PROJECTM_USE_THREADS std::lock_guard guard(m_presetSwitchMutex); #endif + // Update FPS and other timer values. + m_timeKeeper->UpdateTimers(); + + // Update and retrieve audio data + m_audioStorage.UpdateFrameAudioData(m_timeKeeper->SecondsSinceLastFrame(), m_frameCount); + auto audioData = m_audioStorage.GetFrameAudioData(); // Check if the preset isn't locked, and we've not already notified the user if (!m_presetChangeNotified) @@ -161,7 +163,8 @@ void ProjectM::RenderFrame() PresetSwitchRequestedEvent(false); } else if (m_hardCutEnabled && - (m_beatDetect->vol - m_beatDetect->volOld > m_hardCutSensitivity) && + m_frameCount > 50 && + (audioData.vol - m_previousFrameVolume > m_hardCutSensitivity) && m_timeKeeper->CanHardCut()) { m_presetChangeNotified = true; @@ -198,7 +201,6 @@ void ProjectM::RenderFrame() } auto renderContext = GetRenderContext(); - auto audioData = m_beatDetect->GetFrameAudioData(); if (m_transition != nullptr && m_transitioningPreset != nullptr) { @@ -231,6 +233,7 @@ void ProjectM::RenderFrame() } m_frameCount++; + m_previousFrameVolume = audioData.vol; } void ProjectM::Initialize() @@ -245,10 +248,6 @@ void ProjectM::Initialize() /** Initialise per-pixel matrix calculations */ /** We need to initialise this before the builtin param db otherwise bass/mid etc won't bind correctly */ - assert(!m_beatDetect); - - m_beatDetect = std::make_unique(m_pcm); - m_textureManager = std::make_unique(m_textureSearchPaths); m_transitionShaderManager = std::make_unique(); @@ -260,7 +259,6 @@ void ProjectM::Initialize() /* Set the seed to the current time in seconds */ srand(time(nullptr)); - ResetEngine(); LoadIdlePreset(); #if PROJECTM_USE_THREADS @@ -277,17 +275,6 @@ void ProjectM::LoadIdlePreset() assert(m_activePreset); } -/* Reinitializes the engine variables to a default (conservative and sane) value */ -void ProjectM::ResetEngine() -{ - - if (m_beatDetect != NULL) - { - m_beatDetect->Reset(); - m_beatDetect->beatSensitivity = m_beatSensitivity; - } -} - /** Resets OpenGL state */ void ProjectM::ResetOpenGL(size_t width, size_t height) { @@ -361,12 +348,12 @@ auto ProjectM::PresetLocked() const -> bool void ProjectM::SetBeatSensitivity(float sensitivity) { - m_beatDetect->beatSensitivity = std::min(std::max(0.0f, sensitivity), 2.0f); + m_beatSensitivity = std::min(std::max(0.0f, sensitivity), 2.0f); } auto ProjectM::GetBeatSensitivity() const -> float { - return m_beatDetect->beatSensitivity; + return m_beatSensitivity; } auto ProjectM::SoftCutDuration() const -> double @@ -481,7 +468,7 @@ void ProjectM::SetMeshSize(size_t meshResolutionX, size_t meshResolutionY) auto ProjectM::PCM() -> libprojectM::Audio::PCM& { - return m_pcm; + return m_audioStorage; } void ProjectM::Touch(float touchX, float touchY, int pressure, int touchType) diff --git a/src/libprojectM/ProjectM.hpp b/src/libprojectM/ProjectM.hpp index f850b3364..bf5cae969 100644 --- a/src/libprojectM/ProjectM.hpp +++ b/src/libprojectM/ProjectM.hpp @@ -212,8 +212,6 @@ public: private: void Initialize(); - void ResetEngine(); - void StartPresetTransition(std::unique_ptr&& preset, bool hardCut); void LoadIdlePreset(); @@ -226,7 +224,6 @@ private: #endif - class libprojectM::Audio::PCM m_pcm; //!< Audio data buffer and analyzer instance. size_t m_meshX{32}; //!< Per-point mesh horizontal resolution. size_t m_meshY{24}; //!< Per-point mesh vertical resolution. @@ -241,6 +238,7 @@ private: float m_beatSensitivity{1.0}; //!< General beat sensitivity modifier for presets. bool m_aspectCorrection{true}; //!< If true, corrects aspect ratio for non-rectangular windows. float m_easterEgg{0.0}; //!< Random preset duration modifier. See TimeKeeper class. + float m_previousFrameVolume{}; //!< Volume in previous frame, used for hard cuts. std::vector m_textureSearchPaths; ///!< List of paths to search for texture files @@ -252,10 +250,10 @@ private: std::unique_ptr m_presetFactoryManager; //!< Provides access to all available preset factories. + libprojectM::Audio::PCM m_audioStorage; //!< Audio data buffer and analyzer instance. std::unique_ptr m_textureManager; //!< The texture manager. std::unique_ptr m_transitionShaderManager; //!< The transition shader manager. std::unique_ptr m_textureCopier; //!< Class that copies textures 1:1 to another texture or framebuffer. - std::unique_ptr m_beatDetect; //!< The beat detection class. std::unique_ptr m_activePreset; //!< Currently loaded preset. std::unique_ptr m_transitioningPreset; //!< Destination preset when smooth preset switching. std::unique_ptr m_transition; //!< Transition effect used for blending. diff --git a/src/libprojectM/ProjectMCWrapper.cpp b/src/libprojectM/ProjectMCWrapper.cpp index 7e5da6c29..3baabe5fd 100644 --- a/src/libprojectM/ProjectMCWrapper.cpp +++ b/src/libprojectM/ProjectMCWrapper.cpp @@ -2,8 +2,9 @@ #include "projectM-4/projectM.h" +#include "Audio/AudioConstants.hpp" + #include -#include #include void projectMWrapper::PresetSwitchRequestedEvent(bool isHardCut) const @@ -337,7 +338,7 @@ void projectm_set_window_size(projectm_handle instance, size_t width, size_t hei unsigned int projectm_pcm_get_max_samples() { - return libprojectM::Audio::PCM::maxSamples; + return libprojectM::Audio::WaveformSamples; } template @@ -345,14 +346,7 @@ static auto PcmAdd(projectm_handle instance, const BufferType* samples, unsigned { auto* projectMInstance = handle_to_instance(instance); - if (channels == PROJECTM_MONO) - { - projectMInstance->PCM().AddMono(samples, count); - } - else - { - projectMInstance->PCM().AddStereo(samples, count); - } + projectMInstance->PCM().Add(samples, channels, count); } auto projectm_pcm_add_float(projectm_handle instance, const float* samples, unsigned int count, projectm_channels channels) -> void diff --git a/src/libprojectM/TimeKeeper.cpp b/src/libprojectM/TimeKeeper.cpp index 839401e6c..91c35a0ed 100644 --- a/src/libprojectM/TimeKeeper.cpp +++ b/src/libprojectM/TimeKeeper.cpp @@ -17,7 +17,9 @@ void TimeKeeper::UpdateTimers() { auto currentTime = std::chrono::high_resolution_clock::now(); - m_currentTime = std::chrono::duration(currentTime - m_startTime).count(); + double currentFrameTime = std::chrono::duration(currentTime - m_startTime).count(); + m_secondsSinceLastFrame = currentFrameTime - m_currentTime; + m_currentTime = currentFrameTime; m_presetFrameA++; m_presetFrameB++; } diff --git a/src/libprojectM/TimeKeeper.hpp b/src/libprojectM/TimeKeeper.hpp index c79e6c488..273f1c6ec 100644 --- a/src/libprojectM/TimeKeeper.hpp +++ b/src/libprojectM/TimeKeeper.hpp @@ -6,7 +6,6 @@ class TimeKeeper { public: - TimeKeeper(double presetDuration, double smoothDuration, double hardcutDuration, double easterEgg); void UpdateTimers(); @@ -79,27 +78,31 @@ public: m_easterEgg = value; } + inline auto SecondsSinceLastFrame() const -> double + { + return m_secondsSinceLastFrame; + } + private: - /* The first ticks value of the application */ - std::chrono::high_resolution_clock::time_point m_startTime{ std::chrono::high_resolution_clock::now() }; + std::chrono::high_resolution_clock::time_point m_startTime{std::chrono::high_resolution_clock::now()}; - double m_easterEgg{ 0.0 }; + double m_secondsSinceLastFrame{}; - double m_presetDuration{ 0.0 }; - double m_presetDurationA{ 0.0 }; - double m_presetDurationB{ 0.0 }; - double m_softCutDuration{ 0.0 }; - double m_hardCutDuration{ 0.0 }; + double m_easterEgg{}; - double m_currentTime{ 0.0 }; - double m_presetTimeA{ 0.0 }; - double m_presetTimeB{ 0.0 }; + double m_presetDuration{}; + double m_presetDurationA{}; + double m_presetDurationB{}; + double m_softCutDuration{}; + double m_hardCutDuration{}; - int m_presetFrameA{ 0 }; - int m_presetFrameB{ 0 }; - - bool m_isSmoothing{ false }; + double m_currentTime{}; + double m_presetTimeA{}; + double m_presetTimeB{}; + int m_presetFrameA{}; + int m_presetFrameB{}; + bool m_isSmoothing{false}; }; diff --git a/tests/libprojectM/CMakeLists.txt b/tests/libprojectM/CMakeLists.txt index 93346b459..963818ee2 100644 --- a/tests/libprojectM/CMakeLists.txt +++ b/tests/libprojectM/CMakeLists.txt @@ -1,7 +1,6 @@ find_package(GTest 1.10 REQUIRED NO_MODULE) add_executable(projectM-unittest - PCMTest.cpp PresetFileParserTest.cpp $ diff --git a/tests/libprojectM/PCMTest.cpp b/tests/libprojectM/PCMTest.cpp index 7a7e83dd9..eef7daa61 100644 --- a/tests/libprojectM/PCMTest.cpp +++ b/tests/libprojectM/PCMTest.cpp @@ -18,6 +18,24 @@ public: { PCM::CopyPcm(to, channel, count); } + + /** + * @brief Returns a copy of the current frame's PCM data. + * The returned data will 'wrap' if more than maxsamples are requested. + */ + void GetPcm(float* data, uint channel, size_t samples) const + { + PCM::GetPcm(data, channel, samples); + } + + /** Spectrum data + * The returned data will be zero padded if more than FFT_LENGTH values are requested + */ + void GetSpectrum(float* data, uint channel, size_t samples) const + { + PCM::GetSpectrum(data, channel, samples); + } + }; TEST(PCM, AddDataMonoFloat) @@ -32,11 +50,10 @@ TEST(PCM, AddDataMonoFloat) } for (size_t i = 0; i < 10; i++) { - pcm.AddMono(data.data(), samples); + pcm.Add(data.data(), 1, samples); } std::array copy{}; - pcm.ResetAutoLevel(); pcm.CopyPcm(copy.data(), 0, copy.size()); for (size_t i = 0; i < samples; i++) { @@ -62,12 +79,11 @@ TEST(PCM, AddDataStereoFloat) } for (size_t i = 0; i < 10; i++) { - pcm.AddStereo(data.data(), samples); + pcm.Add(data.data(), 2, samples); } std::array copy0{}; std::array copy1{}; - pcm.ResetAutoLevel(); pcm.CopyPcm(copy0.data(), 0, copy0.size()); pcm.CopyPcm(copy1.data(), 1, copy1.size()); for (size_t i = 0; i < samples; i++) @@ -89,22 +105,21 @@ TEST(PCM, DISABLED_FastFourierTransformLowFrequency) data[i * 2] = std::sin(f); data[i * 2 + 1] = std::sin(f + 1.f);// out of phase } - pcm.AddStereo(data.data(), samples); - pcm.AddStereo(data.data(), samples); + pcm.Add(data.data(), 2, samples); + pcm.Add(data.data(), 2, samples); - std::array freq0{}; - std::array freq1{}; - pcm.ResetAutoLevel(); - pcm.GetSpectrum(freq0.data(), CHANNEL_0, fftLength); - pcm.GetSpectrum(freq1.data(), CHANNEL_1, fftLength); + std::array freq0{}; + std::array freq1{}; + pcm.GetSpectrum(freq0.data(), 0, SpectrumSamples); + pcm.GetSpectrum(freq1.data(), 1, SpectrumSamples); // freq0 and freq1 should be equal - for (size_t i = 0; i < fftLength; i++) + for (size_t i = 0; i < SpectrumSamples; i++) { EXPECT_FLOAT_EQ(freq0[i], freq1[i]); } EXPECT_GT(freq0[0], 500); - for (size_t i = 1; i < fftLength; i++) + for (size_t i = 1; i < SpectrumSamples; i++) { EXPECT_LT(freq0[i], 0.1); } @@ -118,23 +133,22 @@ TEST(PCM, FastFourierTransformHighFrequency) std::array constexpr data{1.0, 0.0, 0.0, 1.0}; for (size_t i = 0; i < 1024; i++) { - pcm.AddStereo(data.data(), samples); + pcm.Add(data.data(), 2, samples); } - std::array freq0{}; - std::array freq1{}; - pcm.ResetAutoLevel(); - pcm.GetSpectrum(freq0.data(), CHANNEL_0, fftLength); - pcm.GetSpectrum(freq1.data(), CHANNEL_1, fftLength); + std::array freq0{}; + std::array freq1{}; + pcm.GetSpectrum(freq0.data(), 0, SpectrumSamples); + pcm.GetSpectrum(freq1.data(), 1, SpectrumSamples); // freq0 and freq1 should be equal - for (size_t i = 0; i < fftLength; i++) + for (size_t i = 0; i < SpectrumSamples; i++) { EXPECT_FLOAT_EQ(freq0[i], freq1[i]); } - for (size_t i = 0; i < fftLength - 1; i++) + for (size_t i = 0; i < SpectrumSamples - 1; i++) { EXPECT_FLOAT_EQ(freq0[i], 0); } - EXPECT_GT(freq0[fftLength - 1], 100000); + EXPECT_GT(freq0[SpectrumSamples - 1], 100000); }