diff --git a/Makefile.am b/Makefile.am index 37f9288..eb04920 100644 --- a/Makefile.am +++ b/Makefile.am @@ -67,8 +67,8 @@ libcwSRC += src/libcw/cwIoMidiRecordPlay.cpp src/libcw/cwIoAudioRecordPlay.cpp libcwHDR += src/libcw/cwIoPresetSelApp.h src/libcw/cwPianoScore.h src/libcw/cwPresetSel.h src/libcw/cwVelTableTuner.h libcwSRC += src/libcw/cwIoPresetSelApp.cpp src/libcw/cwPianoScore.cpp src/libcw/cwPresetSel.cpp src/libcw/cwVelTableTuner.cpp -libcwHDR += src/libcw/cwCmInterface.h src/libcw/cwScoreFollower.h -libcwSRC += src/libcw/cwCmInterface.cpp src/libcw/cwScoreFollower.cpp +libcwHDR += src/libcw/cwCmInterface.h src/libcw/cwScoreFollower.h src/libcw/cwMidiState.h src/libcw/cwSvgMidi.h src/libcw/cwSvgScore.h +libcwSRC += src/libcw/cwCmInterface.cpp src/libcw/cwScoreFollower.cpp src/libcw/cwMidiState.cpp src/libcw/cwSvgMidi.cpp src/libcw/cwSvgScore.cpp endif diff --git a/cwMidiState.cpp b/cwMidiState.cpp new file mode 100644 index 0000000..1a368bf --- /dev/null +++ b/cwMidiState.cpp @@ -0,0 +1,1245 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.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 + { + callback_t cbFunc; + void* cbArg; + + unsigned cacheBlockMsgN; + bool cacheEnableFl; + + unsigned pedalUpMidiValue; + unsigned pedalHalfMinMidiValue; + unsigned pedalHalfMaxMidiValue; + + 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->noteChains + (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)); + } + + // 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; + } + + + 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->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->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 ) + { + if( p->cbFunc != nullptr ) + p->cbFunc( p->cbArg, flags, secs, m ); + + if( p->cacheEnableFl ) + { + cwAssert( cwIsFlag( flags,kPedalEvtFl | kNoteEvtFl ) ); + 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->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 ) + { + c->noteState[i].sndGateFl = 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.d1].sndGateFl && (c->dampState == kUpPedalStateId && c->noteState[m->u.midi.d1].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->pedalHalfMinMidiValue ) + { + dampState = kUpPedalStateId; + flags = kUpPedalFl; + } + else + { + // if the pedal is in the half pedal band + if( m->u.midi.d1 <= p->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->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->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; + } + + 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 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::create( handle_t& hRef, + callback_t cbFunc, // set to nullptr to disable callbacks + void* cbArg, + bool cacheEnableFl, + unsigned cacheBlockMsgN, // set to 0 to disable caching + unsigned pedalUpMidiValue, + unsigned pedalHalfMinMidiValue, + unsigned pedalHalfMaxMidiValue ) +{ + rc_t rc; + if((rc = destroy(hRef)) != kOkRC ) + return rc; + + midi_state_t* p = mem::allocZ(); + + p->cbFunc = cbFunc; + p->cbArg = cbArg; + p->cacheEnableFl = cacheEnableFl; + p->cacheBlockMsgN = cacheBlockMsgN; + p->pedalUpMidiValue = pedalUpMidiValue; + p->pedalHalfMinMidiValue = pedalHalfMinMidiValue; + p->pedalHalfMaxMidiValue = pedalHalfMaxMidiValue; + + 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, + bool cacheEnableFl, + const object_t* cfg ) +{ + rc_t rc = kOkRC; + unsigned cache_block_msg_cnt = 128; + unsigned pedal_up_midi_value = 40; + unsigned pedal_half_min_midi_value = 40; + unsigned pedal_half_max_midi_value = 50; + + if((rc = cfg->getv("cache_block_msg_count",cache_block_msg_cnt, + "pedal_up_midi_value",pedal_up_midi_value, + "pedal_half_min_midi_value",pedal_half_min_midi_value, + "pedal_half_max_midi_value",pedal_half_max_midi_value)) != kOkRC ) + { + cwLogError(rc,"MIDI state cfg. parse failed."); + goto errLabel; + } + + rc = create( hRef, + cbFunc, + cbArg, + cacheEnableFl, + cache_block_msg_cnt, + pedal_up_midi_value, + pedal_half_min_midi_value, + pedal_half_max_midi_value ); + + 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->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( d0 > 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->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); + if( p->first_event == nullptr ) + { + event_t* e0 = nullptr; + event_t* e1 = nullptr; + _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; +} + + +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; +} + + +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, + cache_enable_fl, + 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, 60 ); + + 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; +} + diff --git a/cwMidiState.h b/cwMidiState.h new file mode 100644 index 0000000..86d261e --- /dev/null +++ b/cwMidiState.h @@ -0,0 +1,167 @@ +#ifndef cwMidiState_h +#define cwMidiState_h + +namespace cw +{ + namespace midi_state + { + // This object receives MIDI note and pedal (sustain,sostenuto,soft) messages + // and generates voice and pedal control information. + // Primarily it maintains the state of the voice associated with each + // MIDI channel and pitch and notifies the client of the changes to + // that state as various MIDI messages arrive. + // The primary features are: + // + // 1. Maintains note gate and pedal gate information to determine + // when a note is sounding. For example a note gate may be off + // but the voice may still be sounding due to the state of the + // sustain or sostenuto pedal. + // + // 2. Handles reporting 'half' pedal state on the sustain pedal. + // + // 3. Reports 're-attacks' when a note gate is on and receives + // another note-on messages. + // + // 4. Report 'no-change' state when a MIDI message is received + // but does not change the state of the voice or pedal. + // These messages often indicate a problem with the MIDI stream + // format. + + // TODO: + // 1. automatically decrease note-on velocity when soft pedal is down + // + // 2. handle legato and portmento controls + // + // 3. send a 'decay rate' based on the state of the pedals + // when when the note gate goes off (i.e. if the note is being + // held decrease the decay rate. + // + // 4. automatically send note-off / snd-off to all voices that + // are sounding on 'reset()'. + // + // 5. Allow sending arbitrary time markes (e.g. bars and sections) + // though this mechanism so that they can be ordered with the + // MIDI events + + typedef handle handle_t; + + enum { + kNoteOnFl = 0x0001, // note-on + kNoteOffFl = 0x0002, // note-off + kSoundOnFl = 0x0004, // note-on + kSoundOffFl = 0x0008, // note-off,sustain up,sostenuto up + kReattackFl = 0x0010, // note-on (when note gate is already on) + kSoftPedalFl = 0x0020, // soft pedal up/down (also accompanies note-on msg's when the pedal is down) + kUpPedalFl = 0x0040, // soft,sustain,sost pedal up + kHalfPedalFl = 0x0080, // sustain pedal entered half pedal range (also sent to all sounding notes) + kDownPedalFl = 0x0100, // soft,sustain,sost pedal down + kNoChangeFl = 0x0200, // a midi-msg arrived but did not cause a change in note or pedal state + kPedalEvtFl = 0x0400, // This is a pedal event + kNoteEvtFl = 0x0800, // This is a note event + kMarkerEvtFl = 0x1000 // This is a marker event (bar or section) + }; + + typedef struct midi_msg_str + { + unsigned uid; // midi event unique id + uint8_t ch; // 0-15 + uint8_t status; // status w/o channel + uint8_t d0; // + uint8_t d1; // + } midi_msg_t; + + typedef struct marker_msg_str + { + unsigned uid; + unsigned typeId; // marker type id + unsigned value; // marker value + uint8_t ch; + uint8_t pad[3]; + } marker_msg_t; + + enum { + kMidiMsgTId, + kMarkerMsgTId + }; + + typedef struct msg_str + { + unsigned typeId; + union { + midi_msg_t midi; + marker_msg_t marker; + } u; + } msg_t; + + // Cached event record + typedef struct event_str + { + unsigned flags; // see flags above + double secs; // time of event + const msg_t* msg; // accompanying midi info + struct event_str* link; // null terminated list of following events + struct event_str* tlink; // time order link + } event_t; + + const char* flag_to_label( unsigned flag ); + + + // Returned string must be released via mem::release() + unsigned flags_to_string_max_string_length(); + rc_t flags_to_string( unsigned flags, char* str, unsigned strCharCnt ); + + // Note that if the cache is not enabled then 'msg' is only valid during the callback and + // therefore should not be stored. If the cache is enabled then 'msg' is valid until + // the next call to 'reset()' or until the midi_state_t instance is destroyed. + typedef void (*callback_t)( void* arg, unsigned flags, double secs, const msg_t* msg ); + + rc_t create( handle_t& hRef, + callback_t cbFunc, // set to nullptr to disable callbacks + void* cbArg, // callback arg + bool cacheEnableFl, // enable/disable event caching + unsigned cacheBlockMsgN, // count of cache messages to pre-allocate in each block + unsigned pedalHalfMinMidiValue, // sustain half pedal lower value + unsigned pedalHalfMaxMidialue, // sustain pedal half pedal upper value + unsigned pedalUpMidiValue = 64 );// soft and sostenuto pedal down min value + + rc_t create( handle_t& hRef, + callback_t cbFunc, // set to nullptr to disable callbacks + void* cbArg, // callback arg + bool cacheEnableFl, // + const object_t* cfg ); // + + + rc_t destroy( handle_t& hRef ); + + rc_t setMidiMsg( handle_t h, double sec, unsigned uid, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 ); + rc_t setMarker( handle_t h, double sec, unsigned uid, uint8_t ch, unsigned typeId, unsigned value ); + + void reset( handle_t h ); + + // Get the first link in time, then use event.tlink to traverse the + // events in time. + const event_t* get_first_link( handle_t h ); + + // Return cached events. + // Note that the cached events are returned as a link list based on each channel note and pedal. + // This list is ordered in time and gives the recorded state of the voice/pedal. + const event_t* note_event_list( handle_t h, uint8_t ch, uint8_t pitch ); + const event_t* pedal_event_list( handle_t h, uint8_t ch, unsigned pedal_index ); + + // Returns kInvalidIdx if 'midi_ctl_id' is not a valid MIDi pedal control id. + unsigned pedal_midi_ctl_id_to_index( unsigned midi_ctl_id ); + unsigned pedal_index_to_midi_ctl_id( unsigned pedal_idx ); + unsigned pedal_count( handle_t h ); + + void get_note_extents( handle_t h, uint8_t& minPitchRef, uint8_t& maxPitchRef, double& minSecRef, double& maxSecRef ); + void get_pedal_extents( handle_t h, double& minSecRef, double& maxSecRef ); + + + rc_t load_from_midi_file( handle_t h, const char* midi_fname ); + + rc_t test( const object_t* cfg ); + + } +} + +#endif diff --git a/cwSvgMidi.cpp b/cwSvgMidi.cpp new file mode 100644 index 0000000..069c522 --- /dev/null +++ b/cwSvgMidi.cpp @@ -0,0 +1,469 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwObject.h" +#include "cwTime.h" +#include "cwMidi.h" +#include "cwMidiFile.h" +#include "cwPianoScore.h" +#include "cwSvg.h" +#include "cwMidiState.h" +#include "cwSvgMidi.h" + +#define PEDAL_COUNT 3 +#define SUST_PEDAL_IDX 0 +#define SOST_PEDAL_IDX 1 +#define SOFT_PEDAL_IDX 2 + +#define PIX_PER_SEC 100.0 +#define NOTE_HEIGHT 15.0 + +namespace cw +{ + namespace svg_midi + { + + rc_t _write_svg_rect( svg::handle_t svgH, double secs0, double secs1, double y, const char* label, unsigned color ) + { + rc_t rc; + double x = secs0 * PIX_PER_SEC; + double ww = secs1 * PIX_PER_SEC - x; + double hh = NOTE_HEIGHT; + + if((rc = svg::rect( svgH, x, y*NOTE_HEIGHT, ww, hh, "fill", color, "rgb" )) != kOkRC ) + { + rc = cwLogError(rc,"Error creating SVG rect."); + goto errLabel; + } + + if((rc = svg::text( svgH, x, y*NOTE_HEIGHT+NOTE_HEIGHT, label )) != kOkRC ) + { + rc = cwLogError(rc,"Error writing SVG rect label."); + goto errLabel; + } + + errLabel: + return rc; + } + + rc_t _write_svg_vert_line( svg::handle_t svgH, double sec, unsigned color, unsigned minMidiPitch, unsigned maxMidiPitch ) + { + rc_t rc = kOkRC; + + if((rc = line(svgH, sec*PIX_PER_SEC, 0, sec*PIX_PER_SEC, (maxMidiPitch-minMidiPitch)*NOTE_HEIGHT, "stroke", color, "rgb")) != kOkRC ) + { + rc = cwLogError(rc,"Error writing SVG line."); + goto errLabel; + + } + + errLabel: + return rc; + } + + rc_t _write_svg_horz_line( svg::handle_t svgH, double sec0, double sec1, double y, unsigned color ) + { + rc_t rc = kOkRC; + + if((rc = line(svgH, sec0*PIX_PER_SEC, y*NOTE_HEIGHT, sec1*PIX_PER_SEC, y*NOTE_HEIGHT, "stroke", color, "rgb")) != kOkRC ) + { + rc = cwLogError(rc,"Error writing SVG line."); + goto errLabel; + + } + + errLabel: + return rc; + } + + + void _write_note_rect( svg::handle_t svgH, const midi_state::event_t* e0, const midi_state::event_t* e1, unsigned minMidiPitch, unsigned maxMidiPitch ) + { + cwAssert( e0!=nullptr && e1!=nullptr && e0->msg != nullptr && e1->msg !=nullptr); + + const char* sciPitch = midi::midiToSciPitch( e0->msg->u.midi.d0 ); + const unsigned labelCharN = 127; + char label[ labelCharN+1 ]; + + + unsigned muid = e0->msg->u.midi.uid; + snprintf(label,labelCharN,"%s - %i",sciPitch,muid); + + double y = -1.0 * (e0->msg->u.midi.d0 - minMidiPitch) + (maxMidiPitch - minMidiPitch); + + _write_svg_rect( svgH, e0->secs, e1->secs, y, label, 0xafafaf ); + + } + + void _write_sound_line( svg::handle_t svgH, const midi_state::event_t* e0, const midi_state::event_t* e1, unsigned minMidiPitch, unsigned maxMidiPitch ) + { + + cwAssert( e0!=nullptr && e1!=nullptr && e0->msg != nullptr && e1->msg !=nullptr); + + double y = -1.0 * (e0->msg->u.midi.d0 - minMidiPitch) + (maxMidiPitch - minMidiPitch); + + _write_svg_horz_line( svgH, e0->secs, e1->secs, y, 0xafafaf ); + } + + + void _write_svg_ch_note( svg::handle_t svgH, const midi_state::event_t* e0, unsigned minMidiPitch, unsigned maxMidiPitch ) + { + const midi_state::event_t* e = e0; + const midi_state::event_t* n0 = nullptr; + const midi_state::event_t* s0 = nullptr; + + for(; e!=nullptr; e=e->link) + { + if( cwIsFlag(e->flags,midi_state::kNoteOffFl) ) + { + if( n0 == nullptr ) + { + // consecutive note off msgs + } + else + { + _write_note_rect( svgH, n0, e, minMidiPitch, maxMidiPitch ); + } + + n0 = nullptr; + } + + if( cwIsFlag(e->flags,midi_state::kNoteOnFl) ) + { + // if note on without note-off + if( n0 != nullptr ) + { + // TODO: check for reattack flag + _write_note_rect( svgH, n0, e, minMidiPitch, maxMidiPitch ); + } + + n0 = e; + } + + + if( cwIsFlag(e->flags,midi_state::kSoundOnFl) ) + { + if( s0 != nullptr ) + { + // consecutive sound on msgs + _write_sound_line( svgH, s0, e, minMidiPitch, maxMidiPitch ); + } + + s0 = e; + } + + if( cwIsFlag(e->flags,midi_state::kSoundOffFl) ) + { + if( s0 == nullptr ) + { + // consecutive off msgs + } + else + { + _write_sound_line( svgH, s0, e, minMidiPitch, maxMidiPitch ); + } + + s0 = nullptr; + } + } + + } + + /* + rc_t _write_svg_pedal( svg_midi_t* p, svg::handle_t svgH, const graphic_evt_t* ge, unsigned minMidiPitch, unsigned maxMidiPitch ) + { + rc_t rc = kOkRC; + const char* label = nullptr; + unsigned pedal_id = 0; + unsigned color; + + switch( ge->beg_evt->d0 & 0xf0 ) + { + case midi::kSustainCtlMdId: + label = "damp"; + pedal_id = 0; + color = 0xf4a460; + break; + + case midi::kSostenutoCtlMdId: + label = "sost"; + pedal_id = 1; + color = 0x7fffd4; + break; + + case midi::kSoftPedalCtlMdId: + label = "soft"; + pedal_id = 2; + color = 0x98fb98; + break; + } + + + double y = (maxMidiPitch - minMidiPitch) + 1 + pedal_id; + + if((rc = _write_svg_rect( p, svgH, ge, y, label, color )) != kOkRC ) + { + rc = cwLogError(rc,"Error writing SVG pedal rect."); + goto errLabel; + } + + if((rc = _write_svg_line( p, svgH, ge->beg_evt->time, color, minMidiPitch, maxMidiPitch )) != kOkRC ) + { + rc = cwLogError(rc,"Error writing SVG pedal begin line."); + goto errLabel; + } + + if((rc = _write_svg_line( p, svgH, ge->end_evt->time, color, minMidiPitch, maxMidiPitch )) != kOkRC ) + { + rc = cwLogError(rc,"Error writing SVG pedal end line."); + goto errLabel; + } + + errLabel: + return rc; + } + */ + + void _write_svg_ch_pedal( svg::handle_t svgH, const midi_state::event_t* e, unsigned pedal_idx, unsigned minMidiPitch, unsigned maxMidiPitch ) + { + const midi_state::event_t* e0 = nullptr; + unsigned color = 0; + const char* label = nullptr; + switch( midi_state::pedal_index_to_midi_ctl_id(pedal_idx) ) + { + case midi::kSustainCtlMdId: + label = "damp"; + color = 0xf4a460; + break; + + case midi::kSostenutoCtlMdId: + label = "sost"; + color = 0x7fffd4; + break; + + case midi::kSoftPedalCtlMdId: + label = "soft"; + color = 0x98fb98; + break; + default: + assert(0); + } + + for(; e!=nullptr; e=e->link) + { + if( cwIsFlag(e->flags,midi_state::kNoChangeFl) ) + continue; + + if( cwIsFlag(e->flags,midi_state::kDownPedalFl) ) + { + if( e0 != nullptr ) + { + // two consecutive pedal downd - this shouldn't be possible + } + else + { + e0 = e; + } + } + + if( cwIsFlag(e->flags,midi_state::kUpPedalFl)) + { + if( e0 == nullptr ) + { + // two consecutive pedal up's + } + else + { + + // two consecutve pedal ups - this shouldn't be possible + double y = (maxMidiPitch - minMidiPitch) + 1 + pedal_idx; + + _write_svg_rect( svgH, e0->secs, e->secs, y, label, color ); + e0 = nullptr; + } + } + } + } + + + } +} + + + + +cw::rc_t cw::svg_midi::write( const char* fname, midi_state::handle_t msH ) +{ + rc_t rc = kOkRC; + uint8_t minMidiPitch = midi::kMidiNoteCnt; + uint8_t maxMidiPitch = 0; + const midi_state::event_t* evt = nullptr; + + double minSec = 0.0; + double maxSec = 0.0; + + svg::handle_t svgH; + + get_note_extents( msH, minMidiPitch, maxMidiPitch, minSec, maxSec ); + + // create the SVG file object + if((rc = svg::create(svgH)) != kOkRC ) + { + rc = cwLogError(rc,"SVG file object create failed."); + goto errLabel; + } + + // create the note graphics + for(uint8_t i=0; isec); + + if( e->bar != 0 ) + _setEvent(p, kBarTypeId, e->bar, timestamp, i, 0,0,0 ); + + if( e->section != 0 ) + _setEvent(p, kSectionTypeId, e->section, timestamp, i, 0,0,0 ); + + if( e->status != 0 ) + _setEvent(p, kMidiTypeId, 0, timestamp, i, 0,0,0 ); + } + } + + errLabel: + destroy(scH); + + return rc; +} +*/ + +cw::rc_t cw::svg_midi::midi_to_svg_file( const char* midi_fname, const char* out_fname, const object_t* midi_state_args ) +{ + rc_t rc = kOkRC; + midi_state::handle_t msH; + + + // create the MIDI state object - with caching turned on + if((rc = midi_state::create( msH, nullptr, nullptr, true, midi_state_args )) != kOkRC ) + { + rc = cwLogError(rc,"Error creating the midi_state object."); + goto errLabel; + } + + // load the MIDI file + if((rc = midi_state::load_from_midi_file( msH, midi_fname)) != kOkRC ) + { + rc = cwLogError(rc,"Error loading midi file into midi_state object."); + goto errLabel; + } + + // write the SVG file + if((rc = write(out_fname,msH)) != kOkRC ) + { + rc = cwLogError(rc,"Error write the SVG-MIDI file."); + goto errLabel; + } + + errLabel: + destroy(msH); + return rc; +} +/* +cw::rc_t cw::svg_midi::piano_score_to_svg_file( const char* piano_score_fname, const char* out_fname, unsigned midiMsgCacheCnt, unsigned pedalUpMidiValue ) +{ + rc_t rc = kOkRC; + handle_t h; + + // create the SVG-MIDI object + if((rc = create(h,midiMsgCacheCnt,pedalUpMidiValue)) != kOkRC ) + { + rc = cwLogError(rc,"Error creating the SVG-MIDI object."); + goto errLabel; + } + + // load the MIDI file msg events into the svg-midi cache + if((rc = load_from_piano_score(h, piano_score_fname)) != kOkRC ) + { + rc = cwLogError(rc,"Error loading the piano score file."); + goto errLabel; + } + + // write the SVG file + if((rc = write(h,out_fname)) != kOkRC ) + { + rc = cwLogError(rc,"Error write the SVG-MIDI file."); + goto errLabel; + } + + errLabel: + destroy(h); + + return rc; +} +*/ + +cw::rc_t cw::svg_midi::test_midi_file( const object_t* cfg ) +{ + rc_t rc; + const char* midi_fname = nullptr; + const char* out_fname = nullptr; + const object_t* midi_state_args = nullptr; + + if((rc = cfg->getv( "midi_fname", midi_fname, + "out_fname", out_fname, + "midi_state_args",midi_state_args)) != kOkRC ) + { + rc = cwLogError(rc,"Error parsing svg_midi::test_midi_file() arguments."); + goto errLabel; + } + + rc = midi_to_svg_file( midi_fname, out_fname, midi_state_args ); + + errLabel: + return rc; +} diff --git a/cwSvgMidi.h b/cwSvgMidi.h new file mode 100644 index 0000000..2ba1b0a --- /dev/null +++ b/cwSvgMidi.h @@ -0,0 +1,18 @@ +#ifndef cwSvgMidi_h +#define cwSvgMidi_h + +namespace cw +{ + namespace svg_midi + { + rc_t write( const char* fname, midi_state::handle_t msH ); + + rc_t midi_to_svg_file( const char* midi_fname, const char* out_fname, const object_t* midi_state_args ); + //rc_t piano_score_to_svg_file( const char* piano_score_fname, const char* out_fname, const object_t* cfg ); + + rc_t test_midi_file( const object_t* cfg ); + + } +} + +#endif diff --git a/cwSvgScore.cpp b/cwSvgScore.cpp new file mode 100644 index 0000000..3d57324 --- /dev/null +++ b/cwSvgScore.cpp @@ -0,0 +1,126 @@ +#include "cwCommon.h" +#include "cwLog.h" +#include "cwCommonImpl.h" +#include "cwMem.h" +#include "cwObject.h" +#include "cwPianoScore.h" +#include "cwSvg.h" +#include "cwSvgScore.h" + +namespace cw +{ + namespace svg_score + { + + typedef struct svg_score_str + { + score::handle_t pianoScoreH; + + } svg_score_t; + + svg_score_t* _handleToPtr( handle_t h ) + { + return handleToPtr(h); + } + + rc_t _destroy( svg_score_t* p ) + { + rc_t rc = kOkRC; + mem::release(p); + return rc; + } + + } +} + +cw::rc_t cw::svg_score::create( handle_t& hRef ) +{ + rc_t rc; + if((rc = destroy(hRef)) != kOkRC ) + return rc; + + svg_score_t* p = mem::allocZ(); + + //errLabel: + if(rc != kOkRC ) + _destroy(p); + + return rc; +} + +cw::rc_t cw::svg_score::destroy( handle_t& hRef ) +{ + rc_t rc = kOkRC; + + if( !hRef.isValid() ) + return rc; + + svg_score_t* p = _handleToPtr(hRef); + + if((rc = _destroy(p)) != kOkRC ) + return rc; + + hRef.clear(); + + return rc; +} + +cw::rc_t cw::svg_score::setPianoScore( handle_t h, score::handle_t pianoScH ) +{ + svg_score_t* p = _handleToPtr(h); + p->pianoScoreH = pianoScH; + return kOkRC; +} + +cw::rc_t cw::svg_score::write( handle_t h, const char* outFname, const char* cssFname ) +{ + rc_t rc = kOkRC; + return rc; +} + +cw::rc_t cw::svg_score::write( const object_t* cfg ) +{ + rc_t rc = kOkRC; + const char* piano_score_fname = nullptr; + const char* cm_score_fname = nullptr; + const char* css_fname = nullptr; + const char* out_fname = nullptr; + score::handle_t pianoScoreH; + svg_score::handle_t svgScoreH; + + if((rc = cfg->getv("piano_score_fname",piano_score_fname, + "cm_score_fname",cm_score_fname, + "css_fname",css_fname, + "out_fname",out_fname)) != kOkRC ) + { + rc = cwLogError(rc,"Error parsing arguments."); + goto errLabel; + } + + if((rc = create(svgScoreH)) != kOkRC ) + { + rc = cwLogError(rc,"Error creating svg score."); + goto errLabel; + } + + if((rc = create(pianoScoreH,piano_score_fname)) != kOkRC ) + { + rc = cwLogError(rc,"Error opening piano score."); + goto errLabel; + } + + if((rc = setPianoScore(svgScoreH,pianoScoreH)) != kOkRC ) + { + rc = cwLogError(rc,"Error on setPianoScore()."); + goto errLabel; + } + + errLabel: + + destroy(svgScoreH); + + destroy(pianoScoreH); + + return rc; + +} diff --git a/cwSvgScore.h b/cwSvgScore.h new file mode 100644 index 0000000..fd0171b --- /dev/null +++ b/cwSvgScore.h @@ -0,0 +1,22 @@ +#ifndef cwSvgScore_h +#define cwSvgScore_h + +namespace cw +{ + namespace svg_score + { + typedef handle handle_t; + + rc_t create( handle_t& hRef ); + rc_t destroy( handle_t& hRef ); + + rc_t setPianoScore( handle_t h, score::handle_t pianoScH ); + + rc_t write( handle_t h, const char* outFname, const char* cssFname ); + + rc_t write( const object_t* cfg ); + } +} + + +#endif