From 68751ad64eab54865c066b742f2f202c2ce734f1 Mon Sep 17 00:00:00 2001 From: djdiskmachine <110535302+djdiskmachine@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:22:02 +0000 Subject: [PATCH] Initial move clipping to AudioMixer Not rigorously tested, only baseline Moves implementation from AudioOutDriver Clipping pre rendering --- sources/Application/Audio/DummyAudioOut.cpp | 1 - sources/Application/Audio/DummyAudioOut.h | 5 +- sources/Services/Audio/AudioMixer.cpp | 98 +++++++++++++++++++-- sources/Services/Audio/AudioMixer.h | 28 ++++-- sources/Services/Audio/AudioOut.h | 3 - sources/Services/Audio/AudioOutDriver.cpp | 85 ++---------------- sources/Services/Audio/AudioOutDriver.h | 29 ++---- 7 files changed, 128 insertions(+), 121 deletions(-) diff --git a/sources/Application/Audio/DummyAudioOut.cpp b/sources/Application/Audio/DummyAudioOut.cpp index d9c42bd9..8b87223a 100644 --- a/sources/Application/Audio/DummyAudioOut.cpp +++ b/sources/Application/Audio/DummyAudioOut.cpp @@ -53,7 +53,6 @@ void DummyAudioOut::Stop() { SAFE_DELETE(thread_) ; } ; -void DummyAudioOut::SetSoftclip(int clip, int gain) {} void DummyAudioOut::SetMasterVolume(int volume) {} void DummyAudioOut::SendPulse() diff --git a/sources/Application/Audio/DummyAudioOut.h b/sources/Application/Audio/DummyAudioOut.h index 9bd3ed19..654781da 100644 --- a/sources/Application/Audio/DummyAudioOut.h +++ b/sources/Application/Audio/DummyAudioOut.h @@ -28,8 +28,6 @@ class DummyAudioOut: public AudioOut { virtual void Trigger(); - virtual bool Clipped() { return false; }; - virtual int GetPlayedBufferPercentage() { return 0; }; void SendPulse(); @@ -39,8 +37,7 @@ class DummyAudioOut: public AudioOut { virtual int GetAudioBufferSize() ; virtual int GetAudioRequestedBufferSize() ; virtual int GetAudioPreBufferCount() ; - virtual double GetStreamTime() ; - virtual void SetSoftclip(int, int); + virtual double GetStreamTime(); virtual void SetMasterVolume(int); private: diff --git a/sources/Services/Audio/AudioMixer.cpp b/sources/Services/Audio/AudioMixer.cpp index 17608323..d1e7f804 100644 --- a/sources/Services/Audio/AudioMixer.cpp +++ b/sources/Services/Audio/AudioMixer.cpp @@ -1,5 +1,9 @@ #include "AudioMixer.h" #include "System/System/System.h" +#include + +#define MAX_POSITIVE_FIXED i2fp(32767) +#define MAX_NEGATIVE_FIXED i2fp(-32768) AudioMixer::AudioMixer(const char *name): T_SimpleList(false), @@ -8,6 +12,32 @@ AudioMixer::AudioMixer(const char *name): name_(name) { volume_=(i2fp(1)) ; + softclip_ = -1; + softclipGain_ = 0 ; + masterVolume_ = 100 ; + clipped_ = false ; + + // Precalculate constant values for softclipping algorithm + softClipData_[0].alpha = 1.45f; // -1.5db (approx.) + softClipData_[1].alpha = 1.07f; // -3db (approx.) + softClipData_[2].alpha = 0.75f; // -6db (approx.) + softClipData_[3].alpha = 0.53f; // -9db (approx.) + + for (int i = 0; i < 4; i++) { + softClipData_[i].alpha23 = softClipData_[i].alpha * (2.0f / 3.0f); + softClipData_[i].alphaInv = 1.0f / softClipData_[i].alpha; + + if (softClipData_[i].alpha > 1.0f) { + /* calculates gain compensation differently for + * modes with alpha > 1, so there's no drop in loudness + * and we can still drive the hard clipper when the input + * goes over 1.0 + */ + softClipData_[i].gainCmp = 1.0f / (1.0f - (pow(softClipData_[i].alphaInv, 2.0f) / 3.0f)); + } else { + softClipData_[i].gainCmp = 1.0f / softClipData_[i].alpha23; + } + } } ; AudioMixer::~AudioMixer() { @@ -35,13 +65,14 @@ void AudioMixer::EnableRendering(bool enable) { } ; bool AudioMixer::Render(fixed *buffer,int samplecount) { + clipped_ = false; - fixed *mixBuffer=0 ; - bool gotData=false ; - IteratorPtrit(GetIterator()) ; - for (it->Begin();!it->IsDone();it->Next()) { - AudioModule ¤t=it->CurrentItem() ; - if (!gotData) { + fixed *mixBuffer = 0; + bool gotData = false; + IteratorPtr it(GetIterator()); + for (it->Begin(); !it->IsDone(); it->Next()) { + AudioModule ¤t = it->CurrentItem(); + if (!gotData) { gotData=current.Render(buffer,samplecount) ; } else { if (!mixBuffer) { @@ -64,12 +95,22 @@ bool AudioMixer::Render(fixed *buffer,int samplecount) { if (gotData) { fixed *c = buffer; + float damp = pow((float)masterVolume_ / 100, 4.0f); + if (volume_ != i2fp(1)) { for (int i = 0; i < samplecount * 2; i++) { fixed v = fp_mul(*c, volume_); *c++ = v; } } + + // Apply soft/hard clipping before recording + c = buffer; + for (int i = 0; i < samplecount * 2; i++) { + fixed sample = *c; + sample = fl2fp(damp * fp2fl(hardClip(softClip(sample)))); + *c++ = sample; + } } if (enableRendering_&&writer_) { if (!gotData) { @@ -82,3 +123,48 @@ bool AudioMixer::Render(fixed *buffer,int samplecount) { } ; void AudioMixer::SetVolume(fixed volume) { volume_ = volume; } + +void AudioMixer::SetSoftclip(int clip, int gain) { + softclip_ = clip - 1; + softclipGain_ = gain; +} + +void AudioMixer::SetMasterVolume(int volume) { + masterVolume_ = volume; +} + +bool AudioMixer::Clipped() { return clipped_; } + +fixed AudioMixer::hardClip(fixed sample) { + if (sample > MAX_POSITIVE_FIXED || sample < MAX_NEGATIVE_FIXED) { + clipped_ = true; + return sample > 0 ? MAX_POSITIVE_FIXED : MAX_NEGATIVE_FIXED; + } + return sample; +} + +/* Implements standard cubic algorithm + * https://wiki.analog.com/resources/tools-software/sigmastudio/toolbox/nonlinearprocessors/standardcubic + */ +fixed AudioMixer::softClip(fixed sample) { + if (softclip_ == -1 || sample == 0) + return sample; + + float x; + float sampleFloat = fp2fl(sample); + float maxFloat = fp2fl(sampleFloat > 0 ? MAX_POSITIVE_FIXED : MAX_NEGATIVE_FIXED); + SoftClipData* data = &softClipData_[softclip_]; + + x = data->alphaInv * (sampleFloat / maxFloat); + if (x > -1.0f && x < 1.0f) { + sampleFloat = maxFloat * (data->alpha * (x - (pow(x, 3.0f) / 3.0f))); + } else { + sampleFloat = maxFloat * data->alpha23; + } + + if (softclipGain_) { + sampleFloat = sampleFloat * data->gainCmp; + } + + return fl2fp(sampleFloat); +} diff --git a/sources/Services/Audio/AudioMixer.h b/sources/Services/Audio/AudioMixer.h index a3abe92d..5eefeaa9 100644 --- a/sources/Services/Audio/AudioMixer.h +++ b/sources/Services/Audio/AudioMixer.h @@ -6,6 +6,13 @@ #include "Application/Instruments/WavFileWriter.h" #include +struct SoftClipData { + float alpha; + float alpha23; + float alphaInv; + float gainCmp; +}; + class AudioMixer: public AudioModule,public T_SimpleList { public: AudioMixer(const char *name) ; @@ -14,11 +21,22 @@ class AudioMixer: public AudioModule,public T_SimpleList { void SetFileRenderer(const char *path) ; void EnableRendering(bool enable) ; void SetVolume(fixed volume) ; + virtual void SetSoftclip(int clip, int gain); + virtual void SetMasterVolume(int volume) ; + virtual bool Clipped() ; + private: - bool enableRendering_ ; - std::string renderPath_ ; - WavFileWriter *writer_ ; - fixed volume_ ; - std::string name_ ; + fixed hardClip(fixed sample); + fixed softClip(fixed sample); + bool enableRendering_; + std::string renderPath_; + WavFileWriter *writer_; + fixed volume_; + std::string name_; + SoftClipData softClipData_[4]; + int softclip_; + int softclipGain_; + int masterVolume_; + bool clipped_; } ; #endif diff --git a/sources/Services/Audio/AudioOut.h b/sources/Services/Audio/AudioOut.h index 7d25a4b0..c110320e 100644 --- a/sources/Services/Audio/AudioOut.h +++ b/sources/Services/Audio/AudioOut.h @@ -18,13 +18,10 @@ class AudioOut: public AudioMixer,public Observable { virtual bool Start()=0 ; virtual void Stop()=0 ; - virtual void SetSoftclip(int clip, int gain) = 0; virtual void SetMasterVolume(int volume) = 0; virtual void Trigger()=0 ; - virtual bool Clipped()=0 ; - virtual int GetPlayedBufferPercentage()=0 ; virtual std::string GetAudioAPI()=0 ; diff --git a/sources/Services/Audio/AudioOutDriver.cpp b/sources/Services/Audio/AudioOutDriver.cpp index 56c5416f..02588650 100644 --- a/sources/Services/Audio/AudioOutDriver.cpp +++ b/sources/Services/Audio/AudioOutDriver.cpp @@ -9,31 +9,9 @@ #include AudioOutDriver::AudioOutDriver(AudioDriver &driver) { - // Precalculate constant values for softclipping algorithm - softClipData_[0].alpha = 1.45f; // -1.5db (approx.) - softClipData_[1].alpha = 1.07f; // -3db (approx.) - softClipData_[2].alpha = 0.75f; // -6db (approx.) - softClipData_[3].alpha = 0.53f; // -9db (approx.) - - for (int i = 0; i < 4; i++) { - softClipData_[i].alpha23 = softClipData_[i].alpha * (2.0f / 3.0f); - softClipData_[i].alphaInv = 1.0f / softClipData_[i].alpha; - - if (softClipData_[i].alpha > 1.0f) { - /* calculates gain compensation differently for - * modes with alpha > 1, so there's no drop in loudness - * and we can still drive the hard clipper when the input - * goes over 1.0 - */ - softClipData_[i].gainCmp = 1.0f / (1.0f - (pow(softClipData_[i].alphaInv, 2.0f) / 3.0f)); - } else { - softClipData_[i].gainCmp = 1.0f / softClipData_[i].alpha23; - } - } - - driver_=&driver ; - driver.AddObserver(*this) ; - primarySoundBuffer_=0 ; + driver_ = &driver; + driver.AddObserver(*this) ; + primarySoundBuffer_=0 ; mixBuffer_ = 0; SetOwnership(false); } @@ -56,7 +34,6 @@ void AudioOutDriver::Close() { } bool AudioOutDriver::Start() { - clipped_ = false; sampleCount_=0 ; return driver_->Start() ; } @@ -65,8 +42,6 @@ void AudioOutDriver::Stop() { driver_->Stop() ; } -bool AudioOutDriver::Clipped() { return clipped_; }; - void AudioOutDriver::Trigger() { TimeService *ts=TimeService::GetInstance() ; @@ -84,58 +59,15 @@ void AudioOutDriver::Update(Observable &o,I_ObservableData *d) } void AudioOutDriver::prepareMixBuffers() { - sampleCount_=getPlaySampleCount() ; - clipped_=false ; + sampleCount_ = getPlaySampleCount(); } ; -void AudioOutDriver::SetSoftclip(int clip, int gain) { - softclip_ = clip - 1; - softclipGain_ = gain; -} - void AudioOutDriver::SetMasterVolume(int volume) { - masterVolume_ = volume; -} - -fixed AudioOutDriver::hardClip(fixed sample) { - - if (sample > MAX_POSITIVE_FIXED || sample < MAX_NEGATIVE_FIXED) { - clipped_ = true; - return sample > 0 ? MAX_POSITIVE_FIXED : MAX_NEGATIVE_FIXED; - } - - return sample; -} - -/* Implements standard cubic algorithm - * https://wiki.analog.com/resources/tools-software/sigmastudio/toolbox/nonlinearprocessors/standardcubic - */ -fixed AudioOutDriver::softClip(fixed sample) { - if (softclip_ == -1 || sample == 0) - return sample; - - float x; - float sampleFloat = fp2fl(sample); - float maxFloat = fp2fl(sampleFloat > 0 ? MAX_POSITIVE_FIXED : MAX_NEGATIVE_FIXED); - SoftClipData* data = &softClipData_[softclip_]; - - x = data->alphaInv * (sampleFloat / maxFloat); - if (x > -1.0f && x < 1.0f) { - sampleFloat = maxFloat * (data->alpha * (x - (pow(x, 3.0f) / 3.0f))); - } else { - sampleFloat = maxFloat * data->alpha23; - } - - if (softclipGain_) { - sampleFloat = sampleFloat * data->gainCmp; - } - - return fl2fp(sampleFloat); + AudioMixer::SetMasterVolume(volume); } void AudioOutDriver::clipToMix() { - float damp = pow((float)masterVolume_ / 100, 4.0f); bool interlaced = driver_->Interlaced(); if (!hasSound_) { @@ -147,13 +79,10 @@ void AudioOutDriver::clipToMix() { fixed *p = primarySoundBuffer_; - fixed leftSample; - fixed rightSample; - for (int i = 0; i < sampleCount_; i++) { - leftSample = damp * hardClip(softClip(*p++)); - rightSample = damp * hardClip(softClip(*p++)); + fixed leftSample = *p++; + fixed rightSample = *p++; *s1 = short(fp2i(leftSample)); s1 += offset; diff --git a/sources/Services/Audio/AudioOutDriver.h b/sources/Services/Audio/AudioOutDriver.h index 522482a5..14a37af2 100644 --- a/sources/Services/Audio/AudioOutDriver.h +++ b/sources/Services/Audio/AudioOutDriver.h @@ -11,13 +11,6 @@ class AudioDriver ; #define MAX_POSITIVE_FIXED i2fp(32767) #define MAX_NEGATIVE_FIXED i2fp(-32768) -struct SoftClipData { - float alpha; - float alpha23; - float alphaInv; - float gainCmp; -}; - class AudioOutDriver: public AudioOut,protected I_Observer { public: AudioOutDriver(AudioDriver &) ; @@ -28,12 +21,9 @@ class AudioOutDriver: public AudioOut,protected I_Observer { virtual bool Start() ; virtual void Stop() ; - virtual void Trigger() ; - virtual void SetSoftclip(int clip, int gain); + virtual void Trigger(); virtual void SetMasterVolume(int volume); - virtual bool Clipped() ; - virtual int GetPlayedBufferPercentage() ; AudioDriver *GetDriver() ; @@ -49,21 +39,12 @@ class AudioOutDriver: public AudioOut,protected I_Observer { virtual void Update(Observable &o,I_ObservableData *d) ; - void prepareMixBuffers() ; - void mixToPrimary() ; - void clipToMix() ; - fixed hardClip(fixed sample); - fixed softClip(fixed sample); + void prepareMixBuffers(); + void clipToMix(); private: - AudioDriver *driver_ ; - bool clipped_ ; - bool hasSound_ ; - int softclip_ ; - int softclipGain_; - int masterVolume_; - - SoftClipData softClipData_[4]; + AudioDriver *driver_; + bool hasSound_; fixed *primarySoundBuffer_ ; short *mixBuffer_ ;