cwIoAudioMidi.cpp, cwIoAudioRecordPlay.cpp, cwIoMidiRecordPlay.h/cpp, cwMidiPort.cpp: Many changes and additions.

This commit is contained in:
kevin 2021-08-15 16:02:45 -04:00
parent fff1af607e
commit cb44d28430
4 changed files with 662 additions and 71 deletions

View File

@ -11,6 +11,19 @@
#include "cwUiDecls.h" #include "cwUiDecls.h"
#include "cwIo.h" #include "cwIo.h"
#include "cwIoAudioMidi.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 namespace cw
{ {
@ -32,6 +45,10 @@ namespace cw
kStopBtnId, kStopBtnId,
kClearBtnId, kClearBtnId,
kMsgCntId, kMsgCntId,
kAudioRecordCheckId,
kAudioSecsId,
kSaveBtnId, kSaveBtnId,
kOpenBtnId, kOpenBtnId,
kFnStringId kFnStringId
@ -51,11 +68,15 @@ namespace cw
{ kPanelDivId, kIoReportBtnId, "ioReportBtnId" }, { kPanelDivId, kIoReportBtnId, "ioReportBtnId" },
{ kPanelDivId, kReportBtnId, "reportBtnId" }, { kPanelDivId, kReportBtnId, "reportBtnId" },
{ kPanelDivId, kRecordCheckId, "recordCheckId" }, { kPanelDivId, kRecordCheckId, "recordCheckId" },
{ kPanelDivId, kStartBtnId, "startBtnId" }, { kPanelDivId, kAudioRecordCheckId, "audioRecordCheckId" },
{ kPanelDivId, kStopBtnId, "stopBtnId" }, { kPanelDivId, kStartBtnId, "startBtnId" },
{ kPanelDivId, kClearBtnId, "clearBtnId" }, { kPanelDivId, kStopBtnId, "stopBtnId" },
{ kPanelDivId, kMsgCntId, "msgCntId" }, { kPanelDivId, kClearBtnId, "clearBtnId" },
{ kPanelDivId, kMsgCntId, "msgCntId" },
{ kPanelDivId, kAudioRecordCheckId, "audioRecordCheckId" },
{ kPanelDivId, kAudioSecsId, "audioSecsId" },
{ kPanelDivId, kSaveBtnId, "saveBtnId" }, { kPanelDivId, kSaveBtnId, "saveBtnId" },
{ kPanelDivId, kOpenBtnId, "openBtnId" }, { kPanelDivId, kOpenBtnId, "openBtnId" },
@ -67,10 +88,11 @@ namespace cw
typedef struct am_audio_str typedef struct am_audio_str
{ {
time::spec_t timestamp; time::spec_t timestamp;
unsigned chCnt; unsigned chCnt;
unsigned dspFrameCnt;
struct am_audio_str* link;
sample_t audioBuf[]; // [[ch0:dspFramCnt][ch1:dspFrmCnt]] total: chCnt*dspFrameCnt samples
} am_audio_t; } am_audio_t;
typedef struct am_midi_msg_str typedef struct am_midi_msg_str
@ -108,11 +130,20 @@ namespace cw
time::spec_t start_time; time::spec_t start_time;
unsigned midiFilterCnt; 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 recordFl;
bool startedFl; bool startedFl;
handle_t ioH; io::handle_t ioH;
} app_t; } app_t;
rc_t _parseCfg(app_t* app, const object_t* cfg ) 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) ); 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; return rc;
} }
@ -196,7 +226,16 @@ namespace cw
_set_midi_msg_next_play_index(app,0); _set_midi_msg_next_play_index(app,0);
io::timerStart( app->ioH, io::timerIdToIndex(app->ioH, kAmMidiTimerId) ); io::timerStart( app->ioH, io::timerIdToIndex(app->ioH, kAmMidiTimerId) );
time::get(app->play_time); time::get(app->play_time);
} }
if( app->audioRecordFl )
{
}
else
{
app->audioSmpIdx = 0;
}
} }
void _on_stop_btn( app_t* app ) 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 ); 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; rc_t rc = kOkRC;
char* fn = filesys::makeFn( app->record_dir, app->filename, NULL, NULL ); char* fn = filesys::makeFn( app->record_dir, app->filename, NULL, NULL );
@ -267,7 +306,7 @@ namespace cw
return rc; return rc;
} }
rc_t _write_midi( app_t* app ) rc_t _midi_write( app_t* app )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
char* fn = nullptr; 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<uint8_t>( 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; i<chN; ++i)
memcpy( adst.oBufArray[i], a->audioBuf + 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; i<chN; ++i)
memcpy( adst.oBufArray[i] + copy_n_0, a->audioBuf + 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; chIdx<asrc.iBufChCnt; ++chIdx)
memcpy(a->audioBuf + 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; i<chCnt; ++i)
{
memcpy(adst.oBufArray + i*adst.dspFrameCnt, app->audioFile->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; i<chN; ++i)
if( m.oBufArray[i] != NULL )
{
// the input channel is not disabled
if( m.iBufArray[i] != NULL )
{
for(unsigned j=0; j<m.dspFrameCnt; ++j )
m.oBufArray[i][j] = m.iBufArray[i][j];
}
else
{
// the input channel is disabled but the output is not - so fill the output with zeros
memset(m.oBufArray[i], 0, byteCnt);
}
}
}
rc_t _audio_write_as_wav( app_t* app )
{
rc_t rc = kOkRC;
audiofile::handle_t afH;
char* fn = nullptr;
// 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, "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; i<a->chCnt; ++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; i<af_info.chCnt; ++i)
chArray[i] = app->audioFile->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 _onUiInit(app_t* app, const ui_msg_t& m )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
@ -361,18 +694,26 @@ namespace cw
break; break;
case kSaveBtnId: case kSaveBtnId:
_write_midi(app); _midi_write(app);
_audio_write_as_wav(app);
_audio_write_buffer_times(app);
break; break;
case kOpenBtnId: case kOpenBtnId:
printf("open btn\n"); printf("open btn\n");
_read_midi(app); _midi_read(app);
_audio_read(app);
break; break;
case kRecordCheckId: case kRecordCheckId:
cwLogInfo("Record:%i",m.value->u.b); cwLogInfo("Record:%i",m.value->u.b);
app->recordFl = m.value->u.b; app->recordFl = m.value->u.b;
break; break;
case kAudioRecordCheckId:
cwLogInfo("Audio Record:%i",m.value->u.b);
app->audioRecordFl = m.value->u.b;
break;
case kStartBtnId: case kStartBtnId:
cwLogInfo("Start"); cwLogInfo("Start");
@ -558,30 +899,25 @@ namespace cw
return rc; 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; rc_t rc = kOkRC;
unsigned chN = std::min(m.iBufChCnt,m.oBufChCnt); if( app->startedFl )
unsigned byteCnt = m.dspFrameCnt * sizeof(sample_t); {
if( app->audioRecordFl )
// Copy the input to the output {
for(unsigned i=0; i<chN; ++i) if( m.iBufChCnt > 0 )
if( m.oBufArray[i] != NULL ) _audio_record(app,m);
{
// the input channel is not disabled if( m.oBufChCnt > 0 )
if( m.iBufArray[i] != NULL ) _audio_play(app,m);
{
for(unsigned j=0; j<m.dspFrameCnt; ++j )
m.oBufArray[i][j] = m.iBufArray[i][j];
}
else
{
// the input channel is disabled but the output is not - so fill the output with zeros
memset(m.oBufArray[i], 0, byteCnt);
}
} }
}
return rc; return rc;
} }
@ -631,33 +967,12 @@ namespace cw
} }
return rc; return rc;
} }
void _report( handle_t h )
{
for(unsigned i=0; i<serialDeviceCount(h); ++i)
printf("serial: %s\n", serialDeviceLabel(h,i));
for(unsigned i=0; i<midiDeviceCount(h); ++i)
for(unsigned j=0; j<2; ++j)
{
bool inputFl = j==0;
unsigned m = midiDevicePortCount(h,i,inputFl);
for(unsigned k=0; k<m; ++k)
printf("midi: %s: %s : %s\n", inputFl ? "in ":"out", midiDeviceName(h,i), midiDevicePortName(h,i,inputFl,k));
}
for(unsigned i=0; i<audioDeviceCount(h); ++i)
printf("audio: %s\n", audioDeviceName(h,i));
}
} }
} }
} }
cw::rc_t cw::io::audio_midi::main( const object_t* cfg ) cw::rc_t cw::io::audio_midi::main( const object_t* cfg )
{ {

View File

@ -43,6 +43,11 @@ namespace cw
bool recordFl; bool recordFl;
bool startedFl; bool startedFl;
unsigned* audioInChMapA;
unsigned audioInChMapN;
unsigned* audioOutChMapA;
unsigned audioOutChMapN;
} audio_record_play_t; } audio_record_play_t;
audio_record_play_t* _handleToPtr( handle_t h ) audio_record_play_t* _handleToPtr( handle_t h )
@ -85,6 +90,10 @@ namespace cw
rc_t _destroy( audio_record_play_t* p ) rc_t _destroy( audio_record_play_t* p )
{ {
_am_audio_free_list(p); _am_audio_free_list(p);
p->audioInChMapN = 0;
p->audioOutChMapN = 0;
mem::release(p->audioInChMapA);
mem::release(p->audioOutChMapA);
mem::release(p->audioFile); mem::release(p->audioFile);
mem::release(p); mem::release(p);
return kOkRC; return kOkRC;
@ -93,6 +102,38 @@ namespace cw
rc_t _parseCfg(audio_record_play_t* p, const object_t& cfg ) rc_t _parseCfg(audio_record_play_t* p, const object_t& cfg )
{ {
rc_t rc = kOkRC; 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<unsigned>( p->audioInChMapN );
for(unsigned i=0; i<p->audioInChMapN; ++i)
audioInChMapL->child_ele(i)->value(p->audioInChMapA[i]);
}
if( audioOutChMapL != nullptr )
{
p->audioOutChMapN = audioOutChMapL->child_count();
p->audioOutChMapA = mem::allocZ<unsigned>( p->audioOutChMapN );
for(unsigned i=0; i<p->audioOutChMapN; ++i)
audioOutChMapL->child_ele(i)->value(p->audioOutChMapA[i]);
}
errLabel:
return rc; return rc;
} }
@ -123,12 +164,19 @@ namespace cw
void _audio_record( audio_record_play_t* p, const io::audio_msg_t& asrc ) 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); unsigned chCnt = p->audioInChMapN==0 ? asrc.iBufChCnt : p->audioInChMapN;
am_audio_t* a = _am_audio_alloc(asrc.dspFrameCnt,chCnt);
for(unsigned chIdx=0; chIdx<asrc.iBufChCnt; ++chIdx)
memcpy(a->audioBuf + chIdx*asrc.dspFrameCnt, asrc.iBufArray[chIdx], asrc.dspFrameCnt * sizeof(sample_t));
a->chCnt = asrc.iBufChCnt; chCnt = std::min( chCnt, asrc.iBufChCnt );
for(unsigned chIdx=0; chIdx<chCnt; ++chIdx)
{
unsigned srcChIdx = p->audioInChMapA == 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; a->dspFrameCnt = asrc.dspFrameCnt;
if( p->audioEnd != nullptr ) if( p->audioEnd != nullptr )
@ -160,16 +208,25 @@ namespace cw
if((a = _am_audio_from_sample_index(p, p->curFrameIdx, sample_offs )) == nullptr ) if((a = _am_audio_from_sample_index(p, p->curFrameIdx, sample_offs )) == nullptr )
break; break;
unsigned n = std::min(a->dspFrameCnt - sample_offs, adst.dspFrameCnt ); unsigned n = std::min(a->dspFrameCnt - sample_offs, adst.dspFrameCnt );
unsigned chN = std::min(a->chCnt, adst.oBufChCnt );
// TODO: Verify that this is correct - it looks like sample_offs should have to be incremented
for(unsigned i=0; i<chN; ++i) for(unsigned i=0; i<a->chCnt; ++i)
memcpy( adst.oBufArray[i] + adst_idx, a->audioBuf + sample_offs, n * sizeof(sample_t)); {
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; p->curFrameIdx += n;
adst_idx += n; adst_idx += n;
} }
// TODO: zero unused channels
if( adst_idx < adst.dspFrameCnt ) if( adst_idx < adst.dspFrameCnt )
for(unsigned i=0; i<adst.oBufChCnt; ++i) for(unsigned i=0; i<adst.oBufChCnt; ++i)
memset( adst.oBufArray[i] + adst_idx, 0, (adst.dspFrameCnt - adst_idx) * sizeof(sample_t)); memset( adst.oBufArray[i] + adst_idx, 0, (adst.dspFrameCnt - adst_idx) * sizeof(sample_t));

View File

@ -8,6 +8,7 @@
#include "cwTime.h" #include "cwTime.h"
#include "cwMidiDecls.h" #include "cwMidiDecls.h"
#include "cwMidi.h" #include "cwMidi.h"
#include "cwMidiFile.h"
#include "cwUiDecls.h" #include "cwUiDecls.h"
#include "cwIo.h" #include "cwIo.h"
#include "cwIoMidiRecordPlay.h" #include "cwIoMidiRecordPlay.h"
@ -51,7 +52,9 @@ namespace cw
bool thruFl; bool thruFl;
time::spec_t play_time; time::spec_t play_time;
time::spec_t start_time; time::spec_t start_time;
bool pedalFl;
} midi_record_play_t; } midi_record_play_t;
@ -111,6 +114,57 @@ namespace cw
p->msgArrayOutIdx = next_idx; p->msgArrayOutIdx = 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<am_midi_msg_t>(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 _midi_read( midi_record_play_t* p, const char* fn )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
@ -193,6 +247,70 @@ namespace cw
return rc; 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; i<msgArrayCnt; ++i)
{
double secs = time::elapsedMicros( t0, msgArray[i].timestamp ) / 1000000.0;
unsigned atick = secs * midiFileTicksPerQN * midiFileTempoBpm / 60.0;
if((rc = insertTrackChMsg( mfH, midiFileTrkIdx, atick, msgArray[i].ch + msgArray[i].status, msgArray[i].d0, msgArray[i].d1 )) != kOkRC )
{
rc = cwLogError(rc,"MIDI file message insert failed. File: '%s'.",cwStringNullGuard(fn));
goto errLabel;
}
}
if((rc = midi::file::write( mfH, fn )) != kOkRC )
{
rc = cwLogError(rc,"MIDI file write failed on '%s'.",cwStringNullGuard(fn));
goto errLabel;
}
if((rc = midi::file::close( mfH )) != kOkRC )
{
rc = cwLogError(rc,"MIDI file close failed on '%s'.",cwStringNullGuard(fn));
goto errLabel;
}
errLabel:
return rc;
}
void _print_midi_msg( const am_midi_msg_t* mm ) void _print_midi_msg( const am_midi_msg_t* mm )
{ {
printf("%i %i : %10i : %2i 0x%02x 0x%02x 0x%02x\n", mm->devIdx, mm->portIdx, mm->microsec, mm->ch, mm->status, mm->d0, mm->d1 ); printf("%i %i : %10i : %2i 0x%02x 0x%02x 0x%02x\n", mm->devIdx, mm->portIdx, mm->microsec, mm->ch, mm->status, mm->d0, mm->d1 );
@ -241,6 +359,8 @@ namespace cw
// soft pedal // soft pedal
io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, midi::kCtlMdId, 67, 0 ); 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 ); cwLogInfo("Runtime: %5.2f seconds.", time::elapsedMs(p->start_time,t1)/1000.0 );
@ -341,7 +461,25 @@ namespace cw
//_print_midi_msg(mm); //_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 ); _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); midi_record_play_t* p = _handleToPtr(h);
p->startedFl = true; p->startedFl = true;
p->pedalFl = false;
time::get(p->start_time); 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; 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; i<dirEntryCnt; ++i)
{
printf("0x%x %s\n", dirEntryArray[i].flags, dirEntryArray[i].name);
}
errLabel:
mem::release(dirEntryArray);
return rc;
}
cw::rc_t cw::midi_record_play::am_to_midi_file( const object_t* cfg )
{
rc_t rc = kOkRC;
const char* inDir = nullptr;
//
if( cfg == nullptr )
{
rc = cwLogError(kInvalidArgRC,"AM to MIDI file: No input directory.");
goto errLabel;
}
//
if((rc = cfg->getv("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;
}

View File

@ -28,7 +28,11 @@ namespace cw
unsigned event_count( handle_t h ); unsigned event_count( handle_t h );
unsigned event_index( handle_t h ); unsigned event_index( handle_t h );
rc_t exec( handle_t h, const io::msg_t& msg ); 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 );
} }
} }