//| Copyright: (C) 2020-2024 Kevin Larke //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file. #include "cwCommon.h" #include "cwLog.h" #include "cwCommonImpl.h" #include "cwTest.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" 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= fadeFrmN ); // count backward i0 = (int)fadeFrmN; d = -1; offs = frmN-fadeFrmN; } // do a linear fade if( cwIsFlag(flags,kLinearFadeFl) ) { for(int i = i0,j=0; j<(int)fadeFrmN; i+=d,++j ) { assert(0 <= offs+j && offs+j < (int)frmN ); xV[offs+j] *= ((float)i) / fadeFrmN; } } else // do an equal power fade { for(int i = i0,j=0; j<(int)fadeFrmN; i+=d,++j ) { assert(0 <= offs+j && offs+j < (int)frmN ); xV[offs+j] *= std::sqrt(((float)i) / fadeFrmN); } } } void _fadeAllChannels( float* chBufL[], unsigned chN, unsigned frmN, unsigned fadeFrmN, unsigned flags ) { fadeFrmN = std::min(frmN,fadeFrmN); for(unsigned i=0; i(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 end0Sec ) { rc = cwLogError(kInvalidArgRC,"Invalid time selection. Begin time (%f) is greater than end time (%f). ", beg1Sec, end0Sec ); goto errLabel; } // open the source file if((rc = audiofile::open( iH, iFn, &info )) != kOkRC ) goto errLabel; // create the output file if((rc = audiofile::create( oH, oFn, info.srate, outBits, info.chCnt)) != kOkRC ) goto errLabel; else { unsigned beg0FrmIdx = (unsigned)floor(beg0Sec * info.srate); unsigned beg1FrmIdx = (unsigned)floor(beg1Sec * info.srate); unsigned end0FrmIdx = (unsigned)floor(end0Sec * info.srate); unsigned end1FrmIdx = end1Sec==-1 ? info.frameCnt : (unsigned)floor(end1Sec * info.srate); unsigned ttlFrmN = end1FrmIdx - beg0FrmIdx; unsigned actualBufFrmN = 0; float* chBuf[ info.chCnt ]; beg0FrmIdx = std::max(0u,std::min(beg0FrmIdx,info.frameCnt)); beg1FrmIdx = std::max(0u,std::min(beg1FrmIdx,info.frameCnt)); end0FrmIdx = std::max(0u,std::min(end0FrmIdx,info.frameCnt)); end1FrmIdx = std::max(0u,std::min(end1FrmIdx,info.frameCnt)); cwLogInfo("beg:%f %f end: %f %f : src:%s dst:%s", beg0Sec,beg1Sec,end0Sec,end1Sec,iFn,oFn); cwLogInfo("beg:%f %f end: %f %f", beg0FrmIdx/info.srate,beg1FrmIdx/info.srate,end0FrmIdx/info.srate,end1FrmIdx/info.srate); // Seek to the start of the read location if((rc = audiofile::seek( iH, beg0FrmIdx )) != kOkRC ) goto errLabel; // set up the read/write channel buffer for(unsigned i = 0; i(ttlFrmN); // read the audio file if((rc = audiofile::readFloat( iH, ttlFrmN, 0, info.chCnt, chBuf, &actualBufFrmN)) != kOkRC ) { rc = cwLogError(kOpFailRC,"Read failed on file '%s'.", iFn ); goto errLabel; } // if there is a fade-in then generate it if( beg1FrmIdx > beg0FrmIdx ) _fadeAllChannels( chBuf, info.chCnt, ttlFrmN, beg1FrmIdx-beg0FrmIdx, kLinearFadeFl | kFadeInFl ); // if there is a fade-out then generate it if( end1FrmIdx > end0FrmIdx ) { assert( actualBufFrmN >= (end1FrmIdx - end0FrmIdx) ); float* fadeChBuf[ info.chCnt ]; for(unsigned i = 0; igetv("outDir",oDir,"outBits",outBits,"selectL",selectL)) != kOkRC ) goto errLabel; if((rc = cfg->getv_opt("srcFn",src0Fn,"markerFn",markerFn,"fadeInSec",fadeInSec,"fadeOutSec",fadeOutSec,"fadeInPreFl",fadeInPreFl,"fadeOutPreFl",fadeOutPostFl)) != kOkRC ) goto errLabel; if( markerFn != nullptr ) { if((rc = file::open(markerFh, markerFn, file::kWriteFl )) != kOkRC ) { rc = cwLogError(rc,"Marker file '%s' create failed.", cwStringNullGuard(markerFn)); goto errLabel; } } for(unsigned i=0; ichild_count(); ++i) { double begSec = 0; double endSec = -1; const char* dstFn = nullptr; const char* src1Fn = nullptr; if((rc = selectL->child_ele(i)->getv("begSec",begSec,"endSec",endSec,"dst",dstFn)) != kOkRC ) { rc = cwLogError(kSyntaxErrorRC,"Argument error in 'select to file' index %i syntax error."); goto errLabel; } if((rc = selectL->child_ele(i)->getv_opt("src",src1Fn)) != kOkRC ) { rc = cwLogError(kSyntaxErrorRC,"Optional argument parse error in 'select to file' index %i syntax error."); goto errLabel; } if( src0Fn == nullptr && src1Fn == nullptr ) { rc = cwLogError(kInvalidArgRC,"selection at index %i does not have a source file.",i); goto errLabel; } double beg0Sec = fadeInPreFl ? std::max(0.0,begSec-fadeInSec) : begSec; double beg1Sec = fadeInPreFl ? begSec : begSec + fadeInSec; double end0Sec = fadeOutPostFl ? std::max(0.0,endSec - fadeOutSec) : endSec; double end1Sec = fadeOutPostFl ? endSec : endSec+fadeOutSec; beg1Sec = std::max(beg0Sec,beg1Sec); end1Sec = std::max(end0Sec,end1Sec); if( markerFh.isValid() ) { file::printf(markerFh,"%f\t%f\t%i\n",begSec,begSec,i+1); } if((rc = selectToFile( src1Fn==nullptr ? src0Fn : src1Fn, beg0Sec, beg1Sec, end0Sec, end1Sec, outBits, oDir, dstFn )) != kOkRC ) goto errLabel; } errLabel: if( markerFh.isValid() ) { file::close(markerFh); } return rc; } cw::rc_t cw::afop::cutAndMix( const char* dstFn, unsigned dstBits, const char* srcDir, const cutMixArg_t* argL, unsigned argN ) { rc_t rc = kOkRC; unsigned dstChN = 0; double dstSrate = 0; unsigned dstFrmN = 0; unsigned maxSrcFrmN = 0; float* dstV = nullptr; float* srcV = nullptr; _cutMixArg_t xArgL[ argN ]; memset( &xArgL, 0, sizeof(xArgL)); // open each of the source files if((rc = _cutAndMixOpen( srcDir, argL, xArgL, argN, dstChN, dstSrate, dstFrmN, maxSrcFrmN )) != kOkRC ) goto errLabel; else { float* dstChBufL[ dstChN ]; float* srcChBufL[ dstChN ]; dstV = mem::allocZ(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; typedef rc_t (*func_t)( const object_t* cfg ); typedef struct map_str { const char* label; func_t func; } map_t; map_t mapA[] = { { "sine", sine }, { "mix", mix }, { "select_to_file",selectToFile }, { "cut_and_mix",cutAndMix }, { "parallel_mix",parallelMix }, { "convolve", convolve }, { "xfade_convolve", transformApp }, { "generate", generate } }; unsigned mapN = sizeof(mapA)/sizeof(mapA[0]); for(unsigned i=0; ichild_count(); ++i) { const object_t* pair = cfg->child_ele(i); const map_t* m = std::find_if( mapA, mapA+mapN, [pair](const map_t& m){ return textIsEqual(m.label,pair->pair_label()); }); bool enable_fl = false; if( m == mapA+mapN ) { rc = cwLogError(kInvalidArgRC,"The audio file operation '%s' is not valid.",pair->pair_label()); goto errLabel; } if((rc = pair->pair_value()->getv_opt("enable_fl",enable_fl)) != kOkRC ) { goto errLabel; } if( enable_fl ) { if((rc = m->func( pair->pair_value() )) != kOkRC ) { goto errLabel; } } } errLabel: return rc; }