diff --git a/src/userinterface.cpp b/src/userinterface.cpp
index 2df0fdc..9e768de 100644
--- a/src/userinterface.cpp
+++ b/src/userinterface.cpp
@@ -198,11 +198,10 @@ void CUserInterface::PanChanged (unsigned nPan, unsigned nTG)
CString TG;
TG.Format ("TG%u", nTG+1);
- char PanMarker[CConfig::LCDColumns+1];
- memset (PanMarker, '.', CConfig::LCDColumns);
- PanMarker[CConfig::LCDColumns] = '\0';
- unsigned nIndex = nPan * CConfig::LCDColumns / 127;
- if (nIndex == CConfig::LCDColumns)
+ assert (CConfig::LCDColumns == 16);
+ char PanMarker[CConfig::LCDColumns] = ".......:.......";
+ unsigned nIndex = nPan * (CConfig::LCDColumns-1) / 127;
+ if (nIndex == CConfig::LCDColumns-1)
{
nIndex--;
}
diff --git a/src/userinterface.cpp.orig b/src/userinterface.cpp.orig
new file mode 100644
index 0000000..2df0fdc
--- /dev/null
+++ b/src/userinterface.cpp.orig
@@ -0,0 +1,442 @@
+//
+// userinterface.cpp
+//
+// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
+// Copyright (C) 2022 The MiniDexed Team
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "userinterface.h"
+#include "minidexed.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+LOGMODULE ("ui");
+
+CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManager, CConfig *pConfig)
+: m_pMiniDexed (pMiniDexed),
+ m_pGPIOManager (pGPIOManager),
+ m_pConfig (pConfig),
+ m_pLCD (0),
+ m_pLCDBuffered (0),
+ m_pRotaryEncoder (0),
+ m_UIMode (UIModeVoiceSelect),
+ m_nTG (0)
+{
+ for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++)
+ {
+ m_nBank[nTG] = 0;
+ m_nProgram[nTG] = 0;
+ m_nVolume[nTG] = 0;
+ m_nPan[nTG] = 64;
+ m_nMasterTune[nTG] = 0;
+ m_uchMIDIChannel[nTG] = CMIDIDevice::Disabled;
+ }
+}
+
+CUserInterface::~CUserInterface (void)
+{
+ delete m_pRotaryEncoder;
+ delete m_pLCDBuffered;
+ delete m_pLCD;
+}
+
+bool CUserInterface::Initialize (void)
+{
+ assert (m_pConfig);
+
+ if (m_pConfig->GetLCDEnabled ())
+ {
+ m_pLCD = new CHD44780Device (CConfig::LCDColumns, CConfig::LCDRows,
+ m_pConfig->GetLCDPinData4 (),
+ m_pConfig->GetLCDPinData5 (),
+ m_pConfig->GetLCDPinData6 (),
+ m_pConfig->GetLCDPinData7 (),
+ m_pConfig->GetLCDPinEnable (),
+ m_pConfig->GetLCDPinRegisterSelect (),
+ m_pConfig->GetLCDPinReadWrite ());
+ assert (m_pLCD);
+
+ if (!m_pLCD->Initialize ())
+ {
+ return false;
+ }
+
+ m_pLCDBuffered = new CWriteBufferDevice (m_pLCD);
+ assert (m_pLCDBuffered);
+
+ LCDWrite ("\x1B[?25l\x1B""d+"); // cursor off, autopage mode
+
+ LOGDBG ("LCD initialized");
+ }
+
+ if (m_pConfig->GetEncoderEnabled ())
+ {
+ m_pRotaryEncoder = new CKY040 (m_pConfig->GetEncoderPinClock (),
+ m_pConfig->GetEncoderPinData (),
+ m_pConfig->GetEncoderPinSwitch (),
+ m_pGPIOManager);
+ assert (m_pRotaryEncoder);
+
+ if (!m_pRotaryEncoder->Initialize ())
+ {
+ return false;
+ }
+
+ m_pRotaryEncoder->RegisterEventHandler (EncoderEventStub, this);
+
+ LOGDBG ("Rotary encoder initialized");
+ }
+
+ return true;
+}
+
+void CUserInterface::Process (void)
+{
+ if (m_pLCDBuffered)
+ {
+ m_pLCDBuffered->Update ();
+ }
+}
+
+void CUserInterface::BankSelected (unsigned nBankLSB, unsigned nTG)
+{
+ assert (nBankLSB < 128);
+ assert (nTG < CConfig::ToneGenerators);
+ m_nBank[nTG] = nBankLSB;
+
+ assert (m_pMiniDexed);
+ std::string BankName = m_pMiniDexed->GetSysExFileLoader ()->GetBankName (nBankLSB);
+
+ // MIDI numbering starts with 0, user interface with 1
+ printf ("TG%u: Select voice bank %u: \"%s\"\n", nTG+1, nBankLSB+1, BankName.c_str ());
+
+ if ( m_UIMode == UIModeBankSelect
+ && m_nTG == nTG)
+ {
+ CString TG;
+ TG.Format ("TG%u", nTG+1);
+
+ CString String;
+ String.Format ("%u", nBankLSB+1);
+
+ DisplayWrite (TG, "BANK", String, BankName.c_str ());
+ }
+}
+
+void CUserInterface::ProgramChanged (unsigned nProgram, unsigned nTG)
+{
+ assert (nProgram < 128);
+ assert (nTG < CConfig::ToneGenerators);
+ m_nProgram[nTG] = nProgram;
+
+ nProgram++; // MIDI numbering starts with 0, user interface with 1
+
+ assert (m_pMiniDexed);
+ std::string VoiceName = m_pMiniDexed->GetVoiceName (nTG);
+
+ printf ("TG%u: Loading voice %u: \"%s\"\n", nTG+1, nProgram, VoiceName.c_str ());
+
+ if ( m_UIMode == UIModeVoiceSelect
+ && m_nTG == nTG)
+ {
+ CString TG;
+ TG.Format ("TG%u", nTG+1);
+
+ CString String;
+ String.Format ("%u", nProgram);
+
+ DisplayWrite (TG, "VOICE", String, VoiceName.c_str ());
+ }
+}
+
+void CUserInterface::VolumeChanged (unsigned nVolume, unsigned nTG)
+{
+ assert (nVolume < 128);
+ assert (nTG < CConfig::ToneGenerators);
+ m_nVolume[nTG] = nVolume;
+
+ if ( m_UIMode == UIModeVolume
+ && m_nTG == nTG)
+ {
+ CString TG;
+ TG.Format ("TG%u", nTG+1);
+
+ char VolumeBar[CConfig::LCDColumns+1];
+ memset (VolumeBar, 0xFF, sizeof VolumeBar); // 0xFF is the block character
+ VolumeBar[nVolume * CConfig::LCDColumns / 127] = '\0';
+
+ DisplayWrite (TG, "VOLUME", VolumeBar);
+ }
+}
+
+void CUserInterface::PanChanged (unsigned nPan, unsigned nTG)
+{
+ assert (nPan < 128);
+ assert (nTG < CConfig::ToneGenerators);
+ m_nPan[nTG] = nPan;
+
+ if ( m_UIMode == UIModePan
+ && m_nTG == nTG)
+ {
+ CString TG;
+ TG.Format ("TG%u", nTG+1);
+
+ char PanMarker[CConfig::LCDColumns+1];
+ memset (PanMarker, '.', CConfig::LCDColumns);
+ PanMarker[CConfig::LCDColumns] = '\0';
+ unsigned nIndex = nPan * CConfig::LCDColumns / 127;
+ if (nIndex == CConfig::LCDColumns)
+ {
+ nIndex--;
+ }
+ PanMarker[nIndex] = '\xFF';
+
+ DisplayWrite (TG, "PAN", PanMarker);
+ }
+}
+
+void CUserInterface::MasterTuneChanged (int nMasterTune, unsigned nTG)
+{
+ assert (-99 <= nMasterTune && nMasterTune <= 99);
+ assert (nTG < CConfig::ToneGenerators);
+ m_nMasterTune[nTG] = nMasterTune;
+
+ if ( m_UIMode == UIModeMasterTune
+ && m_nTG == nTG)
+ {
+ CString TG;
+ TG.Format ("TG%u", nTG+1);
+
+ CString String;
+ String.Format ("%d", nMasterTune);
+
+ DisplayWrite (TG, "MASTER TUNE", "DETUNE", (const char *) String);
+ }
+}
+
+void CUserInterface::MIDIChannelChanged (uint8_t uchChannel, unsigned nTG)
+{
+ assert (nTG < CConfig::ToneGenerators);
+ m_uchMIDIChannel[nTG] = uchChannel;
+
+ if ( m_UIMode == UIModeMIDI
+ && m_nTG == nTG)
+ {
+ CString TG;
+ TG.Format ("TG%u", nTG+1);
+
+ CString String;
+ switch (uchChannel)
+ {
+ case CMIDIDevice::OmniMode: String = "OMNI"; break;
+ case CMIDIDevice::Disabled: String = "OFF"; break;
+
+ default:
+ String.Format ("%u", (unsigned) uchChannel+1);
+ break;
+ }
+
+ DisplayWrite (TG, "MIDI", "CHANNEL", (const char *) String);
+ }
+}
+
+void CUserInterface::DisplayWrite (const char *pInstance, const char *pMenu,
+ const char *pParam, const char *pValue)
+{
+ assert (pInstance);
+ assert (pMenu);
+ assert (pParam);
+
+ // Do not show instance, if there is only one.
+ if (CConfig::ToneGenerators == 1)
+ {
+ pInstance = "";
+ }
+
+ CString Msg ("\x1B[H"); // cursor home
+
+ // first line
+ Msg.Append (pInstance);
+
+ size_t nLen = strlen (pInstance) + strlen (pMenu);
+ if (nLen < CConfig::LCDColumns)
+ {
+ for (unsigned i = CConfig::LCDColumns-nLen; i > 0; i--)
+ {
+ Msg.Append (" ");
+ }
+ }
+
+ Msg.Append (pMenu);
+
+ // second line
+ CString ParamValue (pParam);
+ if (pValue)
+ {
+ ParamValue.Append ("=");
+ ParamValue.Append (pValue);
+ }
+
+ Msg.Append (ParamValue);
+
+ if (ParamValue.GetLength () < CConfig::LCDColumns)
+ {
+ Msg.Append ("\x1B[K"); // clear end of line
+ }
+
+ LCDWrite (Msg);
+}
+
+void CUserInterface::LCDWrite (const char *pString)
+{
+ if (m_pLCDBuffered)
+ {
+ m_pLCDBuffered->Write (pString, strlen (pString));
+ }
+}
+
+void CUserInterface::EncoderEventHandler (CKY040::TEvent Event)
+{
+ int nStep = 0;
+
+ switch (Event)
+ {
+ case CKY040::EventClockwise:
+ nStep = 1;
+ break;
+
+ case CKY040::EventCounterclockwise:
+ nStep = -1;
+ break;
+
+ case CKY040::EventSwitchClick:
+ m_UIMode = static_cast (m_UIMode+1);
+ if (m_UIMode == UIModeUnknown)
+ {
+ m_UIMode = UIModeStart;
+ }
+ break;
+
+ case CKY040::EventSwitchDoubleClick:
+ if (++m_nTG == CConfig::ToneGenerators)
+ {
+ m_nTG = 0;
+ }
+ break;
+
+ case CKY040::EventSwitchHold:
+ if (m_pRotaryEncoder->GetHoldSeconds () >= 3)
+ {
+ delete m_pLCD; // reset LCD
+
+ reboot ();
+ }
+ else
+ {
+ m_UIMode = UIModeStart;
+ m_nTG = 0;
+ }
+ break;
+
+ default:
+ return;
+ }
+
+ switch (m_UIMode)
+ {
+ case UIModeBankSelect:
+ if (m_nBank[m_nTG] + nStep < 128)
+ {
+ m_pMiniDexed->BankSelectLSB (m_nBank[m_nTG] + nStep, m_nTG);
+ }
+ break;
+
+ case UIModeVoiceSelect:
+ if (m_nProgram[m_nTG] + nStep < 32)
+ {
+ m_pMiniDexed->ProgramChange (m_nProgram[m_nTG] + nStep, m_nTG);
+ }
+ break;
+
+ case UIModeVolume: {
+ const int Increment = 128 / CConfig::LCDColumns;
+
+ int nVolume = m_nVolume[m_nTG] + nStep*Increment;
+ if (nVolume < 0)
+ {
+ nVolume = 0;
+ }
+ else if (nVolume > 127)
+ {
+ nVolume = 127;
+ }
+
+ m_pMiniDexed->SetVolume (nVolume, m_nTG);
+ } break;
+
+ case UIModePan: {
+ const int Increment = 128 / CConfig::LCDColumns;
+
+ int nPan = m_nPan[m_nTG] + nStep*Increment;
+ if (nPan < 0)
+ {
+ nPan = 0;
+ }
+ else if (nPan > 127)
+ {
+ nPan = 127;
+ }
+
+ m_pMiniDexed->SetPan (nPan, m_nTG);
+ } break;
+
+ case UIModeMasterTune: {
+ int nMasterTune = m_nMasterTune[m_nTG] + nStep;
+ if (nMasterTune < -99)
+ {
+ nMasterTune = -99;
+ }
+ else if (nMasterTune > 99)
+ {
+ nMasterTune = 99;
+ }
+
+ m_pMiniDexed->SetMasterTune (nMasterTune, m_nTG);
+ } break;
+
+ case UIModeMIDI:
+ if ((uint8_t) (m_uchMIDIChannel[m_nTG] + nStep) < CMIDIDevice::ChannelUnknown)
+ {
+ m_pMiniDexed->SetMIDIChannel (m_uchMIDIChannel[m_nTG] + nStep, m_nTG);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void CUserInterface::EncoderEventStub (CKY040::TEvent Event, void *pParam)
+{
+ CUserInterface *pThis = static_cast (pParam);
+ assert (pThis != 0);
+
+ pThis->EncoderEventHandler (Event);
+}