cwIoAudioMidi.cpp, cwIoAudioRecordPlay.cpp, cwIoMidiRecordPlay.h/cpp, cwMidiPort.cpp: Many changes and additions.
This commit is contained in:
parent
fff1af607e
commit
cb44d28430
@ -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
|
||||
@ -52,11 +69,15 @@ namespace cw
|
||||
{ kPanelDivId, kReportBtnId, "reportBtnId" },
|
||||
|
||||
{ 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" },
|
||||
{ kPanelDivId, kFnStringId, "filenameId" },
|
||||
@ -69,8 +90,9 @@ namespace cw
|
||||
{
|
||||
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
|
||||
@ -109,10 +131,19 @@ 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;
|
||||
}
|
||||
@ -197,6 +227,15 @@ namespace cw
|
||||
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<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 rc = kOkRC;
|
||||
@ -361,12 +694,15 @@ 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:
|
||||
@ -374,6 +710,11 @@ namespace cw
|
||||
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");
|
||||
_on_start_btn(app);
|
||||
@ -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);
|
||||
if( app->startedFl )
|
||||
{
|
||||
if( app->audioRecordFl )
|
||||
{
|
||||
if( m.iBufChCnt > 0 )
|
||||
_audio_record(app,m);
|
||||
|
||||
// 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);
|
||||
if( m.oBufChCnt > 0 )
|
||||
_audio_play(app,m);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -632,32 +968,11 @@ namespace cw
|
||||
|
||||
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 )
|
||||
{
|
||||
|
||||
|
@ -43,6 +43,11 @@ namespace cw
|
||||
bool recordFl;
|
||||
bool startedFl;
|
||||
|
||||
unsigned* audioInChMapA;
|
||||
unsigned audioInChMapN;
|
||||
unsigned* audioOutChMapA;
|
||||
unsigned audioOutChMapN;
|
||||
|
||||
} audio_record_play_t;
|
||||
|
||||
audio_record_play_t* _handleToPtr( handle_t h )
|
||||
@ -85,6 +90,10 @@ namespace cw
|
||||
rc_t _destroy( audio_record_play_t* 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);
|
||||
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<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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
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));
|
||||
chCnt = std::min( chCnt, asrc.iBufChCnt );
|
||||
|
||||
a->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;
|
||||
|
||||
if( p->audioEnd != nullptr )
|
||||
@ -161,15 +209,24 @@ namespace cw
|
||||
break;
|
||||
|
||||
unsigned n = 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] + adst_idx, a->audioBuf + sample_offs, n * sizeof(sample_t));
|
||||
|
||||
// TODO: Verify that this is correct - it looks like sample_offs should have to be incremented
|
||||
|
||||
for(unsigned i=0; i<a->chCnt; ++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; i<adst.oBufChCnt; ++i)
|
||||
memset( adst.oBufArray[i] + adst_idx, 0, (adst.dspFrameCnt - adst_idx) * sizeof(sample_t));
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "cwTime.h"
|
||||
#include "cwMidiDecls.h"
|
||||
#include "cwMidi.h"
|
||||
#include "cwMidiFile.h"
|
||||
#include "cwUiDecls.h"
|
||||
#include "cwIo.h"
|
||||
#include "cwIoMidiRecordPlay.h"
|
||||
@ -53,6 +54,8 @@ namespace cw
|
||||
time::spec_t play_time;
|
||||
time::spec_t start_time;
|
||||
|
||||
bool pedalFl;
|
||||
|
||||
} midi_record_play_t;
|
||||
|
||||
enum
|
||||
@ -111,6 +114,57 @@ namespace cw
|
||||
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 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; 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 )
|
||||
{
|
||||
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
|
||||
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,6 +461,24 @@ namespace cw
|
||||
|
||||
//_print_midi_msg(mm);
|
||||
|
||||
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; 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;
|
||||
|
||||
}
|
||||
|
@ -30,6 +30,10 @@ namespace cw
|
||||
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 );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user