/* * Dither.h * -------- * Purpose: Dithering when converting to lower resolution sample formats. * Notes : (currently none) * Authors: Olivier Lapicque * OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #pragma once #include "BuildSettings.h" #include "SampleTypes.h" #include "SampleFormatConverters.h" #include "../common/mptRandom.h" OPENMPT_NAMESPACE_BEGIN enum DitherMode { DitherNone = 0, DitherDefault = 1, // chosen by OpenMPT code, might change DitherModPlug = 2, // rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker) DitherSimple = 3, // rectangular, 1 bit depth, simple 1st order noise shaping NumDitherModes }; struct Dither_None { public: template MPT_FORCEINLINE MixSampleInt process(MixSampleInt sample, Trng &) { return sample; } template MPT_FORCEINLINE MixSampleFloat process(MixSampleFloat sample, Trng &) { return sample; } }; struct Dither_ModPlug { public: template MPT_FORCEINLINE MixSampleInt process(MixSampleInt sample, Trng &rng) { if constexpr(targetbits == 0) { MPT_UNREFERENCED_PARAMETER(rng); return sample; } else if constexpr(targetbits + MixSampleIntTraits::mix_headroom_bits() + 1 >= 32) { MPT_UNREFERENCED_PARAMETER(rng); return sample; } else { sample += mpt::rshift_signed(static_cast(mpt::random(rng)), (targetbits + MixSampleIntTraits::mix_headroom_bits() + 1)); return sample; } } template MPT_FORCEINLINE MixSampleFloat process(MixSampleFloat sample, Trng &prng) { SC::ConvertToFixedPoint conv1; SC::ConvertFixedPoint conv2; return conv2(process(conv1(sample), prng)); } }; template struct Dither_SimpleImpl { private: int32 error = 0; public: template MPT_FORCEINLINE MixSampleInt process(MixSampleInt sample, Trng &prng) { if constexpr(targetbits == 0) { MPT_UNREFERENCED_PARAMETER(prng); return sample; } else { static_assert(sizeof(MixSampleInt) == 4); constexpr int rshift = (32-targetbits) - MixSampleIntTraits::mix_headroom_bits(); if constexpr(rshift <= 1) { MPT_UNREFERENCED_PARAMETER(prng); // nothing to dither return sample; } else { constexpr int rshiftpositive = (rshift > 1) ? rshift : 1; // work-around warnings about negative shift with C++14 compilers constexpr int round_mask = ~((1<(prng, noise_bits) + mpt::random(prng, noise_bits)) >> 1; } else { unoise = mpt::random(prng, noise_bits); } int noise = static_cast(unoise) - noise_bias; // un-bias int val = sample; if constexpr(shaped) { val += (e >> 1); } int rounded = (val + noise + round_offset) & round_mask;; e = val - rounded; sample = rounded; error = e; return sample; } } } template MPT_FORCEINLINE MixSampleFloat process(MixSampleFloat sample, Trng &prng) { SC::ConvertToFixedPoint conv1; SC::ConvertFixedPoint conv2; return conv2(process(conv1(sample), prng)); } }; using Dither_Simple = Dither_SimpleImpl<>; template class MultiChannelDither { private: std::array DitherChannels; public: void Reset() { DitherChannels.fill(Tdither()); } template MPT_FORCEINLINE MixSampleInt process(std::size_t channel, MixSampleInt sample, Trng &prng) { return DitherChannels[channel].template process(sample, prng); } template MPT_FORCEINLINE MixSampleFloat process(std::size_t channel, MixSampleFloat sample, Trng &prng) { return DitherChannels[channel].template process(sample, prng); } }; template class DitherTemplate; template class DitherTemplate : public MultiChannelDither { struct {} prng; public: template DitherTemplate(Trd &) { return; } template MPT_FORCEINLINE MixSampleInt process(std::size_t channel, MixSampleInt sample) { return MultiChannelDither::template process(channel, sample, prng); } template MPT_FORCEINLINE MixSampleFloat process(std::size_t channel, MixSampleFloat sample) { return MultiChannelDither::template process(channel, sample, prng); } }; template class DitherTemplate : public MultiChannelDither { private: mpt::rng::modplug_dither prng; public: template DitherTemplate(Trd &) : prng(0, 0) { return; } template MPT_FORCEINLINE MixSampleInt process(std::size_t channel, MixSampleInt sample) { return MultiChannelDither::template process(channel, sample, prng); } template MPT_FORCEINLINE MixSampleFloat process(std::size_t channel, MixSampleFloat sample) { return MultiChannelDither::template process(channel, sample, prng); } }; template class DitherTemplate : public MultiChannelDither { private: mpt::fast_prng prng; public: template DitherTemplate(Trd & rd) : prng(mpt::make_prng(rd)) { return; } template MPT_FORCEINLINE MixSampleInt process(std::size_t channel, MixSampleInt sample) { return MultiChannelDither::template process(channel, sample, prng); } template MPT_FORCEINLINE MixSampleFloat process(std::size_t channel, MixSampleFloat sample) { return MultiChannelDither::template process(channel, sample, prng); } }; class DitherNames { public: static mpt::ustring GetModeName(DitherMode mode); }; template class DitherChannels : public DitherNames { private: DitherTemplate ditherNone; DitherTemplate ditherModPlug; DitherTemplate ditherSimple; DitherMode mode = DitherDefault; public: template DitherChannels(Trd & rd) : ditherNone(rd) , ditherModPlug(rd) , ditherSimple(rd) { return; } void Reset() { ditherModPlug.Reset(); ditherSimple.Reset(); } DitherTemplate & NoDither() { MPT_ASSERT(mode == DitherNone); return ditherNone; } DitherTemplate & DefaultDither() { MPT_ASSERT(mode == DitherDefault); return ditherModPlug; } DitherTemplate & ModPlugDither() { MPT_ASSERT(mode == DitherModPlug); return ditherModPlug; } DitherTemplate & SimpleDither() { MPT_ASSERT(mode == DitherSimple); return ditherSimple; } template auto WithDither(Tfn fn) { switch(GetMode()) { case DitherNone: return fn(NoDither()); break; case DitherModPlug: return fn(ModPlugDither()); break; case DitherSimple: return fn(SimpleDither()); break; case DitherDefault: return fn(DefaultDither()); break; default: return fn(DefaultDither()); break; } } void SetMode(DitherMode mode_) { mode = mode_; } DitherMode GetMode() const { return mode; } }; using Dither = DitherChannels<4>; OPENMPT_NAMESPACE_END