gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit b88e95f965fece5244c26c3527e6a1aecc743bc0
parent b0eb6ae7d5c6b85d241ddcdc4bba7b6fe7a6c471
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Wed, 13 Nov 2024 22:31:06 +0100

implement a first DAC emulation that can reduce the number of output bits and can optionally add dither noise

Diffstat:
Msource/synthLib/CMakeLists.txt | 1+
Asource/synthLib/dac.cpp | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/synthLib/dac.h | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 207 insertions(+), 0 deletions(-)

diff --git a/source/synthLib/CMakeLists.txt b/source/synthLib/CMakeLists.txt @@ -11,6 +11,7 @@ set(SOURCES audiobuffer.cpp audiobuffer.h audioTypes.h buildconfig.h buildconfig.h.in + dac.cpp dac.h device.cpp device.h deviceException.cpp deviceException.h deviceTypes.h diff --git a/source/synthLib/dac.cpp b/source/synthLib/dac.cpp @@ -0,0 +1,101 @@ +#include "dac.h" + +#include "dsp56kEmu/logging.h" + +namespace synthLib +{ + Dac::Dac() : m_processFunc(&DacProcessor<24, 0>::processSample) + { + } + + bool Dac::configure(const uint32_t _outputBits, const uint32_t _noiseBits) + { + auto processFunc = findProcessFunc(_outputBits, _noiseBits); + + if(processFunc == nullptr) + { + LOG("DAC configuration failed, unable to find process function for outputBits << " << _outputBits << " and noise bits " << _noiseBits); + return false; + } + + m_processFunc = processFunc; + m_outputBits = _outputBits; + m_noiseBits = _noiseBits; + + return true; + } + + Dac::ProcessFunc Dac::findProcessFunc(const uint32_t _outputBits, const uint32_t _noiseBits) + { + switch (_outputBits) + { + case 8: + switch (_noiseBits) + { + case 0: return &DacProcessor<8, 0>::processSample; + case 1: return &DacProcessor<8, 1>::processSample; + case 2: return &DacProcessor<8, 2>::processSample; + case 3: return &DacProcessor<8, 3>::processSample; + case 4: return &DacProcessor<8, 4>::processSample; + case 5: return &DacProcessor<8, 5>::processSample; + case 6: return &DacProcessor<8, 6>::processSample; + case 7: return &DacProcessor<8, 7>::processSample; + default: return nullptr; + } + case 12: + switch (_noiseBits) + { + case 0: return &DacProcessor<12, 0>::processSample; + case 1: return &DacProcessor<12, 1>::processSample; + case 2: return &DacProcessor<12, 2>::processSample; + case 3: return &DacProcessor<12, 3>::processSample; + case 4: return &DacProcessor<12, 4>::processSample; + case 5: return &DacProcessor<12, 5>::processSample; + case 6: return &DacProcessor<12, 6>::processSample; + case 7: return &DacProcessor<12, 7>::processSample; + default: return nullptr; + } + case 16: + switch (_noiseBits) + { + case 0: return &DacProcessor<16, 0>::processSample; + case 1: return &DacProcessor<16, 1>::processSample; + case 2: return &DacProcessor<16, 2>::processSample; + case 3: return &DacProcessor<16, 3>::processSample; + case 4: return &DacProcessor<16, 4>::processSample; + case 5: return &DacProcessor<16, 5>::processSample; + case 6: return &DacProcessor<16, 6>::processSample; + case 7: return &DacProcessor<16, 7>::processSample; + default: return nullptr; + } + case 18: + switch (_noiseBits) + { + case 0: return &DacProcessor<18, 0>::processSample; + case 1: return &DacProcessor<18, 1>::processSample; + case 2: return &DacProcessor<18, 2>::processSample; + case 3: return &DacProcessor<18, 3>::processSample; + case 4: return &DacProcessor<18, 4>::processSample; + case 5: return &DacProcessor<18, 5>::processSample; + case 6: return &DacProcessor<18, 6>::processSample; + case 7: return &DacProcessor<18, 7>::processSample; + default: return nullptr; + } + case 24: + switch (_noiseBits) + { + case 0: return &DacProcessor<24, 0>::processSample; + case 1: return &DacProcessor<24, 1>::processSample; + case 2: return &DacProcessor<24, 2>::processSample; + case 3: return &DacProcessor<24, 3>::processSample; + case 4: return &DacProcessor<24, 4>::processSample; + case 5: return &DacProcessor<24, 5>::processSample; + case 6: return &DacProcessor<24, 6>::processSample; + case 7: return &DacProcessor<24, 7>::processSample; + default: return nullptr; + } + default: + return nullptr; + } + } +} diff --git a/source/synthLib/dac.h b/source/synthLib/dac.h @@ -0,0 +1,105 @@ +#pragma once + +#include <cmath> +#include <cstdint> + +#include "dsp56kEmu/types.h" +#include "dsp56kEmu/utils.h" + +namespace juce +{ + class Process; +} + +namespace synthLib +{ + struct DacState + { + uint32_t randomValue = 56362; + }; + + namespace dacHelper + { + inline uint32_t lcg(uint32_t& _state) + { + // https://en.wikipedia.org/wiki/Linear_congruential_generator + constexpr uint32_t a = 1664525; + constexpr uint32_t c = 1013904223; + constexpr uint32_t m = 0xffffffff; + + _state = (a * (_state) + c) & m; + + return _state; + } + } + + template<uint32_t OutputBits, uint32_t NoiseBits> class DacProcessor + { + public: + static constexpr uint32_t InBits = 24; + + // first noise bit is 0.5 bits so add one to the output bits + static constexpr uint32_t OutBits = NoiseBits > 0 ? (OutputBits + 1) : OutputBits; + + static_assert(OutputBits > 0, "OutputBits must be > 0"); + static_assert(NoiseBits < OutBits, "NoiseBits must be <= OutputBits"); + + static constexpr float FloatToIntScale = static_cast<float>(1 << (OutputBits-1)); // 1 bit sign + static constexpr float IntToFloatScale = 1.0f / FloatToIntScale; + + static float processSample(DacState& _dacState, const dsp56k::TWord _in) + { + int32_t v = dsp56k::signextend<int32_t,24>(static_cast<int32_t>(_in)); + + if constexpr (OutBits > InBits) + { + v <<= (OutBits - InBits); + } + else if constexpr (OutBits < InBits) + { + constexpr int32_t rounder = (1<<(InBits - OutBits-1)) - 1; + v += rounder; + v >>= (InBits - OutBits); + } + +// v = 0; + + if constexpr (NoiseBits > 0) + { + constexpr int32_t rounder = (1<<(NoiseBits-1)) - 1; + + _dacState.randomValue = dacHelper::lcg(_dacState.randomValue); + + const int32_t randomValue = _dacState.randomValue >> (32u - NoiseBits); + v += (randomValue-rounder); + + v >>= 1; + } + + return static_cast<float>(v) * IntToFloatScale; + } + }; + + class Dac + { + public: + Dac(); + + using ProcessFunc = float(*)(DacState&, dsp56k::TWord); + + bool configure(uint32_t _outputBits, uint32_t _noiseBits); + + float processSample(const dsp56k::TWord _in) + { + return m_processFunc(m_state, _in); + } + + private: + static ProcessFunc findProcessFunc(uint32_t _outputBits, uint32_t _noiseBits); + + ProcessFunc m_processFunc; + DacState m_state; + uint32_t m_outputBits = 24; + uint32_t m_noiseBits = 1; + }; +}