From 20f631afc5896d7e4cac22bd30e5b146a980309d Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 20 Mar 2022 10:27:46 -0400 Subject: [PATCH] cwIoMidiREcordPlay,cwIoPresetSelApp : Updated to include half-pedal experimentation procedure, and separate velocity tables for each MIDI device. --- cwIoMidiRecordPlay.cpp | 496 ++++++++++++++++++++++++++++++----------- cwIoMidiRecordPlay.h | 4 + cwIoPresetSelApp.cpp | 146 +++++++++++- html/preset_sel/ui.cfg | 9 + 4 files changed, 521 insertions(+), 134 deletions(-) diff --git a/cwIoMidiRecordPlay.cpp b/cwIoMidiRecordPlay.cpp index fee95ff..9396c66 100644 --- a/cwIoMidiRecordPlay.cpp +++ b/cwIoMidiRecordPlay.cpp @@ -41,28 +41,65 @@ namespace cw unsigned midiOutDevIdx; unsigned midiOutPortIdx; bool enableFl; + unsigned velTableN; + uint8_t* velTableArray; + bool pedalMapEnableFl; + unsigned pedalDownVelId; + unsigned pedalDownHalfVelId; + unsigned pedalDownVel; + unsigned pedalDownHalfVel; } midi_device_t; - + + enum + { + kHalfPedalDone, + kWaitForBegin, + kWaitForNoteOn, + kWaitForNoteOff, + kWaitForPedalUp, + kWaitForPedalDown, + }; + + typedef struct midi_record_play_str { io::handle_t ioH; am_midi_msg_t* msgArray; // msgArray[ msgArrayN ] unsigned msgArrayN; // Count of messages allocated in msgArray. - unsigned msgArrayInIdx; // Next available space for incoming MIDI messages (also the current count of msgs in msgArray[]) + unsigned msgArrayInIdx; // Next available space for loaded MIDI messages (also the current count of msgs in msgArray[]) unsigned msgArrayOutIdx; // Next message to transmit in msgArray[] unsigned midi_timer_period_micro_sec; // Timer period in microseconds + am_midi_msg_t* iMsgArray; // msgArray[ msgArrayN ] + unsigned iMsgArrayN; // Count of messages allocated in msgArray. + unsigned iMsgArrayInIdx; // Next available space for incoming MIDI messages (also the current count of msgs in msgArray[]) + + midi_device_t* midiDevA; unsigned midiDevN; bool startedFl; bool recordFl; bool thruFl; + bool logInFl; // log incoming message when not in 'record' mode. + bool logOutFl; // log outgoing messages + bool halfPedalFl; + unsigned halfPedalState; + unsigned halfPedalNextUs; + unsigned halfPedalNoteDelayUs; + unsigned halfPedalNoteDurUs; + unsigned halfPedalUpDelayUs; + unsigned halfPedalDownDelayUs; + uint8_t halfPedalMidiPitch; + uint8_t halfPedalMidiNoteVel; + uint8_t halfPedalMidiPedalVel; + time::spec_t play_time; time::spec_t start_time; time::spec_t end_play_event_timestamp; + time::spec_t store_time; bool pedalFl; @@ -70,7 +107,7 @@ namespace cw void* cb_arg; } midi_record_play_t; - + enum { kMidiRecordPlayTimerId @@ -91,10 +128,12 @@ namespace cw { mem::release(p->midiDevA[i].midiOutDevLabel); mem::release(p->midiDevA[i].midiOutPortLabel); + mem::release(p->midiDevA[i].velTableArray); } mem::release(p->midiDevA); mem::release(p->msgArray); + mem::release(p->iMsgArray); mem::release(p); return rc; @@ -107,12 +146,16 @@ namespace cw if((rc = cfg.getv( "max_midi_msg_count", p->msgArrayN, "midi_timer_period_micro_sec", p->midi_timer_period_micro_sec, - "midi_device_list", midiDevL)) != kOkRC ) + "midi_device_list", midiDevL, + "log_in_flag", p->logInFl, + "log_out_flag", p->logOutFl, + "half_pedal_flag", p->halfPedalFl)) != kOkRC ) { rc = cwLogError(kSyntaxErrorRC,"MIDI record play configuration parse failed."); goto errLabel; } + p->iMsgArrayN = p->msgArrayN; if( midiDevL->child_count() > 0 ) { @@ -126,6 +169,8 @@ namespace cw const object_t* ele = midiDevL->child_ele(i); const char* midiOutDevLabel = nullptr; const char* midiOutPortLabel = nullptr; + const object_t* velTable = nullptr; + const object_t* pedalRecd = nullptr; bool enableFl = false; if((rc = ele->getv( "midi_out_device", midiOutDevLabel, @@ -135,70 +180,240 @@ namespace cw rc = cwLogError(kSyntaxErrorRC,"MIDI record play device list configuration parse failed."); goto errLabel; } + + if((rc = ele->getv_opt( "vel_table", velTable, + "pedal", pedalRecd)) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"MIDI record play device optional argument parsing failed."); + goto errLabel; + } + p->midiDevA[i].midiOutDevLabel = mem::duplStr( midiOutDevLabel); p->midiDevA[i].midiOutPortLabel = mem::duplStr( midiOutPortLabel); p->midiDevA[i].enableFl = enableFl; + + if( velTable != nullptr ) + { + p->midiDevA[i].velTableN = velTable->child_count(); + p->midiDevA[i].velTableArray = mem::allocZ(p->midiDevA[i].velTableN); + + + for(unsigned j=0; jmidiDevA[i].velTableN; ++j) + { + if((rc = velTable->child_ele(j)->value( p->midiDevA[i].velTableArray[j] )) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"An error occured while parsing the velocity table for MIDI device:'%s' port:'%s'.",midiOutDevLabel,midiOutPortLabel); + goto errLabel; + } + } + } + + if( pedalRecd != nullptr ) + { + if((rc = pedalRecd->getv( "down_id", p->midiDevA[i].pedalDownVelId, + "down_vel", p->midiDevA[i].pedalDownVel, + "half_id", p->midiDevA[i].pedalDownHalfVelId, + "half_vel", p->midiDevA[i].pedalDownHalfVel )) != kOkRC ) + { + rc = cwLogError(kSyntaxErrorRC,"An error occured while parsing the pedal record for MIDI device:'%s' port:'%s'.",midiOutDevLabel,midiOutPortLabel); + goto errLabel; + } + else + { + p->midiDevA[i].pedalMapEnableFl = true; + } + } + + } } // allocate the MIDI msg buffer p->msgArray = mem::allocZ( p->msgArrayN ); - + p->iMsgArray = mem::allocZ( p->iMsgArrayN ); + errLabel: return rc; } rc_t _stop( midi_record_play_t* p ); + - rc_t _event_callback( midi_record_play_t* p, unsigned id, const time::spec_t timestamp, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 ) + const am_midi_msg_t* _midi_store( midi_record_play_t* p, unsigned devIdx, unsigned portIdx, const time::spec_t& ts, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 ) + { + am_midi_msg_t* am = nullptr; + + // verify that space exists in the record buffer + if( p->iMsgArrayInIdx < p->iMsgArrayN ) + { + // MAKE THIS ATOMIC + unsigned id = p->iMsgArrayInIdx; + ++p->iMsgArrayInIdx; + + am = p->iMsgArray + id; + + am->id = id; + am->devIdx = devIdx; + am->portIdx = portIdx; + am->timestamp = ts; + am->ch = ch; + am->status = status; + am->d0 = d0; + am->d1 = d1; + + } + + return am; + } + + rc_t _event_callback( midi_record_play_t* p, unsigned id, const time::spec_t timestamp, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1, bool log_fl=true ) { rc_t rc = kOkRC; if( !time::isZero(p->end_play_event_timestamp) && time::isGT(timestamp,p->end_play_event_timestamp)) { rc = _stop(p); + printf("ZERO\n"); } else { + + if( p->halfPedalFl ) + { + if( status==midi::kCtlMdId && d0 == midi::kSustainCtlMdId && d1 != 0 ) + d1 = p->halfPedalMidiPedalVel; + } + + for(unsigned i=0; imidiDevN; ++i) if(p->midiDevA[i].enableFl ) - io::midiDeviceSend( p->ioH, p->midiDevA[i].midiOutDevIdx, p->midiDevA[i].midiOutPortIdx, status + ch, d0, d1 ); + { + uint8_t out_d1 = d1; + + if( !p->halfPedalFl ) + { + // map the note on velocity + if( status==midi::kNoteOnMdId and d1 != 0 and p->midiDevA[i].velTableArray != nullptr ) + { + if( d1 >= p->midiDevA[i].velTableN ) + cwLogError(kInvalidIdRC,"A MIDI note-on velocity (%i) outside the velocity table range was encountered.",d1); + else + out_d1 = p->midiDevA[i].velTableArray[ d1 ]; + } + + // map the pedal down velocity + if( status==midi::kCtlMdId && d0 == midi::kSustainCtlMdId && p->midiDevA[i].pedalMapEnableFl ) + { + if( d1 == p->midiDevA[i].pedalDownVelId ) + out_d1 = p->midiDevA[i].pedalDownVel; + else + if( d1 == p->midiDevA[i].pedalDownHalfVelId ) + out_d1 = p->midiDevA[i].pedalDownHalfVel; + else + cwLogError(kInvalidIdRC,"Unexpected pedal down velocity (%i) during pedal velocity mapping.",d1); + } + } + + io::midiDeviceSend( p->ioH, p->midiDevA[i].midiOutDevIdx, p->midiDevA[i].midiOutPortIdx, status + ch, d0, out_d1 ); + } if( p->cb ) p->cb( p->cb_arg, id, timestamp, ch, status, d0, d1 ); + + if( log_fl && p->logOutFl ) + { + // Note: The device of outgoing messages is set to p->midiDevN + 1 to distinguish it from + // incoming messages. + _midi_store( p, p->midiDevN, 0, timestamp, ch, status, d0, d1 ); + } } return rc; } - rc_t _transmit_msg( midi_record_play_t* p, const am_midi_msg_t* am ) + rc_t _transmit_msg( midi_record_play_t* p, const am_midi_msg_t* am, bool log_fl=true ) { - return _event_callback( p, am->id, am->timestamp, am->ch, am->status, am->d0, am->d1 ); + return _event_callback( p, am->id, am->timestamp, am->ch, am->status, am->d0, am->d1, log_fl ); } - rc_t _transmit_ctl( midi_record_play_t* p, unsigned ch, unsigned ctlId, unsigned ctlVal ) + rc_t _transmit_note( midi_record_play_t* p, unsigned ch, unsigned pitch, unsigned vel, unsigned microsecs ) { time::spec_t ts = {0}; + time::microsecondsToSpec( ts, microsecs ); + return _event_callback( p, kInvalidId, ts, ch, midi::kNoteOnMdId, pitch, vel ); + } + + rc_t _transmit_ctl( midi_record_play_t* p, unsigned ch, unsigned ctlId, unsigned ctlVal, unsigned microsecs ) + { + time::spec_t ts = {0}; + time::microsecondsToSpec( ts, microsecs ); return _event_callback( p, kInvalidId, ts, ch, midi::kCtlMdId, ctlId, ctlVal ); } - rc_t _transmit_pedal( midi_record_play_t* p, unsigned ch, unsigned pedalCtlId, bool pedalDownFl ) + rc_t _transmit_pedal( midi_record_play_t* p, unsigned ch, unsigned pedalCtlId, bool pedalDownFl, unsigned microsecs ) { - return _transmit_ctl( p, ch, pedalCtlId, pedalDownFl ? 127 : 0); + return _transmit_ctl( p, ch, pedalCtlId, pedalDownFl ? 127 : 0, microsecs); } + void _half_pedal_update( midi_record_play_t* p, unsigned cur_time_us ) + { + if( cur_time_us >= p->halfPedalNextUs ) + { + unsigned midi_ch = 0; + switch( p->halfPedalState ) + { + + case kWaitForBegin: + printf("down: %i %i\n",cur_time_us/1000,p->halfPedalMidiPedalVel); + _transmit_ctl( p, midi_ch, midi::kSustainCtlMdId, p->halfPedalMidiPedalVel, cur_time_us); + p->halfPedalState = kWaitForNoteOn; + p->halfPedalNextUs += p->halfPedalNoteDelayUs; + break; + + case kWaitForNoteOn: + printf("note: %i\n",cur_time_us/1000); + _transmit_note( p, midi_ch, p->halfPedalMidiPitch, p->halfPedalMidiNoteVel, cur_time_us ); + p->halfPedalNextUs += p->halfPedalNoteDurUs; + p->halfPedalState = kWaitForNoteOff; + break; + + case kWaitForNoteOff: + printf("off: %i\n",cur_time_us/1000); + _transmit_note( p, midi_ch, p->halfPedalMidiPitch, 0, cur_time_us ); + p->halfPedalNextUs += p->halfPedalUpDelayUs; + p->halfPedalState = kWaitForPedalUp; + break; + + case kWaitForPedalUp: + printf("up: %i\n",cur_time_us/1000); + _transmit_ctl( p, midi_ch, midi::kSustainCtlMdId, 0, cur_time_us); + p->halfPedalNextUs += p->halfPedalDownDelayUs; + p->halfPedalState = kWaitForPedalDown; + break; + case kWaitForPedalDown: + //printf("down: %i\n",cur_time_us/1000); + //_transmit_ctl( p, midi_ch, midi::kSustainCtlMdId, p->halfPedalMidiPedalVel, cur_time_us); + //_stop(p); + p->halfPedalState = kHalfPedalDone; + break; + + + case kHalfPedalDone: + break; + } + } + } + // Set the next location to store an incoming MIDI message void _set_midi_msg_next_index( midi_record_play_t* p, unsigned next_idx ) { - p->msgArrayInIdx = next_idx; - - //io::uiSendValue( p->ioH, kInvalidId, uiFindElementUuId(p->ioH,kMsgCntId), p->midiMsgArrayInIdx ); - + p->iMsgArrayInIdx = next_idx; } + // Set the next index of the next MIDI message to transmit void _set_midi_msg_next_play_index(midi_record_play_t* p, unsigned next_idx) { p->msgArrayOutIdx = next_idx; @@ -254,7 +469,8 @@ namespace cw return rc; } - + + // Fill the play buffer from a previously store AM file. rc_t _midi_read( midi_record_play_t* p, const char* fn ) { rc_t rc = kOkRC; @@ -285,21 +501,24 @@ namespace cw goto errLabel; } - _set_midi_msg_next_index(p, n ); + p->msgArrayInIdx = n; cwLogInfo("Read %i from '%s'.",n,fn); errLabel: + + file::close(fH); return rc; } - + + // Write the record buffer to an AM file rc_t _midi_write( midi_record_play_t* p, const char* fn ) { rc_t rc = kOkRC; file::handle_t fH; - if( p->msgArrayInIdx == 0 ) + if( p->iMsgArrayInIdx == 0 ) { cwLogWarning("Nothing to write."); return rc; @@ -313,30 +532,77 @@ namespace cw } // write the file header - if((rc = write(fH,p->msgArrayInIdx)) != kOkRC ) + if((rc = write(fH,p->iMsgArrayInIdx)) != kOkRC ) { rc = cwLogError(kWriteFailRC,"Header write to '%s' failed.",cwStringNullGuard(fn)); goto errLabel; } // write the file data - if((rc = write(fH,p->msgArray,sizeof(am_midi_msg_t)*p->msgArrayInIdx)) != kOkRC ) + if((rc = write(fH,p->iMsgArray,sizeof(am_midi_msg_t)*p->iMsgArrayInIdx)) != kOkRC ) { rc = cwLogError(kWriteFailRC,"Data write to '%s' failed.",cwStringNullGuard(fn)); goto errLabel; } - // update UI msg count - //io::uiSendValue( p->ioH, kInvalidId, uiFindElementUuId(p->ioH,kMsgCntId), p->msgArrayInIdx ); - + errLabel: file::close(fH); - cwLogInfo("Saved %i events to '%s'.", p->msgArrayInIdx, fn ); + cwLogInfo("Saved %i events to '%s'.", p->iMsgArrayInIdx, fn ); - errLabel: return rc; } + rc_t _write_csv( midi_record_play_t* p, const char* fn ) + { + rc_t rc = kOkRC; + file::handle_t fH; + + if( p->iMsgArrayInIdx == 0 ) + { + cwLogWarning("Nothing to write."); + return rc; + } + + // open the file + if((rc = file::open(fH,fn,file::kWriteFl)) != kOkRC ) + { + rc = cwLogError(kOpenFailRC,"Unable to create the file '%s'.",cwStringNullGuard(fn)); + goto errLabel; + } + + file::printf(fH,"dev,port,microsec,id,sec,ch,status,d0,d1\n"); + + for(unsigned i=0; iiMsgArrayInIdx; ++i) + { + const am_midi_msg_t* m = p->iMsgArray + i; + + double secs = time::elapsedSecs( p->iMsgArray[0].timestamp, p->iMsgArray[i].timestamp ); + + char sciPitch[ midi::kMidiSciPitchCharCnt + 1 ]; + if( m->status == midi::kNoteOnMdId ) + midi::midiToSciPitch( m->d0, sciPitch, midi::kMidiSciPitchCharCnt ); + else + strcpy(sciPitch,""); + + if((rc = file::printf(fH, "%3i,%3i,%8i,%3i,%8.4f,%2i,0x%2x,%5s,%3i,%3i\n", + m->devIdx, m->portIdx, m->microsec, m->id, secs, + m->ch, m->status, sciPitch, m->d0, m->d1 )) != kOkRC ) + { + rc = cwLogError(rc,"Write failed on line:%i", i+1 ); + goto errLabel; + } + } + + errLabel: + file::close(fH); + + cwLogInfo("Saved %i events to '%s'.", p->iMsgArrayInIdx, fn ); + + return rc; + + } + rc_t _midi_file_write( const char* fn, const am_midi_msg_t* msgArray, unsigned msgArrayCnt ) { rc_t rc = kOkRC; @@ -424,13 +690,14 @@ namespace cw time::spec_t t1; time::get(t1); + // if we were recording if( p->recordFl ) { - // set the 'microsec' value for each MIDI msg - for(unsigned i=0; imsgArrayInIdx; ++i) + // set the 'microsec' value for each MIDI msg as an offset from the first message[] + for(unsigned i=0; iiMsgArrayInIdx; ++i) { - p->msgArray[i].microsec = time::elapsedMicros(p->msgArray[0].timestamp,p->msgArray[i].timestamp); + p->msgArray[i].microsec = time::elapsedMicros(p->iMsgArray[0].timestamp,p->iMsgArray[i].timestamp); } cwLogInfo("MIDI messages recorded: %i",p->msgArrayInIdx ); @@ -440,30 +707,19 @@ namespace cw { io::timerStop( p->ioH, io::timerIdToIndex(p->ioH, kMidiRecordPlayTimerId) ); - // TODO: should work for all channels + // TODO: + // BUG BUG BUG: should work for all channels // all notes off - _transmit_ctl( p, 0, 121, 0 ); // reset all controllers - _transmit_ctl( p, 0, 123, 0 ); // all notes off - _transmit_ctl( p, 0, 0, 0 ); // switch to bank 0 - - //_transmit_pedal( p, 0, midi::kSustainCtlMdId, false ); - //_transmit_pedal( p, 0, midi::kSostenutoCtlMdId, false ); - //_transmit_pedal( p, 0, midi::kSoftPedalCtlMdId, false ); - - // send pgm change 0 - //time::spec_t ts = {0}; - //_event_callback( p, kInvalidId, ts, 0, midi::kPgmMdId, 0, 0 ); + _transmit_ctl( p, 0, 121, 0, 0 ); // reset all controllers + _transmit_ctl( p, 0, 123, 0, 0 ); // all notes off + _transmit_ctl( p, 0, 0, 0, 0 ); // switch to bank 0 - p->pedalFl = false; - } - //cwLogInfo("Runtime: %5.2f seconds.", time::elapsedMs(p->start_time,t1)/1000.0 ); - return rc; } - + rc_t _midi_receive( midi_record_play_t* p, const io::midi_msg_t& m ) { rc_t rc = kOkRC; @@ -480,65 +736,42 @@ namespace cw { //if( !midi::isPedal(pkt->msgArray[j].status,pkt->msgArray[j].d0) ) - // printf("0x%x 0x%x 0x%x\n", pkt->msgArray[j].status, pkt->msgArray[j].d0, pkt->msgArray[j].d1 ); + //printf("IN: 0x%x 0x%x 0x%x\n", pkt->msgArray[j].status, pkt->msgArray[j].d0, pkt->msgArray[j].d1 ); - if( p->recordFl && p->startedFl ) + if( (p->recordFl || p->logInFl) && p->startedFl ) { // verify that space exists in the record buffer - if( p->msgArrayInIdx >= p->msgArrayN ) + if( p->iMsgArrayInIdx >= p->iMsgArrayN ) { _stop(p); - rc = cwLogError(kBufTooSmallRC,"MIDI message record buffer is full. % messages.",p->msgArrayN); + rc = cwLogError(kBufTooSmallRC,"MIDI message record buffer is full. % messages.",p->iMsgArrayN); goto errLabel; } else { // copy the msg into the record buffer - am_midi_msg_t* am = p->msgArray + p->msgArrayInIdx; midi::msg_t* mm = pkt->msgArray + j; if( midi::isChStatus(mm->status) ) { + const am_midi_msg_t* am = _midi_store( p, pkt->devIdx, pkt->portIdx, mm->timeStamp, mm->status & 0x0f, mm->status & 0xf0, mm->d0, mm->d1 ); - am->id = p->msgArrayInIdx; - am->devIdx = pkt->devIdx; - am->portIdx = pkt->portIdx; - am->timestamp = mm->timeStamp; - am->ch = mm->status & 0x0f; - am->status = mm->status & 0xf0; - am->d0 = mm->d0; - am->d1 = mm->d1; + if( p->thruFl && am != nullptr ) + _transmit_msg( p, am, false ); - //printf("st:0x%x ch:%i d0:0x%x d1:0x%x\n",am->status,am->ch,am->d0,am->d1); - - p->msgArrayInIdx += 1; - - if( p->thruFl ) - _transmit_msg( p, am ); - - // send msg count - //io::uiSendValue( p->ioH, kInvalidId, uiFindElementUuId(p->ioH,kMsgCntId), p->msgArrayInIdx ); } } } } - /* - if( pkt->msgArray == NULL ) - printf("io midi cb: 0x%x ",pkt->sysExMsg[j]); - else - { - if( !_midi_filter(pkt->msgArray + j) ) - printf("io midi cb: %ld %ld 0x%x %i %i\n", pkt->msgArray[j].timeStamp.tv_sec, pkt->msgArray[j].timeStamp.tv_nsec, pkt->msgArray[j].status, pkt->msgArray[j].d0, pkt->msgArray[j].d1); - } - */ } errLabel: return rc; } + rc_t _timer_callback(midi_record_play_t* p, io::timer_msg_t& m) { rc_t rc = kOkRC; @@ -551,46 +784,27 @@ namespace cw unsigned cur_time_us = time::elapsedMicros(p->play_time,t); - while( p->msgArray[ p->msgArrayOutIdx ].microsec <= cur_time_us ) - { - - am_midi_msg_t* mm = p->msgArray + p->msgArrayOutIdx; - - //_print_midi_msg(mm); - - bool skipFl = false; - - /* - // if this is a pedal message - if( mm->status == midi::kCtlMdId && (mm->d0 == midi::kSustainCtlMdId || mm->d0 == midi::kSostenutoCtlMdId || mm->d0 == midi::kSoftPedalCtlMdId ) ) - { - // if the pedal is down - if( p->pedalFl ) - { - skipFl = mm->d1 > 64; - p->pedalFl = false; - } - else - { - skipFl = mm->d1 <= 64; - p->pedalFl = true; - } - } - */ - - if( !skipFl ) + if( p->halfPedalFl ) + _half_pedal_update( p, cur_time_us ); + else + while( p->msgArray[ p->msgArrayOutIdx ].microsec <= cur_time_us ) { + + am_midi_msg_t* mm = p->msgArray + p->msgArrayOutIdx; + + //_print_midi_msg(mm); + _transmit_msg( p, mm ); - } - _set_midi_msg_next_play_index(p, p->msgArrayOutIdx+1 ); + + _set_midi_msg_next_play_index(p, p->msgArrayOutIdx+1 ); - // if all MIDI messages have been played - if( p->msgArrayOutIdx >= p->msgArrayInIdx ) - { - _stop(p); - break; + // if all MIDI messages have been played + if( p->msgArrayOutIdx >= p->msgArrayInIdx ) + { + _stop(p); + break; + } } - } } return rc; @@ -615,6 +829,16 @@ cw::rc_t cw::midi_record_play::create( handle_t& hRef, io::handle_t ioH, const o p->ioH = ioH; p->cb = cb; p->cb_arg = cb_arg; + p->halfPedalState = kHalfPedalDone; + p->halfPedalNextUs = 0; + p->halfPedalNoteDelayUs = 100 * 1000; + p->halfPedalNoteDurUs = 1000 * 1000; + p->halfPedalUpDelayUs = 1000 * 1000; + p->halfPedalDownDelayUs = 1000 * 1000; + p->halfPedalMidiPitch = 64; + p->halfPedalMidiNoteVel = 64; + p->halfPedalMidiPedalVel = 127; + for( unsigned i=0; i< p->midiDevN; ++i) @@ -673,7 +897,6 @@ cw::rc_t cw::midi_record_play::start( handle_t h, bool rewindFl, const time::spe { midi_record_play_t* p = _handleToPtr(h); p->startedFl = true; - p->pedalFl = false; // set the end play time if( end_play_event_timestamp == nullptr or time::isZero(*end_play_event_timestamp) ) @@ -683,11 +906,12 @@ cw::rc_t cw::midi_record_play::start( handle_t h, bool rewindFl, const time::spe time::get(p->start_time); - if( p->recordFl ) + if( p->recordFl || p->logInFl or p->logOutFl ) { _set_midi_msg_next_index(p, 0 ); } - else + + if( !p->recordFl ) { time::get(p->play_time); @@ -699,6 +923,12 @@ cw::rc_t cw::midi_record_play::start( handle_t h, bool rewindFl, const time::spe // This will cause that event to be played back immediately. time::subtractMicros(p->play_time, p->msgArray[ p->msgArrayOutIdx ].microsec ); } + + if( p->halfPedalFl ) + { + p->halfPedalNextUs = 0; + p->halfPedalState = kWaitForBegin; + } io::timerStart( p->ioH, io::timerIdToIndex(p->ioH, kMidiRecordPlayTimerId) ); } @@ -761,6 +991,12 @@ cw::rc_t cw::midi_record_play::save( handle_t h, const char* fn ) return _midi_write(p,fn); } +cw::rc_t cw::midi_record_play::save_csv( handle_t h, const char* fn ) +{ + midi_record_play_t* p = _handleToPtr(h); + return _write_csv(p,fn); +} + cw::rc_t cw::midi_record_play::open( handle_t h, const char* fn ) { midi_record_play_t* p = _handleToPtr(h); @@ -790,6 +1026,7 @@ cw::rc_t cw::midi_record_play::load( handle_t h, const midi_msg_t* msg, unsigned p->msgArray[i].devIdx = kInvalidIdx; p->msgArray[i].portIdx = kInvalidIdx; p->msgArray[i].microsec = time::elapsedMicros(p->msgArray[0].timestamp,p->msgArray[i].timestamp); + } p->msgArrayInIdx = msg_count; @@ -815,13 +1052,12 @@ cw::rc_t cw::midi_record_play::seek( handle_t h, time::spec_t seek_timestamp ) { p->msgArrayOutIdx = i; - _transmit_pedal( p, mm->ch, midi::kSustainCtlMdId, damp_down_fl ); - _transmit_pedal( p, mm->ch, midi::kSostenutoCtlMdId, sost_down_fl ); - _transmit_pedal( p, mm->ch, midi::kSoftPedalCtlMdId, soft_down_fl ); + _transmit_pedal( p, mm->ch, midi::kSustainCtlMdId, damp_down_fl, 0 ); + _transmit_pedal( p, mm->ch, midi::kSostenutoCtlMdId, sost_down_fl, 0 ); + _transmit_pedal( p, mm->ch, midi::kSoftPedalCtlMdId, soft_down_fl, 0 ); + + printf("PEDAL: %s.\n", damp_down_fl ? "Down" : "Up"); - //io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, mm->status + mm->ch, midi::kSustainCtlMdId, damp_down_fl ? 127 : 0 ); - //io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, mm->status + mm->ch, midi::kSostenutoCtlMdId, sost_down_fl ? 127 : 0 ); - //io::midiDeviceSend( p->ioH, p->midiOutDevIdx, p->midiOutPortIdx, mm->status + mm->ch, midi::kSoftPedalCtlMdId, soft_down_fl ? 127 : 0 ); break; } @@ -862,7 +1098,7 @@ unsigned cw::midi_record_play::event_count( handle_t h ) unsigned cw::midi_record_play::event_index( handle_t h ) { midi_record_play_t* p = _handleToPtr(h); - return p->recordFl ? p->msgArrayInIdx : p->msgArrayOutIdx; + return p->recordFl ? p->iMsgArrayInIdx : p->msgArrayOutIdx; } @@ -923,6 +1159,18 @@ void cw::midi_record_play::enable_device( handle_t h, unsigned devIdx, bool enab } } +void cw::midi_record_play::half_pedal_params( handle_t h, unsigned noteDelayMs, unsigned pitch, unsigned vel, unsigned pedal_vel, unsigned noteDurMs, unsigned downDelayMs ) +{ + midi_record_play_t* p = _handleToPtr(h); + p->halfPedalNoteDelayUs = noteDelayMs * 1000; + p->halfPedalNoteDurUs = noteDurMs * 1000; + p->halfPedalDownDelayUs = downDelayMs * 1000; + p->halfPedalMidiPitch = pitch; + p->halfPedalMidiNoteVel = vel; + p->halfPedalMidiPedalVel= pedal_vel; + +} + cw::rc_t cw::midi_record_play::am_to_midi_file( const char* am_filename, const char* midi_filename ) { rc_t rc = kOkRC; diff --git a/cwIoMidiRecordPlay.h b/cwIoMidiRecordPlay.h index 3e9b55c..8c4db82 100644 --- a/cwIoMidiRecordPlay.h +++ b/cwIoMidiRecordPlay.h @@ -39,6 +39,8 @@ namespace cw bool thru_state( handle_t h ); rc_t save( handle_t h, const char* fn ); + rc_t save_csv( handle_t h, const char* fn ); + rc_t open( handle_t h, const char* fn ); // Load the playback buffer with messages to output. @@ -54,6 +56,8 @@ namespace cw bool is_device_enabled( handle_t h, unsigned devIdx ); void enable_device( handle_t h, unsigned devIdx, bool enableFl ); + void half_pedal_params( handle_t h, unsigned noteDelayMs, unsigned pitch, unsigned vel, unsigned pedal_vel, unsigned noteDurMs, unsigned downDelayMs ); + // Convert an audio-midi file to a MIDI file rc_t am_to_midi_file( const char* am_filename, const char* midi_filename ); rc_t am_to_midi_dir( const char* inDir ); diff --git a/cwIoPresetSelApp.cpp b/cwIoPresetSelApp.cpp index 153df3c..9a287e2 100644 --- a/cwIoPresetSelApp.cpp +++ b/cwIoPresetSelApp.cpp @@ -69,6 +69,13 @@ namespace cw kStatusId, + kHalfPedalPedalVel, + kHalfPedalDelayMs, + kHalfPedalPitch, + kHalfPedalVel, + kHalfPedalDurMs, + kHalfPedalDnDelayMs, + kLogId, kFragListId, @@ -140,6 +147,15 @@ namespace cw { kPanelDivId, kInsertLocId, "insertLocId" }, { kPanelDivId, kInsertBtnId, "insertBtnId" }, { kPanelDivId, kDeleteBtnId, "deleteBtnId" }, + + { kPanelDivId, kHalfPedalPedalVel, "halfPedalPedalVelId" }, + { kPanelDivId, kHalfPedalDelayMs, "halfPedalDelayMsId" }, + { kPanelDivId, kHalfPedalPitch, "halfPedalPitchId" }, + { kPanelDivId, kHalfPedalVel, "halfPedalVelId" }, + { kPanelDivId, kHalfPedalDurMs, "halfPedalDurMsId" }, + { kPanelDivId, kHalfPedalDnDelayMs, "halfPedalDnDelayMsId" }, + + { kPanelDivId, kStatusId, "statusId" }, { kPanelDivId, kLogId, "logId" }, @@ -213,6 +229,15 @@ namespace cw unsigned crossFadeCnt; bool printMidiFl; + + unsigned hpDelayMs; + unsigned hpPedalVel; + unsigned hpPitch; + unsigned hpVel; + unsigned hpDurMs; + unsigned hpDnDelayMs; + + } app_t; @@ -362,13 +387,20 @@ namespace cw void _midi_play_callback( void* arg, unsigned id, const time::spec_t timestamp, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 ) { app_t* app = (app_t*)arg; - if( id != kInvalidId ) - { + if( app->printMidiFl ) { const unsigned buf_byte_cnt = 256; char buf[ buf_byte_cnt ]; - event_to_string( app->scoreH, id, buf, buf_byte_cnt ); + + // if this event is not in the score + if( id == kInvalidId ) + { + // TODO: print this out in the same format as event_to_string() + snprintf(buf,buf_byte_cnt,"ch:%i status:0x%02x d0:%i d1:%i",ch,status,d0,d1); + } + else + score::event_to_string( app->scoreH, id, buf, buf_byte_cnt ); printf("%s\n",buf); } @@ -381,9 +413,6 @@ namespace cw if( f != nullptr ) _do_select_frag( app, f->guiUuId ); } - - - } } loc_map_t* _find_loc( app_t* app, unsigned loc ) @@ -423,6 +452,9 @@ namespace cw rc = _do_stop_play(app); goto errLabel; } + + midi_record_play::half_pedal_params( app->mrpH, app->hpDelayMs, app->hpPitch, app->hpVel, app->hpPedalVel, app->hpDurMs, app->hpDnDelayMs ); + if((begMap = _find_loc(app,begLoc)) == nullptr ) { @@ -971,6 +1003,7 @@ namespace cw m[i].d1 = e->d1; m[i].id = e->uid; + app->locMap[i].loc = e->loc; app->locMap[i].timestamp = m[i].timestamp; @@ -987,6 +1020,8 @@ namespace cw goto errLabel; } + cwLogInfo("%i MIDI events loaded.", midiEventN ); + mem::free(m); } @@ -1031,7 +1066,6 @@ namespace cw return rc; } - rc_t _on_ui_start( app_t* app ) { return _do_play(app, app->beg_play_loc, app->end_play_loc ); @@ -1257,6 +1291,77 @@ namespace cw return rc; } + rc_t _on_ui_half_pedal_value( app_t* app, unsigned appId, unsigned uuId, unsigned value ) + { + switch( appId ) + { + case kHalfPedalDelayMs: + app->hpDelayMs = value; + break; + + case kHalfPedalPedalVel: + app->hpPedalVel = value; + break; + + case kHalfPedalPitch: + app->hpPitch = value; + break; + + case kHalfPedalVel: + app->hpVel = value; + break; + + case kHalfPedalDurMs: + app->hpDurMs = value; + break; + + case kHalfPedalDnDelayMs: + app->hpDnDelayMs = value; + break; + + default: + { assert(0); } + + } + return kOkRC; + } + + rc_t _on_echo_half_pedal( app_t* app, unsigned appId, unsigned uuId ) + { + switch( appId ) + { + case kHalfPedalDelayMs: + io::uiSendValue( app->ioH, uuId, app->hpDelayMs ); + break; + + case kHalfPedalPedalVel: + io::uiSendValue( app->ioH, uuId, app->hpPedalVel ); + break; + + case kHalfPedalPitch: + io::uiSendValue( app->ioH, uuId, app->hpPitch ); + break; + + case kHalfPedalVel: + io::uiSendValue( app->ioH, uuId, app->hpVel ); + break; + + case kHalfPedalDurMs: + io::uiSendValue( app->ioH, uuId, app->hpDurMs ); + break; + + case kHalfPedalDnDelayMs: + io::uiSendValue( app->ioH, uuId, app->hpDnDelayMs ); + break; + + default: + { assert(0); } + + } + return kOkRC; + } + + rc_t _onUiInit(app_t* app, const io::ui_msg_t& m ) { rc_t rc = kOkRC; @@ -1299,7 +1404,8 @@ namespace cw //preset_sel::report( app->psH ); //io_flow::apply_preset( app->ioFlowH, 2000.0, app->tmp==0 ? "a" : "b"); //app->tmp = !app->tmp; - io_flow::print(app->ioFlowH); + //io_flow::print(app->ioFlowH); + midi_record_play::save_csv(app->mrpH,"/home/kevin/temp/mrp_1.csv"); break; case kSaveBtnId: @@ -1371,6 +1477,15 @@ namespace cw _on_ui_delete_btn(app); break; + case kHalfPedalPedalVel: + case kHalfPedalDelayMs: + case kHalfPedalPitch: + case kHalfPedalVel: + case kHalfPedalDurMs: + case kHalfPedalDnDelayMs: + _on_ui_half_pedal_value( app, m.appId, m.uuId, m.value->u.u ); + break; + case kFragInGainId: _on_ui_frag_value( app, m.uuId, m.value->u.d); break; @@ -1495,6 +1610,15 @@ namespace cw case kSyncDelayMsId: _on_echo_master_value( app, preset_sel::kMasterSyncDelayMsVarId, m.uuId ); break; + + case kHalfPedalPedalVel: + case kHalfPedalDelayMs: + case kHalfPedalPitch: + case kHalfPedalVel: + case kHalfPedalDurMs: + case kHalfPedalDnDelayMs: + _on_echo_half_pedal( app, m.appId, m.uuId ); + break; } @@ -1614,8 +1738,9 @@ namespace cw cw::rc_t cw::preset_sel_app::main( const object_t* cfg, const object_t* flow_proc_dict ) { + rc_t rc; - app_t app = { }; + app_t app = { .hpDelayMs=250, .hpPedalVel=127, .hpPitch=64, .hpVel=64, .hpDurMs=500, .hpDnDelayMs=1000 }; const object_t* params_cfg = nullptr; // Parse the configuration @@ -1661,7 +1786,8 @@ cw::rc_t cw::preset_sel_app::main( const object_t* cfg, const object_t* flow_pro rc = cwLogError(rc,"Preset-select app start failed."); goto errLabel; } - + + // execute the io framework while( !isShuttingDown(app.ioH)) { diff --git a/html/preset_sel/ui.cfg b/html/preset_sel/ui.cfg index 082486b..b3460ec 100644 --- a/html/preset_sel/ui.cfg +++ b/html/preset_sel/ui.cfg @@ -49,6 +49,15 @@ button:{ name: deleteBtnId, title:"Delete", enable: false }, }, + row: { + number:{ name: halfPedalDelayMsId, title:"DelayMs:", min:0, max:5000, step:1, decpl:0 }, + number:{ name: halfPedalPedalVelId, title:"PVel:", min:0, max:127, step:1, decpl:0 }, + number:{ name: halfPedalPitchId, title:"Pitch:", min:0, max:127, step:1, decpl:0 }, + number:{ name: halfPedalVelId, title:"Vel:", min:0, max:127, step:1, decpl:0 }, + number:{ name: halfPedalDurMsId, title:"DurMs:", min:0, max:5000, step:1, decpl:0 }, + number:{ name: halfPedalDnDelayMsId, title:"DownMs:", min:0, max:5000, step:1, decpl:0 }, + }, + row: { str_disp:{ name: statusId, title:"Status:", value: "" }, }