Skip to content

Commit e6b7ef5

Browse files
authored
Handle TX216/TX816 style performance sysex messages (#884)
Handle TX216/TX816 style performance sysex messages Thanks @Skerjanc for having compared behavior with an actual TX816
1 parent d70232d commit e6b7ef5

File tree

2 files changed

+151
-73
lines changed

2 files changed

+151
-73
lines changed

src/mididevice.cpp

Lines changed: 128 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,127 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
329329
if (m_ChannelMap[nTG] == ucSysExChannel || m_ChannelMap[nTG] == OmniMode)
330330
{
331331
LOGNOTE("MIDI-SYSEX: channel: %u, len: %u, TG: %u",m_ChannelMap[nTG],nLength,nTG);
332-
HandleSystemExclusive(pMessage, nLength, nCable, nTG);
332+
333+
// Check for TX216/TX816 style performance sysex messages
334+
335+
if (pMessage[3] == 0x04)
336+
{
337+
// TX816/TX216 Performance SysEx message
338+
uint8_t mTG = pMessage[2] & 0x0F; // mTG = module/tone generator number (0-7)
339+
uint8_t par = pMessage[4];
340+
uint8_t val = pMessage[5];
341+
342+
// For parameter 1 (Set MIDI Channel), only process for the TG with the number in pMessage[2]
343+
if (par == 1) {
344+
if (nTG != mTG) continue;
345+
} else {
346+
// For all other parameters, process for all TGs listening on the MIDI channel mTG or OmniMode
347+
if (!(m_ChannelMap[nTG] == mTG || m_ChannelMap[nTG] == OmniMode)) continue;
348+
}
349+
LOGNOTE("MIDI-SYSEX: Assuming TX216/TX816 style performance sysex message because 4th byte is 0x04");
350+
351+
switch (par)
352+
{
353+
case 1: // MIDI Channel
354+
LOGNOTE("MIDI-SYSEX: Set TG%d to MIDI Channel %d", mTG, val & 0x0F);
355+
m_pSynthesizer->SetMIDIChannel(val & 0x0F, mTG);
356+
break;
357+
case 2: // Poly/Mono
358+
LOGNOTE("MIDI-SYSEX: Set Poly/Mono %d to %d", nTG, val & 0x0F);
359+
m_pSynthesizer->setMonoMode(val ? true : false, nTG);
360+
break;
361+
case 3: // Pitch Bend Range
362+
LOGNOTE("MIDI-SYSEX: Set Pitch Bend Range %d to %d", nTG, val & 0x0F);
363+
m_pSynthesizer->setPitchbendRange(val, nTG);
364+
break;
365+
case 4: // Pitch Bend Step
366+
LOGNOTE("MIDI-SYSEX: Set Pitch Bend Step %d to %d", nTG, val & 0x0F);
367+
m_pSynthesizer->setPitchbendStep(val, nTG);
368+
break;
369+
case 5: // Portamento Time
370+
LOGNOTE("MIDI-SYSEX: Set Portamento Time %d to %d", nTG, val & 0x0F);
371+
m_pSynthesizer->setPortamentoTime(val, nTG);
372+
break;
373+
case 6: // Portamento/Glissando
374+
LOGNOTE("MIDI-SYSEX: Set Portamento/Glissando %d to %d", nTG, val & 0x0F);
375+
m_pSynthesizer->setPortamentoGlissando(val, nTG);
376+
break;
377+
case 7: // Portamento Mode
378+
LOGNOTE("MIDI-SYSEX: Set Portamento Mode %d to %d", nTG, val & 0x0F);
379+
m_pSynthesizer->setPortamentoMode(val, nTG);
380+
break;
381+
case 9: // Mod Wheel Sensitivity
382+
{
383+
int scaled = (val * 99) / 15;
384+
LOGNOTE("MIDI-SYSEX: Set Mod Wheel Sensitivity %d to %d (scaled %d)", nTG, val & 0x0F, scaled);
385+
m_pSynthesizer->setModWheelRange(scaled, nTG);
386+
}
387+
break;
388+
case 10: // Mod Wheel Assign
389+
LOGNOTE("MIDI-SYSEX: Set Mod Wheel Assign %d to %d", nTG, val & 0x0F);
390+
m_pSynthesizer->setModWheelTarget(val, nTG);
391+
break;
392+
case 11: // Foot Controller Sensitivity
393+
{
394+
int scaled = (val * 99) / 15;
395+
LOGNOTE("MIDI-SYSEX: Set Foot Controller Sensitivity %d to %d (scaled %d)", nTG, val & 0x0F, scaled);
396+
m_pSynthesizer->setFootControllerRange(scaled, nTG);
397+
}
398+
break;
399+
case 12: // Foot Controller Assign
400+
LOGNOTE("MIDI-SYSEX: Set Foot Controller Assign %d to %d", nTG, val & 0x0F);
401+
m_pSynthesizer->setFootControllerTarget(val, nTG);
402+
break;
403+
case 13: // Aftertouch Sensitivity
404+
{
405+
int scaled = (val * 99) / 15;
406+
LOGNOTE("MIDI-SYSEX: Set Aftertouch Sensitivity %d to %d (scaled %d)", nTG, val & 0x0F, scaled);
407+
m_pSynthesizer->setAftertouchRange(scaled, nTG);
408+
}
409+
break;
410+
case 14: // Aftertouch Assign
411+
LOGNOTE("MIDI-SYSEX: Set Aftertouch Assign %d to %d", nTG, val & 0x0F);
412+
m_pSynthesizer->setAftertouchTarget(val, nTG);
413+
break;
414+
case 15: // Breath Controller Sensitivity
415+
{
416+
int scaled = (val * 99) / 15;
417+
LOGNOTE("MIDI-SYSEX: Set Breath Controller Sensitivity %d to %d (scaled %d)", nTG, val & 0x0F, scaled);
418+
m_pSynthesizer->setBreathControllerRange(scaled, nTG);
419+
}
420+
break;
421+
case 16: // Breath Controller Assign
422+
LOGNOTE("MIDI-SYSEX: Set Breath Controller Assign %d to %d", nTG, val & 0x0F);
423+
m_pSynthesizer->setBreathControllerTarget(val, nTG);
424+
break;
425+
case 26: // Audio Output Level Attenuator
426+
{
427+
LOGNOTE("MIDI-SYSEX: Set Audio Output Level Attenuator %d to %d", nTG, val & 0x0F);
428+
// Example: F0 43 10 04 1A 00 F7 to F0 43 10 04 1A 07 F7
429+
unsigned attenVal = val & 0x07;
430+
// unsigned newVolume = (unsigned)(127.0 * pow(attenVal / 7.0, 2.0) + 0.5); // Logarithmic mapping
431+
// But on the T816, there is an exponential (not logarithmic!) mapping, and 0 results in the same volume as 1:
432+
// 7=127, 6=63, 5=31, 4=15, 3=7, 2=3, 1=1, 0=1
433+
unsigned newVolume = (attenVal == 0) ? 0 : (127 >> (7 - attenVal));
434+
if (newVolume == 0) newVolume = 1; // 0 is like 1 to avoid silence
435+
m_pSynthesizer->SetVolume(newVolume, nTG);
436+
}
437+
break;
438+
case 64: // Master Tuning
439+
LOGNOTE("MIDI-SYSEX: Set Master Tuning");
440+
// TX812 scales from -75 to +75 cents.
441+
m_pSynthesizer->SetMasterTune(maplong(val, 1, 127, -37, 37), nTG); // Would need 37.5 here, due to wrong constrain on dexed_synth module?
442+
break;
443+
default:
444+
// Unknown or unsupported parameter
445+
LOGNOTE("MIDI-SYSEX: Unknown parameter %d for TG %d", par, nTG);
446+
break;
447+
}
448+
}
449+
else
450+
{
451+
HandleSystemExclusive(pMessage, nLength, nCable, nTG);
452+
}
333453
}
334454
}
335455
else
@@ -431,15 +551,15 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
431551
case MIDI_CC_SOSTENUTO:
432552
m_pSynthesizer->setSostenuto (pMessage[2] >= 64, nTG);
433553
break;
434-
554+
435555
case MIDI_CC_PORTAMENTO:
436556
m_pSynthesizer->setPortamentoMode (pMessage[2] >= 64, nTG);
437557
break;
438558

439559
case MIDI_CC_HOLD2:
440560
m_pSynthesizer->setHoldMode (pMessage[2] >= 64, nTG);
441561
break;
442-
562+
443563
case MIDI_CC_RESONANCE:
444564
m_pSynthesizer->SetResonance (maplong (pMessage[2], 0, 127, 0, 99), nTG);
445565
break;
@@ -455,11 +575,12 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
455575
case MIDI_CC_DETUNE_LEVEL:
456576
if (pMessage[2] == 0)
457577
{
458-
// "0 to 127, with 0 being no celeste (detune) effect applied at all."
578+
// 0 to 127, with 0 being no detune effect applied at all
459579
m_pSynthesizer->SetMasterTune (0, nTG);
460580
}
461581
else
462582
{
583+
// Scale to -99 to +99 cents
463584
m_pSynthesizer->SetMasterTune (maplong (pMessage[2], 1, 127, -99, 99), nTG);
464585
}
465586
break;
@@ -594,10 +715,12 @@ bool CMIDIDevice::HandleMIDISystemCC(const u8 ucCC, const u8 ucCCval)
594715
if (ucCC == MIDISystemCCMap[m_nMIDISystemCCDetune][tg]) {
595716
if (ucCCval == 0)
596717
{
718+
// 0 to 127, with 0 being no detune effect applied at all
597719
m_pSynthesizer->SetMasterTune (0, tg);
598720
}
599721
else
600722
{
723+
// Scale to -99 to +99 cents
601724
m_pSynthesizer->SetMasterTune (maplong (ucCCval, 1, 127, -99, 99), tg);
602725
}
603726
return true;
@@ -649,62 +772,6 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL
649772
case -11:
650773
LOGERR("Unknown SysEx message.");
651774
break;
652-
case 64:
653-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
654-
m_pSynthesizer->setMonoMode(pMessage[5],nTG);
655-
break;
656-
case 65:
657-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
658-
m_pSynthesizer->setPitchbendRange(pMessage[5],nTG);
659-
break;
660-
case 66:
661-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
662-
m_pSynthesizer->setPitchbendStep(pMessage[5],nTG);
663-
break;
664-
case 67:
665-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
666-
m_pSynthesizer->setPortamentoMode(pMessage[5],nTG);
667-
break;
668-
case 68:
669-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
670-
m_pSynthesizer->setPortamentoGlissando(pMessage[5],nTG);
671-
break;
672-
case 69:
673-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
674-
m_pSynthesizer->setPortamentoTime(pMessage[5],nTG);
675-
break;
676-
case 70:
677-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
678-
m_pSynthesizer->setModWheelRange(pMessage[5],nTG);
679-
break;
680-
case 71:
681-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
682-
m_pSynthesizer->setModWheelTarget(pMessage[5],nTG);
683-
break;
684-
case 72:
685-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
686-
m_pSynthesizer->setFootControllerRange(pMessage[5],nTG);
687-
break;
688-
case 73:
689-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
690-
m_pSynthesizer->setFootControllerTarget(pMessage[5],nTG);
691-
break;
692-
case 74:
693-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
694-
m_pSynthesizer->setBreathControllerRange(pMessage[5],nTG);
695-
break;
696-
case 75:
697-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
698-
m_pSynthesizer->setBreathControllerTarget(pMessage[5],nTG);
699-
break;
700-
case 76:
701-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
702-
m_pSynthesizer->setAftertouchRange(pMessage[5],nTG);
703-
break;
704-
case 77:
705-
LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]);
706-
m_pSynthesizer->setAftertouchTarget(pMessage[5],nTG);
707-
break;
708775
case 100:
709776
// load sysex-data into voice memory
710777
LOGDBG("One Voice bulk upload");
@@ -756,4 +823,4 @@ void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const unsigned nCable
756823
Iterator->second->Send (voicedump, sizeof(voicedump)*sizeof(uint8_t));
757824
// LOGDBG("Send SYSEX voice dump %u to \"%s\"",nVoice,Iterator->first.c_str());
758825
}
759-
}
826+
}

src/uimenu.cpp

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,12 +1098,14 @@ string CUIMenu::GetOPValueString (unsigned nOPParameter, int nValue)
10981098

10991099
string CUIMenu::ToVolume (int nValue)
11001100
{
1101-
static const size_t MaxChars = CConfig::LCDColumns-2;
1102-
char VolumeBar[MaxChars+1];
1103-
memset (VolumeBar, 0xFF, sizeof VolumeBar); // 0xFF is the block character
1104-
VolumeBar[nValue * MaxChars / 127] = '\0';
1105-
1106-
return VolumeBar;
1101+
constexpr size_t NumSquares = 14;
1102+
char VolumeBar[NumSquares + 1];
1103+
size_t filled = (nValue * NumSquares + 63) / 127;
1104+
for (size_t i = 0; i < NumSquares; ++i) {
1105+
VolumeBar[i] = (i < filled) ? (char)0xFF : '.';
1106+
}
1107+
VolumeBar[NumSquares] = '\0';
1108+
return VolumeBar;
11071109
}
11081110

11091111
string CUIMenu::ToPan (int nValue)
@@ -1394,11 +1396,11 @@ void CUIMenu::PgmUpDownHandler (TMenuEvent Event)
13941396
|| voiceName == "----------"
13951397
|| voiceName == "~~~~~~~~~~" )
13961398
{
1397-
if (Event == MenuEventPgmUp) {
1398-
PgmUpDownHandler (MenuEventPgmUp);
1399+
if (Event == MenuEventStepUp) {
1400+
PgmUpDownHandler (MenuEventStepUp);
13991401
}
1400-
if (Event == MenuEventPgmDown) {
1401-
PgmUpDownHandler (MenuEventPgmDown);
1402+
if (Event == MenuEventStepDown) {
1403+
PgmUpDownHandler (MenuEventStepDown);
14021404
}
14031405
}
14041406
}
@@ -2043,6 +2045,15 @@ void CUIMenu::EditMasterVolume(CUIMenu *pUIMenu, TMenuEvent Event)
20432045
default:
20442046
return;
20452047
}
2046-
std::string valueStr = ToVolume(pUIMenu->m_pMiniDexed->GetMasterVolume127());
2047-
pUIMenu->m_pUI->DisplayWrite("Master Volume", "", valueStr.c_str(), nValue > rParam.Minimum, nValue < rParam.Maximum);
2048+
unsigned lcdCols = pUIMenu->m_pConfig->GetLCDColumns();
2049+
unsigned barLen = (lcdCols > 2) ? lcdCols - 2 : 0;
2050+
std::string valueStr(barLen, '.');
2051+
if (barLen > 0) {
2052+
size_t filled = (nValue * barLen + 63) / 127;
2053+
for (unsigned i = 0; i < barLen; ++i) {
2054+
if (i < filled) valueStr[i] = (char)0xFF;
2055+
}
2056+
}
2057+
// Do NOT add < or > here; let DisplayWrite handle it
2058+
pUIMenu->m_pUI->DisplayWrite("Master Volume", "", valueStr.c_str(), true, true);
20482059
}

0 commit comments

Comments
 (0)