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:
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;
+ };
+}