From ec6a55d07ce659f7879fe154de5ad48d50075e60 Mon Sep 17 00:00:00 2001 From: kevin Date: Fri, 17 Mar 2023 18:17:22 -0400 Subject: [PATCH] cwVelTableTuner.h/cpp : Initial commit. --- cwVelTableTuner.cpp | 1078 +++++++++++++++++++++++++++++++++++++++++++ cwVelTableTuner.h | 83 ++++ 2 files changed, 1161 insertions(+) create mode 100644 cwVelTableTuner.cpp create mode 100644 cwVelTableTuner.h diff --git a/cwVelTableTuner.cpp b/cwVelTableTuner.cpp new file mode 100644 index 0000000..c871cea --- /dev/null +++ b/cwVelTableTuner.cpp @@ -0,0 +1,1078 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwText.h" +#include "cwObject.h" +#include "cwTime.h" +#include "cwPresetSel.h" +#include "cwFile.h" +#include "cwFileSys.h" +#include "cwMidi.h" +#include "cwIo.h" +#include "cwIoMidiRecordPlay.h" +#include "cwVelTableTuner.h" +#include "cwNumericConvert.h" +namespace cw +{ + namespace vtbl + { + ui::appIdMap_t mapA[] = + { + { kInvalidId, kVtDeviceSelectId, "vtDeviceSelectId" }, + { kVtDeviceSelectId, kVtPianoDevId, "vtPianoDevId" }, + { kVtDeviceSelectId, kVtSamplerDevId, "vtSamplerDevId" }, + { kInvalidId, kVtTableSelectId, "vtTableSelectId" }, + { kInvalidId, kVtPlayVelSeqBtnId, "vtPlayVelSeqBtnId" }, + { kInvalidId, kVtPitchId, "vtPitchId" }, + { kInvalidId, kVtPlayPitchSeqBtnId, "vtPlayPitchSeqBtnId" }, + { kInvalidId, kVtVelocityId, "vtVelocityId" }, + { kInvalidId, kVtMinPitchId, "vtMinPitchId" }, + { kInvalidId, kVtMaxPitchId, "vtMaxPitchId" }, + { kInvalidId, kVtIncPitchId, "vtIncPitchId" }, + + { kInvalidId, kVtApplyBtnId, "vtApplyBtnId" }, + { kInvalidId, kVtSaveBtnId, "vtSaveBtnId" }, + { kInvalidId, kVtDuplicateBtnId, "vtDuplicateBtnId" }, + { kInvalidId, kVtNameStrId, "vtNameStrId" }, + { kInvalidId, kVtStatusId, "vtStatusId" }, + + { kInvalidId, kVtEntry0, "vtEntry0" }, + { kInvalidId, kVtEntry1, "vtEntry1" }, + { kInvalidId, kVtEntry2, "vtEntry2" }, + { kInvalidId, kVtEntry3, "vtEntry3" }, + { kInvalidId, kVtEntry4, "vtEntry4" }, + { kInvalidId, kVtEntry5, "vtEntry5" }, + { kInvalidId, kVtEntry6, "vtEntry6" }, + { kInvalidId, kVtEntry7, "vtEntry7" }, + { kInvalidId, kVtEntry8, "vtEntry8" }, + { kInvalidId, kVtEntry9, "vtEntry9" }, + { kInvalidId, kVtEntry10, "vtEntry10" }, + { kInvalidId, kVtEntry11, "vtEntry11" }, + { kInvalidId, kVtEntry12, "vtEntry12" }, + { kInvalidId, kVtEntry13, "vtEntry13" }, + { kInvalidId, kVtEntry14, "vtEntry14" }, + { kInvalidId, kVtEntry15, "vtEntry15" }, + { kInvalidId, kVtEntry16, "vtEntry16" }, + { kInvalidId, kVtEntry17, "vtEntry17" }, + { kInvalidId, kVtEntry18, "vtEntry18" }, + { kInvalidId, kVtEntry19, "vtEntry19" }, + { kInvalidId, kVtEntry20, "vtEntry20" }, + { kInvalidId, kVtEntry21, "vtEntry21" }, + { kInvalidId, kVtEntry22, "vtEntry22" }, + { kInvalidId, kVtEntry23, "vtEntry23" }, + { kInvalidId, kVtEntry24, "vtEntry24" }, + }; + + typedef struct tbl_str + { + bool enableFl; + uint8_t* tableA; + unsigned tableN; + char* name; + unsigned mrpDevIdx; + unsigned appId; // id associated with UI select option for this table + struct tbl_str* link; + } tbl_t; + + enum { + kStoppedStateId, + kNoteOnStateId, // start a new now at 'nextTime' + kNoteOffStateId, // turn off the current note on 'nextTime' + }; + + enum { + kVelSeqModeId, // sequence through current vel table on 'vseqPitch' + kPitchSeqModeId // sequence through min-maxMIdiPitch on 'pseqVelocity' + }; + + typedef struct vtbl_str + { + io::handle_t ioH; // + midi_record_play::handle_t mrpH; // + + object_t* cfg; // + char* cfg_fname; // + char* cfg_backup_dir; // + + tbl_t* tableL; // array of tables + unsigned nextTableAppId; + + tbl_t curTable; // current table being edited + + bool initUiFl; // True if the UI has been initialized (UI initialization happens once at startup) + bool waitForStopFl; // The player was requested to stop but is waiting for the current note cycle to end + unsigned state; // player state id (See ???StateId above) + unsigned mode; // sequence across pitch or velocity (See k???ModeId) + time::spec_t nextTime; // next note on/off time + unsigned nextPitch; // next seq pitch (during vel sequencing) + unsigned nextVelIdx; // next vel table index (during pitch sequencing) + + unsigned noteOnDurMs; // Note on duration + unsigned noteOffDurMs; // Note off duration + + unsigned vseqPitch; // Sequence across this pitch during velocity sequencing + + unsigned pseqVelocity; // Sequence from min to max pitch on this velocity + unsigned minPseqPitch; // during pitch sequencing + unsigned maxPseqPitch; + unsigned incPseqPitch; + + + char* duplicateName; // table name to use with the duplicate + + } vtbl_t; + + typedef struct dev_map_str + { + const char* label; // device name (piano, sampler) + unsigned appId; // device UI app id + unsigned mrpDevIdx; // device MRP device index + } dev_map_t; + + dev_map_t _devMapA[] = { + { "piano", kVtPianoDevId, midi_record_play::kPiano_MRP_DevIdx }, + { "sampler", kVtSamplerDevId, midi_record_play::kSampler_MRP_DevIdx } + }; + + vtbl_t* _handleToPtr( handle_t h ) + { return handleToPtr(h); } + + unsigned _dev_map_count() { return sizeof(_devMapA)/sizeof(_devMapA[0]); } + + const dev_map_t* _dev_map_from_label( const char* label ) + { + for(unsigned i=0; i<_dev_map_count(); ++i) + if( strcmp(_devMapA[i].label,label) == 0 ) + return _devMapA + i; + + return nullptr; + } + + const dev_map_t* _dev_map_from_appId( unsigned appId ) + { + for(unsigned i=0; i<_dev_map_count(); ++i) + if( _devMapA[i].appId == appId ) + return _devMapA + i; + + return nullptr; + } + + const dev_map_t* _dev_map_from_mrpDevIdx( unsigned mrpDevIdx ) + { + for(unsigned i=0; i<_dev_map_count(); ++i) + if( _devMapA[i].mrpDevIdx == mrpDevIdx ) + return _devMapA + i; + + return nullptr; + } + + rc_t _destroy_tbl( tbl_t* t ) + { + mem::release(t->tableA); + mem::release(t->name); + mem::release(t); + return kOkRC; + } + + rc_t _destroy( vtbl_t* p ) + { + rc_t rc = kOkRC; + + tbl_t* t = p->tableL; + while( t!=nullptr ) + { + tbl_t* t0 = t->link; + _destroy_tbl(t); + t = t0; + } + + mem::release(p->cfg_fname); + mem::release(p->cfg_backup_dir); + + mem::release(p->curTable.tableA); + mem::release(p->curTable.name); + + mem::release(p->duplicateName); + + mem::release(p); + return rc; + } + + rc_t _set_statusv( vtbl_t* p, rc_t rc, const char* fmt, va_list vl ) + { + const int sN = 128; + char s[sN]; + vsnprintf(s,sN,fmt,vl); + uiSendValue( p->ioH, uiFindElementUuId(p->ioH,kVtStatusId), s ); + + if( rc != kOkRC ) + rc = cwLogError(rc,s); + + return rc; + } + + rc_t _set_status( vtbl_t* p, rc_t rc, const char* fmt, ... ) + { + va_list vl; + va_start(vl,fmt); + rc = _set_statusv(p, rc, fmt, vl ); + va_end(vl); + return rc; + } + + unsigned _table_count( vtbl_t* p ) + { + unsigned n = 0; + for(tbl_t* t=p->tableL; t!=nullptr; t=t->link) + ++n; + + return n; + } + + const tbl_t* _table_from_name( vtbl_t* p, const char* name ) + { + for(tbl_t* t=p->tableL; t!=nullptr; t=t->link) + if( textCompare(t->name,name) == 0 ) + return t; + return nullptr; + } + + rc_t _insert_menu_option( vtbl_t* p, unsigned appId, const char* name ) + { + rc_t rc = kOkRC; + unsigned uuId; + unsigned selectUuId = io::uiFindElementUuId( p->ioH, kVtTableSelectId ); + + cwAssert( selectUuId != kInvalidId ); + + if((rc = uiCreateOption( p->ioH, uuId, selectUuId, nullptr, appId, kInvalidId, "optClass", name )) != kOkRC ) + { + cwLogError(rc,"Create table selection option failed for '%s'.",cwStringNullGuard(name)); + goto errLabel; + } + + errLabel: + return rc; + } + + rc_t _link_in_table( vtbl_t* p, tbl_t* t ) + { + rc_t rc = kOkRC; + + t->appId = p->nextTableAppId; + + // insert the table in the table selection menu + if((rc = _insert_menu_option( p, t->appId, t->name )) != kOkRC ) + goto errLabel; + + + // link in the new table + if( p->tableL == nullptr ) + p->tableL = t; + else + { + t->link = p->tableL; + p->tableL = t; + } + + + p->nextTableAppId += 1; + + + errLabel: + if( rc != kOkRC ) + rc = _set_status(p,rc,"Table insertion failed for '%s'.",cwStringNullGuard(t->name)); + + return rc; + } + + rc_t _parseCfg( vtbl_t* p, const object_t* cfg ) + { + rc_t rc; + const object_t* tables_node = nullptr; + tbl_t* t = nullptr; + + if((rc = cfg->getv("note_on_ms",p->noteOnDurMs, + "note_off_ms",p->noteOffDurMs, + "vseq_pitch",p->vseqPitch, + "pseq_velocity",p->pseqVelocity, + "min_pitch",p->minPseqPitch, + "max_pitch",p->maxPseqPitch, + "incr_pitch",p->incPseqPitch, + "tables",tables_node)) != kOkRC ) + { + rc = cwLogError(rc,"Velocity table mgr. cfg. file parsing error."); + goto errLabel; + } + + // for each table + for(unsigned i=0; ichild_count(); ++i) + { + const object_t* tbl_hdr = nullptr; + const char* tbl_name = nullptr; + const char* tbl_device = nullptr; + bool tbl_enableFl = false; + const object_t* tbl_node = nullptr; + const dev_map_t*devMap = nullptr; + + // get the tbl hdr node + if((tbl_hdr = tables_node->child_ele(i)) == nullptr ) + { + rc = cwLogError(kSyntaxErrorRC,"The velocity table at index %i was not found.",i); + goto errLabel; + } + + // parse the table record + if((rc = tbl_hdr->getv("name", tbl_name, + "device", tbl_device, + "enableFl",tbl_enableFl, + "table", tbl_node)) != kOkRC ) + { + rc = cwLogError(rc,"Velocity table cfg. file parsing error."); + goto errLabel; + } + + if((devMap = _dev_map_from_label(tbl_device)) == nullptr ) + { + cwLogError(kInvalidArgRC,"The MIDI device '%s' is not valid.", cwStringNullGuard(tbl_device) ); + goto errLabel; + } + + t = mem::allocZ(); + t->name = mem::duplStr(tbl_name); + t->mrpDevIdx= devMap->mrpDevIdx; + t->enableFl = tbl_enableFl; + t->tableN = tbl_node->child_count(); + t->tableA = mem::allocZ( t->tableN ); + + // parse the table velocity values + for(unsigned j=0; jchild_count(); ++j) + { + uint8_t vel; + if((rc = tbl_node->child_ele(j)->value(vel)) != kOkRC ) + { + rc = cwLogError(rc,"Parsing failed on velocity table '%s' index '%i'.", cwStringNullGuard(t->name), j); + _destroy_tbl(t); + goto errLabel; + } + + t->tableA[j] = vel; + } + + // link in the new table and assign it an app id + _link_in_table(p, t ); + + + // prevent the list from being corrupted should the + // parsing of the next table fail + t = nullptr; + } + + errLabel: + if( rc != kOkRC ) + mem::release(t); + return rc; + } + + rc_t _backup( vtbl_t* p ) + { + rc_t rc; + + if((rc = filesys::makeDir(p->cfg_backup_dir)) != kOkRC ) + { + rc = cwLogError(rc,"The vel.table tuner backup directory '%s' could not be created.",cwStringNullGuard(p->cfg_backup_dir)); + goto errLabel; + } + + if((rc = file::backup(p->cfg_fname, p->cfg_backup_dir)) != kOkRC ) + { + rc = cwLogError(rc,"The vel.table tuner file backup failed.",cwStringNullGuard(p->cfg_backup_dir)); + goto errLabel; + } + + errLabel: + return rc; + } + + rc_t _save( vtbl_t* p ) + { + rc_t rc; + object_t* cfg = nullptr; + object_t* tables_node = nullptr; + + // backup the current vel table tuner file + if((rc = _backup(p)) != kOkRC ) + goto errLabel; + + cfg = newDictObject(nullptr); + + newPairObject("note_on_ms", p->noteOnDurMs, cfg); + newPairObject("note_off_ms", p->noteOffDurMs, cfg); + newPairObject("vseq_pitch", p->vseqPitch, cfg); + newPairObject("pseq_velocity",p->pseqVelocity, cfg); + newPairObject("min_pitch", p->minPseqPitch, cfg); + newPairObject("max_pitch", p->maxPseqPitch, cfg); + newPairObject("incr_pitch", p->incPseqPitch, cfg); + tables_node = newPairObject("tables", newListObject(cfg), cfg ); + + for(tbl_t* t=p->tableL; t!=nullptr; t=t->link) + { + const dev_map_t* devMap = _dev_map_from_mrpDevIdx( t->mrpDevIdx ); + cwAssert( devMap != nullptr ); + + object_t* tbl = newDictObject(tables_node); + + tables_node->append_child(tbl); + + newPairObject("name", t->name, tbl); + newPairObject("device", devMap->label,tbl); + newPairObject("enableFl", t->enableFl, tbl); + + object_t* table_node = newPairObject("table", newListObject(nullptr), tbl); + + for(unsigned i=0; itableN; ++i) + newObject( (unsigned)t->tableA[i], table_node ); + } + + if((rc = objectToFile(p->cfg_fname,cfg)) != kOkRC ) + { + rc = cwLogError(rc,"Velocity table tuner write failed."); + goto errLabel; + } + + errLabel: + if(rc != kOkRC ) + rc = cwLogError(rc,"The velocity table tuner save failed."); + + if( cfg != nullptr ) + cfg->free(); + + return rc; + } + + void _do_play(vtbl_t* p, unsigned mode ) + { + if( p->state == kStoppedStateId ) + { + p->mode = mode; + p->state = kNoteOnStateId; + p->nextPitch = p->minPseqPitch; + p->nextVelIdx = 0; + p->waitForStopFl = false; + + time::get(p->nextTime); + time::advanceMs(p->nextTime,500); // turn on note in 500ms + } + else + { + p->waitForStopFl = true; + } + } + + cw::rc_t _play_vel_sequence(vtbl_t* p) + { + rc_t rc = kOkRC; + _do_play(p,kVelSeqModeId); + return rc; + } + + cw::rc_t _play_pitch_sequence(vtbl_t* p) + { + rc_t rc = kOkRC; + _do_play(p,kPitchSeqModeId); + return rc; + } + + cw::rc_t _apply(vtbl_t* p ) + { + rc_t rc = kOkRC; + + const dev_map_t* dm; + if((dm = _dev_map_from_mrpDevIdx(p->curTable.mrpDevIdx)) == nullptr ) + { + rc = _set_status(p,kOpFailRC,"The current table has an invalid device index (%i).",p->curTable.mrpDevIdx ); + goto errLabel; + } + + + if((rc = vel_table_set( p->mrpH, p->curTable.mrpDevIdx, p->curTable.tableA, p->curTable.tableN )) != kOkRC ) + rc = _set_status(p,rc,"Velocity table apply failed."); + else + _set_status(p,kOkRC,"Velocity table applied to '%s'.", cwStringNullGuard(dm->label)); + + errLabel: + return rc; + } + + // Duplicate the source table 't' into the destination table 'dst_tbl'. + // If newTablename is non-null then use it to name the 'dst_tbl' otherwise + // name the table with the source table name. + void _duplicateTable( vtbl_t* p, tbl_t& dst_tbl, const tbl_t* t, const char* newTableName=nullptr ) + { + // allocate a new velocity table + if( dst_tbl.tableN != t->tableN ) + { + dst_tbl.tableA = mem::resize(dst_tbl.tableA,t->tableN); + dst_tbl.tableN = t->tableN; + } + + // copy the src velocities into the destination table + for(unsigned i=0; itableN; ++i) + dst_tbl.tableA[i] = t->tableA[i]; + + if( newTableName == nullptr ) + newTableName = t->name; + + dst_tbl.name = mem::reallocStr(dst_tbl.name,newTableName); + dst_tbl.mrpDevIdx = t->mrpDevIdx; + dst_tbl.appId = t->appId; + dst_tbl.link = nullptr; + } + + void _form_versioned_name( const char* name, char* buf, unsigned bufN, int version ) + { + int i; + + // set 'j' to the index of the final '_' just prior to numeric suffix + for(i=textLength(name)-1; i>=0; --i) + if( !isdigit(name[i]) ) + break; + + // if name is of the form ????_### then name+i is the end of the prefix + if( i>0 && i < (int)textLength(name) && name[i]=='_' ) + { + snprintf(buf,bufN,"%.*s_%i",i,name,version); + } + else // otherwise append a sufix to name + { + snprintf(buf,bufN,"%s_%i",name,version); + } + + } + + cw::rc_t _set_duplicate_name( vtbl_t* p, const char* name ) + { + rc_t rc = kOkRC; + + const tbl_t* t; + unsigned i = 0; + unsigned bufN = textLength(name) + 32; + char buf[ bufN ]; + + strcpy(buf,name); + + // mutate 'name' until it is unique among other tables + while((t = _table_from_name( p, buf )) != nullptr ) + { + _form_versioned_name(name,buf,bufN,i); + + if( i == 999 ) + { + rc = cwLogError(kInvalidOpRC,"The 'new' name could not be formed."); + goto errLabel; + } + + ++i; + } + + // store the new name + p->duplicateName = mem::reallocStr(p->duplicateName,buf); + + // update the UI + uiSendValue(p->ioH, uiFindElementUuId( p->ioH, kVtNameStrId), p->duplicateName ); + + + errLabel: + + return rc; + } + + cw::rc_t _load( vtbl_t* p, unsigned tableOptionAppId ) + { + rc_t rc = kOkRC; + tbl_t* t = p->tableL; + const dev_map_t* dm = nullptr; + + // locate the selected table + for(; t!=nullptr; t=t->link) + if( t->appId == tableOptionAppId ) + break; + + // verify that a table was found + if( t == nullptr ) + { + cwLogError(kOpFailRC,"The table associated with appId %i was not found.",tableOptionAppId ); + goto errLabel; + } + + // duplicate the selected table into 'curTable' + _duplicateTable(p,p->curTable,t); + + // update the UI with the new velocity table values + for(unsigned i = 0; itableN; ++i) + uiSendValue( p->ioH, uiFindElementUuId( p->ioH, kVtEntry0 + i), (unsigned)t->tableA[i] ); + + + // set the device menu + dm = _dev_map_from_mrpDevIdx(t->mrpDevIdx); + // device labels were tested at parse time - so this function can't fail + assert( dm != nullptr ); + uiSendValue( p->ioH, uiFindElementUuId( p->ioH, kVtDeviceSelectId), dm->appId ); + + // set the table menu + uiSendValue( p->ioH, io::uiFindElementUuId( p->ioH, kVtTableSelectId ), t->appId ); + + // Set the 'duplicate name' based on the new table + _set_duplicate_name(p,t->name); + + _set_status(p,kOkRC,"'%s' loaded.",t->name); + + errLabel: + return rc; + } + + cw::rc_t _duplicate( vtbl_t* p ) + { + rc_t rc = kOkRC; + + // a name for the new table must have been given + if( textLength(p->duplicateName) == 0 ) + { + rc = _set_status(p,kInvalidArgRC, "Enter a 'name' for the new table."); + } + else + { + // the name of the table must be unique + if( _table_from_name(p,p->duplicateName) != nullptr ) + rc = _set_status(p,kInvalidArgRC,"'%s' is not a unique table name.",p->duplicateName); + else + { + // create a new table + tbl_t* new_tbl = mem::allocZ(); + + // duplicate 'curTable' into the new table + _duplicateTable(p, *new_tbl, &p->curTable, p->duplicateName); + + // link in the new table + _link_in_table(p, new_tbl ); + + // load the new table + _load(p,new_tbl->appId); + } + } + return rc; + } + + cw::rc_t _set_device( vtbl_t* p, unsigned devAppId ) + { + rc_t rc = kOkRC; + + const dev_map_t* dm = _dev_map_from_appId(devAppId); + + cwAssert( dm != nullptr ); + + p->curTable.mrpDevIdx = dm->mrpDevIdx; + + return rc; + } + + cw::rc_t _set_pitch( vtbl_t* p, unsigned vseqPitch ) + { + rc_t rc = kOkRC; + if( 0<= vseqPitch && vseqPitch < 128) + p->vseqPitch = vseqPitch; + else + { + rc = _set_status(p,kInvalidArgRC,"%i is not a valid MIDI pitch.",vseqPitch); + } + return rc; + } + + cw::rc_t _validate_midi_value( vtbl_t* p, unsigned midiValue ) + { + if( 0 <= midiValue && midiValue < 128 ) + return kOkRC; + + return _set_status(p,kInvalidArgRC,"%i is an invalid 8 bit MIDI value.",midiValue); + } + + uint8_t _cast_int_to_8bits( vtbl_t* p, unsigned value ) + { + uint8_t v = 0; + + if( _validate_midi_value(p,value) == kOkRC ) + { + v = (uint8_t)value; + } + else + { + v = 127; + } + + return v; + } + + cw::rc_t _set_velocity( vtbl_t* p, unsigned midiVel ) + { + rc_t rc = kOkRC; + if((rc = _validate_midi_value(p,midiVel)) == kOkRC ) + { + p->pseqVelocity = midiVel; + } + return rc; + } + + cw::rc_t _set_min_pitch( vtbl_t* p, unsigned midiPitch ) + { + rc_t rc = kOkRC; + if((rc = _validate_midi_value(p,midiPitch)) == kOkRC ) + { + p->minPseqPitch = midiPitch; + } + return rc; + } + + cw::rc_t _set_max_pitch( vtbl_t* p, unsigned midiPitch ) + { + rc_t rc = kOkRC; + if((rc = _validate_midi_value(p,midiPitch)) == kOkRC ) + { + p->maxPseqPitch = midiPitch; + } + return rc; + } + + cw::rc_t _set_inc_pitch( vtbl_t* p, unsigned midiPitch ) + { + rc_t rc = kOkRC; + if((rc = _validate_midi_value(p,midiPitch)) == kOkRC ) + { + p->incPseqPitch = midiPitch; + } + return rc; + } + + cw::rc_t _set_table_entry( vtbl_t* p, unsigned table_idx, unsigned value ) + { + rc_t rc = kOkRC; + if( table_idx >= p->curTable.tableN ) + { + rc = _set_status(p,kInvalidArgRC,"The table index %i is not valid.",table_idx); + } + else + { + if((rc = _validate_midi_value(p,value)) == kOkRC ) + { + p->curTable.tableA[ table_idx ] = value; + _set_status(p,kOkRC,"The table index '%i' was set to the value '%i'.",table_idx,value); + } + } + + return rc; + } + } +} + +unsigned cw::vtbl::get_ui_id_map_count() +{ return sizeof(mapA)/sizeof(mapA[0]); }; + +const cw::ui::appIdMap_t* cw::vtbl::get_ui_id_map( unsigned panelAppId ) +{ + unsigned mapN = get_ui_id_map_count(); + for(unsigned i=0; i(); + + if((rc = objectFromFile( cfg_fname, cfg )) != kOkRC ) + { + rc = cwLogError(rc,"The velocity table tuner cfg. file open failed on '%s'.",cwStringNullGuard(cfg_fname)); + goto errLabel; + } + + p->mrpH = mrpH; + p->ioH = ioH; + p->cfg_fname = mem::duplStr(cfg_fname); + p->cfg_backup_dir = mem::duplStr(cfg_backup_dir); + p->nextTableAppId = kLoadOptionBaseId; + + if((rc = _parseCfg(p, cfg )) != kOkRC ) + { + rc = cwLogError(rc,"Velocity table cfg. parsing failed."); + goto errLabel; + } + + hRef.set(p); + + errLabel: + if( rc != kOkRC ) + _destroy(p); + + if( cfg != nullptr ) + cfg->free(); + + return rc; +} + +cw::rc_t cw::vtbl::destroy( handle_t& hRef ) +{ + rc_t rc = kOkRC; + if( !hRef.isValid() ) + return rc; + + vtbl_t* p = _handleToPtr(hRef); + + if((rc = _destroy(p)) != kOkRC ) + goto errLabel; + + hRef.clear(); + + errLabel: + return rc; +} + +cw::rc_t cw::vtbl::on_ui_value( handle_t h, const io::ui_msg_t& m ) +{ + rc_t rc = kOkRC; + + vtbl_t* p = _handleToPtr(h); + + switch( m.appId ) + { + case kVtTableSelectId: + rc = vtbl::_load(p,m.value->u.u); + break; + + case kVtDeviceSelectId: + rc = vtbl::_set_device(p, m.value->u.u ); + break; + + case kVtPianoDevId: + rc = vtbl::_set_device(p,midi_record_play::kPiano_MRP_DevIdx ); + break; + + case kVtSamplerDevId: + rc = vtbl::_set_device(p,midi_record_play::kSampler_MRP_DevIdx ); + break; + + case kVtPlayVelSeqBtnId: + rc = vtbl::_play_vel_sequence(p); + break; + + case kVtPitchId: + rc = vtbl::_set_pitch(p,m.value->u.u); + break; + + case kVtPlayPitchSeqBtnId: + rc = vtbl::_play_pitch_sequence(p); + break; + + case kVtVelocityId: + rc = vtbl::_set_velocity(p,m.value->u.u); + break; + + case kVtMinPitchId: + rc = vtbl::_set_min_pitch(p,m.value->u.u); + break; + + case kVtMaxPitchId: + rc = vtbl::_set_max_pitch(p,m.value->u.u); + break; + + case kVtIncPitchId: + rc = vtbl::_set_inc_pitch(p,m.value->u.u); + break; + + case kVtApplyBtnId: + rc = vtbl::_apply(p); + break; + + case kVtSaveBtnId: + rc = vtbl::_save(p); + break; + + case kVtDuplicateBtnId: + rc = vtbl::_duplicate(p); + break; + + case kVtNameStrId: + rc = vtbl::_set_duplicate_name(p,m.value->u.s); + break; + + default: + if( kVtEntry0 <= m.appId && m.appId <= kVtEntry24 ) + { + rc = vtbl::_set_table_entry(p, m.appId - kVtEntry0, m.value->u.u ); + break; + } + + /* + if( kLoadOptionBaseId <= m.appId && m.appId < _table_count(p) ) + { + printf("loader:%i\n", m.appId - kLoadOptionBaseId ); + } + */ + } + + return rc; +} + +cw::rc_t cw::vtbl::on_ui_echo( handle_t h, const io::ui_msg_t& m ) +{ + rc_t rc = kOkRC; + vtbl_t* p = _handleToPtr(h); + + switch( m.appId ) + { + case kVtTableSelectId: + if( !p->initUiFl ) + { + // BUG BUG BUG: if multiple UI's are connected this is not the appropriate + // response - echo should simply send the value, not change the state + // of the vtbl + + if( _table_count(p) > 0 ) + if(_load( p, kLoadOptionBaseId ) == kOkRC ) + p->initUiFl = true; + } + break; + + case kVtPitchId: + rc = io::uiSendValue(p->ioH, m.uuId, p->vseqPitch ); + break; + case kVtVelocityId: + rc = io::uiSendValue(p->ioH, m.uuId, p->pseqVelocity ); + break; + case kVtMinPitchId: + rc = io::uiSendValue(p->ioH, m.uuId, p->minPseqPitch ); + break; + case kVtMaxPitchId: + rc = io::uiSendValue(p->ioH, m.uuId, p->maxPseqPitch ); + break; + case kVtIncPitchId: + rc = io::uiSendValue(p->ioH, m.uuId, p->incPseqPitch ); + break; + } + + return rc; +} + +cw::rc_t cw::vtbl::exec( handle_t h ) +{ + rc_t rc = kOkRC; + + vtbl_t* p = _handleToPtr(h); + time::spec_t t0; + + if( p->state != kStoppedStateId ) + { + time::get(t0); + if( time::isGTE(t0,p->nextTime) ) + { + unsigned pitch = 0; + unsigned vel = 0; + + switch( p->state ) + { + case kNoteOnStateId: + { + p->state = kNoteOffStateId; + + time::advanceMs(p->nextTime,p->noteOnDurMs); + + switch( p->mode ) + { + case kVelSeqModeId: + pitch = p->vseqPitch; + cwAssert( p->nextVelIdx < p->curTable.tableN ); + vel = p->curTable.tableA[ p->nextVelIdx ]; + break; + + case kPitchSeqModeId: + vel = p->pseqVelocity; + pitch = p->nextPitch; + break; + + default: + cwAssert(0); + } + } + break; + + case kNoteOffStateId: + { + vel = 0; + p->state = kNoteOnStateId; + + time::advanceMs(p->nextTime,p->noteOffDurMs); + + switch( p->mode ) + { + case kVelSeqModeId: + { + pitch = p->vseqPitch; + + if( p->nextVelIdx + 1 >= p->curTable.tableN || p->waitForStopFl ) + p->state = kStoppedStateId; + else + p->nextVelIdx += 1; + } + break; + + case kPitchSeqModeId: + { + pitch = p->nextPitch; + + if( p->nextPitch + p->incPseqPitch > p->maxPseqPitch || p->waitForStopFl ) + p->state = kStoppedStateId; + else + p->nextPitch += p->incPseqPitch; + } + break; + + default: + cwAssert(0); + + } + } + break; + + default: + cwAssert(0); + } + + _set_status(p,kOkRC,"state:%i mode:%i wfs_fl:%i : dev:%i : pitch:%i vel:%i : nxt %i %i", + p->state,p->mode,p->waitForStopFl, + p->curTable.mrpDevIdx, + pitch,vel, + p->nextPitch,p->nextVelIdx); + + send_midi_msg( p->mrpH, + p->curTable.mrpDevIdx, + 0, + midi::kNoteOnMdId, + _cast_int_to_8bits(p,pitch), + _cast_int_to_8bits(p,vel) ); + + } + } + return rc; +} diff --git a/cwVelTableTuner.h b/cwVelTableTuner.h new file mode 100644 index 0000000..1aed0ff --- /dev/null +++ b/cwVelTableTuner.h @@ -0,0 +1,83 @@ +namespace cw +{ + namespace vtbl + { + + enum + { + kVtMinId=2000, + kVtDeviceSelectId, + kVtPianoDevId, + kVtSamplerDevId, + kVtTableSelectId, + kVtPlayVelSeqBtnId, + kVtPitchId, + kVtPlayPitchSeqBtnId, + kVtVelocityId, + kVtMinPitchId, + kVtMaxPitchId, + kVtIncPitchId, + kVtApplyBtnId, + kVtSaveBtnId, + kVtDuplicateBtnId, + kVtNameStrId, + kVtStatusId, + + kVtEntry0, + kVtEntry1, + kVtEntry2, + kVtEntry3, + kVtEntry4, + kVtEntry5, + kVtEntry6, + kVtEntry7, + kVtEntry8, + kVtEntry9, + kVtEntry10, + kVtEntry11, + kVtEntry12, + kVtEntry13, + kVtEntry14, + kVtEntry15, + kVtEntry16, + kVtEntry17, + kVtEntry18, + kVtEntry19, + kVtEntry20, + kVtEntry21, + kVtEntry22, + kVtEntry23, + kVtEntry24, + + kLoadOptionBaseId = 2500, + + kVtMaxId = 3000 + + }; + + struct vtbl_str; + typedef handle handle_t; + + unsigned get_ui_id_map_count(); + + const cw::ui::appIdMap_t* get_ui_id_map( unsigned panelAppId ); + + + rc_t create( handle_t& hRef, + io::handle_t ioH, + midi_record_play::handle_t mrpH, + const char* cfg_fname, + const char* cfg_backup_dir); + + rc_t destroy( handle_t& hRef ); + + + rc_t on_ui_value( handle_t h, const io::ui_msg_t& m ); + + rc_t on_ui_echo( handle_t h, const io::ui_msg_t& m ); + + // Update the state of the player + rc_t exec( handle_t h ); + + } +}