cwIoAudioMidiApp.h/cpp,cwIoAudioRecordPlay.h/cpp,cwIoMidiRecordPlay.h/cpp: Initial commit.

This commit is contained in:
kevin 2021-05-10 08:37:29 -04:00
parent c90aa58bc5
commit ddad22cde0
10 changed files with 1728 additions and 6 deletions

View File

@ -52,8 +52,13 @@ libcwSRC += src/libcw/cwMidiPort.cpp src/libcw/cwMidiAlsa.cpp src/libcw/cwAudioD
if cwWEBSOCK
libcwHDR += src/libcw/cwIo.h src/libcw/cwIoTest.h src/libcw/cwIoSocketChat.h src/libcw/cwIoAudioPanel.h src/libcw/cwIoAudioMidi.h
libcwSRC += src/libcw/cwIo.cpp src/libcw/cwIoTest.cpp src/libcw/cwIoSocketChat.cpp src/libcw/cwIoAudioPanel.cpp src/libcw/cwIoAudioMidi.cpp
libcwHDR += src/libcw/cwIoMidiRecordPlay.h src/libcw/cwIoAudioRecordPlay.h src/libcw/cwIoAudioMidiApp.h
libcwSRC += src/libcw/cwIoMidiRecordPlay.cpp src/libcw/cwIoAudioRecordPlay.cpp src/libcw/cwIoAudioMidiApp.cpp
endif
endif
libcwHDR += src/libcw/cwAudioBufDecls.h src/libcw/cwAudioBuf.h

555
cwIoAudioMidiApp.cpp Normal file
View File

@ -0,0 +1,555 @@
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"
#include "cwObject.h"
#include "cwText.h"
#include "cwFileSys.h"
#include "cwFile.h"
#include "cwTime.h"
#include "cwMidiDecls.h"
#include "cwMidi.h"
#include "cwUiDecls.h"
#include "cwIo.h"
#include "cwIoAudioMidiApp.h"
#include "cwIoMidiRecordPlay.h"
#include "cwIoAudioRecordPlay.h"
namespace cw
{
namespace audio_midi_app
{
// Application Id's for UI elements
enum
{
// Resource Based elements
kPanelDivId = 1000,
kQuitBtnId,
kIoReportBtnId,
kReportBtnId,
kRecordCheckId,
kStartBtnId,
kStopBtnId,
kClearBtnId,
kMidiThruCheckId,
kCurMidiEvtCntId,
kTotalMidiEvtCntId,
kCurAudioSecsId,
kTotalAudioSecsId,
kSaveBtnId,
kOpenBtnId,
kFnStringId
};
enum
{
kAmMidiTimerId
};
// Application Id's for the resource based UI elements.
ui::appIdMap_t mapA[] =
{
{ ui::kRootAppId, kPanelDivId, "panelDivId" },
{ kPanelDivId, kQuitBtnId, "quitBtnId" },
{ kPanelDivId, kIoReportBtnId, "ioReportBtnId" },
{ kPanelDivId, kReportBtnId, "reportBtnId" },
{ kPanelDivId, kRecordCheckId, "recordCheckId" },
{ kPanelDivId, kStartBtnId, "startBtnId" },
{ kPanelDivId, kStopBtnId, "stopBtnId" },
{ kPanelDivId, kClearBtnId, "clearBtnId" },
{ kPanelDivId, kMidiThruCheckId, "midiThruCheckId" },
{ kPanelDivId, kCurMidiEvtCntId, "curMidiEvtCntId" },
{ kPanelDivId, kTotalMidiEvtCntId, "totalMidiEvtCntId" },
{ kPanelDivId, kCurAudioSecsId, "curAudioSecsId" },
{ kPanelDivId, kTotalAudioSecsId, "totalAudioSecsId" },
{ kPanelDivId, kSaveBtnId, "saveBtnId" },
{ kPanelDivId, kOpenBtnId, "openBtnId" },
{ kPanelDivId, kFnStringId, "filenameId" },
};
unsigned mapN = sizeof(mapA)/sizeof(mapA[0]);
typedef struct app_str
{
io::handle_t ioH;
const char* record_dir;
const char* record_folder;
const char* record_fn_ext;
char* directory;
midi_record_play::handle_t mrpH;
audio_record_play::handle_t arpH;
} app_t;
rc_t _parseCfg(app_t* app, const object_t* cfg )
{
rc_t rc = kOkRC;
if((rc = cfg->getv(
"record_dir", app->record_dir,
"record_folder", app->record_folder,
"record_fn_ext", app->record_fn_ext)) != kOkRC )
{
rc = cwLogError(kSyntaxErrorRC,"Audio MIDI app configuration parse failed.");
}
// verify that the output directory exists
if((rc = filesys::makeDir(app->record_dir)) != kOkRC )
rc = cwLogError(rc,"Unable to create the base output directory:%s.",cwStringNullGuard(app->record_dir));
return rc;
}
rc_t _free( app_t& app )
{
mem::release(app.directory);
return kOkRC;
}
char* _form_versioned_directory(app_t* app)
{
char* dir = nullptr;
for(unsigned version_numb=0; true; ++version_numb)
{
unsigned n = textLength(app->record_folder) + 32;
char folder[n+1];
snprintf(folder,n,"%s_%i",app->record_folder,version_numb);
if((dir = filesys::makeFn(app->record_dir,folder, NULL, NULL)) == nullptr )
{
cwLogError(kOpFailRC,"Unable to form a versioned directory from:'%s'",cwStringNullGuard(app->record_dir));
return nullptr;
}
if( !filesys::isDir(dir) )
break;
mem::release(dir);
}
return dir;
}
rc_t _on_ui_save( app_t* app )
{
rc_t rc0 = kOkRC;
rc_t rc1 = kOkRC;
char* dir = nullptr;
char* fn = nullptr;
if((dir = _form_versioned_directory(app)) == nullptr )
return cwLogError(kOpFailRC,"Unable to form the versioned directory string.");
if( !filesys::isDir(dir) )
if((rc0 = filesys::makeDir(dir)) != kOkRC )
{
rc0 = cwLogError(rc0,"Attempt to create directory: '%s' failed.", cwStringNullGuard(dir));
goto errLabel;
}
if((fn = filesys::makeFn(dir,"midi","am",nullptr)) != nullptr )
{
if((rc0 = midi_record_play::save( app->mrpH, fn )) != kOkRC )
rc0 = cwLogError(rc0,"MIDI file '%s' save failed.",fn);
mem::release(fn);
}
if((fn = filesys::makeFn(dir,"audio","wav",nullptr)) != nullptr )
{
if((rc1 = audio_record_play::save( app->arpH, fn )) != kOkRC )
rc1 = cwLogError(rc1,"Audio file '%s' save failed.",fn);
mem::release(fn);
}
errLabel:
mem::release(dir);
return rcSelect(rc0,rc1);
}
rc_t _on_ui_open( app_t* app )
{
rc_t rc = kOkRC;
char* fn = nullptr;
if((fn = filesys::makeFn(app->record_dir,"midi","am",app->directory,NULL)) == nullptr )
{
rc = cwLogError(kOpFailRC,"Unable to form the MIDI file name.");
goto errLabel;
}
if((rc = midi_record_play::open(app->mrpH,fn)) != kOkRC )
{
rc = cwLogError(rc,"MIDI file '%s' open failed.",cwStringNullGuard(fn));
goto errLabel;
}
mem::release(fn);
if((fn = filesys::makeFn(app->record_dir,"audio","wav",app->directory,NULL)) == nullptr )
{
rc = cwLogError(kOpFailRC,"Unable to form the Audio file name.");
goto errLabel;
}
if((rc = audio_record_play::open(app->arpH,fn)) != kOkRC )
{
rc = cwLogError(rc,"Audio file '%s' open failed.",cwStringNullGuard(fn));
goto errLabel;
}
errLabel:
mem::release(fn);
io::uiSendValue( app->ioH, kInvalidId, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), midi_record_play::event_index(app->mrpH) );
io::uiSendValue( app->ioH, kInvalidId, uiFindElementUuId(app->ioH,kTotalMidiEvtCntId), midi_record_play::event_count(app->mrpH) );
io::uiSendValue( app->ioH, kInvalidId, uiFindElementUuId(app->ioH,kCurAudioSecsId), audio_record_play::current_loc_seconds(app->arpH) );
io::uiSendValue( app->ioH, kInvalidId, uiFindElementUuId(app->ioH,kTotalAudioSecsId), audio_record_play::duration_seconds(app->arpH) );
return rc;
}
rc_t _on_ui_start( app_t* app )
{
rc_t rc;
if((rc = midi_record_play::start(app->mrpH)) != kOkRC )
{
rc = cwLogError(rc,"MIDI start failed.");
goto errLabel;
}
if((rc = audio_record_play::start(app->arpH)) != kOkRC )
{
rc = cwLogError(rc,"Audio start failed.");
goto errLabel;
}
io::uiSendValue( app->ioH, kInvalidId, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), midi_record_play::event_index(app->mrpH) );
io::uiSendValue( app->ioH, kInvalidId, uiFindElementUuId(app->ioH,kCurAudioSecsId), audio_record_play::current_loc_seconds(app->arpH) );
errLabel:
return rc;
}
rc_t _on_ui_stop( app_t* app )
{
rc_t rc;
if((rc = midi_record_play::stop(app->mrpH)) != kOkRC )
{
rc = cwLogError(rc,"MIDI start failed.");
goto errLabel;
}
if((rc = audio_record_play::stop(app->arpH)) != kOkRC )
{
rc = cwLogError(rc,"Audio start failed.");
goto errLabel;
}
errLabel:
return rc;
}
bool _get_record_state( app_t* app )
{
bool midi_record_fl = midi_record_play::record_state(app->mrpH);
bool audio_record_fl = audio_record_play::record_state(app->arpH);
if( midi_record_fl != audio_record_fl )
{
cwLogError(kInvalidStateRC,"Inconsistent record state.");
}
return midi_record_fl || audio_record_fl;
}
rc_t _set_record_state( app_t* app, bool record_fl )
{
rc_t rc0,rc1;
if((rc0 = midi_record_play::set_record_state(app->mrpH,record_fl)) != kOkRC )
rc0 = cwLogError(rc0,"%s MIDI record state failed.",record_fl ? "Enable" : "Disable" );
if((rc1 = audio_record_play::set_record_state(app->arpH,record_fl)) != kOkRC )
rc1 = cwLogError(rc1,"%s audio record state failed.",record_fl ? "Enable" : "Disable" );
return rcSelect(rc0,rc1);
}
rc_t _set_midi_thru_state( app_t* app, bool thru_fl )
{
rc_t rc;
if((rc = midi_record_play::set_thru_state(app->mrpH,thru_fl)) != kOkRC )
rc = cwLogError(rc,"%s MIDI thru state failed.",thru_fl ? "Enable" : "Disable" );
return rc;
}
rc_t _on_ui_clear( app_t* app )
{
rc_t rc;
if((rc = midi_record_play::clear(app->mrpH)) != kOkRC )
{
rc = cwLogError(rc,"MIDI clear failed.");
goto errLabel;
}
if((rc = audio_record_play::stop(app->arpH)) != kOkRC )
{
rc = cwLogError(rc,"Audio clear failed.");
goto errLabel;
}
io::uiSendValue( app->ioH, kInvalidId, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), midi_record_play::event_index(app->mrpH) );
io::uiSendValue( app->ioH, kInvalidId, uiFindElementUuId(app->ioH,kCurAudioSecsId), audio_record_play::current_loc_seconds(app->arpH) );
errLabel:
return rc;
}
rc_t _onUiInit(app_t* app, const io::ui_msg_t& m )
{
rc_t rc = kOkRC;
return rc;
}
rc_t _onUiValue(app_t* app, const io::ui_msg_t& m )
{
rc_t rc = kOkRC;
switch( m.appId )
{
case kQuitBtnId:
io::stop( app->ioH );
break;
case kIoReportBtnId:
io::report( app->ioH );
break;
case kReportBtnId:
break;
case kSaveBtnId:
_on_ui_save(app);
break;
case kOpenBtnId:
_on_ui_open(app);
break;
case kRecordCheckId:
cwLogInfo("Record:%i",m.value->u.b);
_set_record_state(app, m.value->u.b);
break;
case kMidiThruCheckId:
cwLogInfo("MIDI thru:%i",m.value->u.b);
_set_midi_thru_state(app, m.value->u.b);
break;
case kStartBtnId:
_on_ui_start(app);
break;
case kStopBtnId:
_on_ui_stop(app);
break;
case kClearBtnId:
_on_ui_clear(app);
break;
case kFnStringId:
mem::release(app->directory);
app->directory = mem::duplStr(m.value->u.s);
printf("filename:%s\n",app->directory);
break;
}
return rc;
}
rc_t _onUiEcho(app_t* app, const io::ui_msg_t& m )
{
rc_t rc = kOkRC;
return rc;
}
rc_t _ui_callback( app_t* app, const io::ui_msg_t& m )
{
rc_t rc = kOkRC;
switch( m.opId )
{
case ui::kConnectOpId:
cwLogInfo("UI Connected: wsSessId:%i.",m.wsSessId);
break;
case ui::kDisconnectOpId:
cwLogInfo("UI Disconnected: wsSessId:%i.",m.wsSessId);
break;
case ui::kInitOpId:
_onUiInit(app,m);
break;
case ui::kValueOpId:
_onUiValue( app, m );
break;
case ui::kEchoOpId:
_onUiEcho( app, m );
break;
case ui::kIdleOpId:
break;
case ui::kInvalidOpId:
// fall through
default:
assert(0);
break;
}
return rc;
}
// The main application callback
rc_t _io_callback( void* arg, const io::msg_t* m )
{
rc_t rc = kOkRC;
app_t* app = reinterpret_cast<app_t*>(arg);
if( app->mrpH.isValid() )
{
midi_record_play::exec( app->mrpH, *m );
if( midi_record_play::is_started(app->mrpH) )
io::uiSendValue( app->ioH, kInvalidId, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), midi_record_play::event_index(app->mrpH) );
}
if( app->arpH.isValid() )
{
audio_record_play::exec( app->arpH, *m );
if( audio_record_play::is_started(app->arpH) )
io::uiSendValue( app->ioH, kInvalidId, uiFindElementUuId(app->ioH,kCurAudioSecsId), audio_record_play::current_loc_seconds(app->arpH) );
}
switch( m->tid )
{
case io::kTimerTId:
break;
case io::kSerialTId:
break;
case io::kMidiTId:
break;
case io::kAudioTId:
break;
case io::kAudioMeterTId:
break;
case io::kSockTId:
break;
case io::kWebSockTId:
break;
case io::kUiTId:
rc = _ui_callback(app,m->u.ui);
break;
default:
assert(0);
}
return rc;
}
}
}
cw::rc_t cw::audio_midi_app::main( const object_t* cfg )
{
rc_t rc;
app_t app = {};
// Parse the configuration
if((rc = _parseCfg(&app,cfg)) != kOkRC )
goto errLabel;
// create the io framework instance
if((rc = io::create(app.ioH,cfg,_io_callback,&app,mapA,mapN)) != kOkRC )
return rc;
// create the MIDI record-play object
if((rc = midi_record_play::create(app.mrpH,app.ioH,*cfg)) != kOkRC )
{
rc = cwLogError(rc,"MIDI record-play object create failed.");
goto errLabel;
}
// create the audio record-play object
if((rc = audio_record_play::create(app.arpH,app.ioH,*cfg)) != kOkRC )
{
rc = cwLogError(rc,"Audio record-play object create failed.");
goto errLabel;
}
// start the io framework instance
if((rc = io::start(app.ioH)) != kOkRC )
{
rc = cwLogError(rc,"Audio-MIDI app start failed.");
goto errLabel;
}
// execute the io framework
while( !isShuttingDown(app.ioH))
{
exec(app.ioH);
sleepMs(50);
}
errLabel:
_free(app);
io::destroy(app.ioH);
printf("Audio-MIDI Done.\n");
return rc;
}

13
cwIoAudioMidiApp.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef cwIoAudioMidiApp_h
#define cwIoAudioMidiApp_h
namespace cw
{
namespace audio_midi_app
{
rc_t main( const object_t* cfg );
}
}
#endif

518
cwIoAudioRecordPlay.cpp Normal file
View File

@ -0,0 +1,518 @@
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"
#include "cwObject.h"
#include "cwFileSys.h"
#include "cwFile.h"
#include "cwTime.h"
#include "cwMidiDecls.h"
#include "cwMidi.h"
#include "cwUiDecls.h"
#include "cwIo.h"
#include "cwIoAudioRecordPlay.h"
#include "cwAudioFile.h"
namespace cw
{
namespace audio_record_play
{
typedef io::sample_t sample_t;
typedef struct am_audio_str
{
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 audio_record_play_str
{
io::handle_t ioH;
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 srate;
unsigned curFrameCnt;
unsigned curFrameIdx;
bool recordFl;
bool startedFl;
} audio_record_play_t;
audio_record_play_t* _handleToPtr( handle_t h )
{ return handleToPtr<handle_t,audio_record_play_t>(h); }
void _am_audio_free_list( audio_record_play_t* p )
{
for(am_audio_t* a=p->audioBeg; a!=nullptr; )
{
am_audio_t* tmp = a->link;
mem::release(a);
a = tmp;
}
if( p->audioFile == p->audioBeg )
p->audioFile = nullptr;
else
mem::release(p->audioFile);
p->audioBeg = nullptr;
p->audioEnd = nullptr;
p->curFrameIdx = 0;
p->curFrameCnt = 0;
}
am_audio_t* _am_audio_alloc( unsigned dspFrameCnt, unsigned chCnt )
{
unsigned sample_byte_cnt = chCnt * dspFrameCnt * sizeof(sample_t);
void* vp = mem::allocZ<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;
}
rc_t _destroy( audio_record_play_t* p )
{
_am_audio_free_list(p);
mem::release(p->audioFile);
mem::release(p);
return kOkRC;
}
rc_t _parseCfg(audio_record_play_t* p, const object_t& cfg )
{
rc_t rc = kOkRC;
return rc;
}
am_audio_t* _am_audio_from_sample_index( audio_record_play_t* p, unsigned sample_idx, unsigned& sample_offs_ref )
{
unsigned n = 0;
am_audio_t* a = p->audioBeg;
if( p->audioBeg == nullptr )
return nullptr;
for(; a!=nullptr; a=a->link)
{
// if sample index falls inside this buffer
if( n <= sample_idx && sample_idx < n + a->dspFrameCnt )
{
sample_offs_ref = sample_idx - n; // store the offset into this buffer of 'sample_idx'
return a;
}
n += a->dspFrameCnt;
}
return nullptr;
}
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; chIdx<asrc.iBufChCnt; ++chIdx)
memcpy(a->audioBuf + chIdx*asrc.dspFrameCnt, asrc.iBufArray[chIdx], asrc.dspFrameCnt * sizeof(sample_t));
a->chCnt = asrc.iBufChCnt;
a->dspFrameCnt = asrc.dspFrameCnt;
if( p->audioEnd != nullptr )
p->audioEnd->link = a; // link the new audio record to the end of the audio sample buffer chain
p->audioEnd = a; // make the new audio record the last ele. of the chain
// if this is the first ele of the chain
if( p->audioBeg == nullptr )
{
p->audioBeg = a;
p->srate = asrc.srate;
p->curFrameIdx = 0;
p->curFrameCnt = 0;
}
p->curFrameIdx += asrc.dspFrameCnt;
p->curFrameCnt += asrc.dspFrameCnt;
}
void _audio_play( audio_record_play_t* p, io::audio_msg_t& adst )
{
unsigned adst_idx = 0;
while(adst_idx < adst.dspFrameCnt)
{
am_audio_t* a;
unsigned sample_offs = 0;
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 );
for(unsigned i=0; i<chN; ++i)
memcpy( adst.oBufArray[i] + adst_idx, a->audioBuf + sample_offs, n * sizeof(sample_t));
p->curFrameIdx += n;
adst_idx += n;
}
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));
}
void _audio_through( audio_record_play_t* p, 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( audio_record_play_t* p, const char* fn )
{
rc_t rc = kOkRC;
unsigned frameCnt = 0;
audiofile::handle_t afH;
// if there is no audio to write
if( p->audioBeg == nullptr )
return rc;
// create an audio file
if((rc = audiofile::create( afH, fn, p->srate, 0, p->audioBeg->chCnt )) != kOkRC )
{
cwLogError(rc,"Audio file create failed.");
goto errLabel;
}
// write each buffer
for(am_audio_t* a=p->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, chBufArray )) != kOkRC )
{
cwLogError(rc,"An error occurred while writing and audio buffer.");
goto errLabel;
}
frameCnt += a->dspFrameCnt;
}
errLabel:
// close the audio file
if((rc = audiofile::close(afH)) != kOkRC )
{
cwLogError(rc,"Audio file close failed.");
goto errLabel;
}
double secs = p->srate==0 ? 0 : (double)frameCnt/p->srate;
cwLogInfo("Saved %f seconds of audio to %s.", secs, fn);
return rc;
}
rc_t _audio_write_buffer_times( audio_record_play_t* p, const char* fn )
{
rc_t rc = kOkRC;
am_audio_t* a0 = p->audioBeg;
file::handle_t fH;
// if there is no audio to write
if( p->audioBeg == nullptr )
return rc;
// 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=p->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:
return rc;
}
rc_t _audio_read( audio_record_play_t* p, const char* fn )
{
rc_t rc = kOkRC;
audiofile::handle_t afH;
audiofile::info_t af_info;
am_audio_t* am_audioFile;
if((rc = audiofile::open(afH, fn, &af_info)) != kOkRC )
{
rc = cwLogError(kOpFailRC,"Audio file '%s' open failed.",fn);
goto errLabel;
}
if((am_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] = am_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;
}
_am_audio_free_list(p);
p->audioFile = am_audioFile;
p->audioBeg = am_audioFile;
p->audioEnd = am_audioFile;
p->srate = af_info.srate;
p->curFrameCnt = af_info.frameCnt;
p->curFrameIdx = 0;
double secs = p->srate==0 ? 0 : p->curFrameCnt / p->srate;
cwLogInfo("Audio loaded: srate:%f secs:%f file:%s", p->srate, secs, cwStringNullGuard(fn));
}
errLabel:
if((rc = audiofile::close(afH)) != kOkRC )
{
rc = cwLogError(kOpFailRC,"Audio file close failed.");
goto errLabel;
}
return rc;
}
rc_t _audio_callback( audio_record_play_t* p, io::audio_msg_t& m )
{
rc_t rc = kOkRC;
if( p->startedFl )
{
if( p->recordFl )
{
if( m.iBufChCnt > 0 )
_audio_record(p,m);
}
else
{
if( m.oBufChCnt > 0 )
_audio_play(p,m);
}
}
return rc;
}
}
}
cw::rc_t cw::audio_record_play::create( handle_t& hRef, io::handle_t ioH, const object_t& cfg )
{
rc_t rc;
if((rc = destroy(hRef)) != kOkRC )
return rc;
audio_record_play_t* p = mem::allocZ<audio_record_play_t>();
if((rc = _parseCfg(p,cfg)) != kOkRC )
return rc;
p->ioH = ioH;
hRef.set(p);
return rc;
}
cw::rc_t cw::audio_record_play::destroy( handle_t& hRef )
{
rc_t rc = kOkRC;
if( !hRef.isValid() )
return rc;
audio_record_play_t* p = _handleToPtr(hRef);
if((rc = _destroy(p)) != kOkRC )
return rc;
hRef.clear();
return rc;
}
cw::rc_t cw::audio_record_play::start( handle_t h )
{
rc_t rc = kOkRC;
audio_record_play_t* p = _handleToPtr(h);
p->curFrameIdx = 0;
p->startedFl = true;
if( p->recordFl )
_am_audio_free_list(p);
return rc;
}
cw::rc_t cw::audio_record_play::stop( handle_t h )
{
rc_t rc = kOkRC;
audio_record_play_t* p = _handleToPtr(h);
p->startedFl = false;
return rc;
}
bool cw::audio_record_play::is_started( handle_t h )
{
audio_record_play_t* p = _handleToPtr(h);
return p->startedFl;
}
cw::rc_t cw::audio_record_play::rewind( handle_t h )
{
rc_t rc = kOkRC;
audio_record_play_t* p = _handleToPtr(h);
p->curFrameIdx = 0;
return rc;
}
cw::rc_t cw::audio_record_play::clear( handle_t h )
{
rc_t rc = kOkRC;
audio_record_play_t* p = _handleToPtr(h);
_am_audio_free_list(p);
return rc;
}
cw::rc_t cw::audio_record_play::set_record_state( handle_t h, bool record_fl )
{
rc_t rc = kOkRC;
audio_record_play_t* p = _handleToPtr(h);
p->recordFl = true;
return rc;
}
bool cw::audio_record_play::record_state( handle_t h )
{
rc_t rc = kOkRC;
audio_record_play_t* p = _handleToPtr(h);
return p->recordFl;
return rc;
}
cw::rc_t cw::audio_record_play::save( handle_t h, const char* fn )
{
audio_record_play_t* p = _handleToPtr(h);
return _audio_write_as_wav(p,fn);
}
cw::rc_t cw::audio_record_play::open( handle_t h, const char* fn )
{
rc_t rc = kOkRC;
audio_record_play_t* p = _handleToPtr(h);
_audio_read(p,fn);
return rc;
}
double cw::audio_record_play::duration_seconds( handle_t h )
{
audio_record_play_t* p = _handleToPtr(h);
return p->srate == 0 ? 0.0 : (double)p->curFrameCnt / p->srate;
}
double cw::audio_record_play::current_loc_seconds( handle_t h )
{
audio_record_play_t* p = _handleToPtr(h);
if( p->srate == 0 )
return 0;
return (double)(p->startedFl ? p->curFrameIdx : p->curFrameCnt)/ p->srate;
}
cw::rc_t cw::audio_record_play::exec( handle_t h, const io::msg_t& msg )
{
rc_t rc = kOkRC;
audio_record_play_t* p = _handleToPtr(h);
switch( msg.tid )
{
case io::kAudioTId:
if( msg.u.audio != nullptr )
_audio_callback(p,*msg.u.audio);
break;
default:
rc = kOkRC;
}
return rc;
}

30
cwIoAudioRecordPlay.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef cwIoAudioRecordPlay_h
#define cwIoAudioRecordPlay_h
namespace cw
{
namespace audio_record_play
{
typedef handle< struct audio_record_play_str > handle_t;
rc_t create( handle_t& hRef, io::handle_t ioH, const object_t& cfg );
rc_t destroy( handle_t& hRef );
rc_t start( handle_t h );
rc_t stop( handle_t h );
bool is_started( handle_t h );
rc_t rewind( handle_t h );
rc_t clear( handle_t h );
rc_t set_record_state( handle_t h, bool record_fl );
bool record_state( handle_t h );
rc_t save( handle_t h, const char* fn );
rc_t open( handle_t h, const char* fn );
double duration_seconds( handle_t h );
double current_loc_seconds( handle_t h );
rc_t exec( handle_t h, const io::msg_t& msg );
}
}
#endif

542
cwIoMidiRecordPlay.cpp Normal file
View File

@ -0,0 +1,542 @@
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"
#include "cwObject.h"
#include "cwFileSys.h"
#include "cwFile.h"
#include "cwTime.h"
#include "cwMidiDecls.h"
#include "cwMidi.h"
#include "cwUiDecls.h"
#include "cwIo.h"
#include "cwIoMidiRecordPlay.h"
namespace cw
{
namespace midi_record_play
{
typedef struct am_midi_msg_str
{
unsigned devIdx;
unsigned portIdx;
time::spec_t timestamp;
uint8_t ch;
uint8_t status;
uint8_t d0;
uint8_t d1;
unsigned microsec;
} am_midi_msg_t;
typedef struct midi_record_play_str
{
io::handle_t ioH;
am_midi_msg_t* msgArray;
unsigned msgArrayN;
unsigned msgArrayInIdx;
unsigned msgArrayOutIdx;
unsigned midi_timer_period_micro_sec;
char* midiOutDevLabel;
char* midiOutPortLabel;
unsigned midiOutDevIdx;
unsigned midiOutPortIdx;
bool startedFl;
bool recordFl;
bool thruFl;
time::spec_t play_time;
time::spec_t start_time;
} midi_record_play_t;
enum
{
kMidiRecordPlayTimerId
};
midi_record_play_t* _handleToPtr( handle_t h )
{ return handleToPtr<handle_t,midi_record_play_t>(h); }
rc_t _destroy( midi_record_play_t* p )
{
rc_t rc = kOkRC;
mem::release(p->msgArray);
mem::release(p->midiOutDevLabel);
mem::release(p->midiOutPortLabel);
mem::release(p);
return rc;
}
rc_t _parseCfg(midi_record_play_t* p, const object_t& cfg )
{
rc_t rc = kOkRC;
if((rc = cfg.getv(
"max_midi_msg_count", p->msgArrayN,
"midi_timer_period_micro_sec", p->midi_timer_period_micro_sec,
"midi_out_device", p->midiOutDevLabel,
"midi_out_port", p->midiOutPortLabel)) != kOkRC )
{
rc = cwLogError(kSyntaxErrorRC,"MIDI record play configuration parse failed.");
goto errLabel;
}
// allocate the MIDI msg buffer
p->msgArray = mem::allocZ<am_midi_msg_t>( p->msgArrayN );
p->midiOutDevLabel = mem::duplStr( p->midiOutDevLabel);
p->midiOutPortLabel = mem::duplStr( p->midiOutPortLabel);
errLabel:
return rc;
}
void _set_midi_msg_next_index( midi_record_play_t* p, unsigned next_idx )
{
p->msgArrayInIdx = next_idx;
//io::uiSendValue( p->ioH, kInvalidId, uiFindElementUuId(p->ioH,kMsgCntId), p->midiMsgArrayInIdx );
}
void _set_midi_msg_next_play_index(midi_record_play_t* p, unsigned next_idx)
{
p->msgArrayOutIdx = next_idx;
}
rc_t _midi_read( midi_record_play_t* p, const char* fn )
{
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 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( n > p->msgArrayN )
{
cwLogWarning("The count of message in Audio-MIDI file '%s' reduced from %i to %i.", fn, n, p->msgArrayN );
n = p->msgArrayN;
}
if((rc = file::read(fH,p->msgArray,n*sizeof(am_midi_msg_t))) != kOkRC )
{
rc = cwLogError(kReadFailRC,"Data read failed on Audio-MIDI file: '%s'.", fn );
goto errLabel;
}
_set_midi_msg_next_index(p, n );
cwLogInfo("Read %i from '%s'.",n,fn);
errLabel:
return rc;
}
rc_t _midi_write( midi_record_play_t* p, const char* fn )
{
rc_t rc = kOkRC;
file::handle_t fH;
if( p->msgArrayInIdx == 0 )
{
cwLogWarning("Nothing to write.");
return rc;
}
// open the file
if((rc = file::open(fH,fn,file::kWriteFl)) != kOkRC )
{
rc = cwLogError(kOpenFailRC,"Unable to create the file '%s'.",cwStringNullGuard(fn));
goto errLabel;
}
// write the file header
if((rc = write(fH,p->msgArrayInIdx)) != kOkRC )
{
rc = cwLogError(kWriteFailRC,"Header write to '%s' failed.",cwStringNullGuard(fn));
goto errLabel;
}
// write the file data
if((rc = write(fH,p->msgArray,sizeof(am_midi_msg_t)*p->msgArrayInIdx)) != kOkRC )
{
rc = cwLogError(kWriteFailRC,"Data write to '%s' failed.",cwStringNullGuard(fn));
goto errLabel;
}
// update UI msg count
//io::uiSendValue( p->ioH, kInvalidId, uiFindElementUuId(p->ioH,kMsgCntId), p->msgArrayInIdx );
file::close(fH);
cwLogInfo("Saved %i events to '%s'.", p->msgArrayInIdx, fn );
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 );
}
void _report_midi( midi_record_play_t* p )
{
for(unsigned i=0; i<p->msgArrayInIdx; ++i)
{
am_midi_msg_t* mm = p->msgArray + i;
_print_midi_msg(mm);
}
}
rc_t _stop( midi_record_play_t* p )
{
rc_t rc = kOkRC;
p->startedFl = false;
time::spec_t t1;
time::get(t1);
if( p->recordFl )
{
// set the 'microsec' value for each MIDI msg
for(unsigned i=0; i<p->msgArrayInIdx; ++i)
{
p->msgArray[i].microsec = time::elapsedMicros(p->msgArray[0].timestamp,p->msgArray[i].timestamp);
}
cwLogInfo("MIDI messages recorded: %i",p->msgArrayInIdx );
}
else
{
io::timerStop( p->ioH, io::timerIdToIndex(p->ioH, kMidiRecordPlayTimerId) );
// TODO: should work for all channels
// all notes off
io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, midi::kCtlMdId, 123, 0 );
io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, midi::kCtlMdId, midi::kSustainCtlMdId, 0 );
io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, midi::kCtlMdId, midi::kSostenutoCtlMdId, 0 );
// soft pedal
io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, midi::kCtlMdId, 67, 0 );
}
cwLogInfo("Runtime: %5.2f seconds.", time::elapsedMs(p->start_time,t1)/1000.0 );
return rc;
}
rc_t _midi_callback( midi_record_play_t* p, const io::midi_msg_t& m )
{
rc_t rc = kOkRC;
const midi::packet_t* pkt = m.pkt;
// for each midi msg
for(unsigned j=0; j<pkt->msgCnt; ++j)
{
// if this is a sys-ex msg
if( pkt->msgArray == NULL )
{
// this is a sys ex msg use: pkt->sysExMsg[j]
}
else // this is a triple
{
if( p->recordFl && p->startedFl )
{
// verify that space exists in the record buffer
if( p->msgArrayInIdx >= p->msgArrayN )
{
_stop(p);
rc = cwLogError(kBufTooSmallRC,"MIDI message record buffer is full. % messages.",p->msgArrayN);
goto errLabel;
}
else
{
// copy the msg into the record buffer
am_midi_msg_t* am = p->msgArray + p->msgArrayInIdx;
midi::msg_t* mm = pkt->msgArray + j;
if( midi::isChStatus(mm->status) )
{
am->devIdx = pkt->devIdx;
am->portIdx = pkt->portIdx;
am->timestamp = mm->timeStamp;
am->ch = mm->status & 0x0f;
am->status = mm->status & 0xf0;
am->d0 = mm->d0;
am->d1 = mm->d1;
//printf("st:0x%x ch:%i d0:0x%x d1:0x%x\n",am->status,am->ch,am->d0,am->d1);
p->msgArrayInIdx += 1;
if( p->thruFl )
io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, am->status + am->ch, am->d0, am->d1 );
// send msg count
//io::uiSendValue( p->ioH, kInvalidId, uiFindElementUuId(p->ioH,kMsgCntId), p->msgArrayInIdx );
}
}
}
}
/*
if( pkt->msgArray == NULL )
printf("io midi cb: 0x%x ",pkt->sysExMsg[j]);
else
{
if( !_midi_filter(pkt->msgArray + j) )
printf("io midi cb: %ld %ld 0x%x %i %i\n", pkt->msgArray[j].timeStamp.tv_sec, pkt->msgArray[j].timeStamp.tv_nsec, pkt->msgArray[j].status, pkt->msgArray[j].d0, pkt->msgArray[j].d1);
}
*/
}
errLabel:
return rc;
}
rc_t _timer_callback(midi_record_play_t* p, io::timer_msg_t& m)
{
rc_t rc = kOkRC;
// if the MIDI player is started and in 'play' mode and msg remain to be played
if( p->startedFl && (p->recordFl==false) && (p->msgArrayOutIdx < p->msgArrayInIdx))
{
time::spec_t t;
time::get(t);
unsigned cur_time_us = time::elapsedMicros(p->play_time,t);
while( p->msgArray[ p->msgArrayOutIdx ].microsec <= cur_time_us )
{
am_midi_msg_t* mm = p->msgArray + p->msgArrayOutIdx;
//_print_midi_msg(mm);
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 );
// if all MIDI messages have been played
if( p->msgArrayOutIdx >= p->msgArrayInIdx )
{
_stop(p);
break;
}
}
}
return rc;
}
}
}
cw::rc_t cw::midi_record_play::create( handle_t& hRef, io::handle_t ioH, const object_t& cfg )
{
midi_record_play_t* p = nullptr;
rc_t rc;
if((rc = destroy(hRef)) != kOkRC )
return rc;
p = mem::allocZ<midi_record_play_t>();
if((rc = _parseCfg(p,cfg)) != kOkRC )
goto errLabel;
p->ioH = ioH;
if((p->midiOutDevIdx = io::midiDeviceIndex(p->ioH,p->midiOutDevLabel)) == kInvalidIdx )
{
rc = cwLogError(kInvalidArgRC,"The MIDI output device: '%s' was not found.", cwStringNullGuard(p->midiOutDevLabel) );
goto errLabel;
}
if((p->midiOutPortIdx = io::midiDevicePortIndex(p->ioH,p->midiOutDevIdx,false,p->midiOutPortLabel)) == kInvalidIdx )
{
rc = cwLogError(kInvalidArgRC,"The MIDI output port: '%s' was not found.", cwStringNullGuard(p->midiOutPortLabel) );
goto errLabel;
}
// create the MIDI playback timer
if((rc = timerCreate( p->ioH, "midi_record_play_timer", kMidiRecordPlayTimerId, p->midi_timer_period_micro_sec)) != kOkRC )
{
cwLogError(rc,"Audio-MIDI timer create failed.");
goto errLabel;
}
errLabel:
if( rc == kOkRC )
hRef.set(p);
else
_destroy(p);
return rc;
}
cw::rc_t cw::midi_record_play::destroy( handle_t& hRef )
{
rc_t rc = kOkRC;
if( !hRef.isValid() )
return kOkRC;
midi_record_play_t* p = _handleToPtr(hRef);
if((rc = _destroy(p)) != kOkRC )
return rc;
hRef.clear();
return rc;
}
cw::rc_t cw::midi_record_play::start( handle_t h )
{
midi_record_play_t* p = _handleToPtr(h);
p->startedFl = true;
time::get(p->start_time);
if( p->recordFl )
{
_set_midi_msg_next_index(p, 0 );
}
else
{
_set_midi_msg_next_play_index(p,0);
io::timerStart( p->ioH, io::timerIdToIndex(p->ioH, kMidiRecordPlayTimerId) );
time::get(p->play_time);
}
return kOkRC;
}
cw::rc_t cw::midi_record_play::stop( handle_t h )
{
midi_record_play_t* p = _handleToPtr(h);
return _stop(p);
}
bool cw::midi_record_play::is_started( handle_t h )
{
midi_record_play_t* p = _handleToPtr(h);
return p->startedFl;
}
cw::rc_t cw::midi_record_play::clear( handle_t h )
{
rc_t rc = kOkRC;
midi_record_play_t* p = _handleToPtr(h);
_set_midi_msg_next_index(p,0);
return rc;
}
cw::rc_t cw::midi_record_play::set_record_state( handle_t h, bool record_fl )
{
rc_t rc = kOkRC;
midi_record_play_t* p = _handleToPtr(h);
p->recordFl = record_fl;
return rc;
}
bool cw::midi_record_play::record_state( handle_t h )
{
midi_record_play_t* p = _handleToPtr(h);
return p->recordFl;
}
cw::rc_t cw::midi_record_play::set_thru_state( handle_t h, bool thru_fl )
{
rc_t rc = kOkRC;
midi_record_play_t* p = _handleToPtr(h);
p->thruFl = thru_fl;
return rc;
}
bool cw::midi_record_play::thru_state( handle_t h )
{
midi_record_play_t* p = _handleToPtr(h);
return p->thruFl;
}
cw::rc_t cw::midi_record_play::save( handle_t h, const char* fn )
{
midi_record_play_t* p = _handleToPtr(h);
return _midi_write(p,fn);
}
cw::rc_t cw::midi_record_play::open( handle_t h, const char* fn )
{
midi_record_play_t* p = _handleToPtr(h);
return _midi_read(p,fn);
}
unsigned cw::midi_record_play::event_count( handle_t h )
{
midi_record_play_t* p = _handleToPtr(h);
return p->msgArrayInIdx;
}
unsigned cw::midi_record_play::event_index( handle_t h )
{
midi_record_play_t* p = _handleToPtr(h);
return p->recordFl ? p->msgArrayInIdx : p->msgArrayOutIdx;
}
cw::rc_t cw::midi_record_play::exec( handle_t h, const io::msg_t& m )
{
rc_t rc = kOkRC;
midi_record_play_t* p = _handleToPtr(h);
switch( m.tid )
{
case io::kTimerTId:
if( m.u.timer != nullptr )
rc = _timer_callback(p,*m.u.timer);
break;
case io::kMidiTId:
if( m.u.midi != nullptr )
_midi_callback(p,*m.u.midi);
break;
default:
rc = kOkRC;
}
return rc;
}

37
cwIoMidiRecordPlay.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef cwIoMidiRecordPlay_h
#define cwIoMidiRecordPlay_h
namespace cw
{
namespace midi_record_play
{
typedef handle< struct midi_record_play_str > handle_t;
rc_t create( handle_t& hRef, io::handle_t ioH, const object_t& cfg );
rc_t destroy( handle_t& hRef );
rc_t start( handle_t h );
rc_t stop( handle_t h );
bool is_started( handle_t h );
rc_t rewind( handle_t h );
rc_t clear( handle_t h );
rc_t set_record_state( handle_t h, bool record_fl );
bool record_state( handle_t h );
rc_t set_thru_state( handle_t h, bool record_thru );
bool thru_state( handle_t h );
rc_t save( handle_t h, const char* fn );
rc_t open( handle_t h, const char* fn );
unsigned event_count( handle_t h );
unsigned event_index( handle_t h );
rc_t exec( handle_t h, const io::msg_t& msg );
}
}
#endif

View File

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<title>UI Test App</title>
<title>Audio MIDI App</title>
<script type="text/javascript" src="js/ui.js"></script>
<link href="css/ui.css" rel="stylesheet">
@ -14,7 +14,7 @@
<body>
<div id="appTitleDiv" class="uiRow">
<p id="appTitleId">UI Test:</p>
<p id="appTitleId">Audio MIDI:</p>
<p id="connectTitleId">Disconnected</p>
</div>

View File

@ -656,6 +656,12 @@ function ui_create_number_display( parent_ele, d )
}
function ui_create_text_display( parent_ele, d )
{
return ui_create_ctl( parent_ele, "label", d.title, d, "uiTextDisp" );
}
function ui_set_progress( ele_id, value )
{
var ele = dom_id_to_ele(ele_id);
@ -687,6 +693,7 @@ function ui_create_progress( parent_ele, d )
function ui_set_value( d )
{
//console.log(d)
var eleId = d.uuId.toString()
var ele = dom_id_to_ele(eleId)
@ -813,6 +820,10 @@ function ui_create( d )
ele = ui_create_number_display( parent_ele, d );
break;
case "text_disp":
ele = ui_create_text_display( parent_ele, d );
break;
case "progress":
ele = ui_create_progress( parent_ele, d );
break;

View File

@ -20,14 +20,25 @@
button:{ name: startBtnId, title:"Start" },
button:{ name: stopBtnId, title:"Stop" },
button:{ name: clearBtnId, title:"Clear" },
numb_disp: { name: msgCntId, title:"Count" },
},
row: {
check:{ name: midiThruCheckId, title:"MIDI Thru" },
numb_disp: { name: curMidiEvtCntId, title:"Current" },
numb_disp: { name: totalMidiEvtCntId, title:"Total" },
},
row: {
check:{ name: audioThroughCheckId, title:"Audio Thru" },
numb_disp: { name: curAudioSecsId, title:"Current:", decpl:1 },
numb_disp: { name: totalAudioSecsId, title:"Total:", decpl:1 },
},
row: {
string:{ name: filenameId, title:"File Name:", value:"record.am" },
string:{ name: filenameId, title:"File Name:", value:"record" },
button:{ name: openBtnId, title:"Open" },
button:{ name: saveBtnId, title:"Save" },
}
},
}