2024-12-01 19:35:24 +00:00
|
|
|
//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
|
|
|
|
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
|
2019-12-24 15:05:24 +00:00
|
|
|
#include "cwCommon.h"
|
|
|
|
#include "cwLog.h"
|
|
|
|
#include "cwCommonImpl.h"
|
2024-05-29 16:36:57 +00:00
|
|
|
#include "cwTest.h"
|
2019-12-24 15:05:24 +00:00
|
|
|
#include "cwMem.h"
|
2020-03-23 14:41:28 +00:00
|
|
|
#include "cwTime.h"
|
2019-12-24 15:05:24 +00:00
|
|
|
#include "cwMidi.h"
|
|
|
|
|
|
|
|
namespace cw {
|
|
|
|
namespace midi {
|
|
|
|
|
|
|
|
typedef struct statusDesc_str
|
|
|
|
{
|
2020-03-23 14:41:28 +00:00
|
|
|
uint8_t status;
|
|
|
|
uint8_t byteCnt;
|
2019-12-24 15:05:24 +00:00
|
|
|
const char* label;
|
|
|
|
} statusDesc_t;
|
|
|
|
|
|
|
|
statusDesc_t _statusDescArray[] =
|
|
|
|
{
|
|
|
|
// channel messages
|
|
|
|
{ kNoteOffMdId, 2, "nof" },
|
|
|
|
{ kNoteOnMdId, 2, "non" },
|
|
|
|
{ kPolyPresMdId,2, "ppr" },
|
|
|
|
{ kCtlMdId, 2, "ctl" },
|
|
|
|
{ kPgmMdId, 1, "pgm" },
|
|
|
|
{ kChPresMdId, 1, "cpr" },
|
|
|
|
{ kPbendMdId, 2, "pb" },
|
|
|
|
|
|
|
|
|
|
|
|
{ kSysExMdId, kInvalidMidiByte,"sex" },
|
|
|
|
|
|
|
|
// system common
|
|
|
|
{ kSysComMtcMdId, 1, "mtc" },
|
|
|
|
{ kSysComSppMdId, 2, "spp" },
|
|
|
|
{ kSysComSelMdId, 1, "sel" },
|
|
|
|
{ kSysComUndef0MdId, 0, "cu0" },
|
|
|
|
{ kSysComUndef1MdId, 0, "cu1" },
|
|
|
|
{ kSysComTuneMdId, 0, "tun" },
|
|
|
|
{ kSysComEoxMdId, 0, "eox" },
|
|
|
|
|
|
|
|
// system real-time
|
|
|
|
{ kSysRtClockMdId, 0, "clk" },
|
|
|
|
{ kSysRtUndef0MdId,0, "ud0" },
|
|
|
|
{ kSysRtStartMdId, 0, "beg" },
|
|
|
|
{ kSysRtContMdId, 0, "cnt" },
|
|
|
|
{ kSysRtStopMdId, 0, "end" },
|
|
|
|
{ kSysRtUndef1MdId,0, "ud1" },
|
|
|
|
{ kSysRtSenseMdId, 0, "sns" },
|
|
|
|
{ kSysRtResetMdId, 0, "rst" },
|
|
|
|
|
|
|
|
{ kInvalidStatusMdId, kInvalidMidiByte, "ERR" }
|
|
|
|
};
|
|
|
|
|
|
|
|
statusDesc_t _metaStatusDescArray[] =
|
|
|
|
{
|
|
|
|
{ kSeqNumbMdId, 2, "seqn" },
|
|
|
|
{ kTextMdId, 0xff, "text" },
|
|
|
|
{ kCopyMdId, 0xff, "copy" },
|
|
|
|
{ kTrkNameMdId, 0xff, "name" },
|
|
|
|
{ kInstrNameMdId, 0xff, "instr" },
|
|
|
|
{ kLyricsMdId, 0xff, "lyric" },
|
|
|
|
{ kMarkerMdId, 0xff, "mark" },
|
|
|
|
{ kCuePointMdId, 0xff, "cue" },
|
|
|
|
{ kMidiChMdId, 1, "chan" },
|
2021-02-21 13:47:29 +00:00
|
|
|
{ kMidiPortMdId, 1, "port" },
|
2019-12-24 15:05:24 +00:00
|
|
|
{ kEndOfTrkMdId, 0, "eot" },
|
|
|
|
{ kTempoMdId, 3, "tempo" },
|
|
|
|
{ kSmpteMdId, 5, "smpte" },
|
|
|
|
{ kTimeSigMdId, 4, "tsig" },
|
|
|
|
{ kKeySigMdId, 2, "ksig" },
|
|
|
|
{ kSeqSpecMdId, 0xff, "seqs" },
|
|
|
|
{ kInvalidMetaMdId, kInvalidMidiByte, "ERROR"}
|
|
|
|
};
|
|
|
|
|
|
|
|
statusDesc_t _pedalLabel[] =
|
|
|
|
{
|
|
|
|
{ kSustainCtlMdId, 0, "sustn" },
|
|
|
|
{ kPortamentoCtlMdId, 0, "porta" },
|
|
|
|
{ kSostenutoCtlMdId, 0, "sostn" },
|
|
|
|
{ kSoftPedalCtlMdId, 0, "soft" },
|
|
|
|
{ kLegatoCtlMdId, 0, "legat" },
|
|
|
|
{ kInvalidMidiByte, kInvalidMidiByte, "ERROR"}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//====================================================================================================
|
|
|
|
|
2020-03-23 14:41:28 +00:00
|
|
|
const char* cw::midi::statusToLabel( uint8_t status )
|
2019-12-24 15:05:24 +00:00
|
|
|
{
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
if( !isStatus(status) )
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
// remove the channel value from ch msg status bytes
|
|
|
|
if( isChStatus(status) )
|
|
|
|
status &= 0xf0;
|
|
|
|
|
|
|
|
for(i=0; _statusDescArray[i].status != kInvalidStatusMdId; ++i)
|
|
|
|
if( _statusDescArray[i].status == status )
|
|
|
|
return _statusDescArray[i].label;
|
|
|
|
|
|
|
|
return _statusDescArray[i].label;
|
|
|
|
}
|
|
|
|
|
2020-03-23 14:41:28 +00:00
|
|
|
const char* cw::midi::metaStatusToLabel( uint8_t metaStatus )
|
2019-12-24 15:05:24 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for(i=0; _metaStatusDescArray[i].status != kInvalidMetaMdId; ++i)
|
|
|
|
if( _metaStatusDescArray[i].status == metaStatus )
|
|
|
|
break;
|
|
|
|
|
|
|
|
return _metaStatusDescArray[i].label;
|
|
|
|
}
|
|
|
|
|
2020-03-23 14:41:28 +00:00
|
|
|
const char* cw::midi::pedalLabel( uint8_t d0 )
|
2019-12-24 15:05:24 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for(i=0; _pedalLabel[i].status != kInvalidMidiByte; ++i)
|
|
|
|
if( _pedalLabel[i].status == d0 )
|
|
|
|
break;
|
|
|
|
|
|
|
|
return _pedalLabel[i].label;
|
|
|
|
}
|
|
|
|
|
2020-03-23 14:41:28 +00:00
|
|
|
uint8_t cw::midi::statusToByteCount( uint8_t status )
|
2019-12-24 15:05:24 +00:00
|
|
|
{
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
if( !isStatus(status) )
|
|
|
|
return kInvalidMidiByte;
|
|
|
|
|
|
|
|
// remove the channel value from ch msg status bytes
|
|
|
|
if( isChStatus(status) )
|
|
|
|
status &= 0xf0;
|
|
|
|
|
|
|
|
for(i=0; _statusDescArray[i].status != kInvalidStatusMdId; ++i)
|
|
|
|
if( _statusDescArray[i].status == status )
|
|
|
|
return _statusDescArray[i].byteCnt;
|
|
|
|
|
|
|
|
assert(0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-03-23 14:41:28 +00:00
|
|
|
unsigned cw::midi::to14Bits( uint8_t d0, uint8_t d1 )
|
2019-12-24 15:05:24 +00:00
|
|
|
{
|
|
|
|
unsigned val = d0;
|
|
|
|
val <<= 7;
|
|
|
|
val += d1;
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2020-03-23 14:41:28 +00:00
|
|
|
void cw::midi::split14Bits( unsigned v, uint8_t& d0Ref, uint8_t& d1Ref )
|
2019-12-24 15:05:24 +00:00
|
|
|
{
|
|
|
|
d0Ref = (v & 0x3f80) >> 7;
|
|
|
|
d1Ref = v & 0x7f;
|
|
|
|
}
|
|
|
|
|
2020-03-23 14:41:28 +00:00
|
|
|
int cw::midi::toPbend( uint8_t d0, uint8_t d1 )
|
2019-12-24 15:05:24 +00:00
|
|
|
{
|
|
|
|
int v = to14Bits(d0,d1);
|
|
|
|
return v - 8192;
|
|
|
|
}
|
|
|
|
|
2020-03-23 14:41:28 +00:00
|
|
|
void cw::midi::splitPbend( int v, uint8_t& d0Ref, uint8_t& d1Ref )
|
2019-12-24 15:05:24 +00:00
|
|
|
{
|
|
|
|
unsigned uv = v + 8192;
|
|
|
|
split14Bits(uv,d0Ref,d1Ref);
|
|
|
|
}
|
|
|
|
|
|
|
|
//====================================================================================================
|
2020-03-23 14:41:28 +00:00
|
|
|
const char* cw::midi::midiToSciPitch( uint8_t pitch, char* label, unsigned labelCharCnt )
|
2019-12-24 15:05:24 +00:00
|
|
|
{
|
|
|
|
static char buf[ kMidiSciPitchCharCnt ];
|
|
|
|
|
|
|
|
if( label == NULL || labelCharCnt == 0 )
|
|
|
|
{
|
|
|
|
label = buf;
|
|
|
|
labelCharCnt = kMidiSciPitchCharCnt;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert( labelCharCnt >= kMidiSciPitchCharCnt );
|
|
|
|
|
|
|
|
if( /*pitch < 0 ||*/ pitch > 127 )
|
|
|
|
{
|
|
|
|
label[0] = 0;
|
|
|
|
return label;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert( labelCharCnt >= 5 && /*pitch >= 0 &&*/ pitch <= 127 );
|
|
|
|
|
|
|
|
char noteV[] = { 'C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B' };
|
|
|
|
char shrpV[] = { ' ', '#', ' ', '#', ' ', ' ', '#', ' ', '#', ' ', '#', ' ' };
|
|
|
|
int octave = (pitch / 12)-1;
|
|
|
|
unsigned noteIdx = pitch % 12;
|
|
|
|
char noteCh = noteV[ noteIdx ];
|
|
|
|
char sharpCh = shrpV[ noteIdx ];
|
|
|
|
unsigned idx = 1;
|
|
|
|
|
|
|
|
label[labelCharCnt-1] = 0;
|
|
|
|
label[0] = noteCh;
|
|
|
|
|
|
|
|
if( sharpCh != ' ' )
|
|
|
|
{
|
|
|
|
label[1] = sharpCh;
|
|
|
|
idx = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert( -1 <= octave && octave <= 9);
|
|
|
|
|
|
|
|
snprintf(label+idx,kMidiSciPitchCharCnt-idx-1,"%i",octave);
|
|
|
|
|
|
|
|
return label;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-03-23 14:41:28 +00:00
|
|
|
uint8_t cw::midi::sciPitchToMidiPitch( char pitch, int acc, int octave )
|
2019-12-24 15:05:24 +00:00
|
|
|
{
|
|
|
|
int idx = -1;
|
|
|
|
|
|
|
|
switch(tolower(pitch))
|
|
|
|
{
|
|
|
|
case 'a': idx = 9; break;
|
|
|
|
case 'b': idx = 11; break;
|
|
|
|
case 'c': idx = 0; break;
|
|
|
|
case 'd': idx = 2; break;
|
|
|
|
case 'e': idx = 4; break;
|
|
|
|
case 'f': idx = 5; break;
|
|
|
|
case 'g': idx = 7; break;
|
|
|
|
default:
|
|
|
|
return kInvalidMidiPitch;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned rv = (octave*12) + idx + acc + 12;
|
|
|
|
|
|
|
|
if( rv <= 127 )
|
|
|
|
return rv;
|
|
|
|
|
|
|
|
return kInvalidMidiPitch;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-03-23 14:41:28 +00:00
|
|
|
uint8_t cw::midi::sciPitchToMidi( const char* sciPitchStr )
|
2019-12-24 15:05:24 +00:00
|
|
|
{
|
|
|
|
const char* cp = sciPitchStr;
|
|
|
|
bool sharpFl = false;
|
|
|
|
bool flatFl = false;
|
|
|
|
int octave;
|
|
|
|
int acc = 0;
|
|
|
|
|
|
|
|
if( sciPitchStr==NULL || strlen(sciPitchStr) > 5 )
|
|
|
|
return kInvalidMidiPitch;
|
|
|
|
|
|
|
|
// skip over leading letter
|
|
|
|
++cp;
|
|
|
|
|
|
|
|
if( !(*cp) )
|
|
|
|
return kInvalidMidiPitch;
|
|
|
|
|
|
|
|
|
|
|
|
if((sharpFl = *cp=='#') == true )
|
|
|
|
acc = 1;
|
|
|
|
else
|
|
|
|
if((flatFl = *cp=='b') == true )
|
|
|
|
acc = -1;
|
|
|
|
|
|
|
|
if( sharpFl || flatFl )
|
|
|
|
{
|
|
|
|
++cp;
|
|
|
|
|
|
|
|
if( !(*cp) )
|
|
|
|
return kInvalidMidiPitch;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( isdigit(*cp) == false && *cp!='-' )
|
|
|
|
return kInvalidMidiPitch;
|
|
|
|
|
|
|
|
octave = atoi(cp);
|
|
|
|
|
|
|
|
|
|
|
|
return sciPitchToMidiPitch( *sciPitchStr, acc, octave );
|
|
|
|
|
|
|
|
}
|