diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8df5911..a5cf9e8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,6 @@ on: push: branches: [ main ] pull_request: - branches: [ main ] jobs: Build: @@ -19,6 +18,10 @@ jobs: - name: Get specific commits of git submodules run: | sh -ex ./submod.sh + - name: Apply patches + run: | + # Put git hash in startup message + sed -i "s/Loading.../$(date +%Y%m%d)-$(git rev-parse --short HEAD)/g" src/userinterface.cpp - name: Install toolchains run: | set -ex @@ -82,6 +85,13 @@ jobs: zip -r ../MiniDexed_$GITHUB_RUN_NUMBER_$(date +%Y-%m-%d)-$(git rev-parse --short HEAD).zip * echo "artifactName=MiniDexed_$GITHUB_RUN_NUMBER_$(date +%Y-%m-%d)-$(git rev-parse --short HEAD)" >> $GITHUB_ENV cd - + - name: Hardware configration files + run: | + cd hwconfig + sh -ex ./customize.sh + cd - + mkdir -p ./sdcard/hardware/ + cp -r ./hwconfig/minidexed_* ./sdcard/minidexed.ini ./sdcard/hardware/ - uses: actions/upload-artifact@v3 with: name: ${{ env.artifactName }} # Exported above diff --git a/.gitignore b/.gitignore index 93bd001..5e9f987 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,5 @@ sdcard CMSIS_5/ Synth_Dexed/ -circle-stdlib/ \ No newline at end of file +circle-stdlib/ +minidexed_* \ No newline at end of file diff --git a/README.md b/README.md index 68cdb02..25ee4d2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![minidexed](https://user-images.githubusercontent.com/2480569/161813414-bb156a1c-efec-44c0-802a-8926412a08e0.jpg) -MiniDexed is a FM synthesizer closely modeled on the famous DX7 by a well-known Japanese manufacturer running on a bare metal Raspberry Pi (without a Linux kernel or operating system). On Raspberry Pi 2 and larger, it can run 8 tone generators, not unlike the TX816/TX802 (8 DX7 instances without the keyboard in one box). [Featured by HACKADAY](https://hackaday.com/2022/04/19/bare-metal-gives-this-pi-some-classic-synths/), [Adafruit](https://blog.adafruit.com/2022/04/25/free-yamaha-dx7-synth-emulator-on-a-raspberry-pi/), and [Synth Geekery](https://www.youtube.com/watch?v=TDSy5nnm0jA). +MiniDexed is a FM synthesizer closely modeled on the famous DX7 by a well-known Japanese manufacturer running on a bare metal Raspberry Pi (without a Linux kernel or operating system). On Raspberry Pi 2 and larger, it can run 8 tone generators, not unlike the TX816/TX802 (8 DX7 instances without the keyboard in one box). [Featured by HACKADAY](https://hackaday.com/2022/04/19/bare-metal-gives-this-pi-some-classic-synths/), [Adafruit](https://blog.adafruit.com/2022/04/25/free-yamaha-dx7-synth-emulator-on-a-raspberry-pi/), [The MagPi magazine](https://magpi.raspberrypi.com/articles/mini-dexed) (Issue 142 June 2024, [PDF](https://magpi.raspberrypi.com/issues/142)) and [Synth Geekery](https://www.youtube.com/watch?v=TDSy5nnm0jA). ## Demo songs @@ -98,6 +98,8 @@ This project stands on the shoulders of giants. Special thanks to: - [Banana71](https://github.com/Banana71) for the sound design of the [Soundplantage](https://github.com/Banana71/Soundplantage) performances shipped with MiniDexed - [BobanSpasic](https://github.com/BobanSpasic) for the [MiniDexedLibrarian](https://github.com/BobanSpasic/MiniDexedLibrarian) software, [MiniDexed performance converter](https://github.com/BobanSpasic/MDX_PerfConv) and [collection of performances for MiniDexed](https://github.com/BobanSpasic/MDX_Vault) - [diyelectromusic](https://github.com/diyelectromusic/) for many [contributions](https://github.com/probonopd/MiniDexed/commits?author=diyelectromusic) +- [dwhinham/mt32-pi](https://github.com/dwhinham/mt32-pi) for creating networking support for Circle +- [omersiar](https://github.com/omersiar) for porting networking support to MiniDexed ## Stargazers over time [![Stargazers over time](https://starchart.cc/probonopd/MiniDexed.svg?variant=adaptive)](https://starchart.cc/probonopd/MiniDexed) diff --git a/hwconfig/DT-DX.override b/hwconfig/DT-DX.override new file mode 100644 index 0000000..1e6ba75 --- /dev/null +++ b/hwconfig/DT-DX.override @@ -0,0 +1,46 @@ +# DTronics DT-DX +# https://www.dtronics.nl/dt-dx + +SoundDevice=i2s +SampleRate=22000 +ChunkSize=256 +DACI2CAddress=0x0 +ChannelsSwapped=1 + +LCDEnabled=1 +LCDPinEnable=17 +LCDPinRegisterSelect=27 +LCDPinReadWrite=16 +LCDPinData4=22 +LCDPinData5=23 +LCDPinData6=24 +LCDPinData7=25 +LCDI2CAddress=0x00 + +SSD1306LCDI2CAddress=0x00 +SSD1306LCDWidth=128 +SSD1306LCDHeight=32 +SSD1306LCDRotate=0 +SSD1306LCDMirror=0 + +LCDColumns=16 +LCDRows=2 + +ButtonPinPrev=0 +ButtonActionPrev=0 +ButtonPinNext=0 +ButtonActionNext=0 +ButtonPinBack=26 +ButtonActionBack=longpress +ButtonPinSelect=26 +ButtonActionSelect=click +ButtonPinHome=26 +ButtonActionHome=doubleclick +ButtonPinShortcut=26 + +DoubleClickTimeout=400 +LongPressTimeout=400 + +EncoderEnabled=1 +EncoderPinClock=6 +EncoderPinData=5 \ No newline at end of file diff --git a/hwconfig/customize.sh b/hwconfig/customize.sh new file mode 100755 index 0000000..687b568 --- /dev/null +++ b/hwconfig/customize.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +# This script creates a set of ini files from the *.override files +# to provide customized configurations for well-known hardware + +# Find all files named *.override, and run the following on each of them +for file in *.override; do + # Copy the file minidexed.ini to the name of this file but with .ini extension instead + name_of_ini_file=minidexed_$(echo "$file" | sed 's/\.override$/.ini/') + cp ../src/minidexed.ini "$name_of_ini_file" + + # Change the values in the ini file, leaving the rest of the file unchanged + while IFS='=' read -r key value; do + # Skip empty lines and comments + if [ -z "$key" ] || [ "${key#\#}" != "$key" ]; then + continue + fi + value=$(echo "$value" | tr -d '\r') + if [ -n "$value" ]; then + sed -i "s/^$key=.*/$key=$value/" "$name_of_ini_file" + fi + done < "$file" + + # Process the last line of the override file separately, if it doesn't end with a newline + if [ -n "$key" ]; then + value=$(echo "$value" | tr -d '\r') + if [ -n "$value" ]; then + sed -i "s/^$key=.*/$key=$value/" "$name_of_ini_file" + fi + fi + + echo "Created $name_of_ini_file" +done diff --git a/hwconfig/pirate_audio.override b/hwconfig/pirate_audio.override new file mode 100644 index 0000000..680f3fe --- /dev/null +++ b/hwconfig/pirate_audio.override @@ -0,0 +1,32 @@ +# Pimoroni Pirate Audio (screen, buttons and audio output) +# https://shop.pimoroni.com/search?q=pirate%20audio + +SoundDevice=i2s +LCDEnabled=1 + +SPIBus=0 +ST7789Enabled=1 +ST7789Data=9 +ST7789Select=1 +ST7789Reset= +ST7789Backlight=13 +ST7789Width=240 +ST7789Height=240 +ST7789Rotation=90 + +LCDColumns=15 +LCDRows=2 + +ButtonPinPrev=5 +ButtonActionPrev=click +ButtonPinNext=6 +ButtonActionNext=click +ButtonPinBack=16 +ButtonActionBack=click +ButtonPinSelect=24 +ButtonActionSelect=click +ButtonPinHome=16 +ButtonActionHome=doubleclick +ButtonPinShortcut=0 + +EncoderEnabled=0 \ No newline at end of file diff --git a/src/config.cpp b/src/config.cpp index 15a37ad..482b2b2 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -108,6 +108,10 @@ void CConfig::Load (void) m_bMIDIAutoVoiceDumpOnPC = m_Properties.GetNumber ("MIDIAutoVoiceDumpOnPC", 0) != 0; m_bHeaderlessSysExVoices = m_Properties.GetNumber ("HeaderlessSysExVoices", 0) != 0; m_bExpandPCAcrossBanks = m_Properties.GetNumber ("ExpandPCAcrossBanks", 1) != 0; + + m_nMIDISystemCCVol = m_Properties.GetNumber ("MIDISystemCCVol", 0); + m_nMIDISystemCCPan = m_Properties.GetNumber ("MIDISystemCCPan", 0); + m_nMIDISystemCCDetune = m_Properties.GetNumber ("MIDISystemCCDetune", 0); m_bLCDEnabled = m_Properties.GetNumber ("LCDEnabled", 0) != 0; m_nLCDPinEnable = m_Properties.GetNumber ("LCDPinEnable", 4); @@ -160,11 +164,15 @@ void CConfig::Load (void) m_nButtonPinPgmUp = m_Properties.GetNumber ("ButtonPinPgmUp", 0); m_nButtonPinPgmDown = m_Properties.GetNumber ("ButtonPinPgmDown", 0); + m_nButtonPinBankUp = m_Properties.GetNumber ("ButtonPinBankUp", 0); + m_nButtonPinBankDown = m_Properties.GetNumber ("ButtonPinBankDown", 0); m_nButtonPinTGUp = m_Properties.GetNumber ("ButtonPinTGUp", 0); m_nButtonPinTGDown = m_Properties.GetNumber ("ButtonPinTGDown", 0); m_ButtonActionPgmUp = m_Properties.GetString ("ButtonActionPgmUp", ""); m_ButtonActionPgmDown = m_Properties.GetString ("ButtonActionPgmDown", ""); + m_ButtonActionBankUp = m_Properties.GetString ("ButtonActionBankUp", ""); + m_ButtonActionBankDown = m_Properties.GetString ("ButtonActionBankDown", ""); m_ButtonActionTGUp = m_Properties.GetString ("ButtonActionTGUp", ""); m_ButtonActionTGDown = m_Properties.GetString ("ButtonActionTGDown", ""); @@ -178,6 +186,8 @@ void CConfig::Load (void) m_nMIDIButtonPgmUp = m_Properties.GetNumber ("MIDIButtonPgmUp", 0); m_nMIDIButtonPgmDown = m_Properties.GetNumber ("MIDIButtonPgmDown", 0); + m_nMIDIButtonBankUp = m_Properties.GetNumber ("MIDIButtonBankUp", 0); + m_nMIDIButtonBankDown = m_Properties.GetNumber ("MIDIButtonBankDown", 0); m_nMIDIButtonTGUp = m_Properties.GetNumber ("MIDIButtonTGUp", 0); m_nMIDIButtonTGDown = m_Properties.GetNumber ("MIDIButtonTGDown", 0); @@ -283,6 +293,11 @@ unsigned CConfig::GetEngineType (void) const return m_EngineType; } +bool CConfig::GetQuadDAC8Chan (void) const +{ + return m_bQuadDAC8Chan; +} + unsigned CConfig::GetMIDIBaudRate (void) const { return m_nMIDIBaudRate; @@ -323,9 +338,19 @@ bool CConfig::GetExpandPCAcrossBanks (void) const return m_bExpandPCAcrossBanks; } -bool CConfig::GetQuadDAC8Chan (void) const +unsigned CConfig::GetMIDISystemCCVol (void) const { - return m_bQuadDAC8Chan; + return m_nMIDISystemCCVol; +} + +unsigned CConfig::GetMIDISystemCCPan (void) const +{ + return m_nMIDISystemCCPan; +} + +unsigned CConfig::GetMIDISystemCCDetune (void) const +{ + return m_nMIDISystemCCDetune; } bool CConfig::GetLCDEnabled (void) const @@ -542,6 +567,16 @@ unsigned CConfig::GetButtonPinPgmDown (void) const return m_nButtonPinPgmDown; } +unsigned CConfig::GetButtonPinBankUp (void) const +{ + return m_nButtonPinBankUp; +} + +unsigned CConfig::GetButtonPinBankDown (void) const +{ + return m_nButtonPinBankDown; +} + unsigned CConfig::GetButtonPinTGUp (void) const { return m_nButtonPinTGUp; @@ -562,6 +597,16 @@ const char *CConfig::GetButtonActionPgmDown (void) const return m_ButtonActionPgmDown.c_str(); } +const char *CConfig::GetButtonActionBankUp (void) const +{ + return m_ButtonActionBankUp.c_str(); +} + +const char *CConfig::GetButtonActionBankDown (void) const +{ + return m_ButtonActionBankDown.c_str(); +} + const char *CConfig::GetButtonActionTGUp (void) const { return m_ButtonActionTGUp.c_str(); @@ -617,6 +662,16 @@ unsigned CConfig::GetMIDIButtonPgmDown (void) const return m_nMIDIButtonPgmDown; } +unsigned CConfig::GetMIDIButtonBankUp (void) const +{ + return m_nMIDIButtonBankUp; +} + +unsigned CConfig::GetMIDIButtonBankDown (void) const +{ + return m_nMIDIButtonBankDown; +} + unsigned CConfig::GetMIDIButtonTGUp (void) const { return m_nMIDIButtonTGUp; diff --git a/src/config.h b/src/config.h index 166c4bb..5d0cbc1 100644 --- a/src/config.h +++ b/src/config.h @@ -117,6 +117,7 @@ public: unsigned GetDACI2CAddress (void) const; // 0 for auto probing bool GetChannelsSwapped (void) const; unsigned GetEngineType (void) const; + bool GetQuadDAC8Chan (void) const; // false if not specified // MIDI unsigned GetMIDIBaudRate (void) const; @@ -127,7 +128,9 @@ public: bool GetMIDIAutoVoiceDumpOnPC (void) const; // false if not specified bool GetHeaderlessSysExVoices (void) const; // false if not specified bool GetExpandPCAcrossBanks (void) const; // true if not specified - bool GetQuadDAC8Chan (void) const; // false if not specified + unsigned GetMIDISystemCCVol (void) const; + unsigned GetMIDISystemCCPan (void) const; + unsigned GetMIDISystemCCDetune (void) const; // HD44780 LCD // GPIO pin numbers are chip numbers, not header positions @@ -191,12 +194,16 @@ public: // GPIO pin numbers are chip numbers, not header positions unsigned GetButtonPinPgmUp (void) const; unsigned GetButtonPinPgmDown (void) const; + unsigned GetButtonPinBankUp (void) const; + unsigned GetButtonPinBankDown (void) const; unsigned GetButtonPinTGUp (void) const; unsigned GetButtonPinTGDown (void) const; // Action type for buttons: "click", "doubleclick", "longpress", "" const char *GetButtonActionPgmUp (void) const; const char *GetButtonActionPgmDown (void) const; + const char *GetButtonActionBankUp (void) const; + const char *GetButtonActionBankDown (void) const; const char *GetButtonActionTGUp (void) const; const char *GetButtonActionTGDown (void) const; @@ -212,6 +219,8 @@ public: // MIDI Button Program and TG Selection unsigned GetMIDIButtonPgmUp (void) const; unsigned GetMIDIButtonPgmDown (void) const; + unsigned GetMIDIButtonBankUp (void) const; + unsigned GetMIDIButtonBankDown (void) const; unsigned GetMIDIButtonTGUp (void) const; unsigned GetMIDIButtonTGDown (void) const; @@ -245,6 +254,7 @@ private: unsigned m_nDACI2CAddress; bool m_bChannelsSwapped; unsigned m_EngineType; + bool m_bQuadDAC8Chan; unsigned m_nMIDIBaudRate; std::string m_MIDIThruIn; @@ -254,7 +264,9 @@ private: bool m_bMIDIAutoVoiceDumpOnPC; bool m_bHeaderlessSysExVoices; bool m_bExpandPCAcrossBanks; - bool m_bQuadDAC8Chan; + unsigned m_nMIDISystemCCVol; + unsigned m_nMIDISystemCCPan; + unsigned m_nMIDISystemCCDetune; bool m_bLCDEnabled; unsigned m_nLCDPinEnable; @@ -297,6 +309,8 @@ private: unsigned m_nButtonPinShortcut; unsigned m_nButtonPinPgmUp; unsigned m_nButtonPinPgmDown; + unsigned m_nButtonPinBankUp; + unsigned m_nButtonPinBankDown; unsigned m_nButtonPinTGUp; unsigned m_nButtonPinTGDown; @@ -307,6 +321,8 @@ private: std::string m_ButtonActionHome; std::string m_ButtonActionPgmUp; std::string m_ButtonActionPgmDown; + std::string m_ButtonActionBankUp; + std::string m_ButtonActionBankDown; std::string m_ButtonActionTGUp; std::string m_ButtonActionTGDown; @@ -322,6 +338,8 @@ private: unsigned m_nMIDIButtonHome; unsigned m_nMIDIButtonPgmUp; unsigned m_nMIDIButtonPgmDown; + unsigned m_nMIDIButtonBankUp; + unsigned m_nMIDIButtonBankDown; unsigned m_nMIDIButtonTGUp; unsigned m_nMIDIButtonTGDown; diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 504423e..f2b51de 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -53,6 +53,21 @@ LOGMODULE ("mididevice"); #define MIDI_PROGRAM_CHANGE 0b1100 #define MIDI_PITCH_BEND 0b1110 +// MIDI "System" level (i.e. all TG) custom CC maps +// Note: Even if number of TGs is not 8, there are only 8 +// available to be used in the mappings here. +#define NUM_MIDI_CC_MAPS 8 +const unsigned MIDISystemCCMap[NUM_MIDI_CC_MAPS][8] = { + {0,0,0,0,0,0,0,0}, // 0 = disabled + {16,17,18,19,80,81,82,83}, // 1 = General Purpose Controllers 1-8 + {20,21,22,23,24,25,26,27}, + {52,53,54,55,56,57,58,59}, + {102,103,104,105,106,107,108,109}, + {110,111,112,113,114,115,116,117}, + {3,9,14,15,28,29,30,31}, + {35,41,46,47,60,61,62,63} +}; + #define MIDI_SYSTEM_EXCLUSIVE_BEGIN 0xF0 #define MIDI_SYSTEM_EXCLUSIVE_END 0xF7 #define MIDI_TIMING_CLOCK 0xF8 @@ -69,6 +84,34 @@ CMIDIDevice::CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInter { m_ChannelMap[nTG] = Disabled; } + + m_nMIDISystemCCVol = m_pConfig->GetMIDISystemCCVol(); + m_nMIDISystemCCPan = m_pConfig->GetMIDISystemCCPan(); + m_nMIDISystemCCDetune = m_pConfig->GetMIDISystemCCDetune(); + + m_MIDISystemCCBitmap[0] = 0; + m_MIDISystemCCBitmap[1] = 0; + m_MIDISystemCCBitmap[2] = 0; + m_MIDISystemCCBitmap[3] = 0; + + for (int tg=0; tg<8; tg++) + { + if (m_nMIDISystemCCVol != 0) { + u8 cc = MIDISystemCCMap[m_nMIDISystemCCVol][tg]; + m_MIDISystemCCBitmap[cc>>5] |= (1<<(cc%32)); + } + if (m_nMIDISystemCCPan != 0) { + u8 cc = MIDISystemCCMap[m_nMIDISystemCCPan][tg]; + m_MIDISystemCCBitmap[cc>>5] |= (1<<(cc%32)); + } + if (m_nMIDISystemCCDetune != 0) { + u8 cc = MIDISystemCCMap[m_nMIDISystemCCDetune][tg]; + m_MIDISystemCCBitmap[cc>>5] |= (1<<(cc%32)); + } + } + if (m_pConfig->GetMIDIDumpEnabled ()) { + LOGNOTE("MIDI System CC Map: %08X %08X %08X %08X", m_MIDISystemCCBitmap[3],m_MIDISystemCCBitmap[2],m_MIDISystemCCBitmap[1],m_MIDISystemCCBitmap[0]); + } } CMIDIDevice::~CMIDIDevice (void) @@ -239,7 +282,9 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } // Process MIDI for each active Tone Generator - for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators(); nTG++) + bool bSystemCCHandled = false; + bool bSystemCCChecked = false; + for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators() && !bSystemCCHandled; nTG++) { if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN) { @@ -373,6 +418,17 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign m_pSynthesizer->notesOff (pMessage[2], nTG); } break; + + default: + // Check for system-level, cross-TG MIDI Controls, but only do it once. + // Also, if successfully handled, then no need to process other TGs, + // so it is possible to break out of the main TG loop too. + // Note: We handle this here so we get the TG MIDI channel checking. + if (!bSystemCCChecked) { + bSystemCCHandled = HandleMIDISystemCC(pMessage[1], pMessage[2]); + bSystemCCChecked = true; + } + break; } break; @@ -418,6 +474,52 @@ void CMIDIDevice::AddDevice (const char *pDeviceName) s_DeviceMap.insert (std::pair (pDeviceName, this)); } +bool CMIDIDevice::HandleMIDISystemCC(const u8 ucCC, const u8 ucCCval) +{ + // This only makes sense when there are at least 8 TGs. + // Note: If more than 8 TGs then only 8 TGs are controllable this way. + if (m_pConfig->GetToneGenerators() < 8) { + return false; + } + + // Quickly reject any CCs not in the configured maps + if ((m_MIDISystemCCBitmap[ucCC>>5] & (1<<(ucCC%32))) == 0) { + // Not in the map + return false; + } + + // Not looking for duplicate CCs so return once handled + for (unsigned tg=0; tg<8; tg++) { + if (m_nMIDISystemCCVol != 0) { + if (ucCC == MIDISystemCCMap[m_nMIDISystemCCVol][tg]) { + m_pSynthesizer->SetVolume (ucCCval, tg); + return true; + } + } + if (m_nMIDISystemCCPan != 0) { + if (ucCC == MIDISystemCCMap[m_nMIDISystemCCPan][tg]) { + m_pSynthesizer->SetPan (ucCCval, tg); + return true; + } + } + if (m_nMIDISystemCCDetune != 0) { + if (ucCC == MIDISystemCCMap[m_nMIDISystemCCDetune][tg]) { + if (ucCCval == 0) + { + m_pSynthesizer->SetMasterTune (0, tg); + } + else + { + m_pSynthesizer->SetMasterTune (maplong (ucCCval, 1, 127, -99, 99), tg); + } + return true; + } + } + } + + return false; +} + void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const unsigned nCable, const uint8_t nTG) { int16_t sysex_return; diff --git a/src/mididevice.h b/src/mididevice.h index b7e7fe7..44f1691 100644 --- a/src/mididevice.h +++ b/src/mididevice.h @@ -60,12 +60,21 @@ protected: void MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable = 0); void AddDevice (const char *pDeviceName); void HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const unsigned nCable, const uint8_t nTG); + +private: + bool HandleMIDISystemCC(const u8 ucCC, const u8 ucCCval); + private: CMiniDexed *m_pSynthesizer; CConfig *m_pConfig; CUserInterface *m_pUI; u8 m_ChannelMap[CConfig::AllToneGenerators]; + + unsigned m_nMIDISystemCCVol; + unsigned m_nMIDISystemCCPan; + unsigned m_nMIDISystemCCDetune; + u32 m_MIDISystemCCBitmap[4]; // to allow for 128 bit entries std::string m_DeviceName; diff --git a/src/minidexed.ini b/src/minidexed.ini index caebd1f..bcbd936 100644 --- a/src/minidexed.ini +++ b/src/minidexed.ini @@ -95,12 +95,16 @@ ButtonActionHome=doubleclick ButtonPinShortcut=11 # (Shortcut doesn't have an action) -# GPIO Program/TG Selection +# GPIO Program/Bank/TG Selection # Any buttons set to 0 will be ignored ButtonPinPgmUp=0 ButtonActionPgmUp= ButtonPinPgmDown=0 ButtonActionPgmDown= +ButtonPinBankUp=0 +ButtonActionBankUp= +ButtonPinBankDown=0 +ButtonActionBankDown= ButtonPinTGUp=0 ButtonActionTGUp= ButtonPinTGDown=0 @@ -116,17 +120,24 @@ LongPressTimeout=400 # CC channel: 0=OFF; 1-16 MIDI Ch; >16 Omni # If MIDIButtonNotes>0 then treat MIDIButton numbers as MIDI # Note numbers, triggered with NoteOn/NoteOff, not CC numbers. -MIDIButtonCh=0 +MIDIButtonCh=17 MIDIButtonNotes=0 -MIDIButtonPrev=0 -MIDIButtonNext=0 -MIDIButtonBack=0 -MIDIButtonSelect=0 -MIDIButtonHome=0 -MIDIButtonPgmUp=0 -MIDIButtonPgmDown=0 -MIDIButtonTGUp=0 -MIDIButtonTGDown=0 +# Arrow left +MIDIButtonPrev=46 +# Arrow right +MIDIButtonNext=47 +# Arrow up +MIDIButtonBack=48 +# Arrow down +MIDIButtonSelect=49 +# Home button +MIDIButtonHome=50 +MIDIButtonPgmUp=51 +MIDIButtonPgmDown=52 +MIDIButtonBankUp=53 +MIDIButtonBankDown=54 +MIDIButtonTGUp=55 +MIDIButtonTGDown=56 # KY-040 Rotary Encoder EncoderEnabled=1 diff --git a/src/uibuttons.cpp b/src/uibuttons.cpp index 0e361ae..ae206dc 100644 --- a/src/uibuttons.cpp +++ b/src/uibuttons.cpp @@ -257,50 +257,8 @@ CUIButton::BtnTrigger CUIButton::triggerTypeFromString(const char* triggerString } -CUIButtons::CUIButtons ( - unsigned prevPin, const char *prevAction, - unsigned nextPin, const char *nextAction, - unsigned backPin, const char *backAction, - unsigned selectPin, const char *selectAction, - unsigned homePin, const char *homeAction, - unsigned pgmUpPin, const char *pgmUpAction, - unsigned pgmDownPin, const char *pgmDownAction, - unsigned TGUpPin, const char *TGUpAction, - unsigned TGDownPin, const char *TGDownAction, - unsigned doubleClickTimeout, unsigned longPressTimeout, - unsigned notesMidi, unsigned prevMidi, unsigned nextMidi, unsigned backMidi, unsigned selectMidi, unsigned homeMidi, - unsigned pgmUpMidi, unsigned pgmDownMidi, unsigned TGUpMidi, unsigned TGDownMidi -) -: m_doubleClickTimeout(doubleClickTimeout), - m_longPressTimeout(longPressTimeout), - m_prevPin(prevPin), - m_prevAction(CUIButton::triggerTypeFromString(prevAction)), - m_nextPin(nextPin), - m_nextAction(CUIButton::triggerTypeFromString(nextAction)), - m_backPin(backPin), - m_backAction(CUIButton::triggerTypeFromString(backAction)), - m_selectPin(selectPin), - m_selectAction(CUIButton::triggerTypeFromString(selectAction)), - m_homePin(homePin), - m_homeAction(CUIButton::triggerTypeFromString(homeAction)), - m_pgmUpPin(pgmUpPin), - m_pgmUpAction(CUIButton::triggerTypeFromString(pgmUpAction)), - m_pgmDownPin(pgmDownPin), - m_pgmDownAction(CUIButton::triggerTypeFromString(pgmDownAction)), - m_TGUpPin(TGUpPin), - m_TGUpAction(CUIButton::triggerTypeFromString(TGUpAction)), - m_TGDownPin(TGDownPin), - m_TGDownAction(CUIButton::triggerTypeFromString(TGDownAction)), - m_notesMidi(notesMidi), - m_prevMidi(ccToMidiPin(prevMidi)), - m_nextMidi(ccToMidiPin(nextMidi)), - m_backMidi(ccToMidiPin(backMidi)), - m_selectMidi(ccToMidiPin(selectMidi)), - m_homeMidi(ccToMidiPin(homeMidi)), - m_pgmUpMidi(ccToMidiPin(pgmUpMidi)), - m_pgmDownMidi(ccToMidiPin(pgmDownMidi)), - m_TGUpMidi(ccToMidiPin(TGUpMidi)), - m_TGDownMidi(ccToMidiPin(TGDownMidi)), +CUIButtons::CUIButtons (CConfig *pConfig) +: m_pConfig(pConfig), m_eventHandler (0), m_lastTick (0) { @@ -312,6 +270,46 @@ CUIButtons::~CUIButtons (void) boolean CUIButtons::Initialize (void) { + assert (m_pConfig); + + // Read the button configuration + m_doubleClickTimeout = m_pConfig->GetDoubleClickTimeout (); + m_longPressTimeout = m_pConfig->GetLongPressTimeout (); + m_prevPin = m_pConfig->GetButtonPinPrev (); + m_prevAction = CUIButton::triggerTypeFromString( m_pConfig->GetButtonActionPrev ()); + m_nextPin = m_pConfig->GetButtonPinNext (); + m_nextAction = CUIButton::triggerTypeFromString( m_pConfig->GetButtonActionNext ()); + m_backPin = m_pConfig->GetButtonPinBack (); + m_backAction = CUIButton::triggerTypeFromString( m_pConfig->GetButtonActionBack ()); + m_selectPin = m_pConfig->GetButtonPinSelect (); + m_selectAction = CUIButton::triggerTypeFromString( m_pConfig->GetButtonActionSelect ()); + m_homePin = m_pConfig->GetButtonPinHome (); + m_homeAction = CUIButton::triggerTypeFromString( m_pConfig->GetButtonActionHome ()); + m_pgmUpPin = m_pConfig->GetButtonPinPgmUp (); + m_pgmUpAction = CUIButton::triggerTypeFromString( m_pConfig->GetButtonActionPgmUp ()); + m_pgmDownPin = m_pConfig->GetButtonPinPgmDown (); + m_pgmDownAction = CUIButton::triggerTypeFromString( m_pConfig->GetButtonActionPgmDown ()); + m_BankUpPin = m_pConfig->GetButtonPinBankUp (); + m_BankUpAction = CUIButton::triggerTypeFromString( m_pConfig->GetButtonActionBankUp ()); + m_BankDownPin = m_pConfig->GetButtonPinBankDown (); + m_BankDownAction = CUIButton::triggerTypeFromString( m_pConfig->GetButtonActionBankDown ()); + m_TGUpPin = m_pConfig->GetButtonPinTGUp (); + m_TGUpAction = CUIButton::triggerTypeFromString( m_pConfig->GetButtonActionTGUp ()); + m_TGDownPin = m_pConfig->GetButtonPinTGDown (); + m_TGDownAction = CUIButton::triggerTypeFromString( m_pConfig->GetButtonActionTGDown ()); + m_notesMidi = ccToMidiPin( m_pConfig->GetMIDIButtonNotes ()); + m_prevMidi = ccToMidiPin( m_pConfig->GetMIDIButtonPrev ()); + m_nextMidi = ccToMidiPin( m_pConfig->GetMIDIButtonNext ()); + m_backMidi = ccToMidiPin( m_pConfig->GetMIDIButtonBack ()); + m_selectMidi = ccToMidiPin( m_pConfig->GetMIDIButtonSelect ()); + m_homeMidi = ccToMidiPin( m_pConfig->GetMIDIButtonHome ()); + m_pgmUpMidi = ccToMidiPin( m_pConfig->GetMIDIButtonPgmUp ()); + m_pgmDownMidi = ccToMidiPin( m_pConfig->GetMIDIButtonPgmDown ()); + m_BankUpMidi = ccToMidiPin( m_pConfig->GetMIDIButtonBankUp ()); + m_BankDownMidi = ccToMidiPin( m_pConfig->GetMIDIButtonBankDown ()); + m_TGUpMidi = ccToMidiPin( m_pConfig->GetMIDIButtonTGUp ()); + m_TGDownMidi = ccToMidiPin( m_pConfig->GetMIDIButtonTGDown ()); + // First sanity check and convert the timeouts: // Internally values are in tenths of a millisecond, but config values // are in milliseconds @@ -332,16 +330,16 @@ boolean CUIButtons::Initialize (void) // 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_pgmUpPin, m_pgmDownPin, m_TGUpPin, m_TGDownPin, - m_prevMidi, m_nextMidi, m_backMidi, m_selectMidi, m_homeMidi, m_pgmUpMidi, m_pgmDownMidi, m_TGUpMidi, m_TGDownMidi + m_prevPin, m_nextPin, m_backPin, m_selectPin, m_homePin, m_pgmUpPin, m_pgmDownPin, m_BankUpPin, m_BankDownPin, m_TGUpPin, m_TGDownPin, + m_prevMidi, m_nextMidi, m_backMidi, m_selectMidi, m_homeMidi, m_pgmUpMidi, m_pgmDownMidi, m_BankUpMidi, m_BankDownMidi, m_TGUpMidi, m_TGDownMidi }; CUIButton::BtnTrigger triggers[MAX_BUTTONS] = { // Normal buttons m_prevAction, m_nextAction, m_backAction, m_selectAction, m_homeAction, - m_pgmUpAction, m_pgmDownAction, m_TGUpAction, m_TGDownAction, + m_pgmUpAction, m_pgmDownAction, m_BankUpAction, m_BankDownAction, m_TGUpAction, m_TGDownAction, // MIDI Buttons only support a single click (at present) CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, - CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick + CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick }; CUIButton::BtnEvent events[MAX_BUTTONS] = { // Normal buttons @@ -352,6 +350,8 @@ boolean CUIButtons::Initialize (void) CUIButton::BtnEventHome, CUIButton::BtnEventPgmUp, CUIButton::BtnEventPgmDown, + CUIButton::BtnEventBankUp, + CUIButton::BtnEventBankDown, CUIButton::BtnEventTGUp, CUIButton::BtnEventTGDown, // MIDI buttons @@ -362,6 +362,8 @@ boolean CUIButtons::Initialize (void) CUIButton::BtnEventHome, CUIButton::BtnEventPgmUp, CUIButton::BtnEventPgmDown, + CUIButton::BtnEventBankUp, + CUIButton::BtnEventBankDown, CUIButton::BtnEventTGUp, CUIButton::BtnEventTGDown }; diff --git a/src/uibuttons.h b/src/uibuttons.h index 44dcca0..be17934 100644 --- a/src/uibuttons.h +++ b/src/uibuttons.h @@ -27,8 +27,8 @@ #define BUTTONS_UPDATE_NUM_TICKS 100 #define DEBOUNCE_TIME 20 -#define MAX_GPIO_BUTTONS 9 // 5 UI buttons, 4 Program/TG Select buttons -#define MAX_MIDI_BUTTONS 9 +#define MAX_GPIO_BUTTONS 11 // 5 UI buttons, 6 Program/Bank/TG Select buttons +#define MAX_MIDI_BUTTONS 11 #define MAX_BUTTONS (MAX_GPIO_BUTTONS+MAX_MIDI_BUTTONS) class CUIButtons; @@ -54,9 +54,11 @@ public: BtnEventHome = 5, BtnEventPgmUp = 6, BtnEventPgmDown = 7, - BtnEventTGUp = 8, - BtnEventTGDown = 9, - BtnEventUnknown = 10 + BtnEventBankUp = 8, + BtnEventBankDown = 9, + BtnEventTGUp = 10, + BtnEventTGDown = 11, + BtnEventUnknown = 12 }; CUIButton (void); @@ -111,20 +113,7 @@ public: typedef void BtnEventHandler (CUIButton::BtnEvent Event, void *param); public: - CUIButtons ( - unsigned prevPin, const char *prevAction, - unsigned nextPin, const char *nextAction, - unsigned backPin, const char *backAction, - unsigned selectPin, const char *selectAction, - unsigned homePin, const char *homeAction, - unsigned pgmUpPin, const char *pgmUpAction, - unsigned pgmDownPin, const char *pgmDownAction, - unsigned TGUpPin, const char *TGUpAction, - unsigned TGDownPin, const char *TGDownAction, - unsigned doubleClickTimeout, unsigned longPressTimeout, - unsigned notesMidi, unsigned prevMidi, unsigned nextMidi, unsigned backMidi, unsigned selectMidi, unsigned homeMidi, - unsigned pgmUpMidi, unsigned pgmDownMidi, unsigned TGUpMidi, unsigned TGDownMidi - ); + CUIButtons (CConfig *pConfig); ~CUIButtons (void); boolean Initialize (void); @@ -138,6 +127,8 @@ public: void BtnMIDICmdHandler (unsigned nMidiCmd, unsigned nMidiData1, unsigned nMidiData2); private: + CConfig *m_pConfig; + // Array of normal GPIO buttons and "MIDI buttons" CUIButton m_buttons[MAX_BUTTONS]; @@ -163,6 +154,10 @@ private: CUIButton::BtnTrigger m_pgmUpAction; unsigned m_pgmDownPin; CUIButton::BtnTrigger m_pgmDownAction; + unsigned m_BankUpPin; + CUIButton::BtnTrigger m_BankUpAction; + unsigned m_BankDownPin; + CUIButton::BtnTrigger m_BankDownAction; unsigned m_TGUpPin; CUIButton::BtnTrigger m_TGUpAction; unsigned m_TGDownPin; @@ -178,6 +173,8 @@ private: unsigned m_pgmUpMidi; unsigned m_pgmDownMidi; + unsigned m_BankUpMidi; + unsigned m_BankDownMidi; unsigned m_TGUpMidi; unsigned m_TGDownMidi; diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 8b72425..ed36cd4 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -431,6 +431,11 @@ void CUIMenu::EventHandler (TMenuEvent Event) PgmUpDownHandler(Event); break; + case MenuEventBankUp: + case MenuEventBankDown: + BankUpDownHandler(Event); + break; + case MenuEventTGUp: case MenuEventTGDown: TGUpDownHandler(Event); @@ -1375,6 +1380,87 @@ void CUIMenu::PgmUpDownHandler (TMenuEvent Event) } } +void CUIMenu::BankUpDownHandler (TMenuEvent Event) +{ + if (m_pMiniDexed->GetParameter (CMiniDexed::ParameterPerformanceSelectChannel) != CMIDIDevice::Disabled) + { + // Bank Up/Down acts on performances + unsigned nLastPerformanceBank = m_pMiniDexed->GetLastPerformanceBank(); + unsigned nPerformanceBank = m_nSelectedPerformanceBankID; + unsigned nStartBank = nPerformanceBank; + //LOGNOTE("Performance Bank actual=%d, last=%d", nPerformanceBank, nLastPerformanceBank); + if (Event == MenuEventBankDown) + { + do + { + if (nPerformanceBank == 0) + { + // Wrap around + nPerformanceBank = nLastPerformanceBank; + } + else if (nPerformanceBank > 0) + { + --nPerformanceBank; + } + } while ((m_pMiniDexed->IsValidPerformanceBank(nPerformanceBank) != true) && (nPerformanceBank != nStartBank)); + m_nSelectedPerformanceBankID = nPerformanceBank; + // Switch to the new bank and select the first performance voice + m_pMiniDexed->SetParameter (CMiniDexed::ParameterPerformanceBank, nPerformanceBank); + m_pMiniDexed->SetFirstPerformance(); + //LOGNOTE("Performance Bank new=%d, last=%d", m_nSelectedPerformanceBankID, nLastPerformanceBank); + } + else // MenuEventBankUp + { + do + { + if (nPerformanceBank == nLastPerformanceBank) + { + // Wrap around + nPerformanceBank = 0; + } + else if (nPerformanceBank < nLastPerformanceBank) + { + ++nPerformanceBank; + } + } while ((m_pMiniDexed->IsValidPerformanceBank(nPerformanceBank) != true) && (nPerformanceBank != nStartBank)); + m_nSelectedPerformanceBankID = nPerformanceBank; + m_pMiniDexed->SetParameter (CMiniDexed::ParameterPerformanceBank, nPerformanceBank); + m_pMiniDexed->SetFirstPerformance(); + //LOGNOTE("Performance Bank new=%d, last=%d", m_nSelectedPerformanceBankID, nLastPerformanceBank); + } + } + else + { + // Bank Up/Down acts on voices within a TG. + + // If we're not in the root menu, then see if we are already in a TG menu, + // then find the current TG number. Otherwise assume TG1 (nTG=0). + unsigned nTG = 0; + if (m_MenuStackMenu[0] == s_MainMenu && (m_pCurrentMenu == s_TGMenu) || (m_MenuStackMenu[1] == s_TGMenu)) { + nTG = m_nMenuStackSelection[0]; + } + assert (nTG < CConfig::AllToneGenerators); + if (nTG < m_nToneGenerators) + { + int nBank = m_pMiniDexed->GetTGParameter (CMiniDexed::TGParameterVoiceBank, nTG); + + assert (Event == MenuEventBankDown || Event == MenuEventBankUp); + if (Event == MenuEventBankDown) + { + //LOGNOTE("BankDown"); + nBank = m_pMiniDexed->GetSysExFileLoader ()->GetNextBankDown(nBank); + m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterVoiceBank, nBank, nTG); + } + else + { + //LOGNOTE("BankUp"); + nBank = m_pMiniDexed->GetSysExFileLoader ()->GetNextBankUp(nBank); + m_pMiniDexed->SetTGParameter (CMiniDexed::TGParameterVoiceBank, nBank, nTG); + } + } + } +} + void CUIMenu::TGUpDownHandler (TMenuEvent Event) { // This will update the menus to position it for the next TG up or down diff --git a/src/uimenu.h b/src/uimenu.h index 0034278..d9dc3ee 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -48,6 +48,8 @@ public: MenuEventPressAndStepUp, MenuEventPgmUp, MenuEventPgmDown, + MenuEventBankUp, + MenuEventBankDown, MenuEventTGUp, MenuEventTGDown, MenuEventUnknown @@ -119,6 +121,7 @@ private: void OPShortcutHandler (TMenuEvent Event); void PgmUpDownHandler (TMenuEvent Event); + void BankUpDownHandler (TMenuEvent Event); void TGUpDownHandler (TMenuEvent Event); static void TimerHandler (TKernelTimerHandle hTimer, void *pParam, void *pContext); diff --git a/src/userinterface.cpp b/src/userinterface.cpp index aa46f9e..32222a1 100644 --- a/src/userinterface.cpp +++ b/src/userinterface.cpp @@ -162,37 +162,7 @@ bool CUserInterface::Initialize (void) LOGDBG ("LCD initialized"); } - m_pUIButtons = new CUIButtons ( m_pConfig->GetButtonPinPrev (), - m_pConfig->GetButtonActionPrev (), - m_pConfig->GetButtonPinNext (), - m_pConfig->GetButtonActionNext (), - m_pConfig->GetButtonPinBack (), - m_pConfig->GetButtonActionBack (), - m_pConfig->GetButtonPinSelect (), - m_pConfig->GetButtonActionSelect (), - m_pConfig->GetButtonPinHome (), - m_pConfig->GetButtonActionHome (), - m_pConfig->GetButtonPinPgmUp (), - m_pConfig->GetButtonActionPgmUp (), - m_pConfig->GetButtonPinPgmDown (), - m_pConfig->GetButtonActionPgmDown (), - m_pConfig->GetButtonPinTGUp (), - m_pConfig->GetButtonActionTGUp (), - m_pConfig->GetButtonPinTGDown (), - m_pConfig->GetButtonActionTGDown (), - m_pConfig->GetDoubleClickTimeout (), - m_pConfig->GetLongPressTimeout (), - m_pConfig->GetMIDIButtonNotes (), - m_pConfig->GetMIDIButtonPrev (), - m_pConfig->GetMIDIButtonNext (), - m_pConfig->GetMIDIButtonBack (), - m_pConfig->GetMIDIButtonSelect (), - m_pConfig->GetMIDIButtonHome (), - m_pConfig->GetMIDIButtonPgmUp (), - m_pConfig->GetMIDIButtonPgmDown (), - m_pConfig->GetMIDIButtonTGUp (), - m_pConfig->GetMIDIButtonTGDown () - ); + m_pUIButtons = new CUIButtons ( m_pConfig ); assert (m_pUIButtons); if (!m_pUIButtons->Initialize ()) @@ -397,6 +367,14 @@ void CUserInterface::UIButtonsEventHandler (CUIButton::BtnEvent Event) m_Menu.EventHandler (CUIMenu::MenuEventPgmDown); break; + case CUIButton::BtnEventBankUp: + m_Menu.EventHandler (CUIMenu::MenuEventBankUp); + break; + + case CUIButton::BtnEventBankDown: + m_Menu.EventHandler (CUIMenu::MenuEventBankDown); + break; + case CUIButton::BtnEventTGUp: m_Menu.EventHandler (CUIMenu::MenuEventTGUp); break;