libcw/cwDspTransforms.cpp

258 lines
6.5 KiB
C++
Raw Normal View History

#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"
#include "cwFile.h"
#include "cwText.h"
#include "cwObject.h"
#include "cwAudioFile.h"
#include "cwUtility.h"
#include "cwFileSys.h"
#include "cwAudioFileOps.h"
#include "cwVectOps.h"
#include "cwMath.h"
#include "cwDspTypes.h"
#include "cwDsp.h"
#include "cwDspTransforms.h"
namespace cw
{
namespace dsp
{
namespace compressor
{
void _ms_to_samples( obj_t*p, real_t ms, unsigned& outRef )
{
outRef = std::max((real_t)1,(real_t)floor(ms * p->srate / 1000.0));
}
}
}
}
cw::rc_t cw::dsp::compressor::create( obj_t*& p, real_t srate, unsigned procSmpCnt, real_t inGain, real_t rmsWndMaxMs, real_t rmsWndMs, real_t threshDb, real_t ratio_num, real_t atkMs, real_t rlsMs, real_t outGain, bool bypassFl )
{
p = mem::allocZ<obj_t>();
p->srate = srate;
p->procSmpCnt = procSmpCnt;
p->threshDb = threshDb;
p->ratio_num = ratio_num;
set_attack_ms(p,atkMs);
set_release_ms(p,rlsMs);
p->inGain = inGain;
p->outGain = outGain;
p->bypassFl = bypassFl;
p->rmsWndAllocCnt = (unsigned)std::max(1.0,floor(rmsWndMaxMs * srate / (1000.0 * procSmpCnt)));
p->rmsWnd = mem::allocZ<sample_t>(p->rmsWndAllocCnt);
set_rms_wnd_ms(p, rmsWndMs );
p->rmsWndIdx = 0;
p->state = kRlsCompId;
p->timeConstDb = 10.0;
p->accumDb = p->threshDb;
return kOkRC;
}
cw::rc_t cw::dsp::compressor::destroy( obj_t*& p )
{
mem::release(p->rmsWnd);
mem::release(p);
return kOkRC;
}
/*
The ratio determines to what degree a signal above the threshold is reduced.
Given a 2:1 ratio, a signal 2dB above the threshold will be reduced to 1db above the threshold.
Given a 4:1 ratio, a signal 2dB above the threshold will be reduced to 0.25db above the threshold.
Gain_reduction_db = (thresh - signal) / ratio_numerator (difference between the threshold and signal level after reduction)
Gain Coeff = 10^(gain_reduction_db / 20);
Total_reduction_db = signal - threshold + Gain_reduc_db
(total change in signal level)
The attack can be viewed as beginning at the threshold and moving to the peak
over some period of time. In linear terms this will go from 1.0 to the max gain
reductions. In this case we step from thresh to peak at a fixed rate in dB
based on the attack time.
Db: thresh - [thesh:peak] / ratio_num
Linear: pow(10, (thresh - [thesh:peak] / ratio_num)/20 );
During attacks p->accumDb increments toward the p->pkDb.
During release p->accumDb decrements toward the threshold.
(thresh - accumDb) / ratio_num gives the signal level which will be achieved
if this value is converted to linear form and applied as a gain coeff.
See compressor.m
*/
cw::rc_t cw::dsp::compressor::exec( obj_t* p, const sample_t* x, sample_t* y, unsigned n )
{
sample_t xx[n];
vop::mul(xx,x,p->inGain,n); // apply input gain
p->rmsWnd[ p->rmsWndIdx ] = vop::rms(xx, n); // calc and store signal RMS
p->rmsWndIdx = (p->rmsWndIdx + 1) % p->rmsWndCnt; // advance the RMS storage buffer
real_t rmsLin = vop::mean(p->rmsWnd,p->rmsWndCnt); // calc avg RMS
real_t rmsDb = std::max(-100.0,20 * log10(std::max((real_t)0.00001,rmsLin))); // convert avg RMS to dB
rmsDb += 100.0;
// if the compressor is bypassed
if( p->bypassFl )
{
vop::copy(y,x,n); // copy through - with no input gain
return kOkRC;
}
// if the signal is above the threshold
if( rmsDb <= p->threshDb )
p->state = kRlsCompId;
else
{
if( rmsDb > p->pkDb )
p->pkDb = rmsDb;
p->state = kAtkCompId;
}
switch( p->state )
{
case kAtkCompId:
p->accumDb = std::min(p->pkDb, p->accumDb + p->timeConstDb * n / p->atkSmp );
break;
case kRlsCompId:
p->accumDb = std::max(p->threshDb, p->accumDb - p->timeConstDb * n / p->rlsSmp );
break;
}
p->gain = pow(10.0,(p->threshDb - p->accumDb) / (p->ratio_num * 20.0));
vop::mul(y,xx,p->gain * p->outGain,n);
return kOkRC;
}
void cw::dsp::compressor::set_attack_ms( obj_t* p, real_t ms )
{
_ms_to_samples(p,ms,p->atkSmp);
}
void cw::dsp::compressor::set_release_ms( obj_t* p, real_t ms )
{
_ms_to_samples(p,ms,p->rlsSmp);
}
void cw::dsp::compressor::set_rms_wnd_ms( obj_t* p, real_t ms )
{
p->rmsWndCnt = std::max((unsigned)1,(unsigned)floor(ms * p->srate / (1000.0 * p->procSmpCnt)));
// do not allow rmsWndCnt to exceed rmsWndAllocCnt
if( p->rmsWndCnt > p->rmsWndAllocCnt )
p->rmsWndCnt = p->rmsWndAllocCnt;
}
cw::rc_t cw::dsp::recorder::create( obj_t*& pRef, real_t srate, real_t max_secs, unsigned chN )
{
obj_t* p = mem::allocZ<obj_t>();
p->srate = srate;
p->maxFrameN = (unsigned)(max_secs * srate);
p->chN = chN;
p->buf = mem::allocZ<sample_t>( p->maxFrameN * p->chN );
pRef = p;
return kOkRC;
}
cw::rc_t cw::dsp::recorder::destroy( obj_t*& pRef)
{
obj_t* p = pRef;
if( p != nullptr )
{
mem::release(p->buf);
mem::release(p);
}
pRef = nullptr;
return kOkRC;
}
cw::rc_t cw::dsp::recorder::exec( obj_t* p, const sample_t* buf, unsigned chN, unsigned frameN )
{
const sample_t* chA[ chN ];
for(unsigned i=0; i<chN; ++i)
chA[i] = buf + (i*frameN);
return exec(p, chA, chN, frameN );
}
cw::rc_t cw::dsp::recorder::exec( obj_t* p, const sample_t* chA[], unsigned chN, unsigned frameN )
{
chN = std::min( chN, p->chN );
//
if( p->frameIdx + frameN > p->maxFrameN )
frameN = p->maxFrameN - p->frameIdx;
for(unsigned i=0; i<chN; ++i)
for(unsigned j=0; j<frameN; ++j )
p->buf[ i*p->maxFrameN + p->frameIdx + j ] = chA[i][j];
p->frameIdx += frameN;
return kOkRC;
}
cw::rc_t cw::dsp::recorder::write( obj_t* p, const char* fname )
{
file::handle_t h;
cw::rc_t rc;
if((rc = file::open(h,fname, file::kWriteFl )) != kOkRC )
{
rc = cwLogError(rc,"Recorder file open failed on '%s'.", cwStringNullGuard(fname));
goto errLabel;
}
file::printf(h,"{\n");
file::printf(h,"\"srate\":%f,\n",p->srate);
file::printf(h,"\"maxFrameN\":%i,\n",p->frameIdx);
for(unsigned i=0; i<p->chN; ++i)
{
file::printf(h,"\"%i\":[",i);
for(unsigned j=0; j<p->frameIdx; ++j)
file::printf(h,"%f%c\n",p->buf[ p->maxFrameN*i + j ], j+1==p->frameIdx ? ' ' : ',');
file::printf(h,"]\n");
}
file::printf(h,"}\n");
errLabel:
if((rc = file::close(h)) != kOkRC )
{
rc = cwLogError(rc,"Recorder file close failed on '%s'.", cwStringNullGuard(fname));
goto errLabel;
}
return rc;
}