//| Copyright: (C) 2020-2024 Kevin Larke //| 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(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; iiter = ec->begEvt; } for(unsigned i=0; iiter = 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; iiter = _goto_event_greater_than_or_equal( ec->begEvt, secs ); } for(unsigned j=0; jiter = _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; iiter != nullptr && (ec0==nullptr || ec->iter->secs < ec0->iter->secs) ) ec0 = ec; } for(uint8_t j=0; jiter != 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; inext_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; ibegEvt = nullptr; _note_event_chain(p, i, j )->endEvt = nullptr; } for(unsigned j=0; jbegEvt = 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(); mc->msgN = p->cfg.cacheBlockMsgN; mc->msgA = mem::allocZ(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(); ec->eventN = p->cfg.cacheBlockMsgN; ec->eventA = mem::allocZ(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; inoteState[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; inoteState[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; inoteState[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; inoteState[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(); if( cfg == nullptr ) cfg = &default_config(); p->cbFunc = cbFunc; p->cbArg = cbArg; p->cfg = *cfg; for(unsigned i=0; ichState[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; ilink) { 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; ilink) { 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; istatus) ) { 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; ilink) 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; istatus ) { 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; }