From 132b4c52229eec9b54baf8661ea4dbd828850b14 Mon Sep 17 00:00:00 2001 From: Rene Stange Date: Mon, 7 Mar 2022 10:08:19 +0100 Subject: [PATCH] Add initial optional rotary encoder support * Can change between voice and bank select mode by clicking the switch * Turn the knob to select the voice or bank according to current mode * Hold the switch for 3 seconds to reboot the system * Program change from MIDI is only displayed in voice select mode * Configuration for KY-040 added to CConfig and minidexed.ini * Fix: m_I2CMaster has not been initialized in CKernel --- src/config.cpp | 25 ++++++++ src/config.h | 12 ++++ src/kernel.cpp | 17 +++++- src/kernel.h | 2 + src/minidexed.cpp | 28 +++++---- src/minidexed.h | 14 +++-- src/minidexed.ini | 6 ++ src/sysexfileloader.cpp | 30 +++++++++- src/sysexfileloader.h | 3 + src/userinterface.cpp | 127 ++++++++++++++++++++++++++++++++++++++-- src/userinterface.h | 25 +++++++- 11 files changed, 264 insertions(+), 25 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index 9eb18a9..a31e1a4 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -52,6 +52,11 @@ void CConfig::Load (void) m_nLCDPinData6 = m_Properties.GetNumber ("LCDPinData6", 24); m_nLCDPinData7 = m_Properties.GetNumber ("LCDPinData7", 25); + m_bEncoderEnabled = m_Properties.GetNumber ("EncoderEnabled", 0) != 0; + m_nEncoderPinClock = m_Properties.GetNumber ("EncoderPinClock", 5); + m_nEncoderPinData = m_Properties.GetNumber ("EncoderPinData", 6); + m_nEncoderPinSwitch = m_Properties.GetNumber ("EncoderPinSwitch", 26); + m_bMIDIDumpEnabled = m_Properties.GetNumber ("MIDIDumpEnabled", 0) != 0; m_bProfileEnabled = m_Properties.GetNumber ("ProfileEnabled", 0) != 0; } @@ -121,6 +126,26 @@ unsigned CConfig::GetLCDPinData7 (void) const return m_nLCDPinData7; } +bool CConfig::GetEncoderEnabled (void) const +{ + return m_bEncoderEnabled; +} + +unsigned CConfig::GetEncoderPinClock (void) const +{ + return m_nEncoderPinClock; +} + +unsigned CConfig::GetEncoderPinData (void) const +{ + return m_nEncoderPinData; +} + +unsigned CConfig::GetEncoderPinSwitch (void) const +{ + return m_nEncoderPinSwitch; +} + bool CConfig::GetMIDIDumpEnabled (void) const { return m_bMIDIDumpEnabled; diff --git a/src/config.h b/src/config.h index 698bca4..1846743 100644 --- a/src/config.h +++ b/src/config.h @@ -67,6 +67,13 @@ public: unsigned GetLCDPinData6 (void) const; unsigned GetLCDPinData7 (void) const; + // KY-040 Rotary Encoder + // GPIO pin numbers are chip numbers, not header positions + bool GetEncoderEnabled (void) const; + unsigned GetEncoderPinClock (void) const; + unsigned GetEncoderPinData (void) const; + unsigned GetEncoderPinSwitch (void) const; + // Debug bool GetMIDIDumpEnabled (void) const; bool GetProfileEnabled (void) const; @@ -90,6 +97,11 @@ private: unsigned m_nLCDPinData6; unsigned m_nLCDPinData7; + bool m_bEncoderEnabled; + unsigned m_nEncoderPinClock; + unsigned m_nEncoderPinData; + unsigned m_nEncoderPinSwitch; + bool m_bMIDIDumpEnabled; bool m_bProfileEnabled; }; diff --git a/src/kernel.cpp b/src/kernel.cpp index c658854..2ad706d 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -30,6 +30,7 @@ CKernel *CKernel::s_pThis = 0; CKernel::CKernel (void) : CStdlibAppStdio ("minidexed"), m_Config (&mFileSystem), + m_GPIOManager (&mInterrupt), m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE), m_pDexed (0) { @@ -52,6 +53,16 @@ bool CKernel::Initialize (void) mLogger.RegisterPanicHandler (PanicHandler); + if (!m_GPIOManager.Initialize ()) + { + return FALSE; + } + + if (!m_I2CMaster.Initialize ()) + { + return FALSE; + } + m_Config.Load (); // select the sound device @@ -60,19 +71,19 @@ bool CKernel::Initialize (void) { LOGNOTE ("I2S mode"); - m_pDexed = new CMiniDexedI2S (&m_Config, &mInterrupt, &m_I2CMaster); + m_pDexed = new CMiniDexedI2S (&m_Config, &mInterrupt, &m_GPIOManager, &m_I2CMaster); } else if (strcmp (pSoundDevice, "hdmi") == 0) { LOGNOTE ("HDMI mode"); - m_pDexed = new CMiniDexedHDMI (&m_Config, &mInterrupt); + m_pDexed = new CMiniDexedHDMI (&m_Config, &mInterrupt, &m_GPIOManager); } else { LOGNOTE ("PWM mode"); - m_pDexed = new CMiniDexedPWM (&m_Config, &mInterrupt); + m_pDexed = new CMiniDexedPWM (&m_Config, &mInterrupt, &m_GPIOManager); } assert (m_pDexed); diff --git a/src/kernel.h b/src/kernel.h index 5d626f9..a2b5cc3 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -21,6 +21,7 @@ #define _kernel_h #include "circle_stdlib_app.h" +#include #include #include "config.h" #include "minidexed.h" @@ -48,6 +49,7 @@ private: private: // do not change this order CConfig m_Config; + CGPIOManager m_GPIOManager; CI2CMaster m_I2CMaster; CMiniDexed *m_pDexed; diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 14d7251..476a8f3 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -24,10 +24,10 @@ LOGMODULE ("minidexed"); -CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt) +CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, CGPIOManager *pGPIOManager) : CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()), m_pConfig (pConfig), - m_UI (this, pConfig), + m_UI (this, pGPIOManager, pConfig), m_PCKeyboard (this), m_SerialMIDI (this, pInterrupt, pConfig), m_bUseSerial (false), @@ -89,6 +89,11 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) } } +CSysExFileLoader *CMiniDexed::GetSysExFileLoader (void) +{ + return &m_SysExFileLoader; +} + void CMiniDexed::BankSelectLSB (unsigned nBankLSB) { if (nBankLSB > 127) @@ -96,10 +101,9 @@ void CMiniDexed::BankSelectLSB (unsigned nBankLSB) return; } - // MIDI numbering starts with 0, user interface with 1 - printf ("Select voice bank %u\n", nBankLSB+1); - m_SysExFileLoader.SelectVoiceBank (nBankLSB); + + m_UI.BankSelected (nBankLSB); } void CMiniDexed::ProgramChange (unsigned nProgram) @@ -118,8 +122,9 @@ void CMiniDexed::ProgramChange (unsigned nProgram) //// PWM ////////////////////////////////////////////////////////////////////// -CMiniDexedPWM::CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt) -: CMiniDexed (pConfig, pInterrupt), +CMiniDexedPWM::CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt, + CGPIOManager *pGPIOManager) +: CMiniDexed (pConfig, pInterrupt, pGPIOManager), CPWMSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), pConfig->GetChunkSize ()) { @@ -169,8 +174,8 @@ unsigned CMiniDexedPWM::GetChunk (u32 *pBuffer, unsigned nChunkSize) //// I2S ////////////////////////////////////////////////////////////////////// CMiniDexedI2S::CMiniDexedI2S (CConfig *pConfig, CInterruptSystem *pInterrupt, - CI2CMaster *pI2CMaster) -: CMiniDexed (pConfig, pInterrupt), + CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster) +: CMiniDexed (pConfig, pInterrupt, pGPIOManager), CI2SSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), pConfig->GetChunkSize (), false, pI2CMaster, pConfig->GetDACI2CAddress ()) @@ -218,8 +223,9 @@ unsigned CMiniDexedI2S::GetChunk (u32 *pBuffer, unsigned nChunkSize) //// HDMI ///////////////////////////////////////////////////////////////////// -CMiniDexedHDMI::CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt) -: CMiniDexed (pConfig, pInterrupt), +CMiniDexedHDMI::CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt, + CGPIOManager *pGPIOManager) +: CMiniDexed (pConfig, pInterrupt, pGPIOManager), CHDMISoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), pConfig->GetChunkSize ()) { diff --git a/src/minidexed.h b/src/minidexed.h index 5e82ebe..d817f87 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -39,12 +40,15 @@ class CMiniDexed : public CDexedAdapter { public: - CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt); + CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, + CGPIOManager *pGPIOManager); virtual bool Initialize (void); void Process (bool bPlugAndPlayUpdated); + CSysExFileLoader *GetSysExFileLoader (void); + void BankSelectLSB (unsigned nBankLSB); void ProgramChange (unsigned nProgram); @@ -69,7 +73,8 @@ protected: class CMiniDexedPWM : public CMiniDexed, public CPWMSoundBaseDevice { public: - CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt); + CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt, + CGPIOManager *pGPIOManager); bool Initialize (void); @@ -82,7 +87,7 @@ class CMiniDexedI2S : public CMiniDexed, public CI2SSoundBaseDevice { public: CMiniDexedI2S (CConfig *pConfig, CInterruptSystem *pInterrupt, - CI2CMaster *pI2CMaster); + CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster); bool Initialize (void); @@ -94,7 +99,8 @@ public: class CMiniDexedHDMI : public CMiniDexed, public CHDMISoundBaseDevice { public: - CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt); + CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt, + CGPIOManager *pGPIOManager); bool Initialize (void); diff --git a/src/minidexed.ini b/src/minidexed.ini index bf67453..ff1117b 100644 --- a/src/minidexed.ini +++ b/src/minidexed.ini @@ -21,6 +21,12 @@ LCDPinData5=23 LCDPinData6=24 LCDPinData7=25 +# KY-040 Rotary Encoder +EncoderEnabled=1 +EncoderPinClock=5 +EncoderPinData=6 +EncoderPinSwitch=26 + # Debug MIDIDumpEnabled=1 ProfileEnabled=1 diff --git a/src/sysexfileloader.cpp b/src/sysexfileloader.cpp index 63d8601..a22a9a5 100644 --- a/src/sysexfileloader.cpp +++ b/src/sysexfileloader.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include "voices.c" @@ -80,7 +81,7 @@ void CSysExFileLoader::Load (void) size_t nLen = strlen (pEntry->d_name); if ( nLen < 5 // "[NNNN]N[_name].syx" - || strcmp (&pEntry->d_name[nLen-4], ".syx") != 0 + || strcasecmp (&pEntry->d_name[nLen-4], ".syx") != 0 || sscanf (pEntry->d_name, "%u", &nBank) != 1) { LOGWARN ("%s: Invalid filename format", pEntry->d_name); @@ -119,6 +120,8 @@ void CSysExFileLoader::Load (void) && m_pVoiceBank[nBank]->StatusEnd == 0xF7) { LOGDBG ("Bank #%u successfully loaded", nBank); + + m_BankFileName[nBank] = pEntry->d_name; } else { @@ -140,6 +143,31 @@ void CSysExFileLoader::Load (void) closedir (pDirectory); } +std::string CSysExFileLoader::GetBankName (unsigned nBankID) +{ + if (nBankID <= MaxVoiceBankID) + { + std::string Result = m_BankFileName[nBankID]; + + size_t nLen = Result.length (); + if (nLen > 4) + { + Result.resize (nLen-4); // remove file extension + + unsigned nBank; + char BankName[30+1]; + if (sscanf (Result.c_str (), "%u_%30s", &nBank, BankName) == 2) + { + Result = BankName; + + return Result; + } + } + } + + return "NO NAME"; +} + void CSysExFileLoader::SelectVoiceBank (unsigned nBankID) { if (nBankID <= MaxVoiceBankID) diff --git a/src/sysexfileloader.h b/src/sysexfileloader.h index 8b0ebab..fb5d0e6 100644 --- a/src/sysexfileloader.h +++ b/src/sysexfileloader.h @@ -56,6 +56,8 @@ public: void Load (void); + std::string GetBankName (unsigned nBankID); // 0 .. 127 + void SelectVoiceBank (unsigned nBankID); // 0 .. 127 void GetVoice (unsigned nVoiceID, // 0 .. 31 @@ -68,6 +70,7 @@ private: std::string m_DirName; TVoiceBank *m_pVoiceBank[MaxVoiceBankID+1]; + std::string m_BankFileName[MaxVoiceBankID+1]; unsigned m_nBankID; diff --git a/src/userinterface.cpp b/src/userinterface.cpp index 7281118..6cc5b90 100644 --- a/src/userinterface.cpp +++ b/src/userinterface.cpp @@ -21,22 +21,30 @@ #include "minidexed.h" #include #include +#include #include #include +#include #include LOGMODULE ("ui"); -CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CConfig *pConfig) +CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManager, CConfig *pConfig) : m_pMiniDexed (pMiniDexed), + m_pGPIOManager (pGPIOManager), m_pConfig (pConfig), m_pLCD (0), - m_pLCDBuffered (0) + m_pLCDBuffered (0), + m_pRotaryEncoder (0), + m_UIMode (UIModeVoiceSelect), + m_nBank (0), + m_nProgram (0) { } CUserInterface::~CUserInterface (void) { + delete m_pRotaryEncoder; delete m_pLCDBuffered; delete m_pLCD; } @@ -70,6 +78,24 @@ bool CUserInterface::Initialize (void) 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; } @@ -81,8 +107,31 @@ void CUserInterface::Process (void) } } +void CUserInterface::BankSelected (unsigned nBankLSB) +{ + assert (nBankLSB < 128); + m_nBank = nBankLSB; + + assert (m_pMiniDexed); + std::string BankName = m_pMiniDexed->GetSysExFileLoader ()->GetBankName (nBankLSB); + + // MIDI numbering starts with 0, user interface with 1 + printf ("Select voice bank %u: \"%s\"\n", nBankLSB+1, BankName.c_str ()); + + if (m_UIMode == UIModeBankSelect) + { + CString String; + String.Format ("\n\r%-12uBANK%s", nBankLSB+1, BankName.c_str ()); + + LCDWrite (String); + } +} + void CUserInterface::ProgramChanged (unsigned nProgram) { + assert (nProgram < 128); + m_nProgram = nProgram; + nProgram++; // MIDI numbering starts with 0, user interface with 1 // fetch program name from Dexed instance @@ -93,9 +142,13 @@ void CUserInterface::ProgramChanged (unsigned nProgram) printf ("Loading voice %u: \"%s\"\n", nProgram, ProgramName); - CString String; - String.Format ("\n\r%u\n\r%s", nProgram, ProgramName); - LCDWrite (String); + if (m_UIMode == UIModeVoiceSelect) + { + CString String; + String.Format ("\n\r%-11uVOICE%s", nProgram, ProgramName); + + LCDWrite (String); + } } void CUserInterface::LCDWrite (const char *pString) @@ -105,3 +158,67 @@ void CUserInterface::LCDWrite (const char *pString) 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::EventSwitchHold: + if (m_pRotaryEncoder->GetHoldSeconds () >= 3) + { + delete m_pLCD; // reset LCD + + reboot (); + } + return; + + default: + return; + } + + switch (m_UIMode) + { + case UIModeBankSelect: + if (m_nBank + nStep < 128) + { + m_pMiniDexed->BankSelectLSB (m_nBank + nStep); + } + break; + + case UIModeVoiceSelect: + if (m_nProgram + nStep < 32) + { + m_pMiniDexed->ProgramChange (m_nProgram + nStep); + } + break; + + default: + break; + } +} + +void CUserInterface::EncoderEventStub (CKY040::TEvent Event, void *pParam) +{ + CUserInterface *pThis = static_cast (pParam); + assert (pThis != 0); + + pThis->EncoderEventHandler (Event); +} diff --git a/src/userinterface.h b/src/userinterface.h index 5bb24f0..0ce874c 100644 --- a/src/userinterface.h +++ b/src/userinterface.h @@ -21,7 +21,9 @@ #define _userinterface_h #include "config.h" +#include "ky040.h" #include +#include #include class CMiniDexed; @@ -29,24 +31,45 @@ class CMiniDexed; class CUserInterface { public: - CUserInterface (CMiniDexed *pMiniDexed, CConfig *pConfig); + CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManager, CConfig *pConfig); ~CUserInterface (void); bool Initialize (void); void Process (void); + void BankSelected (unsigned nBankLSB); // 0 .. 127 void ProgramChanged (unsigned nProgram); // 0 .. 127 private: void LCDWrite (const char *pString); // Print to optional HD44780 display + void EncoderEventHandler (CKY040::TEvent Event); + static void EncoderEventStub (CKY040::TEvent Event, void *pParam); + +private: + enum TUIMode + { + UIModeStart, + UIModeVoiceSelect = UIModeStart, + UIModeBankSelect, + UIModeUnknown + }; + private: CMiniDexed *m_pMiniDexed; + CGPIOManager *m_pGPIOManager; CConfig *m_pConfig; CHD44780Device *m_pLCD; CWriteBufferDevice *m_pLCDBuffered; + + CKY040 *m_pRotaryEncoder; + + TUIMode m_UIMode; + + unsigned m_nBank; + unsigned m_nProgram; }; #endif