/* * This file is part of AtracDEnc. * * AtracDEnc 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. * * AtracDEnc 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 AtracDEnc; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "atrac_psy_common.h" #include "at1/atrac1.h" #include "at3/atrac3.h" #include "at3/atrac3_qmf.h" #include "at3p/at3p_tables.h" #include "mdct/mdct.h" #include #include #include #include #include #include #include #include #include using namespace NAtracDEnc; namespace { template size_t NumSpecs() { return static_cast(TData::SpecsStartLong[TData::MaxBfus - 1]) + static_cast(TData::SpecsPerBlock[TData::MaxBfus - 1]); } template void VerifyImpulseMapsToSingleBfu() { const size_t numBfu = TData::MaxBfus; const size_t numSpecs = NumSpecs(); std::vector baseEnergy(numSpecs, 1.0f); for (size_t bfu = 0; bfu < numBfu; ++bfu) { std::vector mdctEnergy = baseEnergy; const size_t impulsePos = TData::SpecsStartLong[bfu]; mdctEnergy[impulsePos] = 32.0f; const std::vector flatness = CalcSpectralFlatnessPerBfu(mdctEnergy); ASSERT_EQ(flatness.size(), numBfu); EXPECT_LT(flatness[bfu], 0.95f) << "bfu=" << bfu; for (size_t i = 0; i < numBfu; ++i) { if (i == bfu) { continue; } EXPECT_NEAR(flatness[i], 1.0f, 1e-6f) << "bfu=" << bfu << " changed=" << i; } } } std::vector CalcSineWindow(size_t n) { constexpr float kPi = 3.14159265358979323846f; std::vector w(n); for (size_t i = 0; i < n; ++i) { w[i] = std::sin((kPi * (static_cast(i) + 0.5f)) / static_cast(n)); } return w; } template std::vector BuildAtrac3EnergyViaQmfMdct(TSampleFn&& sampleFn) { NAtrac3::TAtrac3Data initTables; (void)initTables; constexpr size_t kFrameSz = NAtrac3::TAtrac3Data::NumSamples; // 1024 constexpr size_t kNumFrames = 2; constexpr size_t kSubbands = 4; constexpr size_t kSubbandSamples = 256; constexpr size_t kMdctInput = 512; std::array pcm{}; for (size_t i = 0; i < pcm.size(); ++i) { pcm[i] = sampleFn(i); } Atrac3AnalysisFilterBank analysis; NMDCT::TMDCT mdct512(1.0f); std::array, kSubbands> bandState{}; std::array, kSubbands> subbands{}; std::array subPtrs = { subbands[0].data(), subbands[1].data(), subbands[2].data(), subbands[3].data() }; std::array specs = {}; for (size_t frame = 0; frame < kNumFrames; ++frame) { analysis.Analysis(&pcm[frame * kFrameSz], subPtrs.data()); for (size_t band = 0; band < kSubbands; ++band) { auto& state = bandState[band]; for (size_t i = 0; i < kSubbandSamples; ++i) { state[kSubbandSamples + i] = subbands[band][i]; } std::array tmp = {}; std::copy_n(state.data(), kSubbandSamples, tmp.data()); for (size_t i = 0; i < kSubbandSamples; ++i) { const float cur = state[kSubbandSamples + i]; state[i] = NAtrac3::TAtrac3Data::EncodeWindow[i] * cur; tmp[kSubbandSamples + i] = NAtrac3::TAtrac3Data::EncodeWindow[kSubbandSamples - 1 - i] * cur; } const std::vector& specBand = mdct512(tmp.data()); float* dst = specs.data() + band * kSubbandSamples; std::copy_n(specBand.data(), kSubbandSamples, dst); if (band & 1) { std::reverse(dst, dst + kSubbandSamples); } } } std::vector e(NAtrac3::TAtrac3Data::NumSpecs, 1e-12f); for (size_t i = 0; i < e.size(); ++i) { e[i] += specs[i] * specs[i]; } return e; } template std::vector BuildToneEnergy(float toneHz = 1000.0f) { auto genTone = [toneHz](size_t i) { constexpr float kPi = 3.14159265358979323846f; constexpr float kSampleRate = 44100.0f; const float phase = 2.0f * kPi * toneHz * static_cast(i) / kSampleRate + 0.37f; return std::sin(phase); }; if constexpr (std::is_same_v) { constexpr size_t n = 1024; std::vector in(n); const std::vector w = CalcSineWindow(n); for (size_t i = 0; i < n; ++i) { in[i] = genTone(i) * w[i]; } NMDCT::TMDCT mdct(n); const auto& spec = mdct(in.data()); std::vector e(spec.size(), 1e-12f); for (size_t i = 0; i < spec.size(); ++i) { e[i] += spec[i] * spec[i]; } return e; } else if constexpr (std::is_same_v) { return BuildAtrac3EnergyViaQmfMdct([&](size_t i) { return genTone(i); }); } else { static_assert(std::is_same_v, "Unsupported codec table for tone energy"); constexpr size_t n = 4096; std::vector in(n); const std::vector w = CalcSineWindow(n); for (size_t i = 0; i < n; ++i) { in[i] = genTone(i) * w[i]; } NMDCT::TMDCT mdct(n); const auto& spec = mdct(in.data()); std::vector e(spec.size(), 1e-12f); for (size_t i = 0; i < spec.size(); ++i) { e[i] += spec[i] * spec[i]; } return e; } } template std::vector BuildWhiteNoiseEnergy() { std::mt19937 gen(0xC0FFEEu + static_cast(NumSpecs())); std::normal_distribution dist(0.0f, 1.0f); auto genNoise = [&gen, &dist](size_t, size_t) { return dist(gen); }; if constexpr (std::is_same_v) { constexpr size_t n = 1024; std::vector in(n); const std::vector w = CalcSineWindow(n); for (size_t i = 0; i < n; ++i) { in[i] = genNoise(i, n) * w[i]; } NMDCT::TMDCT mdct(n); const auto& spec = mdct(in.data()); std::vector e(spec.size(), 1e-12f); for (size_t i = 0; i < spec.size(); ++i) { e[i] += spec[i] * spec[i]; } return e; } else if constexpr (std::is_same_v) { return BuildAtrac3EnergyViaQmfMdct([&](size_t i) { return genNoise(i, NAtrac3::TAtrac3Data::NumSpecs * 2); }); } else { static_assert(std::is_same_v, "Unsupported codec table for noise energy"); constexpr size_t n = 4096; std::vector in(n); const std::vector w = CalcSineWindow(n); for (size_t i = 0; i < n; ++i) { in[i] = genNoise(i, n) * w[i]; } NMDCT::TMDCT mdct(n); const auto& spec = mdct(in.data()); std::vector e(spec.size(), 1e-12f); for (size_t i = 0; i < spec.size(); ++i) { e[i] += spec[i] * spec[i]; } return e; } } template std::vector CalcBfuEnergy(const std::vector& mdctEnergy) { const size_t numBfu = TData::MaxBfus; std::vector bfuEnergy(numBfu, 0.0f); for (size_t bfu = 0; bfu < numBfu; ++bfu) { const size_t start = TData::SpecsStartLong[bfu]; const size_t len = TData::SpecsPerBlock[bfu]; float sum = 0.0f; for (size_t i = start; i < start + len; ++i) { sum += mdctEnergy[i]; } bfuEnergy[bfu] = sum; } return bfuEnergy; } float WeightedMean(const std::vector& values, const std::vector& weights) { EXPECT_EQ(values.size(), weights.size()); if (values.size() != weights.size()) { return 0.0f; } const float wsum = std::accumulate(weights.begin(), weights.end(), 0.0f); EXPECT_GT(wsum, 0.0f); if (wsum <= 0.0f) { return 0.0f; } float sum = 0.0f; for (size_t i = 0; i < values.size(); ++i) { sum += values[i] * weights[i]; } return sum / wsum; } template void VerifyToneVsNoiseFlatness(const char* codecName) { const std::vector toneEnergy = BuildToneEnergy(); const std::vector noiseEnergy = BuildWhiteNoiseEnergy(); ASSERT_EQ(toneEnergy.size(), NumSpecs()); ASSERT_EQ(noiseEnergy.size(), NumSpecs()); const std::vector toneFlatness = CalcSpectralFlatnessPerBfu(toneEnergy); const std::vector noiseFlatness = CalcSpectralFlatnessPerBfu(noiseEnergy); const std::vector toneBfuEnergy = CalcBfuEnergy(toneEnergy); const std::vector noiseBfuEnergy = CalcBfuEnergy(noiseEnergy); const float toneWeightedFlatness = WeightedMean(toneFlatness, toneBfuEnergy); const float noiseWeightedFlatness = WeightedMean(noiseFlatness, noiseBfuEnergy); std::cerr << "[FlatnessUT] codec=" << codecName << " signal=tone weighted=" << std::fixed << std::setprecision(6) << toneWeightedFlatness << "\n"; std::cerr << "[FlatnessUT] codec=" << codecName << " signal=noise weighted=" << std::fixed << std::setprecision(6) << noiseWeightedFlatness << "\n"; EXPECT_GT(noiseWeightedFlatness, toneWeightedFlatness + 0.08f); } void VerifyAtrac3ToneFrequencyFlatness(float toneHz) { const std::vector toneEnergy = BuildToneEnergy(toneHz); const std::vector noiseEnergy = BuildWhiteNoiseEnergy(); const std::vector toneFlatness = CalcSpectralFlatnessPerBfu(toneEnergy); const std::vector noiseFlatness = CalcSpectralFlatnessPerBfu(noiseEnergy); const std::vector toneBfuEnergy = CalcBfuEnergy(toneEnergy); const std::vector noiseBfuEnergy = CalcBfuEnergy(noiseEnergy); for (size_t bfu = 0; bfu < toneFlatness.size(); ++bfu) { std::cerr << "[FlatnessUT] codec=atrac3 signal=tone freq_hz=" << toneHz << " bfu=" << bfu << " flatness=" << std::fixed << std::setprecision(6) << toneFlatness[bfu] << "\n"; } for (size_t bfu = 0; bfu < noiseFlatness.size(); ++bfu) { std::cerr << "[FlatnessUT] codec=atrac3 signal=noise" << " bfu=" << bfu << " flatness=" << std::fixed << std::setprecision(6) << noiseFlatness[bfu] << "\n"; } const float toneWeightedFlatness = WeightedMean(toneFlatness, toneBfuEnergy); const float noiseWeightedFlatness = WeightedMean(noiseFlatness, noiseBfuEnergy); std::cerr << "[FlatnessUT] codec=atrac3 signal=tone freq_hz=" << toneHz << " weighted=" << std::fixed << std::setprecision(6) << toneWeightedFlatness << "\n"; std::cerr << "[FlatnessUT] codec=atrac3 signal=noise weighted=" << std::fixed << std::setprecision(6) << noiseWeightedFlatness << "\n"; EXPECT_GT(noiseWeightedFlatness, toneWeightedFlatness + 0.05f); } } // namespace TEST(AtracPsyCommon, SpectralFlatnessUniformBlock) { const uint32_t start[1] = {0}; const uint32_t size[1] = {8}; const std::vector mdctEnergy(8, 4.0f); const std::vector flatness = CalcSpectralFlatnessPerBfu(mdctEnergy, start, size, 1); ASSERT_EQ(flatness.size(), 1u); EXPECT_NEAR(flatness[0], 1.0f, 1e-6f); } TEST(AtracPsyCommon, SpectralFlatnessBfuMappingAtrac1) { VerifyImpulseMapsToSingleBfu(); } TEST(AtracPsyCommon, SpectralFlatnessBfuMappingAtrac3) { VerifyImpulseMapsToSingleBfu(); } TEST(AtracPsyCommon, SpectralFlatnessBfuMappingAtrac3Plus) { VerifyImpulseMapsToSingleBfu(); } TEST(AtracPsyCommon, SpectralFlatnessToneVsNoiseAtrac1) { VerifyToneVsNoiseFlatness("atrac1"); } TEST(AtracPsyCommon, SpectralFlatnessToneVsNoiseAtrac3) { VerifyToneVsNoiseFlatness("atrac3"); } TEST(AtracPsyCommon, SpectralFlatnessToneVsNoiseAtrac3Plus) { VerifyToneVsNoiseFlatness("atrac3plus"); } TEST(AtracPsyCommon, SpectralFlatness10kToneAtrac3) { VerifyAtrac3ToneFrequencyFlatness(10000.0f); }