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); +}