From 02cd79c61f183229ffcc8f21384b595ec4f28871 Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 15 Aug 2021 16:05:44 -0400 Subject: [PATCH] cwAudioFileProc.h/cpp, cwAudioTransforms.h/cpp, cwPvAudioFileProc.h/cpp: Initial commit. Minor update to cwAudioFileOps.cpp. --- cwAudioFileOps.cpp | 2 + cwAudioFileProc.cpp | 650 ++++++++++++++++++++++++ cwAudioFileProc.h | 63 +++ cwAudioTransforms.cpp | 262 ++++++++++ cwAudioTransforms.h | 1117 +++++++++++++++++++++++++++++++++++++++++ cwPvAudioFileProc.cpp | 495 ++++++++++++++++++ cwPvAudioFileProc.h | 41 ++ 7 files changed, 2630 insertions(+) create mode 100644 cwAudioFileProc.cpp create mode 100644 cwAudioFileProc.h create mode 100644 cwAudioTransforms.cpp create mode 100644 cwAudioTransforms.h create mode 100644 cwPvAudioFileProc.cpp create mode 100644 cwPvAudioFileProc.h diff --git a/cwAudioFileOps.cpp b/cwAudioFileOps.cpp index 4b71c5e..03e71bd 100644 --- a/cwAudioFileOps.cpp +++ b/cwAudioFileOps.cpp @@ -9,8 +9,10 @@ #include "cwUtility.h" #include "cwFileSys.h" #include "cwAudioFileOps.h" +#include "cwMath.h" #include "cwVectOps.h" #include "cwDsp.h" +#include "cwAudioTransforms.h" namespace cw { diff --git a/cwAudioFileProc.cpp b/cwAudioFileProc.cpp new file mode 100644 index 0000000..9210604 --- /dev/null +++ b/cwAudioFileProc.cpp @@ -0,0 +1,650 @@ +#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 "cwMath.h" +#include "cwVectOps.h" +#include "cwDsp.h" +#include "cwAudioTransforms.h" +#include "cwAudioFileProc.h" + +namespace cw +{ + namespace afop + { + //------------------------------------------------------------------------------------------------ + // Template Process + // + namespace process + { + typedef struct process_str + { + int foo; + double blah; + } process_t; + + rc_t open( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + process_t* p = mem::allocZ(); + ctx->userPtr = p; + + if((rc = ctx->args->getv( "foo", p->foo, + "blah", p->blah)) != kOkRC ) + { + rc = cwLogError(rc,"Parsing of 'template' args. failed."); + } + + + return rc; + } + + rc_t close( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + process_t* p = (process_t*)ctx->userPtr; + mem::release(p); + return rc; + } + + rc_t process( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + return rc; + } + + rc_t main( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + + switch( ctx->procId ) + { + case kOpenProcId: + rc = open(ctx); + break; + + case kCloseProcId: + rc = close(ctx); + break; + + case kProcProcId: + rc = process(ctx); + break; + } + return rc; + } + } + + //------------------------------------------------------------------------------------------------ + // Phase Vocoder Process + // + namespace pvoc + { + typedef struct process_str + { + dsp::pv_anl::fobj_t** anlA; // anlA[chCnt] + dsp::pv_syn::fobj_t** synA; // synA[chCnt] + unsigned chCnt; // + unsigned procSmpN; // + unsigned wndSmpN; // + unsigned hopSmpN; // + double inGain; // + double outGain; // + } process_t; + + rc_t open( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + process_t* p = mem::allocZ(); + ctx->userPtr = p; + + if((rc = ctx->args->getv( "procSmpN", p->procSmpN, + "hopSmpN", p->hopSmpN, + "wndSmpN", p->wndSmpN)) != kOkRC ) + { + rc = cwLogError(rc,"Parsing of 'pvoc' required arguments failed."); + } + + + p->chCnt = std::min( ctx->srcChN, ctx->dstChN ); + p->anlA = mem::allocZ< dsp::pv_anl::fobj_t* >( p->chCnt ); + p->synA = mem::allocZ< dsp::pv_syn::fobj_t* >( p->chCnt ); + + for(unsigned i=0; ichCnt; ++i) + { + if((rc = dsp::pv_anl::create( p->anlA[i], p->procSmpN, ctx->srcSrate, p->wndSmpN, p->hopSmpN, dsp::pv_anl::kNoCalcHzPvaFl )) != kOkRC ) + { + rc = cwLogError(rc,"PVOC analysis component create failed."); + goto errLabel; + } + + if((rc = dsp::pv_syn::create( p->synA[i], p->procSmpN, ctx->dstSrate, p->wndSmpN, p->hopSmpN )) != kOkRC ) + { + rc = cwLogError(rc,"PVOC synthesis component create failed."); + goto errLabel; + } + } + + + + errLabel: + return rc; + } + + rc_t close( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + process_t* p = (process_t*)ctx->userPtr; + + for(unsigned i=0; ichCnt; ++i) + { + if( p->anlA ) + dsp::pv_anl::destroy(p->anlA[i]); + + if( p->synA ) + dsp::pv_syn::destroy(p->synA[i]); + } + + mem::release(p); + return rc; + } + + rc_t process( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + process_t* p = (process_t*)ctx->userPtr; + + for(unsigned i=0; ichCnt; ++i) + { + if( dsp::pv_anl::exec( p->anlA[i], ctx->srcChV[i], p->hopSmpN) ) + { + + float buf[ p->anlA[i]->binCnt ]; + vop::mul( buf, p->anlA[i]->magV, p->anlA[i]->binCnt/2, p->anlA[i]->binCnt ); + + if((rc = dsp::pv_syn::exec( p->synA[i], buf, p->anlA[i]->phsV )) != kOkRC ) + { + rc = cwLogError(rc,"Pvoc synthesis failed."); + goto errLabel; + } + + vop::copy( ctx->dstChV[i], p->synA[i]->ola->outV, p->hopSmpN ); + } + } + + errLabel: + return rc; + } + + rc_t main( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + + switch( ctx->procId ) + { + case kOpenProcId: + rc = open(ctx); + break; + + case kCloseProcId: + rc = close(ctx); + break; + + case kProcProcId: + rc = process(ctx); + break; + } + return rc; + } + } + + //------------------------------------------------------------------------------------------------ + // Tremelo Process + // + namespace tremelo + { + + typedef struct tremelo_str + { + double hz; + double depth; + double phase; + } tremelo_t; + + rc_t open( proc_ctx_t* ctx ) + { + rc_t rc; + + tremelo_t* p = mem::allocZ(); + ctx->userPtr = p; + + if((rc = ctx->args->getv( "hz", p->hz, + "depth", p->depth)) != kOkRC ) + { + rc = cwLogError(rc,"Parsing of 'tremelo' ctx. failed."); + } + + + return rc; + } + + rc_t close( proc_ctx_t* ctx ) + { + tremelo_t* p = (tremelo_t*)ctx->userPtr; + mem::release(p); + return kOkRC; + } + + rc_t process( proc_ctx_t* ctx ) + { + tremelo_t* p = (tremelo_t*)ctx->userPtr; + unsigned chCnt = std::min( ctx->srcChN, ctx->dstChN ); + unsigned hopSmpN = std::min( ctx->srcHopSmpN, ctx->dstHopSmpN ); + + + for(unsigned i=0; idepth * std::sin( p->phase ); + + for(unsigned j=0; jdstChV[j][i] = gain * ctx->srcChV[j][i]; + + + p->phase += 2*M_PI*p->hz / ctx->srcSrate; + } + + return kOkRC; + } + + rc_t main( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + + switch( ctx->procId ) + { + case kOpenProcId: + rc = open(ctx); + break; + + case kCloseProcId: + rc = close(ctx); + break; + + case kProcProcId: + rc = process(ctx); + break; + } + return rc; + } + + } + } +} + + +//------------------------------------------------------------------------------------------------ +// Audio File Processor +// + +cw::rc_t cw::afop::file_processor( const char* srcFn, const char* dstFn, proc_func_t func, unsigned wndSmpN, unsigned hopSmpN, void* userPtr, const object_t* args, const object_t* recorder_cfg, unsigned recordChN ) +{ + rc_t rc = kOkRC; + + typedef float sample_t; + + audiofile::handle_t srcAfH; + audiofile::handle_t dstAfH; + audiofile::info_t info; + proc_ctx_t proc_ctx = {0}; + + if( hopSmpN > wndSmpN ) + return cwLogError(kInvalidArgRC,"The hop sample count (%i) cannot exceed the window sample count (%i).", hopSmpN, wndSmpN ); + + proc_ctx.userPtr = userPtr; + proc_ctx.args = args; + + // By default wndSmpN and hopSmpN set both the src and dst wndSmpN and hopSmpN parameters + proc_ctx.srcWndSmpN = wndSmpN; + proc_ctx.srcHopSmpN = hopSmpN; + proc_ctx.dstWndSmpN = wndSmpN; + proc_ctx.dstHopSmpN = hopSmpN; + + // if a source audio file was given + if( srcFn != nullptr ) + { + // open the source audio file + if((rc = audiofile::open(srcAfH,srcFn,&info)) != kOkRC ) + { + rc = cwLogError(rc,"Unable to open the source file:%s", cwStringNullGuard(srcFn) ); + goto errLabel; + } + + // Configure the input signal + proc_ctx.srcFn = mem::duplStr(srcFn); + proc_ctx.srcSrate = info.srate; + proc_ctx.srcChN = info.chCnt; + proc_ctx.srcBits = info.bits; + + // By default the output signal has the same configuration as the input file + proc_ctx.dstSrate = info.srate; + proc_ctx.dstChN = info.chCnt; + proc_ctx.dstBits = info.bits; // TODO: allow setting output file bits as optional parameter (0=float) + // Be sure if settting bits from input file with floating point sample format + // that this case is correctly handled here. + } + + // During the 'open' call the user defined function (UDF) can override the destination file configuration + proc_ctx.procId = kOpenProcId; + if((rc = func( &proc_ctx )) != kOkRC ) + { + rc = cwLogError(rc,"Open processes failed."); + goto errLabel; + } + + // Create the output file + if( dstFn != nullptr ) + { + + if((rc = audiofile::create(dstAfH,dstFn, proc_ctx.dstSrate, proc_ctx.dstBits, proc_ctx.dstChN )) != kOkRC ) + { + rc = cwLogError(rc,"Unable to create the destination file:%s", cwStringNullGuard(dstFn) ); + goto errLabel; + } + + proc_ctx.dstFn = mem::duplStr(dstFn); + + } + + if( rc == kOkRC ) + { + sample_t* srcChFileBuf[ proc_ctx.srcChN ]; // srcChFileBuf[ srcChN ][ srcHopSmpCnt ] - src file ch buffer + sample_t* dstChFileBuf[ proc_ctx.dstChN ]; // dstChFileBuf[ dstChN ][ dstHopSmpCnt ] - dst file ch buffer + const sample_t* iChBuf[ proc_ctx.srcChN ]; // iChBuf[ srcChN ][ srcWndSmpCnt ] - src processing buffer + sample_t* oChBuf[ proc_ctx.dstChN ]; // oChBuf[ dstChN ][ dstWndSmpCnt ] - dst processing buffer + + struct dsp::shift_buf::obj_str* srcShiftBufA[ proc_ctx.srcChN ]; // src shift buffer + struct dsp::shift_buf::obj_str* dstShiftBufA[ proc_ctx.dstChN ]; // dst shift buffer + + // Allocate memory for the source/dest file buffer + sample_t* srcFileBuf = mem::allocZ( proc_ctx.srcChN * proc_ctx.srcHopSmpN ); + sample_t* dstFileBuf = mem::allocZ( proc_ctx.dstChN * proc_ctx.dstHopSmpN ); + sample_t* procFileBuf = mem::allocZ( proc_ctx.dstChN * proc_ctx.dstHopSmpN ); + + // Setup the input and output processing buffer + proc_ctx.srcChV = iChBuf; + proc_ctx.dstChV = oChBuf; + + // For each source channel - setup the source file buffer and create the src shift buffer + for(unsigned i = 0; i( recordChN ); + for(unsigned i = 0; ioutV; + } + + if(!rd_fl) + break; // src shift-buf iterations are done + } + + // Call the processing function + proc_ctx.procId = kProcProcId; + if((rc = func( &proc_ctx )) != kOkRC ) + { + // kEof isn't an error + if( rc == kEofRC ) + rc = kOkRC; + else + rc = cwLogError(rc,"Audio file process reported an error."); + + goto doneLabel; + } + + + // if output samples exist - shift the new samples into the output shift buffer + if( proc_ctx.dstChN ) + { + + do // destination shif-buffer iteration processing loop + { + bool wr_fl = false; + + // Update the dst shift buffers + // Note that the shift buffer has to be called until it returns false therefore + // we must iterate over the file writing process writing hopSmpN frames on each call. + for(unsigned j=0; joutV; + } + } + + // if no true's were returned by the shift-buffer (Note that all channels must return the same + // value because they are all configured the same way.) + if( !wr_fl ) + break; + + // If a destination file was specified + if( dstFn != nullptr ) + { + if((rc = audiofile::writeFloat( dstAfH, proc_ctx.dstHopSmpN, proc_ctx.dstChN, dstChFileBuf )) != kOkRC ) + { + rc = cwLogError(rc,"Audio process source file '%s' read failed.", srcFn ); + goto doneLabel; + } + } + + }while(1); // desination shift-buffer interation processing loop + } + + + }while(1); // source shift-buffer iteration processing loop + + + proc_ctx.cycleIndex += 1; + } // end while + + doneLabel: + + for(unsigned i=0; igetv("srcFn", srcFn, + "dstFn", dstFn, + "cfg", cfgLabel)) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"Error parsing the main audio file proc configuration record."); + goto errLabel; + } + + // locate the cfg for the specific process function to run + if((rc = cfg->getv(cfgLabel, pgm)) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"The audio file proc. configuration '%s' was not found.",cwStringNullGuard(cfgLabel)); + goto errLabel; + } + + // parse the specific process function configuration record + if((rc = pgm->getv("wndSmpN", wndSmpN, + "hopSmpN", hopSmpN, + "program", program, + "args", args)) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"The audio file proc. configuration '%s' parse failed.",cwStringNullGuard(cfgLabel)); + goto errLabel; + } + + // parse the recorder spec + if((rc = cfg->getv_opt("recordChN", recordChN, + "recorder", recorder)) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"Error parsing the main audio file proc optional configuration fields."); + goto errLabel; + } + + + // TODO: add optional args: inGain,outGain,dstBits + + // locate the executable function associated with the specified process function + for(unsigned i=0; true; ++i) + { + // if the function was not found + if( labelFuncA[i].func == nullptr ) + { + rc = cwLogError(kInvalidArgRC,"The audio processing program '%s' could not be found.", cwStringNullGuard(program)); + goto errLabel; + } + + // if this is the specified function + if( textCompare(labelFuncA[i].label,program ) == 0 ) + { + procFunc = labelFuncA[i].func; + break; + } + } + + // run the processr + if((rc = file_processor( srcFn, dstFn, procFunc, wndSmpN, hopSmpN, nullptr, args, recorder, recordChN)) != kOkRC ) + { + rc = cwLogError(rc,"The audio file proc. failed."); + goto errLabel; + } + + + errLabel: + return rc; + +} + diff --git a/cwAudioFileProc.h b/cwAudioFileProc.h new file mode 100644 index 0000000..c2bceab --- /dev/null +++ b/cwAudioFileProc.h @@ -0,0 +1,63 @@ +namespace cw +{ + namespace afop + { + + enum + { + kOpenProcId, + kProcProcId, + kCloseProcId + }; + + typedef struct proc_ctx_str + { + unsigned procId; + + void* userPtr; + const object_t* args; // cfg. for the selected process func + unsigned cycleIndex; + + char* srcFn; + float srcSrate; + unsigned srcChN; + unsigned srcBits; + const float** srcChV; // srcChV[ srcChN ][ srcWndSmpN ] - read incoming samples from this buffer + unsigned srcWndSmpN; // + unsigned srcHopSmpN; // + + char* dstFn; + float dstSrate; + unsigned dstChN; + unsigned dstBits; + float** dstChV; // dstChV[ dstChN ][ dstWndSmpN ] + unsigned dstWndSmpN; // + unsigned dstHopSmpN; // + + + dsp::data_recorder::fobj_t** recordChA; // recordChA[ recordChN ] + + } proc_ctx_t; + + // Open + // Accept or modify the destination configuration. The src signal parameters are definted by the driver program. + // + // Proc: + // If srcChN is non-zero then srcChV will be valid. The last srcHopSmpN will contain new samples for this iteration + // If dstChN is non-zero then fill at least the first dstHopSmpN samples in dstChV[][]. + // + // Note that the srcChV[][] and dstChV[][] both point to buffers of lengh src/dstWndSmpN but only the first src/dstHopSmpN + // are finalized for a given cycle. The training wndSmpN-hopSmpN will be available (shifted right by hopSmpN samples) + // on the next call. + // + // Return kEofRC if no input file is given and processing is complete. + + + typedef rc_t (*proc_func_t)( proc_ctx_t* ctx ); + rc_t file_processor( const char* srcFn, const char* dstFn, proc_func_t func, unsigned wndSmpN, unsigned hopSmpN, void* userArg, const object_t* args, const object_t* recorder_cfg, unsigned recordChN=0 ); + rc_t file_processor( const object_t* cfg ); + + + + } +} diff --git a/cwAudioTransforms.cpp b/cwAudioTransforms.cpp new file mode 100644 index 0000000..1dd0840 --- /dev/null +++ b/cwAudioTransforms.cpp @@ -0,0 +1,262 @@ +#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 "cwDsp.h" +#include "cwAudioTransforms.h" + +namespace cw +{ + namespace dsp + { + + namespace wnd_func + { + + idLabelPair_t wndLabelArray[] = + { + { kHannWndId, "hann" }, + { kHammingWndId, "hamming" }, + { kTriangleWndId, "triangle" }, + { kKaiserWndId, "kaiser" }, + { kHannMatlabWndId, "hann_matlab" }, + { kUnityWndId, "unity" }, + { kInvalidWndId, "" } + }; + + const char* wndIdToLabel( unsigned id ) + { return cw::idToLabel( wndLabelArray, id, kInvalidWndId ); } + + unsigned wndLabelToId( const char* label ) + { return cw::labelToId( wndLabelArray, label, kInvalidWndId ); } + + rc_t _test( const char* windowLabel, const double* wndV, unsigned wndN ) + { + rc_t rc = kOkRC; + wnd_func::fobj_t* p = nullptr; + + unsigned wndId = wndLabelToId( windowLabel ); + + if((rc = create(p,wndId,wndN,3)) == kOkRC ) + { + vop::print(p->wndV, p->wndN, "%f ", windowLabel); + + printf("diff: %f\n", vop::sum_sq_diff( wndV, p->wndV, wndN)); + + destroy(p); + } + + return rc; + } + + rc_t test( const cw::object_t* args ) + { + double hann_15[] = { 0.0, 0.04951557, 0.1882551 , 0.38873953, 0.61126047, 0.8117449, 0.95048443, 1.0, 0.95048443, 0.8117449, 0.61126047, 0.38873953, 0.1882551, 0.04951557, 0.0 }; + double hann_16[] = { 0.0, 0.04322727, 0.1654347 , 0.3454915 , 0.55226423, 0.75, 0.9045085 , 0.9890738, 0.9890738, 0.9045085, 0.75, 0.55226423, 0.3454915, 0.1654347 , 0.04322727, 0.0 }; + double hamm_15[] = { 0.08,0.12555432, 0.25319469, 0.43764037, 0.64235963, 0.82680531, 0.95444568, 1.0, 0.95444568, 0.82680531,0.64235963, 0.43764037, 0.25319469, 0.12555432, 0.08 }; + double hamm_16[] = { 0.08,0.11976909, 0.23219992, 0.39785218, 0.58808309, 0.77, 0.91214782, 0.9899479, 0.9899479 , 0.91214782,0.77, 0.58808309, 0.39785218, 0.23219992, 0.11976909, 0.08}; + double tri_15[] = { 0.0, 0.14285714, 0.28571429, 0.42857143, 0.57142857, 0.71428571, 0.85714286, 1.0, 0.85714286, 0.71428571,0.57142857, 0.42857143, 0.28571429, 0.14285714, 0.0 }; + double tri_16[] = { 0.0, 0.13333333, 0.26666667, 0.4, 0.53333333, 0.66666667, 0.8, 0.93333333, 0.93333333, 0.8, 0.66666667, 0.53333333, 0.4, 0.26666667, 0.13333333, 0.0 }; + double ones_5[] = { 1.0, 1.0, 1.0, 1.0, 1.0 }; + + rc_t rc = kOkRC; + + _test( "hann", hann_15, 15); + _test( "hann", hann_16, 16); + _test( "hamming", hamm_15, 15); + _test( "hamming", hamm_16, 16); + _test( "triangle", tri_15, 15); + _test( "triangle", tri_16, 16); + _test( "unity", ones_5, 5); + + if( rc != kOkRC ) + cwLogError(rc,"Window test failed."); + return rc; + } + } + + namespace ola + { + rc_t test( const cw::object_t* args ) + { + typedef float sample_t; + + rc_t rc = kOkRC; + unsigned wndSmpCnt = 16; + unsigned hopSmpCnt = 4; + unsigned procSmpCnt = 2; + unsigned wndTypeId = wnd_func::kUnityWndId; + unsigned hopCnt = 8; + unsigned oSmpCnt = hopCnt * hopSmpCnt; + unsigned i,j,k; + + sample_t cV[] = { 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, 4,4,4,4, 4,4,4,4, 4,4,4,4, 4,4,4,4 }; + sample_t* x = mem::allocZ(wndSmpCnt); + sample_t* y = mem::allocZ(oSmpCnt); + vop::fill(x,wndSmpCnt,1.0); + + ola::fobj_t* p = nullptr; + + if((rc = ola::create(p, wndSmpCnt, hopSmpCnt, procSmpCnt, wndTypeId )) == kOkRC ) + { + + // Each iteration represents a single audio cylce + // which sources/sinks procSmpCnt samples + for(i=0,j=0,k=0; k hopSmpCnt ) + { + j -= hopSmpCnt; + ola::exec(p,x,p->wndSmpCnt); + } + + const sample_t* op; + + // Get procSmpCnt samples from the output. + if( (op=ola::execOut(p)) != NULL ) + { + assert( y + k + p->procSmpCnt <= y + oSmpCnt ); + vop::copy(y+k, op, p->procSmpCnt); + k += p->procSmpCnt; + } + } + } + + vop::print(cV,oSmpCnt,"%f ","Correct "); + vop::print(y, oSmpCnt,"%f ","Computed"); + printf("diff:%f\n", vop::sum_sq_diff(cV,y,oSmpCnt)); + + ola::destroy(p); + mem::release(x); + mem::release(y); + return rc; + + } + } + + namespace shift_buf + { + rc_t test( const object_t* args ) + { + rc_t rc = kOkRC; + typedef float sample_t; + + sample_t m[] = + { + 1, 2, 3, 4, 5, 6, 7, + 7, 8, 9,10,11,12,13, + 13,14,15,16,17,18,19, + 19,20,21,22,23,24,25, + 25,26,27,28,29,30,31, + 31,32,33,34,35,36,37, + 37,38,39,40,41,42,43, + 43,44,45,46,47,48,49 + }; + + unsigned procSmpCnt = 5; // count of samples to be fed to the shift buffer on each cycle + unsigned hopSmpCnt = 6; // count of samples between shift buffer outputs + unsigned wndSmpCnt = 7; // count of samples in each shift buffer output + + unsigned iSmpCnt = 49; // count of samples in the input test signal + unsigned oColCnt = iSmpCnt / hopSmpCnt; + unsigned oSmpCnt = oColCnt * wndSmpCnt; // count of samples in the output test signal + unsigned i,j; + + shift_buf::fobj_t* p = nullptr; + + if((rc = shift_buf::create(p,procSmpCnt,wndSmpCnt,hopSmpCnt)) == kOkRC ) + { + sample_t* x = mem::allocZ(iSmpCnt); + sample_t* y = mem::allocZ(oSmpCnt); + + vop::seq(x,iSmpCnt,1.0f,1.0f); + + for(i=0,j=0; ioutV, wndSmpCnt ); + + vop::print(p->outV, wndSmpCnt, "%f "); + vop::print(m + j, wndSmpCnt, "%f "); + + if( !vop::is_equal(p->outV, m+j, wndSmpCnt )) + { + rc = cwLogError(kTestFailRC,"shift_buf test failed."); + goto errLabel; + } + + } + } + + mem::release(x); + mem::release(y); + shift_buf::destroy(p); + + } + + errLabel: + return rc; + } + } + + namespace pv_anl + { + rc_t test( const object_t* args ) + { + rc_t rc = kOkRC; + pv_anl::fobj_t* pva = nullptr; + pv_syn::fobj_t* pvs = nullptr; + unsigned procSmpCnt = 0; + float srate = 0; + float out_srate = 0; + unsigned wndSmpCnt = 0; + unsigned hopSmpCnt = 0; + unsigned flags = kCalcHzPvaFl; + unsigned wndTypeId = wnd_func::kHannWndId; + + if((rc = create( pva, procSmpCnt, srate, wndSmpCnt, hopSmpCnt, flags )) != kOkRC ) + { + } + + if((rc = create( pvs, procSmpCnt, out_srate, wndSmpCnt, hopSmpCnt, wndTypeId )) != kOkRC ) + { + } + + + destroy(pva); + destroy(pvs); + + return rc; + } + + } + + rc_t test( const cw::object_t* args ) + { + wnd_func::test(args); + ola::test(args); + shift_buf::test(args); + return kOkRC; + } + } +} diff --git a/cwAudioTransforms.h b/cwAudioTransforms.h new file mode 100644 index 0000000..731f11c --- /dev/null +++ b/cwAudioTransforms.h @@ -0,0 +1,1117 @@ +#ifndef cwAudioTransforms_h +#define cwAudioTransforms_h + + +namespace cw +{ + namespace dsp + { + + + //--------------------------------------------------------------------------------------------------------------------------------- + // Window Function + // + namespace wnd_func + { + + enum + { + kInvalidWndId = 0x000, + kHannWndId = 0x001, + kHammingWndId = 0x002, + kTriangleWndId = 0x004, + kKaiserWndId = 0x008, + kHannMatlabWndId= 0x010, + kUnityWndId = 0x020, + + kWndIdMask = 0x0ff, + + kNormByLengthWndFl = 0x100, // mult by 1/wndSmpCnt + kNormBySumWndFl = 0x200, // mult by wndSmpCnt/sum(wndV) + kSlRejIsBetaWndFl = 0x400 // kaiser beta arg. is being passed as kaiserSideLobeRejectDb arg. + }; + + template< typename sample_t > + struct obj_str + { + unsigned wndTypeId; // + unsigned flags; // + sample_t* wndV; // wndV[ wndN ] + unsigned wndN; // length of wndV[] and outV[] + sample_t* outV; // outV[ wndN ] + double kaiserSLRejectDb; // + }; + + typedef struct obj_str fobj_t; + typedef struct obj_str dobj_t; + + const char* wndIdToLabel( unsigned id ); // window type to label + unsigned wndLabelToId( const char* label ); // window type label to id + + template< typename sample_t > + rc_t create( struct obj_str*& p, unsigned wndId, unsigned wndSmpCnt, double kaiserSideLobeRejectDb ) + { + rc_t rc = kOkRC; + + p = mem::allocZ< struct obj_str< sample_t > >(); + + p->wndV = mem::allocZ(wndSmpCnt); + p->outV = mem::allocZ(wndSmpCnt); + p->wndN = wndSmpCnt; + p->wndTypeId = wndId & kWndIdMask; + p->flags = wndId & ~kWndIdMask; + + + switch( p->wndTypeId ) + { + case kHannWndId: + hann(p->wndV,p->wndN); + break; + + case kHammingWndId: + hamming(p->wndV,p->wndN); + break; + + case kTriangleWndId: + triangle(p->wndV,p->wndN); + break; + + case kKaiserWndId: + { + sample_t beta = (p->flags & kSlRejIsBetaWndFl) ? kaiserSideLobeRejectDb : kaiser_beta_from_sidelobe_reject(kaiserSideLobeRejectDb); + kaiser(p->wndV,p->wndN, beta); + } + break; + + case kHannMatlabWndId: + hann_matlab(p->wndV, p->wndN); + break; + + case kUnityWndId: + vop::fill(p->wndV,p->wndN,1); + break; + + default: + rc = cwLogError(kInvalidArgRC,"The window id '%i' (0x%x) is not valid.", wndId, wndId ); + } + + + sample_t den = 0; + sample_t num = 1; + if( cwIsFlag(p->flags,kNormBySumWndFl) ) + { + den = vop::sum(p->wndV, p->wndN); + num = wndSmpCnt; + } + + if( cwIsFlag(p->flags,kNormByLengthWndFl) ) + den += wndSmpCnt; + + if( den > 0 ) + { + vop::mul(p->wndV,num,p->wndN); + vop::div(p->wndV,den,p->wndN); + } + + if( rc != kOkRC ) + destroy(p); + + return rc; + + } + + template< typename sample_t > + rc_t destroy( struct obj_str*& p ) + { + if( p != nullptr ) + { + mem::release(p->outV); + mem::release(p->wndV); + mem::release(p); + } + return kOkRC; + } + + template< typename sample_t > + rc_t exec( struct obj_str* p, const sample_t* sigV, unsigned sigN, sample_t* outV=nullptr, unsigned outN=0 ) + { + rc_t rc = kOkRC; + + if( outN > p->wndN ) + return cwLogError(kInvalidArgRC,"The signal size (%i) is greater than the window size (%i). ",outN,p->wndN); + + if( outV == nullptr ) + { + outV = p->outV; + outN = p->wndN; + } + + vop::mul( outV, p->wndV, sigV, outN ); + + return rc; + } + + rc_t test( const cw::object_t* args ); + + } + + + //--------------------------------------------------------------------------------------------------------------------------------- + // Overlap Add + // + namespace ola + { + template< typename sample_t > + struct obj_str + { + wnd_func::obj_str* wf; // + unsigned wndSmpCnt; // + unsigned hopSmpCnt; // + unsigned procSmpCnt; // + sample_t* bufV; // bufV[wndSmpCnt] overlap add buffer + sample_t* outV; // outV[hopSmpCnt] output vector + sample_t* outPtr; // outPtr[procSmpCnt] output vector + unsigned idx; // idx of next val in bufV[] to be moved to outV[] + }; + + typedef struct obj_str fobj_t; + typedef struct obj_str dobj_t; + + // hopSmpCnt must be <= wndSmpCnt. + // hopSmpCnt must be an even multiple of procSmpCnt. + // Call exec() at the spectral frame rate. + // Call execOut() at the time domain audio frame rate. + + // Set wndTypeId to one of the cmWndFuncXXX enumerated widnow type id's. + + template< typename sample_t > + rc_t create( struct obj_str*& p, unsigned wndSmpCnt, unsigned hopSmpCnt, unsigned procSmpCnt, unsigned wndTypeId ) + { + rc_t rc = kOkRC; + + p = mem::allocZ< struct obj_str >(); + + if((rc = wnd_func::create( p->wf, wndTypeId, wndSmpCnt, 0)) != kOkRC ) + return rc; + + p->bufV = mem::allocZ( wndSmpCnt ); + p->outV = mem::allocZ( hopSmpCnt ); + p->outPtr = p->outV + hopSmpCnt; + + // hopSmpCnt must be an even multiple of procSmpCnt + assert( hopSmpCnt % procSmpCnt == 0 ); + + assert( wndSmpCnt >= hopSmpCnt ); + + p->wndSmpCnt = wndSmpCnt; + p->hopSmpCnt = hopSmpCnt; + p->procSmpCnt = procSmpCnt; + p->idx = 0; + + return rc; + } + + template< typename sample_t > + rc_t destroy( struct obj_str*& p ) + { + rc_t rc = kOkRC; + if( p != nullptr ) + { + wnd_func::destroy(p->wf); + + mem::release( p->bufV ); + mem::release( p->outV ); + mem::release( p ); + } + return rc; + } + + template< typename sample_t > + rc_t exec( struct obj_str* p, const sample_t* sp, unsigned sN ) + { + rc_t rc = kOkRC; + + assert( sN == p->wndSmpCnt ); + const sample_t* ep = sp + sN; + const sample_t* wp = p->wf->wndV; + int i,j,k,n; + + // [Sum head of incoming samples with tail of ola buf] + // fill outV with the bufV[idx:idx+hopSmpCnt] + sp[hopSmpCnt] + for(i=0; i<(int)p->hopSmpCnt; ++i) + { + p->outV[i] = p->bufV[p->idx++] + (*sp++ * *wp++); + + if( p->idx == p->wndSmpCnt ) + p->idx = 0; + } + + // [Sum middle of incoming samples with middle of ola buf] + // sum next wndSmpCnt - hopSmpCnt samples of sp[] into bufV[] + n = p->wndSmpCnt - (2*p->hopSmpCnt); + k = p->idx; + + for(j=0; jbufV[k++] += (*sp++ * *wp++); + if( k == (int)p->wndSmpCnt ) + k = 0; + } + + // [Assign tail of incoming to tail of ola buf] + // assign ending samples from sp[] into bufV[] + while( sp < ep ) + { + p->bufV[k++] = (*sp++ * *wp++); + + if( k == (int)p->wndSmpCnt ) + k = 0; + } + + p->outPtr = p->outV; + + return rc; + } + + template< typename sample_t > + const sample_t* execOut( struct obj_str* p ) + { + const sample_t* sp = p->outPtr; + if( sp >= p->outV + p->hopSmpCnt ) + return NULL; + + p->outPtr += p->procSmpCnt; + + return sp; + } + + + rc_t test( const cw::object_t* args ); + + } + + //--------------------------------------------------------------------------------------------------------------------------------- + // Shift Buffer + // + namespace shift_buf + { + template< typename sample_t > + struct obj_str + { + unsigned bufSmpCnt; // wndSmpCnt + hopSmpCnt + sample_t* bufV; // bufV[bufSmpCnt] all other pointers use this memory + sample_t* outV; // output window outV[ outN ] + unsigned outN; // outN == wndSmpCnt + unsigned procSmpCnt; // input sample count + unsigned wndSmpCnt; // output sample count + unsigned hopSmpCnt; // count of samples to shift the buffer by on each call to cmShiftExec() + sample_t* inPtr; // ptr to location in outV[] to recv next sample + bool fl; // reflects the last value returned by cmShiftBufExec(). + }; + + typedef obj_str fobj_t; + typedef obj_str dobj_t; + + template< typename sample_t > + rc_t create( struct obj_str*& p, unsigned procSmpCnt, unsigned wndSmpCnt, unsigned hopSmpCnt ) + { + rc_t rc = kOkRC; + + p = mem::allocZ< struct obj_str >(); + + if( hopSmpCnt > wndSmpCnt ) + return cwLogError( kInvalidArgRC, "The window sample count (%i) must be greater than or equal to the hop sample count (%i).", wndSmpCnt, hopSmpCnt ); + + // The worst case storage requirement is where there are wndSmpCnt-1 samples in outV[] and procSmpCnt new samples arrive. + p->bufSmpCnt = wndSmpCnt + procSmpCnt; + p->bufV = mem::allocZ( p->bufSmpCnt ); + p->outV = p->bufV; + p->outN = wndSmpCnt; + p->wndSmpCnt = wndSmpCnt; + p->procSmpCnt = procSmpCnt; + p->hopSmpCnt = hopSmpCnt; + p->inPtr = p->outV; + p->fl = false; + return rc; + } + + template< typename sample_t > + rc_t destroy( struct obj_str*& p ) + { + if( p != nullptr ) + { + mem::release(p->outV); + mem::release(p); + } + return kOkRC; + } + + template< typename sample_t > + bool exec( struct obj_str* p, const sample_t* sp, unsigned sn ) + { + assert( sn <= p->procSmpCnt ); + + // The active samples are in outV[wndSmpCnt] + // Stored samples are between outV + wndSmpCnt and inPtr. + + // if the previous call to this function returned true then the buffer must be + // shifted by hopSmpCnt samples - AND sp[] is ignored. + if( p->fl ) + { + // shift the output buffer to the left to remove expired samples + p->outV += p->hopSmpCnt; + + // if there are not wndSmpCnt samples left in the buffer + if( p->inPtr - p->outV < p->wndSmpCnt ) + { + // then copy the remaining active samples (between outV and inPtr) + // to the base of the physicalbuffer + unsigned n = p->inPtr - p->outV; + memmove( p->bufV, p->outV, n * sizeof(sample_t)); + + p->inPtr = p->bufV + n; // update the input and output positions + p->outV = p->bufV; + } + } + else + { + // if the previous call to this function returned false then sp[sn] should not be ignored + assert( p->inPtr + sn <= p->outV + p->bufSmpCnt ); + // copy the incoming samples into the buffer + vop::copy(p->inPtr,sp,sn); + p->inPtr += sn; + } + + // if there are at least wndSmpCnt available samples in outV[] + p->fl = p->inPtr - p->outV >= p->wndSmpCnt; + + return p->fl; + } + + rc_t test( const cw::object_t* args ); + + } + + + //--------------------------------------------------------------------------------------------------------------------------------- + // Phase to Frequency + // + namespace phs_to_frq + { + template< typename T > + struct obj_str + { + T* hzV; // hzV[binCnt] output vector - frequency in Hertz + T* phsV; // phsV[binCnt] + T* wV; // bin freq in rads/hop + double srate; + unsigned hopSmpCnt; + unsigned binCnt; + }; + + typedef obj_str< float > fobj_t; + typedef obj_str< double> dobj_t; + + template< typename T > + rc_t create( struct obj_str*& p, const T& srate, unsigned binCnt, unsigned hopSmpCnt ) + { + rc_t rc = kOkRC; + + p = mem::allocZ< struct obj_str >(); + + p->hzV = mem::allocZ( binCnt ); + p->phsV = mem::allocZ( binCnt ); + p->wV = mem::allocZ( binCnt ); + p->srate = srate; + p->binCnt = binCnt; + p->hopSmpCnt = hopSmpCnt; + + for(unsigned i=0; iwV[i] = M_PI * i * hopSmpCnt / (binCnt-1); + + + return rc; + } + + template< typename T > + rc_t destroy( struct obj_str*& p ) + { + if( p != nullptr ) + { + mem::release( p->hzV ); + mem::release( p->phsV ); + mem::release( p->wV ); + mem::release( p ); + } + return kOkRC; + } + + template< typename T > + rc_t exec( struct obj_str* p, const T* phsV ) + { + rc_t rc = kOkRC; + unsigned i; + double twoPi = 2.0 * M_PI; + double den = twoPi * p->hopSmpCnt; + + for(i=0; ibinCnt; ++i) + { + T dPhs = phsV[i] - p->phsV[i]; + + // unwrap phase - see phase_study.m for explanation + T k = round( (p->wV[i] - dPhs) / twoPi); + + // convert phase change to Hz + p->hzV[i] = (k * twoPi + dPhs) * p->srate / den; + + // store phase for next iteration + p->phsV[i] = phsV[i]; + + } + + return rc; + + } + + } + + //--------------------------------------------------------------------------------------------------------------------------------- + // Phase Vocoder (Analysis) + // + + namespace pv_anl + { + + enum + { + kNoCalcHzPvaFl = 0x00, + kCalcHzPvaFl = 0x01, + }; + + template< typename T > + struct obj_str + { + struct shift_buf::obj_str* sb; + struct fft::obj_str* ft; + struct wnd_func::obj_str* wf; + struct phs_to_frq::obj_str* pf; + + unsigned flags; + unsigned procSmpCnt; + T srate; + unsigned wndSmpCnt; + unsigned hopSmpCnt; + unsigned binCnt; + + const T* magV; // amplitude NOT power - alias to ft->magV + const T* phsV; // alias to ft->phsV + const T* hzV; + + }; + + typedef obj_str< float > fobj_t; + typedef obj_str< double> dobj_t; + + template< typename T > + rc_t create( struct obj_str*& p, unsigned procSmpCnt, const T& srate, unsigned wndSmpCnt, unsigned hopSmpCnt, unsigned flags ) + { + rc_t rc = kOkRC; + + p = mem::allocZ< struct obj_str >(); + + shift_buf::create( p->sb, procSmpCnt, wndSmpCnt, hopSmpCnt ); + wnd_func::create( p->wf, wnd_func::kHannWndId | wnd_func::kNormByLengthWndFl, wndSmpCnt, 0 ); + fft::create( p->ft, wndSmpCnt, fft::kToPolarFl); + phs_to_frq::create(p->pf, srate, p->ft->binN, hopSmpCnt ); + + p->flags = flags; + p->procSmpCnt = procSmpCnt; + p->wndSmpCnt = wndSmpCnt; + p->hopSmpCnt = hopSmpCnt; + p->binCnt = p->ft->binN; + + p->magV = p->ft->magV; + p->phsV = p->ft->phsV; + p->hzV = p->pf->hzV; + + return rc; + } + + template< typename T > + rc_t destroy( struct obj_str*& p ) + { + if( p != nullptr ) + { + shift_buf::destroy( p->sb ); + wnd_func::destroy( p->wf ); + fft::destroy( p->ft ); + phs_to_frq::destroy( p->pf ); + mem::release( p ); + } + return kOkRC; + } + + template< typename T > + bool exec( struct obj_str* p, const T* x, unsigned xN ) + { + bool fl = false; + while( shift_buf::exec(p->sb,x,xN) ) + { + wnd_func::exec(p->wf, p->sb->outV, p->sb->wndSmpCnt ); + + fft::exec(p->ft, p->wf->outV, p->wf->wndN); + + if( cwIsFlag(p->flags,kCalcHzPvaFl) ) + phs_to_frq::exec(p->pf,p->phsV); + + fl = true; + } + + return fl; + + } + } + + //--------------------------------------------------------------------------------------------------------------------------------- + // Phase Vocoder (Synthesis) + // + + namespace pv_syn + { + template< typename T > + struct obj_str + { + ifft::obj_str* ft; + wnd_func::obj_str* wf; + ola::obj_str* ola; + + T* minRphV; + T* maxRphV; + T* itrV; + T* phs0V; + T* mag0V; + T* phsV; + T* magV; + + double outSrate; + unsigned procSmpCnt; + unsigned wndSmpCnt; + unsigned hopSmpCnt; + unsigned binCnt; + + }; + + typedef obj_str< float > fobj_t; + typedef obj_str< double> dobj_t; + + template< typename T > + rc_t create( struct obj_str*& p, unsigned procSmpCnt, const T& outSrate, unsigned wndSmpCnt, unsigned hopSmpCnt, unsigned wndTypeId=wnd_func::kHannWndId ) + { + rc_t rc = kOkRC; + + p = mem::allocZ< struct obj_str >(); + + int k; + double twoPi = 2.0 * M_PI; + bool useHannFl = true; + int m = useHannFl ? 2 : 1; + + p->outSrate = outSrate; + p->procSmpCnt = procSmpCnt; + p->wndSmpCnt = wndSmpCnt; + p->hopSmpCnt = hopSmpCnt; + p->binCnt = wndSmpCnt / 2 + 1; + + p->minRphV = mem::allocZ( p->binCnt ); + p->maxRphV = mem::allocZ( p->binCnt ); + p->itrV = mem::allocZ( p->binCnt ); + p->phs0V = mem::allocZ( p->binCnt ); + p->phsV = mem::allocZ( p->binCnt ); + p->mag0V = mem::allocZ( p->binCnt ); + p->magV = mem::allocZ( p->binCnt ); + + + wnd_func::create( p->wf, wndTypeId, wndSmpCnt, 0); + ifft::create( p->ft, p->binCnt ); + ola::create( p->ola, wndSmpCnt, hopSmpCnt, procSmpCnt, wndTypeId ); + + for(k=0; k<(int)p->binCnt; ++k) + { + // complete revolutions per hop in radians + p->itrV[k] = twoPi * floor((double)k * hopSmpCnt / wndSmpCnt ); + + p->minRphV[k] = ((T)(k-m)) * hopSmpCnt * twoPi / wndSmpCnt; + p->maxRphV[k] = ((T)(k+m)) * hopSmpCnt * twoPi / wndSmpCnt; + + //printf("%f %f %f\n",p->itrV[k],p->minRphV[k],p->maxRphV[k]); + } + + return rc; + } + + template< typename T > + rc_t destroy( struct obj_str*& p ) + { + if( p != nullptr ) + { + wnd_func::destroy(p->wf); + ifft::destroy(p->ft); + ola::destroy(p->ola); + + mem::release(p->minRphV); + mem::release(p->maxRphV); + mem::release(p->itrV); + mem::release(p->phs0V); + mem::release(p->phsV); + mem::release(p->mag0V); + mem::release(p->magV); + + mem::release( p ); + } + return kOkRC; + } + + template< typename T > + rc_t exec( struct obj_str* p, const T* magV, const T* phsV ) + { + + double twoPi = 2.0 * M_PI; + unsigned k; + + for(k=0; kbinCnt; ++k) + { + // phase dist between cur and prv frame + T dp = phsV[k] - p->phs0V[k]; + + // dist must be positive (accum phase always increases) + if( dp < -0.00001 ) + dp += twoPi; + + // add in complete revolutions based on the bin frequency + // (these would have been lost from 'dp' due to phase wrap) + dp += p->itrV[k]; + + // constrain the phase change to lie within the range of the kth bin + if( dp < p->minRphV[k] ) + dp += twoPi; + + if( dp > p->maxRphV[k] ) + dp -= twoPi; + + p->phsV[k] = p->phs0V[k] + dp; + p->magV[k] = p->mag0V[k]; + + + p->phs0V[k] = phsV[k]; + p->mag0V[k] = magV[k]; + } + + ifft::exec_polar( p->ft, magV, phsV ); + + ola::exec( p->ola, p->ft->outV, p->ft->outN ); + + //printf("%i %i\n",p->binCnt,p->ft.binCnt ); + + //cmVOR_Print( p->obj.ctx->outFuncPtr, 1, p->binCnt, magV ); + //cmVOR_Print( p->obj.ctx->outFuncPtr, 1, p->binCnt, p->phsV ); + //cmVOS_Print( p->obj.ctx->outFuncPtr, 1, 10, p->ft.outV ); + + return kOkRC; + + } + + } + + //--------------------------------------------------------------------------------------------------------------------------------- + // Spectral Distortion + // + + namespace spec_dist + { + template< typename T0, typename T1 > + struct obj_str + { + T1 ceiling; + T1 expo; + T1 mix; + T1 thresh; + T1 uprSlope; + T1 lwrSlope; + + T0 ogain; + + T0* outMagV; + T0* outPhsV; + + }; + + typedef struct obj_str fobj_t; + typedef struct obj_str dobj_t; + + template< typename T0, typename T1 > + rc_t create( struct obj_str*& p, unsigned binN, T1 ceiling=30, T1 expo=2, T1 thresh=60, T1 uprSlope=0, T1 lwrSlope=2, T1 mix=0 ) + { + rc_t rc = kOkRC; + + p = mem::allocZ< struct obj_str >(); + + p->ceiling = ceiling; + p->expo = expo; + p->thresh = thresh; + p->uprSlope = uprSlope; + p->lwrSlope = lwrSlope; + p->mix = mix; + p->ogain = 1; + + p->outMagV = mem::allocZ( binN ); + p->outPhsV = mem::allocZ( binN ); + + return rc; + } + + template< typename T0, typename T1 > + rc_t destroy( struct obj_str*& p ) + { + rc_t rc = kOkRC; + if( p != nullptr ) + { + mem::release(p->outMagV); + mem::release(p->outPhsV); + mem::release(p); + } + return rc; + } + + template< typename T0, typename T1 > + void _cmSpecDist2Bump( struct obj_str* p, T0* x, unsigned binCnt, T1 thresh, T1 expo) + { + unsigned i = 0; + T1 minDb = -100.0; + + thresh = -fabs(thresh); + + for(i=0; i thresh ) + y = 1; + else + { + y = (minDb - x[i])/(minDb - thresh); + y += y - pow(y,expo); + } + + x[i] = minDb + (-minDb) * y; + + } + } + + template< typename T0, typename T1 > + void _cmSpecDist2BasicMode( struct obj_str* p, T0* X1m, unsigned binCnt, T1 thresh, T1 upr, T1 lwr ) + { + + unsigned i=0; + + if( lwr < 0.3 ) + lwr = 0.3; + + for(i=0; i 0 ) + X1m[i] -= (lwr*d); + else + X1m[i] -= (upr*d); + } + + } + + template< typename T0, typename T1 > + rc_t exec( struct obj_str* p, const T0* magV, const T0* phsV, unsigned binN ) + { + rc_t rc = kOkRC; + + T0 X0m[binN]; + T0 X1m[binN]; + + // take the mean of the the input magntitude spectrum + T0 u0 = vop::mean(magV,binN); + + // convert magnitude to db (range=-1000.0 to 0.0) + vop::ampl_to_db(X0m, magV, binN ); + vop::copy(X1m,X0m,binN); + + // bump transform X0m + _cmSpecDist2Bump(p,X0m, binN, p->ceiling, p->expo); + + // xfade bump output with raw input: X1m = (X0m*mix) + (X1m*(1.0-mix)) + vop::mul(X0m, p->mix, binN ); + vop::mul(X1m, 1.0 - p->mix, binN ); + vop::add(X1m, X0m, binN ); + + + // basic transform + _cmSpecDist2BasicMode(p,X1m,binN,p->thresh,p->uprSlope,p->lwrSlope); + + // convert db back to magnitude + vop::db_to_ampl(X1m, X1m, binN ); + + // convert the mean input magnitude to db + T0 idb = 20*log10(u0); + + // get the mean output magnitude spectra + T0 u1 = vop::mean(X1m,binN); + + if( idb > -150.0 ) + { + // set the output gain such that the mean output magnitude + // will match the mean input magnitude + p->ogain = u0/u1; + } + else + { + T0 a0 = 0.9; + p->ogain *= a0; + } + + // apply the output gain + vop::mul( p->outMagV, X1m, std::min((T1)4.0,p->ogain), binN); + vop::copy( p->outPhsV, phsV, binN); + + return rc; + } + + + } + + + //--------------------------------------------------------------------------------------------------------------------------------- + // Data Recorder + // + + namespace data_recorder + { + + template< typename T > + struct block_str + { + T* buf; // buf[frameN,sigN] + struct block_str* link; // link to next block in chain + }; + + template< typename T > + struct obj_str + { + unsigned sigN; // count of channels per frame + unsigned frameN; // count of frames per block + struct block_str* head; // first block + struct block_str* tail; // last block and the one being currrently filled + + unsigned frameIdx; // index into tail of frame to fill + char* fn; + char** colLabelA; + unsigned colLabelN; + bool enableFl; + }; + + typedef struct obj_str fobj_t; + typedef struct obj_str dobj_t; + + template< typename T > + struct block_str* _block_alloc( struct obj_str* p ) + { + struct block_str* block = mem::allocZ< struct block_str >(); + + block->buf = mem::alloc( p->frameN * p->sigN ); + + if( p->head == nullptr ) + p->head = block; + + if( p->tail != nullptr ) + p->tail->link = block; + + p->tail = block; + + return block; + } + + template< typename T > + rc_t create( struct obj_str*& p, unsigned sigN, unsigned frameCacheN, const char* fn, const char** colLabelA, unsigned colLabelN, bool enableFl ) + { + rc_t rc = kOkRC; + + p = mem::allocZ< struct obj_str >(); + + p->frameN = frameCacheN; + p->sigN = sigN; + p->fn = mem::duplStr(fn); + p->colLabelN = colLabelN; + p->colLabelA = mem::allocZ< char* >( colLabelN ); + p->enableFl = enableFl; + + for(unsigned i=0; icolLabelA[i] = mem::duplStr(colLabelA[i]); + + _block_alloc(p); + + return rc; + } + + template< typename T > + rc_t create( struct obj_str*& p, const object_t* cfg ) + { + rc_t rc = kOkRC; + bool enableFl = true; + unsigned sigN = 0; + unsigned frameN = 0; + const char* filename = nullptr; + const object_t* colLabelL = nullptr; + + // parse the recorder spec + if((rc = cfg->getv("enableFl", enableFl, + "sigN", sigN, + "frameN", frameN, + "filename", filename, + "colLabelL",colLabelL)) != kOkRC ) + { + rc = cwLogError(rc,"Record cfg. parse failed."); + } + else + { + unsigned labelN = colLabelL->child_count(); + const char* labelL[ labelN ]; + + for(unsigned i=0; ichild_ele(i)->value( labelL[i] ); + + rc = create(p, sigN, frameN, filename, labelL, labelN, enableFl ); + } + + return rc; + } + + template< typename T> + rc_t destroy( struct obj_str*& p ) + { + if( p != nullptr ) + { + + if( p->enableFl && p->fn != nullptr && textLength(p->fn)!=0 ) + write_as_csv(p,p->fn); + + for(unsigned i=0; icolLabelN; ++i) + mem::release( p->colLabelA[i] ); + + struct block_str* b0 = p->head; + while( b0 != nullptr ) + { + struct block_str* b1 = b0->link; + mem::release(b0->buf); + mem::release(b0); + b0 = b1; + } + + mem::release(p->fn); + mem::release(p); + } + return kOkRC; + } + + template< typename T> + rc_t exec( struct obj_str* p, const T* xV, unsigned xN, unsigned chIdx=0, bool advance_fl = true ) + { + struct block_str* b = p->tail; + + if( chIdx + xN > p->sigN ) + return cwLogError(kInvalidArgRC,"Channel index (%i) plus channel count (%i) is out of range of the allocated channe count (%i).", chIdx, xN, p->sigN ); + + + if( p->enableFl ) + { + for(unsigned i=chIdx; i-chIdxframeIdx * p->sigN + i < p->frameN * p->sigN ); + b->buf[ p->frameIdx * p->sigN + i ] = xV[i-chIdx]; + } + + if( advance_fl ) + advance(p); + } + + return kOkRC; + } + + template< typename T> + rc_t advance( struct obj_str* p, unsigned frameN=1 ) + { + for(unsigned i=0; iframeIdx += 1; + + if( p->frameIdx >= p->frameN ) + { + _block_alloc(p); + p->frameIdx = 0; + } + } + + return kOkRC; + } + + template< typename T> + rc_t write_as_csv( const struct obj_str* p, const char* fn ) + { + rc_t rc = kOkRC; + file::handle_t h; + struct block_str* b = p->head; + + if((rc = file::open(h,fn,file::kWriteFl)) != kOkRC ) + { + rc = cwLogError(kOpenFailRC,"Create failed on the data recorder output file '%s'.", cwStringNullGuard(fn)); + goto errLabel; + } + + for(; b!=nullptr; b=b->link) + { + unsigned frameN = b->link==NULL ? p->frameIdx : p->frameN; + for(unsigned fi=0, rowIdx=0; ficolLabelN; ++ci) + file::printf(h,"%s, ", p->colLabelA[ci] ); + } + else + { + for(unsigned ci=0; cisigN; ++ci) + file::printf(h,"%f,", b->buf[ fi*p->sigN + ci ]); + } + + file::print(h,"\n"); + } + } + + + errLabel: + + file::close(h); + return rc; + } + + } + + rc_t test( const cw::object_t* args ); + + } // dsp +} // cw + + +#endif diff --git a/cwPvAudioFileProc.cpp b/cwPvAudioFileProc.cpp new file mode 100644 index 0000000..d3ce15c --- /dev/null +++ b/cwPvAudioFileProc.cpp @@ -0,0 +1,495 @@ +#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 "cwMath.h" +#include "cwVectOps.h" +#include "cwDsp.h" +#include "cwAudioTransforms.h" +#include "cwAudioFileProc.h" +#include "cwPvAudioFileProc.h" + +//------------------------------------------------------------------------------------------------ +// Phase Vocoder File Processor +// +namespace cw +{ + namespace afop + { + // PV Template + namespace pvoc_template + { + typedef struct process_str + { + int foo; + int blah; + } process_t; + + rc_t open( pvoc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + process_t* p = mem::allocZ(); + + if((rc = ctx->args->getv( "foo", p->foo, + "blah", p->blah)) != kOkRC ) + { + rc = cwLogError(rc,"Parsing of 'pvoc_template' args. failed."); + } + + ctx->userPtr = p; + + return rc; + } + + rc_t close( pvoc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + process_t* p = (process_t*)ctx->userPtr; + if( p != nullptr ) + { + mem::release(ctx->userPtr); + } + return rc; + } + + rc_t process( pvoc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + + unsigned chCnt = std::min(ctx->srcChN,ctx->dstChN); + unsigned binN = ctx->binN; + + for(unsigned i=0; idstMagChA[i][j] = ctx->srcMagChA[i][j]; + ctx->dstPhsChA[i][j] = ctx->srcPhsChA[i][j]; + } + + return rc; + } + + rc_t main( pvoc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + + switch( ctx->procId ) + { + case kOpenProcId: + rc = open(ctx); + break; + + case kCloseProcId: + rc = close(ctx); + break; + + case kProcProcId: + rc = process(ctx); + break; + } + return rc; + } + } + + + // PV Spec Dist + namespace pvoc_spec_dist + { + typedef struct process_str + { + dsp::spec_dist::fobj_t** sdChA; + } process_t; + + rc_t open( pvoc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + + process_t* p = mem::allocZ(); + ctx->userPtr = p; + + float ceiling = 30; + float expo = 2; + float thresh = 60; + float uprSlope = -0.7; + float lwrSlope = 2; + float mix = 0; + + + + if((rc = ctx->args->getv( "ceiling", ceiling, + "expo", expo, + "thresh", thresh, + "uprSlope", uprSlope, + "lwrSlope", lwrSlope, + "mix", mix)) != kOkRC ) + { + rc = cwLogError(rc,"Parsing of 'pvoc_template' args. failed."); + goto errLabel; + } + + p->sdChA = mem::allocZ< dsp::spec_dist::fobj_t* >( ctx->srcChN ); + + for(unsigned i=0; isrcChN; ++i) + if((rc = dsp::spec_dist::create( p->sdChA[i], ctx->binN, ceiling, expo, thresh, uprSlope, lwrSlope, mix )) != kOkRC ) + { + rc = cwLogError(rc,"Spec Dist processor channel create failed."); + goto errLabel; + } + + + errLabel: + return rc; + } + + rc_t close( pvoc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + process_t* p = (process_t*)ctx->userPtr; + + if( p != nullptr ) + { + if( p->sdChA ) + { + for(unsigned i=0; isrcChN; ++i) + dsp::spec_dist::destroy( p->sdChA[i] ); + mem::release(p->sdChA); + } + + mem::release( p ); + } + return rc; + } + + rc_t process( pvoc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + + process_t* p = (process_t*)ctx->userPtr; + unsigned chCnt = std::min(ctx->srcChN,ctx->dstChN); + + for(unsigned i=0; isdChA[i], ctx->srcMagChA[i], ctx->srcPhsChA[i], ctx->binN ); + + for(unsigned j=0; jbinN; ++j) + { + ctx->dstMagChA[i][j] = p->sdChA[i]->outMagV[j]; + ctx->dstPhsChA[i][j] = p->sdChA[i]->outPhsV[j]; + } + } + + return rc; + } + + rc_t main( pvoc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + + switch( ctx->procId ) + { + case kOpenProcId: + rc = open(ctx); + break; + + case kCloseProcId: + rc = close(ctx); + break; + + case kProcProcId: + rc = process(ctx); + break; + } + return rc; + } + } + + + namespace pv_file_proc + { + typedef struct process_str + { + dsp::pv_anl::fobj_t** anlA; // anlA[chCnt] + dsp::pv_syn::fobj_t** synA; // synA[chCnt] + pvoc_ctx_t pvoc_ctx; // + const char* functionLabel;; // + pvoc_func_t function; // + float* dstBuf; + float* srcBuf; + } process_t; + + typedef struct labelFunc_str + { + const char* label; + pvoc_func_t func; + } labelFunc_t; + + labelFunc_t labelFuncA[] = + { + { "pvoc_template",pvoc_template::main }, + { "spec_dist", pvoc_spec_dist::main }, + { nullptr, nullptr } + }; + + rc_t open( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + process_t* p = mem::allocZ(); + ctx->userPtr = p; + + + // parse the specific process function configuration record + if((rc = ctx->args->getv( "wndSmpN", p->pvoc_ctx.wndSmpN, + "hopSmpN", p->pvoc_ctx.hopSmpN, + "procSmpN", p->pvoc_ctx.procSmpN, + "function", p->functionLabel, + "args", p->pvoc_ctx.args)) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"The pvoc file proc. configuration parse failed."); + goto errLabel; + } + + if((rc = ctx->args->getv_opt( "inGain", p->pvoc_ctx.inGain, + "outGain", p->pvoc_ctx.outGain )) != kOkRC ) + { + rc = cwLogError(rc,"Parsing of pvoc file optional arguments failed."); + goto errLabel; + } + + + // locate the executable function associated with the specified process function + for(unsigned i=0; true; ++i) + { + // if the function was not found + if( labelFuncA[i].func == nullptr ) + { + rc = cwLogError(kInvalidArgRC,"The audio processing program '%s' could not be found.", cwStringNullGuard(p->functionLabel)); + goto errLabel; + } + + // if this is the specified function + if( textCompare(labelFuncA[i].label,p->functionLabel ) == 0 ) + { + p->function = labelFuncA[i].func; + break; + } + } + + p->pvoc_ctx.td_ctx = ctx; + p->pvoc_ctx.srcChN = ctx->srcChN; + p->pvoc_ctx.dstChN = ctx->dstChN; + + + p->pvoc_ctx.srcMagChA = mem::allocZ< const float * >( p->pvoc_ctx.srcChN ); + p->pvoc_ctx.srcPhsChA = mem::allocZ< const float * >( p->pvoc_ctx.srcChN ); + + p->pvoc_ctx.dstMagChA = mem::allocZ< float* >( p->pvoc_ctx.dstChN ); + p->pvoc_ctx.dstPhsChA = mem::allocZ< float* >( p->pvoc_ctx.dstChN ); + + p->anlA = mem::allocZ< dsp::pv_anl::fobj_t* >( p->pvoc_ctx.srcChN ); + p->synA = mem::allocZ< dsp::pv_syn::fobj_t* >( p->pvoc_ctx.dstChN ); + + + for(unsigned i=0; ipvoc_ctx.srcChN; ++i) + { + if((rc = dsp::pv_anl::create( p->anlA[i], p->pvoc_ctx.procSmpN, ctx->srcSrate, p->pvoc_ctx.wndSmpN, p->pvoc_ctx.hopSmpN, dsp::pv_anl::kNoCalcHzPvaFl )) != kOkRC ) + { + rc = cwLogError(rc,"PVOC analysis component create failed."); + goto errLabel; + } + + p->pvoc_ctx.binN = p->anlA[i]->binCnt; // All input and ouput frames have the same bin count + } + + // Call the open function + p->pvoc_ctx.procId = kOpenProcId; + if((rc = p->function( &p->pvoc_ctx )) != kOkRC ) + goto errLabel; + + + // Allocate the vector memory for the src/dst buffer + p->srcBuf = mem::allocZ< float >( p->pvoc_ctx.srcChN * p->pvoc_ctx.binN ); + p->dstBuf = mem::allocZ< float >( 2* p->pvoc_ctx.dstChN * p->pvoc_ctx.binN ); + + for(unsigned i=0; ipvoc_ctx.dstChN; ++i) + { + if((rc = dsp::pv_syn::create( p->synA[i], p->pvoc_ctx.procSmpN, ctx->dstSrate, p->pvoc_ctx.wndSmpN, p->pvoc_ctx.hopSmpN )) != kOkRC ) + { + rc = cwLogError(rc,"PVOC synthesis component create failed."); + goto errLabel; + } + + p->pvoc_ctx.dstMagChA[i] = p->dstBuf + (2*i*p->pvoc_ctx.binN); + p->pvoc_ctx.dstPhsChA[i] = p->pvoc_ctx.dstMagChA[i] + p->pvoc_ctx.binN; + } + + errLabel: + return rc; + } + + rc_t close( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + process_t* p = (process_t*)ctx->userPtr; + + if( p != nullptr ) + { + + p->pvoc_ctx.procId = kCloseProcId; + p->function( &p->pvoc_ctx ); + + if( p->anlA ) + for(unsigned i=0; ipvoc_ctx.srcChN; ++i) + dsp::pv_anl::destroy(p->anlA[i]); + + if( p->synA ) + for(unsigned i=0; ipvoc_ctx.dstChN; ++i) + dsp::pv_syn::destroy(p->synA[i]); + + mem::release( p->anlA ); + mem::release( p->synA ); + mem::release( p->pvoc_ctx.srcMagChA ); + mem::release( p->pvoc_ctx.srcPhsChA ); + mem::release( p->pvoc_ctx.dstMagChA ); + mem::release( p->pvoc_ctx.dstPhsChA ); + mem::release( p->dstBuf ); + mem::release( p->srcBuf ); + mem::release(p); + } + + return rc; + } + + rc_t process( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + process_t* p = (process_t*)ctx->userPtr; + bool fl = false; + + // Setup the source spectral data + for(unsigned i=0; ipvoc_ctx.srcChN; ++i) + { + p->pvoc_ctx.srcMagChA[i] = nullptr; + p->pvoc_ctx.srcPhsChA[i] = nullptr; + + if( dsp::pv_anl::exec( p->anlA[i], ctx->srcChV[i], p->pvoc_ctx.hopSmpN) ) + { + float* srcChV = p->srcBuf + (i*p->pvoc_ctx.binN); + + // apply input gain + vop::mul( srcChV, p->anlA[i]->magV, p->pvoc_ctx.inGain * p->pvoc_ctx.binN, p->pvoc_ctx.binN ); + + p->pvoc_ctx.srcMagChA[i] = (const float*)srcChV; + p->pvoc_ctx.srcPhsChA[i] = (const float*)p->anlA[i]->phsV; + + fl = true; + } + } + + if( fl ) + { + p->pvoc_ctx.procId = kProcProcId; + p->function( &p->pvoc_ctx ); + + // Get the dest. spectral data. + for(unsigned i=0; ipvoc_ctx.dstChN; ++ i) + { + if((rc = dsp::pv_syn::exec( p->synA[i], p->pvoc_ctx.dstMagChA[i], p->pvoc_ctx.dstPhsChA[i] )) != kOkRC ) + { + rc = cwLogError(rc,"Pvoc synthesis failed."); + goto errLabel; + } + + // apply output gain + vop::mul( ctx->dstChV[i], p->synA[i]->ola->outV, p->pvoc_ctx.outGain, p->pvoc_ctx.hopSmpN ); + //vop::copy( ctx->dstChV[i], p->synA[i]->ola->outV, p->pvoc_ctx.hopSmpN ); + } + } + + + errLabel: + return rc; + } + + rc_t main( proc_ctx_t* ctx ) + { + rc_t rc = kOkRC; + + switch( ctx->procId ) + { + case kOpenProcId: + rc = open(ctx); + break; + + case kCloseProcId: + rc = close(ctx); + break; + + case kProcProcId: + rc = process(ctx); + break; + } + return rc; + } + } + } +} + +cw::rc_t cw::afop::pvoc_file_processor( const object_t* cfg ) +{ + rc_t rc = kOkRC; + const char* srcFn = nullptr; + const char* dstFn = nullptr; + const char* pgmLabel = nullptr; + const object_t* pgm = nullptr; + unsigned hopSmpN = 0; + unsigned recordChN = 0; + const object_t* recorder = nullptr; + + // parse the main audio file processor cfg record + if((rc = cfg->getv("srcFn", srcFn, + "dstFn", dstFn, + "program", pgmLabel)) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"Error parsing the main audio file proc configuration record."); + goto errLabel; + } + + // parse the recorder spec + if((rc = cfg->getv_opt("recordChN", recordChN, + "recorder", recorder)) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"Error parsing the main audio file proc optional configuration fields."); + goto errLabel; + } + + // locate the cfg for the specific process function to run + if((rc = cfg->getv(pgmLabel, pgm)) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"The audio file proc. configuration '%s' was not found.",cwStringNullGuard(pgmLabel)); + goto errLabel; + } + + // parse the specific process function configuration record + if((rc = pgm->getv("hopSmpN", hopSmpN)) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"The audio file proc. configuration '%s' parse failed.",cwStringNullGuard(pgmLabel)); + goto errLabel; + } + + // run the processr + if((rc = file_processor( srcFn, dstFn, pv_file_proc::main, hopSmpN, hopSmpN, nullptr, pgm, recorder, recordChN)) != kOkRC ) + { + rc = cwLogError(rc,"The audio file proc. failed."); + goto errLabel; + } + + errLabel: + return rc; +} diff --git a/cwPvAudioFileProc.h b/cwPvAudioFileProc.h new file mode 100644 index 0000000..46e0260 --- /dev/null +++ b/cwPvAudioFileProc.h @@ -0,0 +1,41 @@ +#ifndef cwPvAudioFileProc_h +#define cwPvAudioFileProc_h + +namespace cw +{ + namespace afop + { + + typedef struct pvoc_ctx_str + { + unsigned procId; + + proc_ctx_t* td_ctx; // time domain context (userPtr = pgmLabel) + const object_t* args; // program args + void* userPtr; + + unsigned wndSmpN; // TODO: change thise to src and dst variables + unsigned hopSmpN; + unsigned procSmpN; + unsigned binN; + + double inGain; + double outGain; + + unsigned srcChN; + const float** srcMagChA; // srcMagChA[ chN ][ binN ] + const float** srcPhsChA; // srcPhsChA[ chN ][ binN ] + + unsigned dstChN; + float** dstMagChA; // dstMagChA[ chN ][ binN ] + float** dstPhsChA; // dstPhsChA[ chN ][ binN ] + + } pvoc_ctx_t; + + typedef rc_t (*pvoc_func_t)( pvoc_ctx_t* ctx ); + + rc_t pvoc_file_processor( const object_t* cfg ); + } +} + +#endif