cwAudioFileProc.h/cpp, cwAudioTransforms.h/cpp, cwPvAudioFileProc.h/cpp: Initial commit.

Minor update to cwAudioFileOps.cpp.
This commit is contained in:
kevin 2021-08-15 16:05:44 -04:00
parent 9d8d8ee051
commit 02cd79c61f
7 changed files with 2630 additions and 0 deletions

View File

@ -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
{

650
cwAudioFileProc.cpp Normal file
View File

@ -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<process_t>();
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<process_t>();
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; i<p->chCnt; ++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; i<p->chCnt; ++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; i<p->chCnt; ++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<tremelo_t>();
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; i<hopSmpN; ++i)
{
float gain = p->depth * std::sin( p->phase );
for(unsigned j=0; j<chCnt; ++j)
ctx->dstChV[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<sample_t>* srcShiftBufA[ proc_ctx.srcChN ]; // src shift buffer
struct dsp::shift_buf::obj_str<sample_t>* dstShiftBufA[ proc_ctx.dstChN ]; // dst shift buffer
// Allocate memory for the source/dest file buffer
sample_t* srcFileBuf = mem::allocZ<sample_t>( proc_ctx.srcChN * proc_ctx.srcHopSmpN );
sample_t* dstFileBuf = mem::allocZ<sample_t>( proc_ctx.dstChN * proc_ctx.dstHopSmpN );
sample_t* procFileBuf = mem::allocZ<sample_t>( 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<proc_ctx.srcChN; ++i)
{
srcChFileBuf[i] = srcFileBuf + (i*proc_ctx.srcHopSmpN );
dsp::shift_buf::create( srcShiftBufA[i], proc_ctx.srcHopSmpN, proc_ctx.srcWndSmpN, proc_ctx.srcHopSmpN );
}
// For each dest. channel - setup the dest file buffer and create the dst shift buffer
for(unsigned i = 0; i<proc_ctx.dstChN; ++i)
{
oChBuf[i] = procFileBuf + (i*proc_ctx.dstHopSmpN );
dsp::shift_buf::create( dstShiftBufA[i], proc_ctx.dstHopSmpN, proc_ctx.dstWndSmpN, proc_ctx.dstHopSmpN );
}
// create the data recorder
if( recordChN )
{
proc_ctx.recordChA = mem::allocZ< dsp::data_recorder::fobj_t* >( recordChN );
for(unsigned i = 0; i<recordChN; ++i)
{
if(dsp::data_recorder::create( proc_ctx.recordChA[i], recorder_cfg ) != kOkRC )
{
cwLogWarning( "Data recorder create failed." );
break;
}
}
}
//
while( true )
{
unsigned rdFrmCnt = 0;
// if a source file exists then read the next srcHopSmpN samples into srcChFileBuf[][]
if( srcFn )
{
// Read the next srcHopSmpN samples
rc = audiofile::readFloat(srcAfH, proc_ctx.srcHopSmpN, 0, proc_ctx.srcChN, srcChFileBuf, &rdFrmCnt );
// if the end of the file was encountered or no samples were returned
if( rc == kEofRC || rdFrmCnt == 0)
{
rc = kOkRC;
break;
}
if( rc != kOkRC )
{
rc = cwLogError(rc,"Audio process source file '%s' read failed.", srcFn );
break;
}
}
do // src shift-buffer processing loop
{
if( srcFn )
{
// Shift the new source sample into the source shift buffer.
// Note that the shift buffer must be called until they return false.
bool rd_fl = false;
for(unsigned j=0; j<proc_ctx.srcChN; ++j)
if( dsp::shift_buf::exec( srcShiftBufA[j], srcChFileBuf[j], proc_ctx.srcHopSmpN ) )
{
rd_fl = true;
iChBuf[j] = srcShiftBufA[j]->outV;
}
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; j<proc_ctx.dstChN; ++j)
{
if(dsp::shift_buf::exec( dstShiftBufA[j], oChBuf[j], proc_ctx.dstHopSmpN ))
{
wr_fl = true;
dstChFileBuf[j] = dstShiftBufA[j]->outV;
}
}
// 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; i<recordChN; ++i)
dsp::data_recorder::destroy( proc_ctx.recordChA[i] );
mem::release(proc_ctx.recordChA);
for(unsigned i=0; i<proc_ctx.srcChN; ++i)
dsp::shift_buf::destroy( srcShiftBufA[i] );
for(unsigned i=0; i<proc_ctx.dstChN; ++i)
dsp::shift_buf::destroy( dstShiftBufA[i] );
mem::release(srcFileBuf);
mem::release(dstFileBuf);
mem::release(procFileBuf);
}
errLabel:
proc_ctx.procId = kCloseProcId;
func( &proc_ctx );
close(srcAfH);
close(dstAfH);
mem::release(proc_ctx.srcFn);
mem::release(proc_ctx.dstFn);
return rc;
}
cw::rc_t cw::afop::file_processor( const object_t* cfg )
{
rc_t rc = kOkRC;
const char* srcFn = nullptr;
const char* dstFn = nullptr;
const char* cfgLabel = nullptr;
const object_t* pgm = nullptr;
unsigned wndSmpN = 0;
unsigned hopSmpN = 0;
const char* program = nullptr;
const object_t* args = nullptr;
unsigned recordChN = 0;
const object_t* recorder = nullptr;
proc_func_t procFunc = nullptr;
typedef struct labelFunc_str
{
const char* label;
proc_func_t func;
} labelFunc_t;
labelFunc_t labelFuncA[] =
{
{ "tremelo",tremelo::main },
{ "pvoc", pvoc::main },
{ nullptr, nullptr }
};
// parse the main audio file processor cfg record
if((rc = cfg->getv("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;
}

63
cwAudioFileProc.h Normal file
View File

@ -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 );
}
}

262
cwAudioTransforms.cpp Normal file
View File

@ -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, "<invalid>" }
};
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<sample_t>(wndSmpCnt);
sample_t* y = mem::allocZ<sample_t>(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<oSmpCnt; i+=procSmpCnt)
{
j += procSmpCnt;
// if there are hopSmpCnt new samples available then there must
// be a new window of samples available -
if( j > 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<sample_t>(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<sample_t>(iSmpCnt);
sample_t* y = mem::allocZ<sample_t>(oSmpCnt);
vop::seq(x,iSmpCnt,1.0f,1.0f);
for(i=0,j=0; i<iSmpCnt; i+=procSmpCnt)
{
// Give the shift buffer a block of procSmpCnt samples.
// If it returns 'true' then it has hopSmpCnt new samples and
// a buffer of at least wndSmpCnt.
for(; shift_buf::exec( p, x + i, procSmpCnt ); j+=wndSmpCnt )
{
// cmShiftBufExec() returned true then there are wndSmpCnt samples available.
assert( y + j + wndSmpCnt <= y + oSmpCnt );
vop::copy(y + j, p->outV, 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;
}
}
}

1117
cwAudioTransforms.h Normal file

File diff suppressed because it is too large Load Diff

495
cwPvAudioFileProc.cpp Normal file
View File

@ -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<process_t>();
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; i<chCnt; ++i)
for(unsigned j=0; j<binN; ++j)
{
ctx->dstMagChA[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<process_t>();
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; i<ctx->srcChN; ++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; i<ctx->srcChN; ++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; i<chCnt; ++i)
{
dsp::spec_dist::exec( p->sdChA[i], ctx->srcMagChA[i], ctx->srcPhsChA[i], ctx->binN );
for(unsigned j=0; j<ctx->binN; ++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<process_t>();
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; i<p->pvoc_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; i<p->pvoc_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; i<p->pvoc_ctx.srcChN; ++i)
dsp::pv_anl::destroy(p->anlA[i]);
if( p->synA )
for(unsigned i=0; i<p->pvoc_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; i<p->pvoc_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; i<p->pvoc_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;
}

41
cwPvAudioFileProc.h Normal file
View File

@ -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