From ddad22cde0a9a47859b12d5cdc97c93bd92a998a Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 10 May 2021 08:37:29 -0400 Subject: [PATCH] cwIoAudioMidiApp.h/cpp,cwIoAudioRecordPlay.h/cpp,cwIoMidiRecordPlay.h/cpp: Initial commit. --- Makefile.am | 5 + cwIoAudioMidiApp.cpp | 555 +++++++++++++++++++++++++++++++++++++ cwIoAudioMidiApp.h | 13 + cwIoAudioRecordPlay.cpp | 518 ++++++++++++++++++++++++++++++++++ cwIoAudioRecordPlay.h | 30 ++ cwIoMidiRecordPlay.cpp | 542 ++++++++++++++++++++++++++++++++++++ cwIoMidiRecordPlay.h | 37 +++ html/audio_midi/index.html | 4 +- html/audio_midi/js/ui.js | 11 + html/audio_midi/ui.cfg | 19 +- 10 files changed, 1728 insertions(+), 6 deletions(-) create mode 100644 cwIoAudioMidiApp.cpp create mode 100644 cwIoAudioMidiApp.h create mode 100644 cwIoAudioRecordPlay.cpp create mode 100644 cwIoAudioRecordPlay.h create mode 100644 cwIoMidiRecordPlay.cpp create mode 100644 cwIoMidiRecordPlay.h diff --git a/Makefile.am b/Makefile.am index 06d538e..b1816fe 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/cwIoAudioMidiApp.cpp b/cwIoAudioMidiApp.cpp new file mode 100644 index 0000000..abb521a --- /dev/null +++ b/cwIoAudioMidiApp.cpp @@ -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(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; + +} diff --git a/cwIoAudioMidiApp.h b/cwIoAudioMidiApp.h new file mode 100644 index 0000000..6c60acf --- /dev/null +++ b/cwIoAudioMidiApp.h @@ -0,0 +1,13 @@ +#ifndef cwIoAudioMidiApp_h +#define cwIoAudioMidiApp_h + +namespace cw +{ + namespace audio_midi_app + { + rc_t main( const object_t* cfg ); + } +} + + +#endif diff --git a/cwIoAudioRecordPlay.cpp b/cwIoAudioRecordPlay.cpp new file mode 100644 index 0000000..19b6d75 --- /dev/null +++ b/cwIoAudioRecordPlay.cpp @@ -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(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( 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; chIdxaudioBuf + 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; iaudioBuf + sample_offs, n * sizeof(sample_t)); + + p->curFrameIdx += n; + adst_idx += n; + } + + if( adst_idx < adst.dspFrameCnt ) + for(unsigned i=0; iaudioBeg == 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; ichCnt; ++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; iaudioBuf + (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(); + + 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; +} diff --git a/cwIoAudioRecordPlay.h b/cwIoAudioRecordPlay.h new file mode 100644 index 0000000..13273bb --- /dev/null +++ b/cwIoAudioRecordPlay.h @@ -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 diff --git a/cwIoMidiRecordPlay.cpp b/cwIoMidiRecordPlay.cpp new file mode 100644 index 0000000..1fc60cb --- /dev/null +++ b/cwIoMidiRecordPlay.cpp @@ -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(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( 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; imsgArrayInIdx; ++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; imsgArrayInIdx; ++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; jmsgCnt; ++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(); + + 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; +} + diff --git a/cwIoMidiRecordPlay.h b/cwIoMidiRecordPlay.h new file mode 100644 index 0000000..110acab --- /dev/null +++ b/cwIoMidiRecordPlay.h @@ -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 diff --git a/html/audio_midi/index.html b/html/audio_midi/index.html index 1821edd..bf8dfe8 100644 --- a/html/audio_midi/index.html +++ b/html/audio_midi/index.html @@ -2,7 +2,7 @@ - UI Test App + Audio MIDI App @@ -14,7 +14,7 @@
-

UI Test:

+

Audio MIDI:

Disconnected

diff --git a/html/audio_midi/js/ui.js b/html/audio_midi/js/ui.js index 4f0b125..f47dae2 100644 --- a/html/audio_midi/js/ui.js +++ b/html/audio_midi/js/ui.js @@ -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; diff --git a/html/audio_midi/ui.cfg b/html/audio_midi/ui.cfg index 2660f10..5a64ea8 100644 --- a/html/audio_midi/ui.cfg +++ b/html/audio_midi/ui.cfg @@ -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" }, - } - + }, } \ No newline at end of file