#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 "cwDsp.h" cw::rc_t cw::afop::sine( const char* fn, double srate, unsigned bits, double hz, double gain, double secs ) { rc_t rc = kOkRC; unsigned bN = srate * secs; float* b = mem::alloc(bN); unsigned chCnt = 1; unsigned i; for(i=0; igetv("fn",fn,"srate",srate,"bits",bits,"hz",hz,"gain",gain,"secs",secs)) != kOkRC ) return cwLogError(kSyntaxErrorRC,"Invalid parameter to audio file sine test."); char* afn = filesys::expandPath(fn); rc = sine( afn, srate, bits, hz, gain, secs ); mem::release(afn); return rc; } cw::rc_t cw::afop::clicks(const char* fn, double srate, unsigned bits, double secs, const unsigned* clickSmpOffsArray, unsigned clickSmpOffsN, double clickAmp, double decay, double burstMs ) { rc_t rc = kOkRC; unsigned xN = srate * secs; float* x = mem::alloc(xN); unsigned bN = srate * burstMs / 1000.0; unsigned chCnt = 1; float b[ bN ]; for(unsigned i=0; i= xN ) return cwLogError(kInvalidArgRC,"Click index %i (%f secs) is greater than the signal length: %i (%f secs).", smp_idx, smp_idx/srate, xN, secs ); for(unsigned j=0; j maxActualFrmN ) maxActualFrmN = actualFrmN; } // write the mixed output buffer if((rc = audiofile::writeFloat(oH, maxActualFrmN, maxChN, oChBuf )) != kOkRC ) { rc = cwLogError(kOpFailRC,"Write failed on output file '%s'.", outFn ); goto errLabel; } } } errLabel: if( rc != kOkRC ) cwLogError(kOpFailRC,"Mix failed."); // close the source audio files for(unsigned i=0; igetv("outFn",oFn,"outBits",outBits,"srcL",srcL)) != kOkRC ) goto errLabel; else { char* outFn = filesys::expandPath(oFn); unsigned srcN = srcL->child_count(); const char* fnV[ srcN ]; float gainV[ srcN ]; memset(fnV,0,sizeof(fnV)); // read each source record for(unsigned i=0; ichild_ele(i)->getv("gain",gainV[i],"src",fn)) != kOkRC ) rc = cwLogError(kSyntaxErrorRC,"Mix source index %i syntax error."); else fnV[i] = filesys::expandPath(fn); } if( rc == kOkRC ) rc = mix( fnV, gainV, srcN, outFn, outBits); mem::free(outFn); for(unsigned i=0; i= endSec ) { rc = cwLogError(kInvalidArgRC,"Invalid time selection. Begin time (%f) is greater than end time (%f). ", begSec, endSec ); goto errLabel; } // create the output file if((rc = audiofile::create( oH, oFn, info.srate, outBits, info.chCnt)) != kOkRC ) goto errLabel; else { unsigned begFrmIdx = (unsigned)floor(begSec * info.srate); unsigned endFrmIdx = endSec==-1 ? info.frameCnt : (unsigned)floor(endSec * info.srate); unsigned ttlFrmN = endFrmIdx - begFrmIdx; unsigned actualBufFrmN = 0; const unsigned bufFrmN = 8196; // read/write buffer size float buf[ bufFrmN*info.chCnt ]; float* chBuf[ info.chCnt ]; // set up the read/write channel buffer for(unsigned i = 0; i ttlFrmN ) actualBufFrmN -= ttlFrmN - curFrmN; // write the buffer to the output file if((rc = audiofile::writeFloat(oH, actualBufFrmN, info.chCnt, chBuf )) != kOkRC ) { rc = cwLogError(kOpFailRC,"Write failed on output file '%s'.", oFn ); goto errLabel; } } } errLabel: if( rc != kOkRC ) cwLogError(rc,"selectToFile failed."); audiofile::close(oH); audiofile::close(iH); mem::release(iFn); mem::release(dstDir); mem::release(oFn); return rc; } cw::rc_t cw::afop::selectToFile( const object_t* cfg ) { rc_t rc = kOkRC; const object_t* selectL = nullptr; const char* oDir = nullptr; unsigned outBits = 16; // read the top level cfg record if((rc = cfg->getv("outDir",oDir,"outBits",outBits,"selectL",selectL)) != kOkRC ) goto errLabel; else { unsigned selN = selectL->child_count(); for(unsigned i=0; ichild_ele(i)->getv("begSec",begSec,"endSec",endSec,"dst",dstFn, "src",srcFn)) != kOkRC ) { rc = cwLogError(kSyntaxErrorRC,"'Select to file' index %i syntax error."); goto errLabel; } if((rc = selectToFile( srcFn, begSec, endSec, outBits, oDir, dstFn )) != kOkRC ) goto errLabel; } } errLabel: return rc; } namespace cw { namespace afop { typedef struct _cutMixArg_str { const cutMixArg_t* arg; char* srcFn; unsigned srcFrmIdx; unsigned srcFrmN; unsigned srcFadeInFrmN; unsigned srcFadeOutFrmN; unsigned dstFrmIdx; audiofile::handle_t afH; audiofile::info_t afInfo; } _cutMixArg_t; rc_t _cutAndMixOpen( const char* srcDir, const cutMixArg_t* argL, _cutMixArg_t* xArgL, unsigned argN, unsigned& chN_Ref, double& srate_Ref, unsigned& dstFrmN_Ref, unsigned& maxSrcFrmN_Ref ) { rc_t rc = kOkRC; maxSrcFrmN_Ref = 0; chN_Ref = 0; for(unsigned i = 0; i argL[i].srcEndSec ) { rc = cwLogError(kInvalidArgRC,"The end time is prior to the begin time on source file '%s'.", xArgL[i].srcFn); goto errLabel; } xArgL[i].arg = argL + i; xArgL[i].srcFrmIdx = floor(argL[i].srcBegSec * srate_Ref); xArgL[i].srcFrmN = floor(argL[i].srcEndSec * srate_Ref) - xArgL[i].srcFrmIdx; xArgL[i].srcFadeInFrmN = floor(argL[i].srcBegFadeSec * srate_Ref); xArgL[i].srcFadeOutFrmN = floor(argL[i].srcEndFadeSec * srate_Ref); xArgL[i].dstFrmIdx = floor(argL[i].dstBegSec * srate_Ref); //printf("cm beg:%f end:%f dst:%f gain:%f %s\n", argL[i].srcBegSec, argL[i].srcEndSec, argL[i].dstBegSec, argL[i].gain, argL[i].srcFn ); chN_Ref = std::max( chN_Ref, xArgL[i].afInfo.chCnt ); maxSrcFrmN_Ref = std::max( maxSrcFrmN_Ref, xArgL[i].srcFrmN ); dstFrmN_Ref = std::max( dstFrmN_Ref, xArgL[i].dstFrmIdx + xArgL[i].srcFrmN ); } errLabel: return rc; } rc_t _cutAndMixClose( _cutMixArg_t* xArgL, unsigned argN ) { rc_t rc = kOkRC; for(unsigned i = 0; i(dstFrmN*dstChN); // output signal buffer srcV = mem::alloc(maxSrcFrmN*dstChN); // source signal buffer // create the src read/ dst write buffer for(unsigned i=0; igain * srcChBufL[j][k]; } } // write the output file if((rc = audiofile::writeFileFloat( dstFn, dstSrate, dstBits, dstFrmN, dstChN, dstChBufL)) != kOkRC ) { cwLogError(rc,"Output file ('%s') write failed.", cwStringNullGuard(dstFn)); goto errLabel; } } errLabel: _cutAndMixClose( xArgL, argN ); mem::release(dstV); mem::release(srcV); if( rc != kOkRC ) cwLogError(rc,"Cross-fade failed."); return rc; } cw::rc_t cw::afop::cutAndMix( const object_t* cfg ) { rc_t rc = kOkRC; const char* srcDir = nullptr; const char* dstFn = nullptr; unsigned dstBits = 16; char* afSrcDir = nullptr; char* afDstFn = nullptr; double crossFadeSec = 0; const object_t* argNodeL = nullptr; unsigned i; // read the top level cfg record if((rc = cfg->getv("dstFn",dstFn,"dstBits",dstBits,"srcDir",srcDir,"crossFadeSec",crossFadeSec,"argL",argNodeL)) != kOkRC ) goto errLabel; if( argNodeL == nullptr ) { rc = cwLogError(kInvalidArgRC,"No crossfades were specified."); } else { unsigned argN = argNodeL->child_count(); cutMixArg_t argL[ argN ]; // for each source file for(i=0; ichild_count(); ++i) { const object_t* o = argNodeL->child_ele(i); // parse the non-optional parameters if((rc = o->getv("srcBegSec", argL[i].srcBegSec, "srcEndSec", argL[i].srcEndSec, "srcFn", argL[i].srcFn )) != kOkRC ) { rc = cwLogError(kInvalidArgRC,"Invalid crossfade argument at argument index %i.",i); } else { argL[i].dstBegSec = argL[i].srcBegSec; // By default the src is moved to the same location argL[i].srcBegFadeSec = crossFadeSec; // By default the beg/end fade is the global fade time. argL[i].srcEndFadeSec = crossFadeSec; argL[i].gain = 1; // parse the optional parameters if((rc = o->getv_opt("dstBegSec", argL[i].dstBegSec, "srcBegFadeSec", argL[i].srcBegFadeSec, "srcEndFadeSec", argL[i].srcEndFadeSec, "gain", argL[i].gain )) != kOkRC ) { rc = cwLogError(kInvalidArgRC,"Invalid crossfade optional argument at argument index %i.",i); } } //printf("cm beg:%f end:%f dst:%f gain:%f %s\n", argL[i].srcBegSec, argL[i].srcEndSec, argL[i].dstBegSec, argL[i].gain, argL[i].srcFn ); } afSrcDir = filesys::expandPath(srcDir); afDstFn = filesys::expandPath(dstFn); // call cross-fader if((rc = cutAndMix( afDstFn, dstBits, afSrcDir, argL, argN )) != kOkRC ) { rc = cwLogError(rc,""); goto errLabel; } } errLabel: mem::release(afSrcDir); mem::release(afDstFn); if( rc != kOkRC ) rc = cwLogError(rc,"Cut and mix failed."); return rc; } cw::rc_t cw::afop::parallelMix( const char* dstFn, unsigned dstBits, const char* srcDir, const parallelMixArg_t* argL, unsigned argN ) { double fadeInSec = 0; cutMixArg_t cmArgL[ argN ]; double dstBegSec = 0; memset(&cmArgL,0,sizeof(cmArgL)); for(unsigned i=0; igetv("dstFn",dstFn,"dstBits",dstBits,"srcDir",srcDir,"argL",argNodeL)) != kOkRC ) goto errLabel; if( argNodeL == nullptr ) { rc = cwLogError(kInvalidArgRC,"No crossfades were specified."); } else { unsigned argN = argNodeL->child_count(); parallelMixArg_t argL[ argN ]; // for each source file for(i=0; ichild_count(); ++i) { const object_t* o = argNodeL->child_ele(i); // parse the non-optional parameters if((rc = o->getv("srcBegSec", argL[i].srcBegSec, "srcEndSec", argL[i].srcEndSec, "fadeOutSec", argL[i].fadeOutSec, "srcFn", argL[i].srcFn )) != kOkRC ) { rc = cwLogError(kInvalidArgRC,"Invalid xform app argument at argument index %i.",i); } else { argL[i].gain = 1; // parse the optional parameters if((rc = o->getv_opt("gain", argL[i].gain )) != kOkRC ) { rc = cwLogError(kInvalidArgRC,"Invalid xform app optional argument at argument index %i.",i); } } //printf("beg:%f end:%f fade:%f %s\n", argL[i].srcBegSec, argL[i].srcEndSec, argL[i].fadeOutSec, argL[i].srcFn ); } afSrcDir = filesys::expandPath(srcDir); afDstFn = filesys::expandPath(dstFn); // call cross-fader if((rc = parallelMix( afDstFn, dstBits, afSrcDir, argL, argN )) != kOkRC ) goto errLabel; } errLabel: mem::release(afSrcDir); mem::release(afDstFn); if( rc != kOkRC ) rc = cwLogError(rc,"Parallel-mix failed."); return rc; } cw::rc_t cw::afop::transformApp( const object_t* cfg ) { rc_t rc = kOkRC; const char* srcDir = nullptr; const char* dryFn = nullptr; const char* dstPreFn = nullptr; const char* dstRevFn = nullptr; unsigned dstBits = 16; const object_t* argNodeL = nullptr; bool irEnableFl=false; const char* irFn = nullptr; double irScale = 1; char* expSrcDir = nullptr; char* expDryFn = nullptr; char* expDstPreFn = nullptr; char* expDstRevFn = nullptr; char* expIrFn = nullptr; unsigned i; // read the top level cfg record if((rc = cfg->getv("dstPreFn",dstPreFn,"dstRevFn",dstRevFn,"dstBits",dstBits,"srcDir",srcDir,"argL",argNodeL,"dryFn",dryFn,"irEnableFl",irEnableFl,"irFn",irFn,"irScale",irScale)) != kOkRC ) goto errLabel; expSrcDir = filesys::expandPath(srcDir); expDryFn = filesys::expandPath(dryFn); expDstPreFn = filesys::expandPath(dstPreFn); expDstRevFn = filesys::expandPath(dstRevFn); if( argNodeL == nullptr ) { rc = cwLogError(kInvalidArgRC,"No crossfades were specified."); } else { unsigned argN = argNodeL->child_count() * 2; parallelMixArg_t argL[ argN ]; // for each source file for(i=0; ichild_count(); ++i) { const object_t* o = argNodeL->child_ele(i); unsigned j = i*2; // parse the non-optional parameters if((rc = o->getv("srcBegSec", argL[j].srcBegSec, "srcEndSec", argL[j].srcEndSec, "fadeOutSec", argL[j].fadeOutSec, "srcFn", argL[j].srcFn )) != kOkRC ) { rc = cwLogError(kInvalidArgRC,"Invalid xform app argument at argument index %i.",i); } else { argL[j].gain = 1; // parse the optional parameters if((rc = o->getv_opt("wetGain", argL[j].gain )) != kOkRC ) { rc = cwLogError(kInvalidArgRC,"Invalid xform app optional argument at argument index %i.",i); } } // expand the source file name argL[j].srcFn = filesys::makeFn(expSrcDir, argL[j].srcFn, NULL, NULL); // form the dry file name argL[j+1] = argL[j]; argL[j+1].srcFn = expDryFn; argL[j+1].gain = 1.0 - argL[j].gain; } // call cross-fader rc = parallelMix( expDstPreFn, dstBits, "", argL, argN ); for(unsigned i=0; i(argL[i].srcFn)); if( rc == kOkRC && irEnableFl ) { expIrFn = filesys::expandPath(irFn); rc = convolve( expDstRevFn, dstBits, expDstPreFn, expIrFn, irScale ); } } errLabel: mem::release(expSrcDir); mem::release(expDryFn); mem::release(expDstPreFn); mem::release(expDstRevFn); mem::release(expIrFn); if( rc != kOkRC ) rc = cwLogError(rc,"Transform-app failed."); return rc; } cw::rc_t cw::afop::convolve( const char* dstFn, unsigned dstBits, const char* srcFn, const char* impulseResponseFn, float irScale ) { rc_t rc = kOkRC; float** hChBuf = nullptr; unsigned hChN = 0; unsigned hFrmN = 0; double hSrate = 0; float** xChBuf = nullptr; unsigned xChN = 0; unsigned xFrmN = 0; audiofile::info_t info; audiofile::reportInfo(impulseResponseFn); audiofile::reportInfo(srcFn); // read the impulse response audio file if((rc = audiofile::allocFloatBuf(impulseResponseFn, hChBuf, hChN, hFrmN, info)) != kOkRC ) { rc = cwLogError(rc,"The impulse audio file read failed on '%s'.", cwStringNullGuard(impulseResponseFn)); goto errLabel; } hSrate = info.srate; // read the source audio file if((rc = audiofile::allocFloatBuf(srcFn, xChBuf, xChN, xFrmN, info)) != kOkRC ) { rc = cwLogError(rc,"The source audio file read failed on '%s'.", cwStringNullGuard(srcFn)); goto errLabel; } // the sample rate of impulse response and src audio signals must be the same if( hSrate != info.srate ) { rc = cwLogError(kInvalidArgRC,"The soure file sample rate %f does not match the impulse response sample rate %f.",info.srate,hSrate); } else { // allocate the output buffer float* yChBuf[ xChN ]; unsigned yFrmN = xFrmN + hFrmN; for(unsigned i=0; i( yFrmN ); //printf("xFrmN:%i xChN:%i hFrmN:%i hChN:%i yFrmN:%i\n",xFrmN,xChN,hFrmN,hChN,yFrmN); // scale the impulse response for(unsigned i=0; i= hChN ? 0 : i; // select the impulse response channel // convolve this channel with the impulse response and store into the output buffer rc = dsp::convolve::apply( xChBuf[i], xFrmN, hChBuf[j], hFrmN, yChBuf[i], yFrmN ); } // write the output file. if( rc == kOkRC ) rc = audiofile::writeFileFloat( dstFn, info.srate, dstBits, yFrmN, xChN, yChBuf); // release the output buffer for(unsigned i=0; igetv("dstFn",dstFn,"dstBits",dstBits,"srcFn",srcFn,"irFn",irFn,"irScale",irScale)) != kOkRC ) { cwLogError(rc,"convolve() arg. parse failed."); } else { char* sFn = filesys::expandPath(srcFn); char* dFn = filesys::expandPath(dstFn); char* iFn = filesys::expandPath(irFn); rc = convolve( dFn, dstBits, sFn, iFn, irScale ); mem::release(sFn); mem::release(dFn); mem::release(iFn); } return rc; } cw::rc_t cw::afop::generate( const object_t* cfg ) { rc_t rc = kOkRC; const char* dstFn = nullptr; char* outFn = nullptr; unsigned dstBits = 16; double dstSrate = 48000; double dstSecs = 1.0; const char* opLabel = nullptr; const object_t* sineNode = nullptr; const object_t* clickNode= nullptr; // read the top level cfg record if((rc = cfg->getv("dstFn",dstFn,"dstBits",dstBits,"dstSrate",dstSrate,"dstSecs",dstSecs,"op",opLabel,"sine",sineNode,"click",clickNode)) != kOkRC ) { cwLogError(rc,"generate() arg. parse failed."); } else { outFn = filesys::expandPath(dstFn); if( textCompare(opLabel,"sine") == 0 ) { double gain = 1.0; double hz = 100.0; if((rc = sineNode->getv("gain",gain,"hertz",hz)) != kOkRC ) { rc = cwLogError(rc,"Error parsing generator 'sine' parameters."); goto errLabel; } else { if((rc = sine( dstFn, dstSrate, dstBits, hz, gain, dstSecs)) != kOkRC ) { rc = cwLogError(rc,"Sine generator failed.."); goto errLabel; } } } if( textCompare(opLabel,"click") == 0 ) { double gain = 1.0; double decay = 0.7; const object_t* msL = nullptr; if((rc = clickNode->getv("gain",gain,"decay",decay,"msL",msL)) != kOkRC ) { rc = cwLogError(rc,"Error parsing generator 'click' parameters."); goto errLabel; } else { unsigned msA_N = msL->child_count(); unsigned msA[ msA_N ]; for(unsigned i=0; ichild_ele(i)->value( msA[i] )) != kOkRC ) { rc = cwLogError(rc,"Error parsing generator 'click' msL[] values."); goto errLabel; } if((rc = clicks( outFn, dstSrate, dstBits, dstSecs, msA, msA_N, gain, -decay )) != kOkRC ) { rc = cwLogError(rc,"Error generating click audio file."); goto errLabel; } } } } errLabel: mem::release(outFn); return rc; } cw::rc_t cw::afop::test( const object_t* cfg ) { rc_t rc = kOkRC; const object_t* o; if((o = cfg->find("sine")) != nullptr ) rc = sine(o); return rc; }