1349 lines
37 KiB
C++
1349 lines
37 KiB
C++
//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
|
|
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
|
|
#include "cwCommon.h"
|
|
#include "cwLog.h"
|
|
#include "cwCommonImpl.h"
|
|
#include "cwTest.h"
|
|
#include "cwMem.h"
|
|
#include "cwFile.h"
|
|
#include "cwText.h"
|
|
#include "cwObject.h"
|
|
#include "cwTime.h"
|
|
#include "cwMidi.h"
|
|
#include "cwMidiState.h"
|
|
#include "cwMidiFile.h"
|
|
|
|
namespace cw
|
|
{
|
|
namespace midi_state
|
|
{
|
|
enum {
|
|
kUpPedalStateId,
|
|
kHalfPedalStateId,
|
|
kDownPedalStateId
|
|
};
|
|
|
|
enum {
|
|
kSustainPedalIdx,
|
|
kSostenutoPedalIdx,
|
|
kSoftPedalIdx,
|
|
kPedalCnt,
|
|
};
|
|
|
|
typedef struct msg_cache_str
|
|
{
|
|
unsigned msgN;
|
|
msg_t* msgA;
|
|
unsigned next_idx;
|
|
struct msg_cache_str* link;
|
|
} msg_cache_t;
|
|
|
|
typedef struct event_cache_str
|
|
{
|
|
unsigned eventN;
|
|
event_t* eventA;
|
|
unsigned next_idx;
|
|
struct event_cache_str* link;
|
|
} event_cache_t;
|
|
|
|
typedef struct event_chain_str
|
|
{
|
|
event_t* begEvt;
|
|
event_t* endEvt;
|
|
event_t* iter;
|
|
} event_chain_t;
|
|
|
|
typedef struct note_state_str
|
|
{
|
|
bool noteGateFl; // true if the note gate is on
|
|
bool sndGateFl; // true if the note is sounding
|
|
bool sostHoldFl; // true if this note is being held by the sost. pedal
|
|
} note_state_t;
|
|
|
|
typedef struct ch_state_str
|
|
{
|
|
unsigned chIdx; // this channels MIDI ch index
|
|
bool sostFl; // true if the sost. pedal is down
|
|
bool softFl; // true if the soft pedal is down
|
|
unsigned dampState; // current sustain pedal state: kUp/kHalf/kDown - PedalStateId
|
|
note_state_t noteState[ midi::kMidiNoteCnt ]; // current note state
|
|
} ch_state_t;
|
|
|
|
typedef struct midi_state_str
|
|
{
|
|
config_t cfg;
|
|
|
|
callback_t cbFunc;
|
|
void* cbArg;
|
|
|
|
msg_cache_t* beg_msg_cache;
|
|
msg_cache_t* end_msg_cache;
|
|
|
|
event_cache_t* beg_event_cache;
|
|
event_cache_t* end_event_cache;
|
|
|
|
ch_state_t chState[ midi::kMidiChCnt ];
|
|
|
|
event_chain_t noteChains[ midi::kMidiChCnt * midi::kMidiNoteCnt ];
|
|
event_chain_t pedalChains[ midi::kMidiChCnt * kPedalCnt ];
|
|
|
|
event_t* first_event;
|
|
} midi_state_t;
|
|
|
|
typedef struct map_str
|
|
{
|
|
unsigned index;
|
|
unsigned midiId;
|
|
} map_t;
|
|
|
|
map_t map[] = {
|
|
{ kSustainPedalIdx, midi::kSustainCtlMdId },
|
|
{ kSostenutoPedalIdx, midi::kSostenutoCtlMdId },
|
|
{ kSoftPedalIdx, midi::kSoftPedalCtlMdId },
|
|
{ kInvalidIdx, kInvalidIdx }
|
|
};
|
|
|
|
typedef struct flag_map_str
|
|
{
|
|
unsigned flag;
|
|
const char* label;
|
|
} flag_map_t;
|
|
|
|
flag_map_t flag_map[] = {
|
|
{ kNoteOnFl, "Non"},
|
|
{ kNoteOffFl, "Nof"},
|
|
{ kSoundOnFl, "Son"},
|
|
{ kSoundOffFl, "Sof"},
|
|
{ kReattackFl, "Rat"},
|
|
{ kSoftPedalFl,"Sft"},
|
|
{ kUpPedalFl, "PUp"},
|
|
{ kHalfPedalFl,"PHf"},
|
|
{ kDownPedalFl,"PDn"},
|
|
{ kNoChangeFl, "---"},
|
|
{ kPedalEvtFl, "EPd"},
|
|
{ kNoteEvtFl, "ENt"},
|
|
{ kMarkerEvtFl,"EMk"},
|
|
{ 0, nullptr }
|
|
};
|
|
|
|
const char* _flagToLabel( unsigned flag )
|
|
{
|
|
for(unsigned i=0; flag_map[i].label!=nullptr; ++i)
|
|
if( flag_map[i].flag == flag )
|
|
return flag_map[i].label;
|
|
return "???";
|
|
}
|
|
|
|
unsigned _pedalMidiToIndex( uint8_t ctlId )
|
|
{
|
|
for(unsigned i=0; map[i].index!=kInvalidIdx; ++i)
|
|
if( ctlId == map[i].midiId )
|
|
return map[i].index;
|
|
|
|
return kInvalidIdx;
|
|
}
|
|
|
|
unsigned _pedalIndexToMidi( uint8_t index )
|
|
{
|
|
for(unsigned i=0; map[i].index!=kInvalidIdx; ++i)
|
|
if( index == map[i].index )
|
|
return map[i].midiId;
|
|
|
|
cwLogError(kInvalidArgRC,"Invalid pedal index:%i",index);
|
|
return kInvalidIdx;
|
|
}
|
|
|
|
|
|
midi_state_t* _handleToPtr( handle_t h )
|
|
{ return handleToPtr<handle_t,midi_state_t>(h); }
|
|
|
|
event_chain_t* _note_event_chain( midi_state_t* p, uint8_t ch, uint8_t pitch )
|
|
{
|
|
cwAssert( ch < midi::kMidiChCnt && pitch < midi::kMidiNoteCnt );
|
|
return p->noteChains + (ch * midi::kMidiNoteCnt + pitch );
|
|
}
|
|
|
|
event_chain_t* _pedal_event_chain_from_pedal_idx( midi_state_t* p, uint8_t ch, unsigned pedal_idx )
|
|
{
|
|
if( pedal_idx >= kPedalCnt )
|
|
return nullptr;
|
|
|
|
return p->pedalChains + (ch * kPedalCnt + pedal_idx);
|
|
}
|
|
|
|
event_chain_t* _pedal_event_chain_from_midi_ctl_id( midi_state_t* p, uint8_t ch, uint8_t d0 )
|
|
{
|
|
return _pedal_event_chain_from_pedal_idx(p,ch,_pedalMidiToIndex(d0));
|
|
}
|
|
|
|
int _format_marker( const marker_msg_t* m, char* buf, unsigned bufCharN )
|
|
{
|
|
return snprintf(buf,bufCharN,"%5i %3i %5i %5i ",m->uid,m->ch,m->typeId,m->value);
|
|
}
|
|
|
|
int _format_midi_msg( const midi_msg_t* m, char* buf, unsigned bufCharN )
|
|
{
|
|
return snprintf(buf,bufCharN," %5i %3i 0x%2x %3i %3i ",m->uid,m->ch,m->status,m->d0, m->d1);
|
|
}
|
|
|
|
rc_t _format_event( const event_t* e, char *buf, unsigned bufCharN )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
int n = snprintf(buf,bufCharN,"%7.3f ",e->secs);
|
|
assert( n<=(int)bufCharN);
|
|
buf += n;
|
|
bufCharN -= n;
|
|
|
|
if( e->msg != nullptr )
|
|
{
|
|
if( cwIsFlag(e->flags,kMarkerEvtFl) )
|
|
{
|
|
n = _format_marker(&e->msg->u.marker,buf,bufCharN);
|
|
assert( n<=(int)bufCharN );
|
|
buf += n;
|
|
bufCharN -= n;
|
|
}
|
|
|
|
if( cwIsFlag(e->flags,kNoteEvtFl|kPedalEvtFl) )
|
|
{
|
|
n = _format_midi_msg(&e->msg->u.midi,buf,bufCharN);
|
|
assert( n<=(int)bufCharN );
|
|
buf += n;
|
|
bufCharN -= n;
|
|
}
|
|
}
|
|
|
|
if( bufCharN < flags_to_string_max_string_length() )
|
|
rc = cwLogError(kBufTooSmallRC,"The event char buf is too small.");
|
|
else
|
|
rc = flags_to_string( e->flags, buf, bufCharN );
|
|
|
|
if( rc != kOkRC )
|
|
rc = cwLogError(rc,"Event format failed.");
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
// Rewind the chain iterator.
|
|
void _rewind_chain_iterator( midi_state_t* p )
|
|
{
|
|
for(unsigned i=0; i<midi::kMidiChCnt; ++i)
|
|
for(unsigned j=0; j<midi::kMidiNoteCnt; ++j)
|
|
{
|
|
event_chain_t* ec = _note_event_chain(p,i,j);
|
|
ec->iter = ec->begEvt;
|
|
}
|
|
|
|
for(unsigned i=0; i<midi::kMidiChCnt; ++i)
|
|
for(unsigned j=0; j<kPedalCnt; ++j)
|
|
{
|
|
event_chain_t* ec = _pedal_event_chain_from_pedal_idx(p,i,j);
|
|
ec->iter = ec->begEvt;
|
|
}
|
|
}
|
|
|
|
// Given a list events linked in time order return the event that
|
|
// is equal to or minimally greater than 'sec'.
|
|
// Return nullptr if all events are less than 'sec'.
|
|
event_t* _goto_event_greater_than_or_equal( event_t* e, double sec )
|
|
{
|
|
if( e == nullptr )
|
|
return nullptr;
|
|
|
|
// if sec-e->sec > 0 then e->sec is still behind sec
|
|
while( e!=nullptr and sec - e->secs > 0 )
|
|
e = e->link;
|
|
|
|
// if e is nullptr then all events in the chain are less than 'sec'.
|
|
|
|
return e;
|
|
}
|
|
|
|
|
|
// Set the chain iterators to the event that is equal to, or minimally greater than, 'secs'.
|
|
// For chains which end prior to 'sec'.
|
|
void _seek_chain_iterator( midi_state_t* p, double secs )
|
|
{
|
|
_rewind_chain_iterator(p);
|
|
|
|
for(unsigned i=0; i<midi::kMidiChCnt; ++i)
|
|
{
|
|
for(unsigned j=0; j<midi::kMidiNoteCnt; ++j)
|
|
{
|
|
event_chain_t* ec = _note_event_chain(p,i,j);
|
|
ec->iter = _goto_event_greater_than_or_equal( ec->begEvt, secs );
|
|
}
|
|
|
|
for(unsigned j=0; j<kPedalCnt; ++j)
|
|
{
|
|
event_chain_t* ec = _pedal_event_chain_from_pedal_idx(p,i,j);
|
|
ec->iter = _goto_event_greater_than_or_equal( ec->begEvt, secs);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// use the chain iterators to return the next event in time
|
|
event_t* _step_chain_iterator( midi_state_t* p )
|
|
{
|
|
event_chain_t* ec0 = nullptr;
|
|
event_t* e = nullptr;
|
|
|
|
for(uint8_t i=0; i<midi::kMidiChCnt; ++i)
|
|
{
|
|
for(uint8_t j=0; j<midi::kMidiNoteCnt; ++j)
|
|
{
|
|
event_chain_t* ec = _note_event_chain(p,i,j);
|
|
if( ec->iter != nullptr && (ec0==nullptr || ec->iter->secs < ec0->iter->secs) )
|
|
ec0 = ec;
|
|
}
|
|
|
|
for(uint8_t j=0; j<kPedalCnt; ++j)
|
|
{
|
|
event_chain_t* ec = _pedal_event_chain_from_pedal_idx(p,i,j);
|
|
if( ec->iter != nullptr && (ec0==nullptr || ec->iter->secs < ec0->iter->secs) )
|
|
ec0 = ec;
|
|
}
|
|
}
|
|
|
|
if( ec0 != nullptr )
|
|
{
|
|
e = ec0->iter;
|
|
ec0->iter = ec0->iter->link;
|
|
}
|
|
|
|
return e;
|
|
}
|
|
|
|
unsigned _count_null_tlinks( midi_state_t* p )
|
|
{
|
|
event_cache_t* ec = p->beg_event_cache;
|
|
unsigned n = 0;
|
|
for(; ec!=nullptr; ec=ec->link)
|
|
for(unsigned i=0; i<ec->next_idx; ++i)
|
|
if( ec->eventA[i].tlink == nullptr )
|
|
++n;
|
|
|
|
return n;
|
|
}
|
|
|
|
const event_t* _get_first_link( midi_state_t* p )
|
|
{
|
|
// if the first_event has not yet been set ....
|
|
if( p->first_event == nullptr )
|
|
{
|
|
event_t* e0 = nullptr;
|
|
event_t* e1 = nullptr;
|
|
// ... the use the chain iterator to get the time order of the events and set 'tlink'
|
|
_seek_chain_iterator(p, 0.0);
|
|
|
|
while((e1 = _step_chain_iterator( p )) != nullptr )
|
|
{
|
|
if( e0 == nullptr )
|
|
p->first_event = e1;
|
|
else
|
|
e0->tlink = e1;
|
|
|
|
e0 = e1;
|
|
}
|
|
}
|
|
|
|
cwAssert( _count_null_tlinks(p) == 1 );
|
|
|
|
return p->first_event;
|
|
}
|
|
|
|
void _reset( midi_state_t* p )
|
|
{
|
|
// TODO: it would be better if once allocated the cache memory
|
|
// was reused on the next session rather than released
|
|
// and reallocated
|
|
|
|
p->first_event = nullptr;
|
|
|
|
// release the MIDI cache
|
|
msg_cache_t* mc = p->beg_msg_cache;
|
|
while( mc != nullptr )
|
|
{
|
|
msg_cache_t* mc0 = mc->link;
|
|
mem::release(mc->msgA);
|
|
mem::release(mc);
|
|
mc = mc0;
|
|
}
|
|
|
|
// release the event cache
|
|
event_cache_t* ev = p->beg_event_cache;
|
|
while( ev != nullptr )
|
|
{
|
|
event_cache_t* ev0 = ev->link;
|
|
mem::release(ev->eventA);
|
|
mem::release(ev);
|
|
ev = ev0;
|
|
}
|
|
|
|
p->beg_msg_cache = nullptr;
|
|
p->end_msg_cache = nullptr;
|
|
p->beg_event_cache = nullptr;
|
|
p->end_event_cache = nullptr;
|
|
|
|
for(unsigned i=0; i<midi::kMidiChCnt; ++i)
|
|
{
|
|
for(unsigned j=0; j<midi::kMidiNoteCnt; ++j)
|
|
{
|
|
_note_event_chain(p, i, j )->begEvt = nullptr;
|
|
_note_event_chain(p, i, j )->endEvt = nullptr;
|
|
}
|
|
|
|
for(unsigned j=0; j<kPedalCnt; ++j)
|
|
{
|
|
_pedal_event_chain_from_pedal_idx(p, i, j)->begEvt = nullptr;
|
|
_pedal_event_chain_from_pedal_idx(p, i, j)->endEvt = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc_t _destroy( midi_state_t* p )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
|
|
_reset(p);
|
|
|
|
mem::release(p);
|
|
|
|
return rc;
|
|
}
|
|
|
|
msg_t* _fill_midi_msg( msg_t* m, unsigned uid, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 )
|
|
{
|
|
m->typeId = kMidiMsgTId;
|
|
|
|
m->u.midi.uid = uid;
|
|
m->u.midi.ch = ch;
|
|
m->u.midi.status = status;
|
|
m->u.midi.d0 = d0;
|
|
m->u.midi.d1 = d1;
|
|
return m;
|
|
}
|
|
|
|
msg_t* _fill_marker_msg( msg_t* m, unsigned uid, uint8_t ch, unsigned markerTypeId, unsigned markerValue )
|
|
{
|
|
m->typeId = kMarkerMsgTId;
|
|
|
|
m->u.marker.uid = uid;
|
|
m->u.marker.ch = ch;
|
|
m->u.marker.typeId = markerTypeId;
|
|
m->u.marker.value = markerValue;
|
|
return m;
|
|
}
|
|
|
|
msg_t* _insert_msg( midi_state_t* p)
|
|
{
|
|
if( p->end_msg_cache == nullptr || p->end_msg_cache->next_idx >= p->end_msg_cache->msgN )
|
|
{
|
|
msg_cache_t* mc = mem::allocZ<msg_cache_t>();
|
|
mc->msgN = p->cfg.cacheBlockMsgN;
|
|
mc->msgA = mem::allocZ<msg_t>(mc->msgN);
|
|
if( p->end_msg_cache == nullptr )
|
|
p->beg_msg_cache = mc;
|
|
else
|
|
p->end_msg_cache->link = mc;
|
|
p->end_msg_cache = mc;
|
|
}
|
|
|
|
return p->end_msg_cache->msgA + p->end_msg_cache->next_idx;
|
|
}
|
|
|
|
msg_t* _insert_midi_msg( midi_state_t* p, unsigned uid, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 )
|
|
{
|
|
msg_t* m = _insert_msg(p);
|
|
|
|
_fill_midi_msg(m, uid, ch, status, d0, d1 );
|
|
|
|
p->end_msg_cache->next_idx++;
|
|
|
|
return m;
|
|
}
|
|
|
|
msg_t* _insert_marker_msg( midi_state_t* p, unsigned uid, uint8_t ch, unsigned markerTypeId, unsigned markerValue )
|
|
{
|
|
msg_t* m = _insert_msg(p);
|
|
|
|
_fill_marker_msg(m, uid, ch, markerTypeId, markerValue );
|
|
|
|
p->end_msg_cache->next_idx++;
|
|
|
|
return m;
|
|
}
|
|
|
|
event_t* _insert_event( midi_state_t* p, unsigned flags, double secs, const msg_t* m=nullptr )
|
|
{
|
|
if( p->end_event_cache == nullptr || p->end_event_cache->next_idx >= p->end_event_cache->eventN )
|
|
{
|
|
event_cache_t* ec = mem::allocZ<event_cache_t>();
|
|
ec->eventN = p->cfg.cacheBlockMsgN;
|
|
ec->eventA = mem::allocZ<event_t>(ec->eventN);
|
|
if( p->end_event_cache == nullptr )
|
|
p->beg_event_cache = ec;
|
|
else
|
|
p->end_event_cache->link = ec;
|
|
p->end_event_cache = ec;
|
|
}
|
|
|
|
event_t* e = p->end_event_cache->eventA + p->end_event_cache->next_idx;
|
|
e->flags = flags;
|
|
e->secs = secs;
|
|
e->msg = m;
|
|
e->link = nullptr;
|
|
e->tlink = nullptr;
|
|
|
|
p->end_event_cache->next_idx++;
|
|
|
|
return e;
|
|
}
|
|
|
|
|
|
void _onStateChange( midi_state_t* p, unsigned flags, double secs, const msg_t* m )
|
|
{
|
|
// notice when a voice is being switched off
|
|
if( cwIsFlag(flags,kSoundOffFl) )
|
|
p->chState[m->u.midi.ch].noteState[m->u.midi.d0].sndGateFl = false;
|
|
|
|
if( p->cbFunc != nullptr )
|
|
p->cbFunc( p->cbArg, flags, secs, m );
|
|
|
|
if( p->cfg.cacheEnableFl )
|
|
{
|
|
cwAssert( cwIsFlag( flags,kPedalEvtFl | kNoteEvtFl | kMarkerEvtFl ) );
|
|
event_t* e = _insert_event( p, flags, secs, m );
|
|
event_chain_t* ec = cwIsFlag(flags,kPedalEvtFl) ? _pedal_event_chain_from_midi_ctl_id(p,m->u.midi.ch,m->u.midi.d0) : _note_event_chain(p,m->u.midi.ch,m->u.midi.d0);
|
|
|
|
if( ec->begEvt == nullptr )
|
|
ec->begEvt = e;
|
|
else
|
|
ec->endEvt->link = e;
|
|
|
|
ec->endEvt = e;
|
|
}
|
|
|
|
}
|
|
|
|
void _onMidiNoteStateChange( midi_state_t* p, unsigned flags, double secs, unsigned uid, unsigned chIdx, uint8_t status, uint8_t pitch, uint8_t vel )
|
|
{
|
|
msg_t msg;
|
|
const msg_t* m = &msg;
|
|
|
|
// if the cache is enabled then we need a valid msg_t record which will
|
|
// be stored in the cached event - in _onStateChange() ...
|
|
if( p->cfg.cacheEnableFl )
|
|
{
|
|
event_chain_t* ec = _note_event_chain( p, chIdx, pitch );
|
|
assert( ec != nullptr && ec->endEvt != nullptr && ec->endEvt->msg != nullptr );
|
|
m = ec->endEvt->msg;
|
|
}
|
|
else
|
|
{
|
|
// ... if cache is not enabled then we can pass a msg_t record which will
|
|
// only be valid during the callback
|
|
_fill_midi_msg(&msg, uid, chIdx, status, pitch, vel );
|
|
}
|
|
|
|
_onStateChange( p, flags, secs, m );
|
|
|
|
}
|
|
|
|
|
|
void _turn_off_all_released_notes( midi_state_t* p, ch_state_t* c, double sec, unsigned uid )
|
|
{
|
|
// if the sustain pedal is not up then all sounding notes remain sounding
|
|
if( c->dampState == kUpPedalStateId )
|
|
{
|
|
// for each note
|
|
for(unsigned i=0; i<midi::kMidiNoteCnt; ++i)
|
|
{
|
|
// if this note is sounding and not being held by the note gate or sostenuto pedal
|
|
if( c->noteState[i].sndGateFl && c->noteState[i].sostHoldFl==false && c->noteState[i].noteGateFl==false )
|
|
{
|
|
// turn sounding note off
|
|
_onMidiNoteStateChange(p,kSoundOffFl | kNoteEvtFl, sec, uid, c->chIdx, midi::kNoteOffMdId, i, 0);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
rc_t _setMidiNoteOnMsg( midi_state_t* p, double sec, const msg_t* m )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
unsigned flags = kSoundOnFl | kNoteOnFl;
|
|
ch_state_t* c = p->chState + m->u.midi.ch;
|
|
|
|
if( c->noteState[ m->u.midi.d0 ].noteGateFl )
|
|
flags |= kReattackFl;
|
|
|
|
if( p->chState[ m->u.midi.ch ].softFl )
|
|
flags |= kSoftPedalFl;
|
|
|
|
c->noteState[ m->u.midi.d0 ].noteGateFl = true;
|
|
c->noteState[ m->u.midi.d0 ].sndGateFl = true;
|
|
|
|
_onStateChange( p, flags | kNoteEvtFl, sec, m );
|
|
|
|
return rc;
|
|
}
|
|
|
|
rc_t _setMidiNoteOffMsg( midi_state_t* p, double sec, const msg_t* m )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
unsigned flags = kNoteOffFl;
|
|
ch_state_t* c = p->chState + m->u.midi.ch;
|
|
|
|
if( c->noteState[ m->u.midi.d0 ].noteGateFl == false )
|
|
flags |= kNoChangeFl;
|
|
|
|
c->noteState[ m->u.midi.d0 ].noteGateFl = false;
|
|
|
|
// if the note is sounding and is not being held on by the sost or damper - then turn it off
|
|
if( c->noteState[m->u.midi.d0].sndGateFl && (c->dampState == kUpPedalStateId && c->noteState[m->u.midi.d0].sostHoldFl==false) )
|
|
{
|
|
// turn off the note
|
|
flags |= kSoundOffFl;
|
|
}
|
|
|
|
_onStateChange( p, flags | kNoteEvtFl, sec, m );
|
|
|
|
return rc;
|
|
}
|
|
|
|
rc_t _setMidiSustainMsg( midi_state_t* p, double sec, const msg_t* m )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
ch_state_t* c = p->chState + m->u.midi.ch;
|
|
unsigned dampState = kDownPedalStateId;
|
|
unsigned flags = kDownPedalFl;
|
|
|
|
// if the pedal is going up
|
|
if( m->u.midi.d1 < p->cfg.pedalHalfMinMidiValue )
|
|
{
|
|
dampState = kUpPedalStateId;
|
|
flags = kUpPedalFl;
|
|
}
|
|
else
|
|
{
|
|
// if the pedal is in the half pedal band
|
|
if( m->u.midi.d1 <= p->cfg.pedalHalfMaxMidiValue )
|
|
{
|
|
dampState = kHalfPedalStateId;
|
|
flags = kHalfPedalFl;
|
|
}
|
|
}
|
|
|
|
// if the pedal state is not changing
|
|
if( dampState == c->dampState )
|
|
flags |= kNoChangeFl;
|
|
else
|
|
c->dampState = dampState;
|
|
|
|
// update client state
|
|
_onStateChange( p, flags | kPedalEvtFl, sec, m );
|
|
|
|
// if the pedal changed state
|
|
if( !cwIsFlag( flags, kNoChangeFl ) )
|
|
{
|
|
// if the pedal went up - then release notes
|
|
if( dampState == kUpPedalStateId )
|
|
_turn_off_all_released_notes(p,c,sec,m->u.midi.uid);
|
|
else
|
|
{
|
|
// if the pedal went into the half pedal range ...
|
|
if(dampState == kHalfPedalStateId )
|
|
{
|
|
// ... then notify all notes that they should enter the half pedal range
|
|
for(unsigned i=0; i<midi::kMidiNoteCnt; ++i)
|
|
if( c->noteState[i].sndGateFl )
|
|
{
|
|
_onMidiNoteStateChange( p, flags | kHalfPedalFl | kNoteEvtFl, sec, m->u.midi.uid, m->u.midi.ch, m->u.midi.status, i, 0 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
rc_t _setMidiSostenutoMsg( midi_state_t* p, double sec, const msg_t* m )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
bool pedalDownFl = m->u.midi.d1 > p->cfg.pedalUpMidiValue;
|
|
unsigned flags = 0;
|
|
ch_state_t* c = p->chState + m->u.midi.ch;
|
|
|
|
// if the sost pedal is not changing state
|
|
if( c->sostFl == pedalDownFl )
|
|
flags |= kNoChangeFl;
|
|
else
|
|
flags = pedalDownFl ? kDownPedalFl : kUpPedalFl;
|
|
|
|
// update the sost pedal state
|
|
c->sostFl = pedalDownFl;
|
|
|
|
// if the sost pedal changed state
|
|
if( !cwIsFlag(flags, kNoChangeFl ) )
|
|
{
|
|
// update the client state
|
|
_onStateChange( p, flags | kPedalEvtFl, sec, m );
|
|
|
|
// if the sost pedal went down...
|
|
if( pedalDownFl )
|
|
{
|
|
// ... mark all notes whose note-gate is on to be held until the sost pedal goes up
|
|
for(unsigned i=0; i<midi::kMidiNoteCnt; ++i)
|
|
c->noteState[i].sostHoldFl = c->noteState[i].noteGateFl;
|
|
}
|
|
else // if the sost pedal went up
|
|
{
|
|
// turn off the sost hold flag on all notes
|
|
for(unsigned i=0; i<midi::kMidiNoteCnt; ++i)
|
|
c->noteState[i].sostHoldFl = false;
|
|
|
|
// release any notes which were held by the sost pedal
|
|
_turn_off_all_released_notes( p, c, sec, m->u.midi.uid);
|
|
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
rc_t _setMidiSoftPedalMsg( midi_state_t* p, double sec, const msg_t* m )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
bool pedalDownFl = m->u.midi.d1 >= p->cfg.pedalUpMidiValue;
|
|
unsigned flags = 0;
|
|
ch_state_t* c = p->chState + m->u.midi.ch;
|
|
|
|
if( c->softFl == pedalDownFl )
|
|
flags |= kNoChangeFl;
|
|
else
|
|
flags = pedalDownFl ? kDownPedalFl : kUpPedalFl;
|
|
|
|
c->softFl = pedalDownFl;
|
|
|
|
_onStateChange( p, flags | kPedalEvtFl, sec, m );
|
|
|
|
return rc;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
const char* cw::midi_state::flag_to_label( unsigned flag )
|
|
{
|
|
return _flagToLabel(flag);
|
|
}
|
|
|
|
unsigned cw::midi_state::flags_to_string_max_string_length()
|
|
{
|
|
unsigned charN = 0;
|
|
for(unsigned i=0; flag_map[i].label != nullptr; ++i)
|
|
charN += textLength(flag_map[i].label) + 1;
|
|
|
|
return charN+1;
|
|
}
|
|
|
|
cw::rc_t cw::midi_state::flags_to_string( unsigned flags, char* str, unsigned strCharN )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
unsigned si=0;
|
|
for(unsigned i=0; flag_map[i].label != nullptr && si < strCharN; ++i)
|
|
if( cwIsFlag(flags,flag_map[i].flag) )
|
|
si += snprintf(str + si, strCharN-si,"%s ",flag_map[i].label);
|
|
|
|
if( si >= strCharN )
|
|
rc = cwLogError(kBufTooSmallRC,"The flags_to_string() buffer is too small.");
|
|
|
|
return rc;
|
|
}
|
|
|
|
cw::rc_t cw::midi_state::format_event( const event_t* e, char* buf, unsigned bufCharN )
|
|
{
|
|
return _format_event(e,buf,bufCharN);
|
|
}
|
|
|
|
|
|
const cw::midi_state::config_t& cw::midi_state::default_config()
|
|
{
|
|
static config_t c = {
|
|
.cacheEnableFl = true,
|
|
.cacheBlockMsgN = 1024,
|
|
.pedalHalfMinMidiValue = 42,
|
|
.pedalHalfMaxMidiValue = 46,
|
|
.pedalUpMidiValue = 64
|
|
};
|
|
|
|
return c;
|
|
}
|
|
|
|
cw::rc_t cw::midi_state::create( handle_t& hRef,
|
|
callback_t cbFunc,
|
|
void* cbArg,
|
|
const config_t* cfg )
|
|
{
|
|
rc_t rc;
|
|
if((rc = destroy(hRef)) != kOkRC )
|
|
return rc;
|
|
|
|
midi_state_t* p = mem::allocZ<midi_state_t>();
|
|
|
|
if( cfg == nullptr )
|
|
cfg = &default_config();
|
|
|
|
p->cbFunc = cbFunc;
|
|
p->cbArg = cbArg;
|
|
p->cfg = *cfg;
|
|
|
|
for(unsigned i=0; i<midi::kMidiChCnt; ++i)
|
|
p->chState[i].chIdx = i;
|
|
|
|
hRef.set(p);
|
|
|
|
return rc;
|
|
}
|
|
|
|
cw::rc_t cw::midi_state::create( handle_t& hRef,
|
|
callback_t cbFunc,
|
|
void* cbArg,
|
|
const object_t* cfg )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
config_t c;
|
|
|
|
if((rc = cfg->getv("cache_enable_fl",c.cacheEnableFl,
|
|
"cache_block_msg_count",c.cacheBlockMsgN,
|
|
"pedal_up_midi_value",c.pedalUpMidiValue,
|
|
"pedal_half_min_midi_value",c.pedalHalfMinMidiValue,
|
|
"pedal_half_max_midi_value",c.pedalHalfMaxMidiValue)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"MIDI state cfg. parse failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
if((rc = create( hRef,cbFunc,cbArg,&c)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"midi_state object create faild.");
|
|
goto errLabel;
|
|
}
|
|
|
|
errLabel:
|
|
return rc;
|
|
}
|
|
|
|
|
|
cw::rc_t cw::midi_state::destroy( handle_t& hRef )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
if(!hRef.isValid())
|
|
return rc;
|
|
|
|
midi_state_t* p = _handleToPtr(hRef);
|
|
|
|
if((rc = _destroy(p)) != kOkRC )
|
|
return rc;
|
|
|
|
hRef.clear();
|
|
|
|
return rc;
|
|
}
|
|
|
|
cw::rc_t cw::midi_state::setMidiMsg( handle_t h, double sec, unsigned uid, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
midi_state_t* p = _handleToPtr(h);
|
|
const msg_t* m = nullptr;
|
|
msg_t mr;
|
|
|
|
status = status & 0xf0; // be sure that the MIDI channel has been cleared from the status byte
|
|
|
|
// convert the midi arg's into a midi_msg_t record
|
|
if( p->cfg.cacheEnableFl )
|
|
m = _insert_midi_msg(p, uid, ch, status, d0, d1 );
|
|
else
|
|
m = _fill_midi_msg(&mr, uid, ch, status, d0, d1 );
|
|
|
|
switch( status )
|
|
{
|
|
case midi::kNoteOnMdId:
|
|
if( d1 > 0 )
|
|
rc = _setMidiNoteOnMsg(p,sec,m);
|
|
else
|
|
rc = _setMidiNoteOffMsg(p,sec,m);
|
|
break;
|
|
|
|
case midi::kNoteOffMdId:
|
|
rc = _setMidiNoteOffMsg(p,sec,m);
|
|
break;
|
|
|
|
case midi::kCtlMdId:
|
|
switch( d0 )
|
|
{
|
|
case midi::kSustainCtlMdId:
|
|
rc = _setMidiSustainMsg(p,sec,m);
|
|
break;
|
|
|
|
case midi::kSostenutoCtlMdId:
|
|
rc = _setMidiSostenutoMsg(p,sec,m);
|
|
break;
|
|
|
|
case midi::kSoftPedalCtlMdId:
|
|
rc = _setMidiSoftPedalMsg(p,sec,m);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
cw::rc_t cw::midi_state::setMarker( handle_t h, double sec, unsigned uid, uint8_t ch, unsigned typeId, unsigned value )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
midi_state_t* p = _handleToPtr(h);
|
|
const msg_t* m = nullptr;
|
|
msg_t mr;
|
|
|
|
// convert the midi arg's into a midi_msg_t record
|
|
if( p->cfg.cacheEnableFl )
|
|
m = _insert_marker_msg(p, uid, ch, typeId, value );
|
|
else
|
|
m = _fill_marker_msg(&mr, uid, ch, typeId, value );
|
|
|
|
_onStateChange( p, kMarkerEvtFl, sec, m );
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
void cw::midi_state::reset( handle_t h )
|
|
{
|
|
midi_state_t* p = _handleToPtr(h);
|
|
_reset(p);
|
|
}
|
|
|
|
const cw::midi_state::event_t* cw::midi_state::get_first_link( handle_t h )
|
|
{
|
|
midi_state_t* p = _handleToPtr(h);
|
|
return _get_first_link(p);
|
|
}
|
|
|
|
|
|
const cw::midi_state::event_t* cw::midi_state::note_event_list( handle_t h, uint8_t ch, uint8_t pitch )
|
|
{
|
|
midi_state_t* p = _handleToPtr(h);
|
|
return _note_event_chain(p,ch,pitch)->begEvt;
|
|
}
|
|
|
|
const cw::midi_state::event_t* cw::midi_state::pedal_event_list( handle_t h, uint8_t ch, unsigned pedal_idx )
|
|
{
|
|
midi_state_t* p = _handleToPtr(h);
|
|
const event_t* e = nullptr;
|
|
event_chain_t* ec;
|
|
|
|
if((ec = _pedal_event_chain_from_pedal_idx(p,ch,pedal_idx)) != nullptr )
|
|
e = ec->begEvt;
|
|
else
|
|
cwLogError(kInvalidArgRC,"'%i is not a valid pedal index.",pedal_idx);
|
|
|
|
return e;
|
|
}
|
|
|
|
unsigned cw::midi_state::pedal_count( handle_t h )
|
|
{
|
|
return kPedalCnt;
|
|
}
|
|
|
|
unsigned cw::midi_state::pedal_midi_ctl_id_to_index( unsigned midi_ctl_id )
|
|
{
|
|
return _pedalMidiToIndex( midi_ctl_id );
|
|
}
|
|
unsigned cw::midi_state::pedal_index_to_midi_ctl_id( unsigned pedal_idx )
|
|
{
|
|
return _pedalIndexToMidi( pedal_idx );
|
|
}
|
|
|
|
|
|
void cw::midi_state::get_note_extents( handle_t h, uint8_t& minPitchRef, uint8_t& maxPitchRef, double& minSecRef, double& maxSecRef )
|
|
{
|
|
minSecRef = -1;
|
|
maxSecRef = 0;
|
|
minPitchRef = midi::kMidiNoteCnt;
|
|
maxPitchRef = 0;
|
|
|
|
const event_t* e;
|
|
for(unsigned i=0; i<midi::kMidiChCnt; ++i)
|
|
for(unsigned j=0; j<midi::kMidiNoteCnt; ++j)
|
|
if((e = note_event_list(h,i,j)) != nullptr )
|
|
{
|
|
minPitchRef = std::min(minPitchRef,(uint8_t)j);
|
|
maxPitchRef = std::max(maxPitchRef,(uint8_t)j);
|
|
|
|
for(; e!=nullptr; e=e->link)
|
|
{
|
|
minSecRef = minSecRef==-1 ? e->secs : std::min(minSecRef,e->secs);
|
|
maxSecRef = std::max(maxSecRef,e->secs);
|
|
}
|
|
}
|
|
}
|
|
|
|
void cw::midi_state::get_pedal_extents( handle_t h, double& minSecRef, double& maxSecRef )
|
|
{
|
|
minSecRef = -1;
|
|
maxSecRef = 0;
|
|
|
|
const event_t* e;
|
|
for(unsigned i=0; i<midi::kMidiChCnt; ++i)
|
|
for(unsigned j=0; j<kPedalCnt; ++j)
|
|
if((e = pedal_event_list(h,i,j)) != nullptr )
|
|
for(; e!=nullptr; e=e->link)
|
|
{
|
|
minSecRef = minSecRef==-1 ? e->secs : std::min(minSecRef,e->secs);
|
|
maxSecRef = std::max(maxSecRef,e->secs);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
cw::rc_t cw::midi_state::load_from_midi_file( handle_t h, const char* midi_fname )
|
|
{
|
|
rc_t rc;
|
|
midi::file::handle_t mfH;
|
|
|
|
// open the MIDI file
|
|
if((rc = midi::file::open(mfH,midi_fname)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"MIDI file open failed on '%s'.",cwStringNullGuard(midi_fname));
|
|
goto errLabel;
|
|
}
|
|
else
|
|
{
|
|
const midi::file::trackMsg_t** msgA = msgArray(mfH);
|
|
unsigned long long usec0 = 0;
|
|
|
|
// for each MIDI msg
|
|
for(unsigned i=0; i<msgCount(mfH); ++i)
|
|
{
|
|
const midi::file::trackMsg_t* msg = msgA[i];
|
|
|
|
// if this is a channel msg
|
|
if( midi::isChStatus(msg->status) )
|
|
{
|
|
|
|
unsigned long long micros = 0;
|
|
if( usec0 == 0)
|
|
usec0 = msg->amicro;
|
|
else
|
|
micros = msg->amicro - usec0;
|
|
|
|
double sec = (double)micros/1000000.0;
|
|
|
|
// cache the event
|
|
if((rc = setMidiMsg(h, sec, msg->uid, msg->status & 0x0f, msg->status & 0xf0, msg->u.chMsgPtr->d0, msg->u.chMsgPtr->d1 ) ) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"Error on MIDI event insertion.");
|
|
goto errLabel;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
errLabel:
|
|
|
|
// close the MIDI file
|
|
close(mfH);
|
|
return rc;
|
|
}
|
|
|
|
cw::rc_t cw::midi_state::report_events( handle_t h, const char* out_fname )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
midi_state_t* p = _handleToPtr(h);
|
|
file::handle_t fH;
|
|
|
|
if((rc = file::open(fH,out_fname,file::kWriteFl)) != kOkRC )
|
|
{
|
|
cwLogError(rc,"The report file create failed:'%s'.",out_fname);
|
|
goto errLabel;
|
|
}
|
|
else
|
|
{
|
|
const unsigned bufCharN = 511;
|
|
char buf[ bufCharN+1 ];
|
|
const event_t* e = nullptr;
|
|
const event_t* e0 = nullptr;
|
|
if((e = _get_first_link(p)) != nullptr )
|
|
{
|
|
for(; e!=nullptr; e=e->tlink)
|
|
{
|
|
if((rc = _format_event( e, buf, bufCharN )) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"Formst event failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
double dsec = e0==nullptr ? 0 : e->secs - e0->secs;
|
|
file::printf(fH,"%7.3f %s\n",dsec,buf);
|
|
e0 = e;
|
|
}
|
|
}
|
|
}
|
|
|
|
errLabel:
|
|
if((rc = file::close(fH)) != kOkRC )
|
|
rc = cwLogError(rc,"The report file close failed:'%s'.",out_fname);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
namespace cw
|
|
{
|
|
namespace midi_state
|
|
{
|
|
typedef struct test_arg_str
|
|
{
|
|
bool printMsgsFl;
|
|
} test_arg_t;
|
|
|
|
void _testCallback( void* arg, unsigned flags, double secs, const msg_t* m )
|
|
{
|
|
test_arg_t* t = (test_arg_t*)arg;
|
|
|
|
if( t->printMsgsFl && m->typeId == kMidiMsgTId )
|
|
{
|
|
printf("%6.3f %2x %4i : %2i %2x %3i %3i :", secs, flags, m->u.midi.uid, m->u.midi.ch, m->u.midi.status, m->u.midi.d0, m->u.midi.d1 );
|
|
for(unsigned i=0; flag_map[i].label!=nullptr; ++i)
|
|
if( cwIsFlag(flags,flag_map[i].flag) )
|
|
printf("%s ",flag_map[i].label);
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
void _testPrintAllFlags()
|
|
{
|
|
unsigned strCharN = flags_to_string_max_string_length();
|
|
char str[ strCharN ];
|
|
rc_t rc = flags_to_string( 0xffffffff, str, strCharN );
|
|
printf("All Flags: %i %i %li : %s\n", rc, strCharN, strlen(str)+1, str );
|
|
}
|
|
|
|
void _testPrintNoteCount( handle_t h, uint8_t ch=0 )
|
|
{
|
|
const event_t* e = nullptr;
|
|
for(unsigned i=0; i<midi::kMidiNoteCnt; ++i)
|
|
{
|
|
unsigned n = 0;
|
|
if((e= note_event_list(h, ch, i )) != nullptr )
|
|
{
|
|
for(; e!=nullptr; e=e->link)
|
|
if( cwIsFlag(e->flags, kNoteOnFl ) )
|
|
++n;
|
|
|
|
printf("%3i %3i %3x %s\n",n,i,i,midi::midiToSciPitch(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
void _testPrintNoteEvent( handle_t h, uint8_t ch, uint8_t pitch )
|
|
{
|
|
const event_t* e;
|
|
if((e= note_event_list(h, ch, pitch )) == nullptr )
|
|
{
|
|
cwLogError(kInvalidArgRC,"There are no notes for pitch %i.",pitch);
|
|
}
|
|
else
|
|
{
|
|
unsigned flagStrCharN = flags_to_string_max_string_length();
|
|
char flagStr[ flagStrCharN ];
|
|
for(; e!=nullptr; e=e->link)
|
|
{
|
|
flags_to_string( e->flags, flagStr, flagStrCharN );
|
|
printf("%6.3f %s\n",e->secs, flagStr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _testPrintOrderedNoteEvent( handle_t h, uint8_t ch, uint8_t pitch )
|
|
{
|
|
midi_state_t* p = _handleToPtr(h);
|
|
event_chain_t* ec = _note_event_chain(p,ch,pitch);
|
|
|
|
if( ec->begEvt == nullptr )
|
|
{
|
|
cwLogWarning("Pitch %i on channel %i has no events.",pitch,ch);
|
|
}
|
|
else
|
|
{
|
|
event_t* e;
|
|
unsigned flagStrCharN = flags_to_string_max_string_length();
|
|
char flagStr[ flagStrCharN+1 ];
|
|
|
|
_seek_chain_iterator( p, ec->begEvt->secs );
|
|
|
|
while((e = _step_chain_iterator(p)) != nullptr )
|
|
{
|
|
// print all non-note (pedal & marker) events - but skip no-change events
|
|
bool non_note_fl = cwIsNotFlag(e->flags,kNoteEvtFl) && cwIsNotFlag(e->flags,kNoChangeFl);
|
|
|
|
// this is a note on the requested pitch
|
|
bool pitch_fl = cwIsFlag(e->flags,kNoteEvtFl) && e->msg->u.midi.d0 == pitch;
|
|
|
|
unsigned uid = e->msg->typeId == kMidiMsgTId ? e->msg->u.midi.uid : 0;
|
|
|
|
if( pitch_fl || non_note_fl )
|
|
{
|
|
flags_to_string( e->flags, flagStr, flagStrCharN );
|
|
printf("%5i %6.3f %s\n",uid, e->secs, flagStr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void _testPrintTimeLinkedNoteEvent( handle_t h, uint8_t ch, uint8_t pitch )
|
|
{
|
|
unsigned flagStrCharN = flags_to_string_max_string_length();
|
|
char flagStr[ flagStrCharN+1 ];
|
|
|
|
for(const event_t* e=get_first_link(h); e!=nullptr; e=e->tlink)
|
|
{
|
|
// print all non-note (pedal & marker) events - but skip no-change events
|
|
bool non_note_fl = cwIsNotFlag(e->flags,kNoteEvtFl) && cwIsNotFlag(e->flags,kNoChangeFl);
|
|
|
|
// this is a note on the requested pitch
|
|
bool pitch_fl = cwIsFlag(e->flags,kNoteEvtFl) && e->msg->u.midi.d0 == pitch;
|
|
|
|
unsigned uid = e->msg->typeId == kMidiMsgTId ? e->msg->u.midi.uid : 0;
|
|
|
|
if( pitch_fl || non_note_fl )
|
|
{
|
|
flags_to_string( e->flags, flagStr, flagStrCharN );
|
|
printf("%5i %6.3f %s\n",uid, e->secs, flagStr);
|
|
}
|
|
}
|
|
}
|
|
|
|
rc_t _testPrintPedalEvent( handle_t h, uint8_t ch, uint8_t pedalCtlId )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
const event_t* e = nullptr;
|
|
unsigned pedal_idx = kInvalidIdx;
|
|
|
|
if((pedal_idx = pedal_midi_ctl_id_to_index( pedalCtlId )) == kInvalidIdx )
|
|
{
|
|
rc = cwLogError(rc,"%i is not a valid pedal control id",pedalCtlId);
|
|
}
|
|
|
|
if((e= pedal_event_list(h, ch, pedal_idx )) == nullptr )
|
|
{
|
|
cwLogWarning("There are no events for pedal idx:%i ctl:%i.",pedal_idx,pedalCtlId);
|
|
}
|
|
else
|
|
{
|
|
unsigned flagStrCharN = flags_to_string_max_string_length();
|
|
char flagStr[ flagStrCharN ];
|
|
for(; e!=nullptr; e=e->link)
|
|
if( cwIsNotFlag(e->flags,kNoChangeFl) )
|
|
{
|
|
flags_to_string( e->flags, flagStr, flagStrCharN );
|
|
printf("%6.3f %s\n",e->secs, flagStr);
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
cw::rc_t cw::midi_state::test( const object_t* cfg )
|
|
{
|
|
rc_t rc = kOkRC;
|
|
const char* midi_fname = nullptr;
|
|
bool cache_enable_fl = false;
|
|
const object_t* args = nullptr;
|
|
unsigned mN = 0;
|
|
const midi::file::trackMsg_t** mA = nullptr;
|
|
midi::file::handle_t mfH;
|
|
midi_state::handle_t msH;
|
|
test_arg_t test_arg = { .printMsgsFl=false };
|
|
|
|
if((rc = cfg->getv("midi_fname",midi_fname,
|
|
"cache_enable_fl", cache_enable_fl,
|
|
"args", args)) != kOkRC )
|
|
{
|
|
cwLogError(rc,"MIDI state cfg. parse failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
// create the midi_state object
|
|
if((rc = midi_state::create(msH,
|
|
_testCallback,
|
|
&test_arg,
|
|
args )) != kOkRC )
|
|
{
|
|
cwLogError(rc,"MIDI state object create failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
// open a MIDI file
|
|
if((rc = midi::file::open(mfH,midi_fname)) != kOkRC )
|
|
{
|
|
cwLogError(rc,"MIDI file '%s' open failed.",cwStringNullGuard(midi_fname));
|
|
goto errLabel;
|
|
}
|
|
|
|
mN = msgCount(mfH);
|
|
mA = msgArray(mfH);
|
|
|
|
// update the MIDI state from the MIDI file - and print the changing state events from _testCallback()
|
|
for(unsigned i=0; i<mN; ++i)
|
|
{
|
|
const midi::file::trackMsg_t* m = mA[i];
|
|
|
|
switch( m->status )
|
|
{
|
|
case midi::kNoteOnMdId:
|
|
case midi::kNoteOffMdId:
|
|
case midi::kCtlMdId:
|
|
{
|
|
double sec = m->amicro / 1000000.0;
|
|
setMidiMsg( msH, sec, m->uid, m->u.chMsgPtr->ch, m->status, m->u.chMsgPtr->d0, m->u.chMsgPtr->d1 );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//_testPrintNoteCount(msH);
|
|
//_testPrintAllFlags();
|
|
//_testPrintPedalEvent(msH, 0, midi::kSustainCtlMdId );
|
|
//_testPrintNoteEvent(msH, 0, 60 );
|
|
//_testPrintOrderedNoteEvent( msH, 0, 60 );
|
|
_testPrintTimeLinkedNoteEvent( msH, 0, 33 );
|
|
|
|
errLabel:
|
|
|
|
// close the MIDI file
|
|
if((rc = close(mfH)) != kOkRC )
|
|
{
|
|
cwLogError(rc,"MIDI file '%s' close failed.",cwStringNullGuard(midi_fname));
|
|
}
|
|
|
|
// close the midi_state object
|
|
if((rc = destroy(msH)) != kOkRC )
|
|
{
|
|
rc = cwLogError(rc,"MIDI state object destroy failed.");
|
|
goto errLabel;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|