// // 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); assert (CConfig::LCDColumns == 16); char PanMarker[CConfig::LCDColumns] = ".......:......."; unsigned nIndex = nPan * (CConfig::LCDColumns-1) / 127; if (nIndex == CConfig::LCDColumns-1) { 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); }