From cb44d2843033a621f8b4c93794bee92d71ff1372 Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 15 Aug 2021 16:02:45 -0400 Subject: [PATCH] cwIoAudioMidi.cpp, cwIoAudioRecordPlay.cpp, cwIoMidiRecordPlay.h/cpp, cwMidiPort.cpp: Many changes and additions. --- cwIoAudioMidi.cpp | 433 ++++++++++++++++++++++++++++++++++------ cwIoAudioRecordPlay.cpp | 75 ++++++- cwIoMidiRecordPlay.cpp | 219 +++++++++++++++++++- cwIoMidiRecordPlay.h | 6 +- 4 files changed, 662 insertions(+), 71 deletions(-) diff --git a/cwIoAudioMidi.cpp b/cwIoAudioMidi.cpp index feee438..861b29b 100644 --- a/cwIoAudioMidi.cpp +++ b/cwIoAudioMidi.cpp @@ -11,6 +11,19 @@ #include "cwUiDecls.h" #include "cwIo.h" #include "cwIoAudioMidi.h" +#include "cwAudioFile.h" + +/* + +TODO: +0. Check for leaks with valgrind +1. Add audio recording +2. Add audio metering panel +3. Turn into a reusable component. + + + */ + namespace cw { @@ -32,6 +45,10 @@ namespace cw kStopBtnId, kClearBtnId, kMsgCntId, + + kAudioRecordCheckId, + kAudioSecsId, + kSaveBtnId, kOpenBtnId, kFnStringId @@ -51,11 +68,15 @@ namespace cw { kPanelDivId, kIoReportBtnId, "ioReportBtnId" }, { kPanelDivId, kReportBtnId, "reportBtnId" }, - { kPanelDivId, kRecordCheckId, "recordCheckId" }, - { kPanelDivId, kStartBtnId, "startBtnId" }, - { kPanelDivId, kStopBtnId, "stopBtnId" }, - { kPanelDivId, kClearBtnId, "clearBtnId" }, - { kPanelDivId, kMsgCntId, "msgCntId" }, + { kPanelDivId, kRecordCheckId, "recordCheckId" }, + { kPanelDivId, kAudioRecordCheckId, "audioRecordCheckId" }, + { kPanelDivId, kStartBtnId, "startBtnId" }, + { kPanelDivId, kStopBtnId, "stopBtnId" }, + { kPanelDivId, kClearBtnId, "clearBtnId" }, + { kPanelDivId, kMsgCntId, "msgCntId" }, + + { kPanelDivId, kAudioRecordCheckId, "audioRecordCheckId" }, + { kPanelDivId, kAudioSecsId, "audioSecsId" }, { kPanelDivId, kSaveBtnId, "saveBtnId" }, { kPanelDivId, kOpenBtnId, "openBtnId" }, @@ -67,10 +88,11 @@ namespace cw typedef struct am_audio_str { - time::spec_t timestamp; - unsigned chCnt; - - + time::spec_t timestamp; + unsigned chCnt; + unsigned dspFrameCnt; + struct am_audio_str* link; + sample_t audioBuf[]; // [[ch0:dspFramCnt][ch1:dspFrmCnt]] total: chCnt*dspFrameCnt samples } am_audio_t; typedef struct am_midi_msg_str @@ -108,11 +130,20 @@ namespace cw time::spec_t start_time; unsigned midiFilterCnt; + + am_audio_t* audioBeg; // first in a chain of am_audio_t audio buffers + am_audio_t* audioEnd; // last in a chain of am_audio_t audio buffers + + am_audio_t* audioFile; // one large audio buffer holding the last loaded audio file + + double audioSrate; + bool audioRecordFl; + unsigned audioSmpIdx; bool recordFl; bool startedFl; - handle_t ioH; + io::handle_t ioH; } app_t; rc_t _parseCfg(app_t* app, const object_t* cfg ) @@ -160,7 +191,6 @@ namespace cw rc = cwLogError(kInvalidArgRC,"The MIDI output port: '%s' was not found.", cwStringNullGuard(app->midiOutPortLabel) ); } - printf("MIDI DEV: %i PORT:%i\n",app->midiOutDevIdx,app->midiOutPortIdx); return rc; } @@ -196,7 +226,16 @@ namespace cw _set_midi_msg_next_play_index(app,0); io::timerStart( app->ioH, io::timerIdToIndex(app->ioH, kAmMidiTimerId) ); time::get(app->play_time); - } + } + + if( app->audioRecordFl ) + { + } + else + { + app->audioSmpIdx = 0; + } + } void _on_stop_btn( app_t* app ) @@ -226,7 +265,7 @@ namespace cw cwLogInfo("Runtime: %5.2f seconds.", time::elapsedMs(app->start_time,t1)/1000.0 ); } - rc_t _read_midi( app_t* app ) + rc_t _midi_read( app_t* app ) { rc_t rc = kOkRC; char* fn = filesys::makeFn( app->record_dir, app->filename, NULL, NULL ); @@ -267,7 +306,7 @@ namespace cw return rc; } - rc_t _write_midi( app_t* app ) + rc_t _midi_write( app_t* app ) { rc_t rc = kOkRC; char* fn = nullptr; @@ -335,6 +374,300 @@ namespace cw } } + am_audio_t* _am_audio_alloc( unsigned dspFrameCnt, unsigned chCnt ) + { + unsigned sample_byte_cnt = chCnt * dspFrameCnt * sizeof(sample_t); + void* vp = mem::alloc( sizeof(am_audio_t) + sample_byte_cnt ); + am_audio_t* a = (am_audio_t*)vp; + + a->chCnt = chCnt; + a->dspFrameCnt = dspFrameCnt; + + return a; + } + + am_audio_t* _am_audio_from_sample_index( app_t* app, unsigned sample_idx, unsigned& sample_offs_ref ) + { + unsigned n = 0; + am_audio_t* a = app->audioBeg; + + if( app->audioBeg == nullptr ) + return nullptr; + + for(; a!=nullptr; a=a->link) + { + if( n < sample_idx ) + { + sample_offs_ref = sample_idx - n; + return a; + } + + n += a->dspFrameCnt; + } + + return nullptr; + } + + void _audio_file_buffer( app_t* app, io::audio_msg_t& adst ) + { + unsigned sample_offs = 0; + am_audio_t* a = _am_audio_from_sample_index(app, app->audioSmpIdx, sample_offs ); + unsigned copy_n_0 = std::min(a->dspFrameCnt - sample_offs, adst.dspFrameCnt ); + unsigned chN = std::min(a->chCnt, adst.oBufChCnt ); + + for(unsigned i=0; iaudioBuf + sample_offs, copy_n_0 * sizeof(sample_t)); + + app->audioSmpIdx += copy_n_0; + + if( copy_n_0 < adst.dspFrameCnt ) + { + a = _am_audio_from_sample_index(app, app->audioSmpIdx, sample_offs ); + + unsigned copy_n_1 = std::min(a->dspFrameCnt - sample_offs, adst.dspFrameCnt - copy_n_0 ); + + for(unsigned i=0; iaudioBuf + sample_offs, copy_n_1 * sizeof(sample_t)); + } + } + + void _audio_record( app_t* app, const io::audio_msg_t& asrc ) + { + am_audio_t* a = _am_audio_alloc(asrc.dspFrameCnt,asrc.iBufChCnt); + + for(unsigned chIdx=0; chIdxaudioBuf + chIdx*asrc.dspFrameCnt, asrc.iBufArray[chIdx], asrc.dspFrameCnt * sizeof(sample_t)); + + app->audioEnd->link = a; // link the new audio record to the end of the audio sample buffer chain + app->audioEnd = a; // make the new audio record the last ele. of the chain + + // if this is the first ele of the chain + if( app->audioBeg == nullptr ) + { + app->audioBeg = a; + app->audioSrate = asrc.srate; + } + } + + void _audio_play( app_t* app, io::audio_msg_t& adst ) + { + if( app->audioFile == nullptr ) + return; + + if( app->audioSmpIdx >= app->audioFile->dspFrameCnt ) + return; + + unsigned chCnt = std::min( adst.oBufChCnt, app->audioFile->chCnt ); + unsigned copy_n = std::min( adst.dspFrameCnt, app->audioFile->dspFrameCnt - app->audioSmpIdx); + unsigned extra_n = adst.dspFrameCnt - copy_n; + unsigned i; + + for(i=0; iaudioFile->audioBuf + i*app->audioFile->dspFrameCnt, copy_n * sizeof(sample_t)); + memset(adst.oBufArray + i*adst.dspFrameCnt + copy_n, 0, extra_n * sizeof(sample_t)); + } + } + + void _audio_through( app_t* app, io::audio_msg_t& m ) + { + unsigned chN = std::min(m.iBufChCnt,m.oBufChCnt); + unsigned byteCnt = m.dspFrameCnt * sizeof(sample_t); + + // Copy the input to the output + for(unsigned i=0; iaudioBeg == nullptr ) + return rc; + + // form the filename + if((fn = filesys::makeVersionedFn( app->record_dir, app->record_fn, "wav", NULL )) == nullptr ) + { + rc = cwLogError(kOpFailRC,"Unable to form versioned filename in '%s' with prefix: '%s' and extension: '%s'.", + cwStringNullGuard(app->record_dir), + cwStringNullGuard(app->record_fn), + cwStringNullGuard("wav")); + } + + // create an audio file + if((rc = audiofile::create( afH, fn, app->audioSrate, 16, app->audioBeg->chCnt )) != kOkRC ) + { + cwLogError(rc,"Audio file create failed."); + goto errLabel; + } + + // write each buffer + for(am_audio_t* a=app->audioBeg; a!=nullptr; a=a->link) + { + float* chBufArray[ a->chCnt ]; + for(unsigned i=0; ichCnt; ++i) + chBufArray[i] = a->audioBuf + (i*a->dspFrameCnt); + + if((rc = writeFloat( afH, a->dspFrameCnt, a->chCnt, (const float**)chBufArray )) != kOkRC ) + { + cwLogError(rc,"An error occurred while writing and audio buffer."); + goto errLabel; + } + } + + errLabel: + + // close the audio file + if((rc == audiofile::close(afH)) != kOkRC ) + { + cwLogError(rc,"Audio file close failed."); + goto errLabel; + } + + mem::free(fn); + return rc; + } + + rc_t _audio_write_buffer_times( app_t* app ) + { + rc_t rc = kOkRC; + char* fn = nullptr; + am_audio_t* a0 = app->audioBeg; + file::handle_t fH; + + // if there is no audio to write + if( app->audioBeg == nullptr ) + return rc; + + // form the filename + if((fn = filesys::makeVersionedFn( app->record_dir, app->record_fn, "txt", NULL )) == nullptr ) + { + rc = cwLogError(kOpFailRC,"Unable to form versioned filename in '%s' with prefix: '%s' and extension: '%s'.", + cwStringNullGuard(app->record_dir), + cwStringNullGuard(app->record_fn), + cwStringNullGuard("wav")); + } + + // create the file + if((rc = file::open(fH,fn,file::kWriteFl)) != kOkRC ) + { + cwLogError(rc,"Create audio buffer time file failed."); + goto errLabel; + } + + file::print(fH,"{ [\n"); + + // write each buffer + for(am_audio_t* a=app->audioBeg; a!=nullptr; a=a->link) + { + unsigned elapsed_us = time::elapsedMicros( a0->timestamp, a->timestamp ); + file::printf(fH,"{ elapsed_us:%i chCnt:%i frameCnt:%i }\n", elapsed_us, a->chCnt, a->dspFrameCnt ); + a0 = a; + } + + file::print(fH,"] }\n"); + + // close the file + if((rc = file::close(fH)) != kOkRC ) + { + cwLogError(rc,"Close the audio buffer time file."); + goto errLabel; + } + + errLabel: + mem::release(fn); + + return rc; + } + + rc_t _audio_read( app_t* app ) + { + rc_t rc = kOkRC; + char* fn = nullptr; + filesys::pathPart_t* pp = nullptr; + audiofile::handle_t afH; + audiofile::info_t af_info; + + + if((fn = filesys::makeFn( app->record_dir, app->filename, NULL, NULL )) != nullptr ) + { + rc = cwLogError(kOpFailRC,"Unable to form the audio file:%s",fn); + goto errLabel; + } + + if((pp = filesys::pathParts(fn)) != nullptr ) + { + rc = cwLogError(kOpFailRC,"Unable to parse audio file name:%s",fn); + goto errLabel; + } + + mem::release(fn); + + if((fn = filesys::makeFn( app->record_dir, pp->fnStr, "wav", NULL )) != nullptr ) + { + rc = cwLogError(kOpFailRC,"Unable form audio file wav name:%s",fn); + goto errLabel; + } + + if((rc = audiofile::open(afH, fn, &af_info)) != kOkRC ) + { + rc = cwLogError(kOpFailRC,"Audio file '%s' open failed.",fn); + goto errLabel; + } + + if((app->audioFile = _am_audio_alloc(af_info.frameCnt,af_info.chCnt)) != nullptr ) + { + rc = cwLogError(kOpFailRC,"Allocate audio buffer (%i samples) failed.",af_info.frameCnt*af_info.chCnt); + goto errLabel; + } + else + { + unsigned audioFrameCnt = 0; + float* chArray[ af_info.chCnt ]; + + for(unsigned i=0; iaudioFile->audioBuf + (i*af_info.frameCnt); + + if((rc = audiofile::readFloat(afH, af_info.frameCnt, 0, af_info.chCnt, chArray, &audioFrameCnt)) != kOkRC ) + { + rc = cwLogError(kOpFailRC,"Audio file read failed."); + goto errLabel; + } + + double audioSecs = (double)af_info.frameCnt / af_info.srate; + io::uiSendValue( app->ioH, kInvalidId, uiFindElementUuId(app->ioH,kAudioSecsId), audioSecs ); + + } + + errLabel: + if((rc = audiofile::close(afH)) != kOkRC ) + { + rc = cwLogError(kOpFailRC,"Audio file close failed."); + goto errLabel; + } + + mem::release(pp); + mem::release(fn); + return rc; + } + rc_t _onUiInit(app_t* app, const ui_msg_t& m ) { rc_t rc = kOkRC; @@ -361,18 +694,26 @@ namespace cw break; case kSaveBtnId: - _write_midi(app); + _midi_write(app); + _audio_write_as_wav(app); + _audio_write_buffer_times(app); break; case kOpenBtnId: printf("open btn\n"); - _read_midi(app); + _midi_read(app); + _audio_read(app); break; case kRecordCheckId: cwLogInfo("Record:%i",m.value->u.b); app->recordFl = m.value->u.b; break; + + case kAudioRecordCheckId: + cwLogInfo("Audio Record:%i",m.value->u.b); + app->audioRecordFl = m.value->u.b; + break; case kStartBtnId: cwLogInfo("Start"); @@ -558,30 +899,25 @@ namespace cw return rc; } - rc_t audioCb( app_t* app, const audio_msg_t& m ) + rc_t audioCb( app_t* app, audio_msg_t& m ) { rc_t rc = kOkRC; - unsigned chN = std::min(m.iBufChCnt,m.oBufChCnt); - unsigned byteCnt = m.dspFrameCnt * sizeof(sample_t); - - // Copy the input to the output - for(unsigned i=0; istartedFl ) + { + if( app->audioRecordFl ) + { + if( m.iBufChCnt > 0 ) + _audio_record(app,m); + + if( m.oBufChCnt > 0 ) + _audio_play(app,m); } - + + + } + + return rc; } @@ -631,33 +967,12 @@ namespace cw } return rc; - } - - void _report( handle_t h ) - { - for(unsigned i=0; iaudioInChMapN = 0; + p->audioOutChMapN = 0; + mem::release(p->audioInChMapA); + mem::release(p->audioOutChMapA); mem::release(p->audioFile); mem::release(p); return kOkRC; @@ -93,6 +102,38 @@ namespace cw rc_t _parseCfg(audio_record_play_t* p, const object_t& cfg ) { rc_t rc = kOkRC; + + const object_t* audioInChMapL = nullptr; + const object_t* audioOutChMapL = nullptr; + + if((rc = cfg.getv_opt("audio_in_ch_map", audioInChMapL, + "audio_out_ch_map", audioOutChMapL)) != kOkRC ) + { + rc = cwLogError(rc,"Parse cfg failed."); + goto errLabel; + } + + if( audioInChMapL != nullptr ) + { + p->audioInChMapN = audioInChMapL->child_count(); + p->audioInChMapA = mem::allocZ( p->audioInChMapN ); + + for(unsigned i=0; iaudioInChMapN; ++i) + audioInChMapL->child_ele(i)->value(p->audioInChMapA[i]); + } + + + if( audioOutChMapL != nullptr ) + { + p->audioOutChMapN = audioOutChMapL->child_count(); + p->audioOutChMapA = mem::allocZ( p->audioOutChMapN ); + + for(unsigned i=0; iaudioOutChMapN; ++i) + audioOutChMapL->child_ele(i)->value(p->audioOutChMapA[i]); + } + + + errLabel: return rc; } @@ -123,12 +164,19 @@ namespace cw void _audio_record( audio_record_play_t* p, const io::audio_msg_t& asrc ) { - am_audio_t* a = _am_audio_alloc(asrc.dspFrameCnt,asrc.iBufChCnt); - - for(unsigned chIdx=0; chIdxaudioBuf + chIdx*asrc.dspFrameCnt, asrc.iBufArray[chIdx], asrc.dspFrameCnt * sizeof(sample_t)); + unsigned chCnt = p->audioInChMapN==0 ? asrc.iBufChCnt : p->audioInChMapN; + am_audio_t* a = _am_audio_alloc(asrc.dspFrameCnt,chCnt); - a->chCnt = asrc.iBufChCnt; + chCnt = std::min( chCnt, asrc.iBufChCnt ); + + for(unsigned chIdx=0; chIdxaudioInChMapA == nullptr ? chIdx : p->audioInChMapA[chIdx]; + + memcpy(a->audioBuf + chIdx*asrc.dspFrameCnt, asrc.iBufArray[ srcChIdx ], asrc.dspFrameCnt * sizeof(sample_t)); + } + + a->chCnt = chCnt; a->dspFrameCnt = asrc.dspFrameCnt; if( p->audioEnd != nullptr ) @@ -160,16 +208,25 @@ namespace cw if((a = _am_audio_from_sample_index(p, p->curFrameIdx, sample_offs )) == nullptr ) break; - unsigned n = std::min(a->dspFrameCnt - sample_offs, adst.dspFrameCnt ); - unsigned chN = std::min(a->chCnt, adst.oBufChCnt ); + unsigned n = std::min(a->dspFrameCnt - sample_offs, adst.dspFrameCnt ); + + + // TODO: Verify that this is correct - it looks like sample_offs should have to be incremented - for(unsigned i=0; iaudioBuf + sample_offs, n * sizeof(sample_t)); + for(unsigned i=0; ichCnt; ++i) + { + unsigned dstChIdx = p->audioOutChMapA != nullptr && i < p->audioOutChMapN ? p->audioOutChMapA[i] : i; + + if( dstChIdx < adst.oBufChCnt ) + memcpy( adst.oBufArray[ dstChIdx ] + adst_idx, a->audioBuf + sample_offs, n * sizeof(sample_t)); + } p->curFrameIdx += n; adst_idx += n; } + // TODO: zero unused channels + if( adst_idx < adst.dspFrameCnt ) for(unsigned i=0; imsgArrayOutIdx = next_idx; } + + // Read the am_midi_msg_t records from a file written by _midi_write() + // If msgArrayCntRef==0 and msgArrayRef==NULL then an array will be allocated and it is up + // to the caller to release it, otherwise the msgArrayCntRef should be set to the count + // of available records in msgArrayRef[]. Note the if there are more records in the file + // than there are record in msgArrayRef[] then a warning will be issued and only + // msgArrayCntRef records will be returned. + cw::rc_t _am_file_read( const char* fn, unsigned& msgArrayCntRef, am_midi_msg_t*& msgArrayRef ) + { + rc_t rc = kOkRC; + unsigned n = 0; + file::handle_t fH; + + if((rc = file::open(fH,fn,file::kReadFl)) != kOkRC ) + { + rc = cwLogError(kOpenFailRC,"Unable to locate the AM file: '%s'.", fn ); + goto errLabel; + } + + if((rc = file::read(fH,n)) != kOkRC ) + { + rc = cwLogError(kReadFailRC,"Header read failed on Audio-MIDI file: '%s'.", fn ); + goto errLabel; + } + + if( msgArrayCntRef == 0 || msgArrayRef == nullptr ) + { + msgArrayRef = mem::allocZ(n); + } + else + { + if( n > msgArrayCntRef ) + { + cwLogWarning("The count of message in Audio-MIDI file '%s' reduced from %i to %i.", fn, n, msgArrayCntRef ); + n = msgArrayCntRef; + } + } + + if((rc = file::read(fH,msgArrayRef,n*sizeof(am_midi_msg_t))) != kOkRC ) + { + rc = cwLogError(kReadFailRC,"Data read failed on Audio-MIDI file: '%s'.", fn ); + goto errLabel; + } + + msgArrayCntRef = n; + + errLabel: + + return rc; + } + rc_t _midi_read( midi_record_play_t* p, const char* fn ) { rc_t rc = kOkRC; @@ -193,6 +247,70 @@ namespace cw return rc; } + rc_t _midi_file_write( const char* fn, const am_midi_msg_t* msgArray, unsigned msgArrayCnt ) + { + rc_t rc = kOkRC; + const unsigned midiFileTrackCnt = 1; + const unsigned midiFileTicksPerQN = 192; + const unsigned midiFileTempoBpm = 120; + const unsigned midiFileTrkIdx = 0; + file::handle_t fH; + midi::file::handle_t mfH; + time::spec_t t0; + + + if( msgArrayCnt == 0 ) + { + cwLogWarning("Nothing to write."); + return rc; + } + + if((rc = midi::file::create( mfH, midiFileTrackCnt, midiFileTicksPerQN )) != kOkRC ) + { + rc = cwLogError(rc,"MIDI file create failed. File:'%s'", cwStringNullGuard(fn)); + goto errLabel; + } + + if((rc = midi::file::insertTrackTempoMsg( mfH, midiFileTrkIdx, 0, midiFileTempoBpm )) != kOkRC ) + { + rc = cwLogError(rc,"MIDI file tempo message insert failed. File:'%s'", cwStringNullGuard(fn)); + goto errLabel; + } + + t0 = msgArray[0].timestamp; + + for(unsigned i=0; idevIdx, mm->portIdx, mm->microsec, mm->ch, mm->status, mm->d0, mm->d1 ); @@ -241,6 +359,8 @@ namespace cw // soft pedal io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, midi::kCtlMdId, 67, 0 ); + p->pedalFl = false; + } cwLogInfo("Runtime: %5.2f seconds.", time::elapsedMs(p->start_time,t1)/1000.0 ); @@ -341,7 +461,25 @@ namespace cw //_print_midi_msg(mm); - io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, mm->status + mm->ch, mm->d0, mm->d1 ); + bool skipFl = false; + + if( mm->status == midi::kCtlMdId && (mm->d0 == midi::kSustainCtlMdId || mm->d0 == midi::kSostenutoCtlMdId || mm->d0 == midi::kSoftPedalCtlMdId ) ) + { + // if the pedal is down + if( p->pedalFl ) + { + skipFl = mm->d1 > 64; + p->pedalFl = false; + } + else + { + skipFl = mm->d1 <= 64; + p->pedalFl = true; + } + } + + if( !skipFl ) + io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, mm->status + mm->ch, mm->d0, mm->d1 ); _set_midi_msg_next_play_index(p, p->msgArrayOutIdx+1 ); @@ -424,6 +562,7 @@ cw::rc_t cw::midi_record_play::start( handle_t h ) { midi_record_play_t* p = _handleToPtr(h); p->startedFl = true; + p->pedalFl = false; time::get(p->start_time); @@ -540,3 +679,79 @@ cw::rc_t cw::midi_record_play::exec( handle_t h, const io::msg_t& m ) return rc; } + + +cw::rc_t cw::midi_record_play::am_to_midi_file( const char* am_filename, const char* midi_filename ) +{ + rc_t rc = kOkRC; + unsigned msgArrayCnt = 0; + am_midi_msg_t* msgArray = nullptr; + + if((rc = _am_file_read( am_filename, msgArrayCnt, msgArray )) != kOkRC ) + { + rc = cwLogError(rc,"Unable to read AM file '%s'.", cwStringNullGuard(am_filename)); + goto errLabel; + } + + if((rc = _midi_file_write( midi_filename, msgArray, msgArrayCnt )) != kOkRC ) + { + rc = cwLogError(rc,"Unable to write AM file '%s' to '%s'.", cwStringNullGuard(am_filename),cwStringNullGuard(midi_filename)); + goto errLabel; + } + + errLabel: + mem::release(msgArray); + + return rc; + +} + +cw::rc_t cw::midi_record_play::am_to_midi_dir( const char* inDir ) +{ + rc_t rc = kOkRC; + filesys::dirEntry_t* dirEntryArray = nullptr; + unsigned dirEntryCnt = 0; + + if(( dirEntryArray = dirEntries( inDir, filesys::kDirFsFl, &dirEntryCnt )) == nullptr ) + goto errLabel; + + for(unsigned i=0; igetv("inDir",inDir)) != kOkRC ) + { + rc = cwLogError(rc,"AM to MIDI file: Unable to parse input arg's."); + goto errLabel; + } + + // + if((rc = am_to_midi_dir(inDir)) != kOkRC ) + { + rc = cwLogError(rc,"AM to MIDI file conversion on directory:'%s' failed.", cwStringNullGuard(inDir)); + goto errLabel; + } + + errLabel: + return rc; + +} diff --git a/cwIoMidiRecordPlay.h b/cwIoMidiRecordPlay.h index 110acab..dd3eea4 100644 --- a/cwIoMidiRecordPlay.h +++ b/cwIoMidiRecordPlay.h @@ -28,7 +28,11 @@ namespace cw unsigned event_count( handle_t h ); unsigned event_index( handle_t h ); rc_t exec( handle_t h, const io::msg_t& msg ); - + + + rc_t am_to_midi_file( const char* am_filename, const char* midi_filename ); + rc_t am_to_midi_dir( const char* inDir ); + rc_t am_to_midi_file( const object_t* cfg ); } }