diff --git a/src/Makefile b/src/Makefile index d379b88..540ae68 100644 --- a/src/Makefile +++ b/src/Makefile @@ -9,7 +9,7 @@ CMSIS_DIR = ../CMSIS_5/CMSIS OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ sysexfileloader.o performanceconfig.o perftimer.o \ - effect_compressor.o effect_platervbstereo.o uibuttons.o + effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o OPTIMIZE = -O3 diff --git a/src/config.cpp b/src/config.cpp index b38991f..45248f4 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -104,6 +104,13 @@ void CConfig::Load (void) m_nDoubleClickTimeout = m_Properties.GetNumber ("DoubleClickTimeout", 400); m_nLongPressTimeout = m_Properties.GetNumber ("LongPressTimeout", 600); + m_nMIDIButtonCh = m_Properties.GetNumber ("MIDIButtonCh", 255); + m_nMIDIButtonPrev = m_Properties.GetNumber ("MIDIButtonPrev", 0); + m_nMIDIButtonNext = m_Properties.GetNumber ("MIDIButtonNext", 0); + m_nMIDIButtonBack = m_Properties.GetNumber ("MIDIButtonBack", 0); + m_nMIDIButtonSelect = m_Properties.GetNumber ("MIDIButtonSelect", 0); + m_nMIDIButtonHome = m_Properties.GetNumber ("MIDIButtonHome", 0); + m_bEncoderEnabled = m_Properties.GetNumber ("EncoderEnabled", 0) != 0; m_nEncoderPinClock = m_Properties.GetNumber ("EncoderPinClock", 10); m_nEncoderPinData = m_Properties.GetNumber ("EncoderPinData", 9); @@ -293,6 +300,36 @@ unsigned CConfig::GetLongPressTimeout (void) const return m_nLongPressTimeout; } +unsigned CConfig::GetMIDIButtonCh (void) const +{ + return m_nMIDIButtonCh; +} + +unsigned CConfig::GetMIDIButtonPrev (void) const +{ + return m_nMIDIButtonPrev; +} + +unsigned CConfig::GetMIDIButtonNext (void) const +{ + return m_nMIDIButtonNext; +} + +unsigned CConfig::GetMIDIButtonBack (void) const +{ + return m_nMIDIButtonBack; +} + +unsigned CConfig::GetMIDIButtonSelect (void) const +{ + return m_nMIDIButtonSelect; +} + +unsigned CConfig::GetMIDIButtonHome (void) const +{ + return m_nMIDIButtonHome; +} + bool CConfig::GetEncoderEnabled (void) const { return m_bEncoderEnabled; diff --git a/src/config.h b/src/config.h index dc5e7a3..b970ebb 100644 --- a/src/config.h +++ b/src/config.h @@ -117,6 +117,14 @@ public: unsigned GetDoubleClickTimeout (void) const; unsigned GetLongPressTimeout (void) const; + // MIDI Button Navigation + unsigned GetMIDIButtonCh (void) const; + unsigned GetMIDIButtonPrev (void) const; + unsigned GetMIDIButtonNext (void) const; + unsigned GetMIDIButtonBack (void) const; + unsigned GetMIDIButtonSelect (void) const; + unsigned GetMIDIButtonHome (void) const; + // KY-040 Rotary Encoder // GPIO pin numbers are chip numbers, not header positions bool GetEncoderEnabled (void) const; @@ -177,6 +185,13 @@ private: unsigned m_nDoubleClickTimeout; unsigned m_nLongPressTimeout; + unsigned m_nMIDIButtonCh; + unsigned m_nMIDIButtonPrev; + unsigned m_nMIDIButtonNext; + unsigned m_nMIDIButtonBack; + unsigned m_nMIDIButtonSelect; + unsigned m_nMIDIButtonHome; + bool m_bEncoderEnabled; unsigned m_nEncoderPinClock; unsigned m_nEncoderPinData; diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 7b3683f..7bc1263 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -27,6 +27,7 @@ #include "config.h" #include #include +#include "userinterface.h" LOGMODULE ("mididevice"); @@ -181,6 +182,19 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } else { + // Perform any MiniDexed level MIDI handling before specific Tone Generators + switch (ucType) + { + case MIDI_CONTROL_CHANGE: + if (nLength < 3) + { + break; + } + CUserInterface::UIMIDICCHandler (ucChannel, pMessage[1], pMessage[2]); + break; + } + + // Process MIDI for each Tone Generator for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) { if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN) diff --git a/src/midipin.cpp b/src/midipin.cpp new file mode 100644 index 0000000..e382502 --- /dev/null +++ b/src/midipin.cpp @@ -0,0 +1,55 @@ +// +// midipin.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 "midipin.h" +#include +#include + +LOGMODULE ("midipin"); + +CMIDIPin::CMIDIPin (unsigned nPinNumber) +: m_nPinNumber (nPinNumber), + m_nValue (HIGH) +{ +} + +CMIDIPin::~CMIDIPin (void) +{ +} + +unsigned CMIDIPin::read (void) +{ + return m_nValue; +} + +void CMIDIPin::write (unsigned nValue) +{ + // Takes values in the MIDI controller range 0 to 127 + // and OFF < 64 < ON. + // Simulates a PULLUP IO pin, so "true" is LOW (0) + if (nValue >= 64) { + // "on" + m_nValue = LOW; + } else { + // "off" + m_nValue = HIGH; + } + return; +} + diff --git a/src/midipin.h b/src/midipin.h new file mode 100644 index 0000000..a730d01 --- /dev/null +++ b/src/midipin.h @@ -0,0 +1,54 @@ +// +// midipin.h +// +// 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 . +// +#ifndef _midipin_h +#define _midipin_h + +#include +#include + +// MIDI CC numbers go 0 to 127. +// Normal GPIO pins are below 100. +// So use a "pin number" of 128 + MIDI CC message for a "MIDI Pin" +#define MIDI_PINS 128 +#define ccToMidiPin(c) ((c)+MIDI_PINS) +#define MidiPinToCC(p) ((p)-MIDI_PINS) +#define isMidiPin(p) (((p)>=MIDI_PINS)?1:0) + +class CMIDIPin : public CGPIOPin +{ +public: + CMIDIPin (unsigned nPinNumber); // pinNumber = ccToMidiPin (MIDI CC number) + ~CMIDIPin (void); + + // Will return MP_HIGH or MP_LOW. + // Should be treated as a PULLED UP IO pin + // i.e. treated as "active low" (LOW) when pressed. + unsigned read (void); + + // MIDI CC values >=64 will set the MIDI pin to LOW ("on") + // MIDI CC values <= 63 will set the MIDI pin to HIGH ("off") + void write (unsigned nValue); + +private: + unsigned m_nPinNumber; + unsigned m_nValue; +}; + +#endif diff --git a/src/minidexed.ini b/src/minidexed.ini index d9beec5..00826b1 100644 --- a/src/minidexed.ini +++ b/src/minidexed.ini @@ -57,6 +57,17 @@ ButtonPinShortcut=11 DoubleClickTimeout=400 LongPressTimeout=400 +# MIDI Button Navigation +# Specify MIDI CC to act as a button +# NB: Off < 64 < ON +# CC channel: 0=OMNI; 1-16 MIDI Ch; >16 OFF +MIDIButtonCh=255 +MIDIButtonPrev=85 +MIDIButtonNext=86 +MIDIButtonHome=87 +MIDIButtonSelect=89 +MIDIButtonBack=90 + # KY-040 Rotary Encoder EncoderEnabled=1 EncoderPinClock=10 diff --git a/src/uibuttons.cpp b/src/uibuttons.cpp index 7635b18..e0e7b98 100644 --- a/src/uibuttons.cpp +++ b/src/uibuttons.cpp @@ -4,9 +4,6 @@ // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi // Copyright (C) 2022 The MiniDexed Team // -// Original author of this class: -// R. Stange -// // 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 @@ -72,7 +69,12 @@ boolean CUIButton::Initialize (unsigned pinNumber, unsigned doubleClickTimeout, if (m_pinNumber != 0) { - m_pin = new CGPIOPin (m_pinNumber, GPIOModeInputPullUp); + if (isMidiPin(m_pinNumber)) + { + m_pin = new CMIDIPin (m_pinNumber); + } else { + m_pin = new CGPIOPin (m_pinNumber, GPIOModeInputPullUp); + } } return TRUE; } @@ -233,7 +235,8 @@ CUIButtons::CUIButtons ( unsigned backPin, const char *backAction, unsigned selectPin, const char *selectAction, unsigned homePin, const char *homeAction, - unsigned doubleClickTimeout, unsigned longPressTimeout + unsigned doubleClickTimeout, unsigned longPressTimeout, + unsigned prevMidi, unsigned nextMidi, unsigned backMidi, unsigned selectMidi, unsigned homeMidi ) : m_doubleClickTimeout(doubleClickTimeout), m_longPressTimeout(longPressTimeout), @@ -247,6 +250,11 @@ CUIButtons::CUIButtons ( m_selectAction(CUIButton::triggerTypeFromString(selectAction)), m_homePin(homePin), m_homeAction(CUIButton::triggerTypeFromString(homeAction)), + m_prevMidi(prevMidi), + m_nextMidi(nextMidi), + m_backMidi(backMidi), + m_selectMidi(selectMidi), + m_homeMidi(homeMidi), m_eventHandler (0), m_lastTick (0) { @@ -274,15 +282,27 @@ boolean CUIButtons::Initialize (void) longPressTimeout = doubleClickTimeout; } - // Each button can be assigned up to 3 actions: click, doubleclick and - // longpress. We may not initialise all of the buttons + // Each normal button can be assigned up to 3 actions: click, doubleclick and + // longpress. We may not initialise all of the buttons. + // MIDI buttons only support a single click. unsigned pins[MAX_BUTTONS] = { - m_prevPin, m_nextPin, m_backPin, m_selectPin, m_homePin + m_prevPin, m_nextPin, m_backPin, m_selectPin, m_homePin, + m_prevMidi, m_nextMidi, m_backMidi, m_selectMidi, m_homeMidi }; CUIButton::BtnTrigger triggers[MAX_BUTTONS] = { - m_prevAction, m_nextAction, m_backAction, m_selectAction, m_homeAction + // Normal buttons + m_prevAction, m_nextAction, m_backAction, m_selectAction, m_homeAction, + // MIDI Buttons only support a single click (at present) + CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick }; CUIButton::BtnEvent events[MAX_BUTTONS] = { + // Normal buttons + CUIButton::BtnEventPrev, + CUIButton::BtnEventNext, + CUIButton::BtnEventBack, + CUIButton::BtnEventSelect, + CUIButton::BtnEventHome, + // MIDI buttons CUIButton::BtnEventPrev, CUIButton::BtnEventNext, CUIButton::BtnEventBack, @@ -290,7 +310,8 @@ boolean CUIButtons::Initialize (void) CUIButton::BtnEventHome }; - for (unsigned i=0; i -// // 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 @@ -25,11 +22,14 @@ #include #include +#include "midipin.h" #include "config.h" #define BUTTONS_UPDATE_NUM_TICKS 100 #define DEBOUNCE_TIME 100 -#define MAX_BUTTONS 5 +#define MAX_GPIO_BUTTONS 5 +#define MAX_MIDI_BUTTONS 5 +#define MAX_BUTTONS (MAX_GPIO_BUTTONS+MAX_MIDI_BUTTONS) class CUIButtons; @@ -110,7 +110,8 @@ public: unsigned backPin, const char *backAction, unsigned selectPin, const char *selectAction, unsigned homePin, const char *homeAction, - unsigned doubleClickTimeout, unsigned longPressTimeout + unsigned doubleClickTimeout, unsigned longPressTimeout, + unsigned prevMidi, unsigned nextMidi, unsigned backMidi, unsigned selectMidi, unsigned homeMidi ); ~CUIButtons (void); @@ -123,7 +124,7 @@ public: void ResetButton (unsigned pinNumber); private: - // Array of 5 buttons + // Array of normal GPIO buttons and "MIDI buttons" CUIButton m_buttons[MAX_BUTTONS]; // Timeout for double click in tenths of a millisecond @@ -142,6 +143,13 @@ private: CUIButton::BtnTrigger m_selectAction; unsigned m_homePin; CUIButton::BtnTrigger m_homeAction; + + // MIDI button configuration + unsigned m_prevMidi; + unsigned m_nextMidi; + unsigned m_backMidi; + unsigned m_selectMidi; + unsigned m_homeMidi; BtnEventHandler *m_eventHandler; void *m_eventParam; diff --git a/src/userinterface.cpp b/src/userinterface.cpp index 17083bd..f892853 100644 --- a/src/userinterface.cpp +++ b/src/userinterface.cpp @@ -27,6 +27,8 @@ LOGMODULE ("ui"); +unsigned CUserInterface::nMIDIButtonCh = 255; + CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, CConfig *pConfig) : m_pMiniDexed (pMiniDexed), m_pGPIOManager (pGPIOManager), @@ -114,7 +116,13 @@ bool CUserInterface::Initialize (void) m_pConfig->GetButtonPinHome (), m_pConfig->GetButtonActionHome (), m_pConfig->GetDoubleClickTimeout (), - m_pConfig->GetLongPressTimeout () ); + m_pConfig->GetLongPressTimeout (), + m_pConfig->GetMIDIButtonNext (), + m_pConfig->GetMIDIButtonPrev (), + m_pConfig->GetMIDIButtonBack (), + m_pConfig->GetMIDIButtonSelect (), + m_pConfig->GetMIDIButtonHome () + ); assert (m_pUIButtons); if (!m_pUIButtons->Initialize ()) @@ -123,6 +131,7 @@ bool CUserInterface::Initialize (void) } m_pUIButtons->RegisterEventHandler (UIButtonsEventStub, this); + nMIDIButtonCh = m_pConfig->GetMIDIButtonCh (); LOGDBG ("Button User Interface initialized"); @@ -322,3 +331,12 @@ void CUserInterface::UIButtonsEventStub (CUIButton::BtnEvent Event, void *pParam pThis->UIButtonsEventHandler (Event); } + +void CUserInterface::UIMIDICCHandler (unsigned nMidiCh, unsigned nMidiCC, unsigned nMidiData) +{ + if ((nMIDIButtonCh != nMidiCh) && (nMIDIButtonCh != 0)) + { + // Message not on the MIDI Button channel and MIDI buttons not in OMNI mode + return; + } +} diff --git a/src/userinterface.h b/src/userinterface.h index 8d03503..956b6b6 100644 --- a/src/userinterface.h +++ b/src/userinterface.h @@ -52,6 +52,9 @@ public: void DisplayWrite (const char *pMenu, const char *pParam, const char *pValue, bool bArrowDown, bool bArrowUp); + // To be called from the MIDI device on reception of a MIDI CC message + static void UIMIDICCHandler (unsigned nMidiCh, unsigned nMidiCC, unsigned nMidiData); + private: void LCDWrite (const char *pString); // Print to optional HD44780 display @@ -73,6 +76,8 @@ private: CUIButtons *m_pUIButtons; + static unsigned nMIDIButtonCh; + CKY040 *m_pRotaryEncoder; bool m_bSwitchPressed;