From b0b62a7640e524eec9c275468c90b245a1c3c5cc Mon Sep 17 00:00:00 2001 From: Rene Stange Date: Tue, 1 Mar 2022 19:49:18 +0100 Subject: [PATCH] Move MIDI handling from CMiniDexed to specific classes * CMIDIDevice is the generic MIDI handler * CMIDIKeyboard handles USB audio class MIDI devices * CSerialMIDIDevice handles the serial MIDI device * Now all MIDI inputs can work simultaneous * Program change and bank select work with serial MIDI --- src/Makefile | 3 +- src/mididevice.cpp | 143 ++++++++++++++++++++++++++++++ src/mididevice.h | 45 ++++++++++ src/midikeyboard.cpp | 71 +++++++++++++++ src/midikeyboard.h | 53 +++++++++++ src/minidexed.cpp | 184 +++++---------------------------------- src/minidexed.h | 37 ++++---- src/serialmididevice.cpp | 98 +++++++++++++++++++++ src/serialmididevice.h | 52 +++++++++++ 9 files changed, 500 insertions(+), 186 deletions(-) create mode 100644 src/mididevice.cpp create mode 100644 src/mididevice.h create mode 100644 src/midikeyboard.cpp create mode 100644 src/midikeyboard.h create mode 100644 src/serialmididevice.cpp create mode 100644 src/serialmididevice.h diff --git a/src/Makefile b/src/Makefile index 26a9067..31ed5d8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -5,7 +5,8 @@ CIRCLE_STDLIB_DIR = ../circle-stdlib SYNTH_DEXED_DIR = ../Synth_Dexed/src -OBJS = main.o kernel.o minidexed.o config.o userinterface.o pckeyboard.o \ +OBJS = main.o kernel.o minidexed.o config.o userinterface.o \ + mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ sysexfileloader.o perftimer.o \ $(SYNTH_DEXED_DIR)/synth_dexed.o diff --git a/src/mididevice.cpp b/src/mididevice.cpp new file mode 100644 index 0000000..3115420 --- /dev/null +++ b/src/mididevice.cpp @@ -0,0 +1,143 @@ +// +// mididevice.cpp +// +// 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 +// (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 "mididevice.h" +#include "minidexed.h" +#include "config.h" +#include +#include + +#define MIDI_NOTE_OFF 0b1000 +#define MIDI_NOTE_ON 0b1001 +#define MIDI_AFTERTOUCH 0b1010 // TODO +#define MIDI_CONTROL_CHANGE 0b1011 + #define MIDI_CC_BANK_SELECT_MSB 0 // TODO + #define MIDI_CC_BANK_SELECT_LSB 32 +#define MIDI_PROGRAM_CHANGE 0b1100 +#define MIDI_PITCH_BEND 0b1110 + +CMIDIDevice::CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig) +: m_pSynthesizer (pSynthesizer), + m_pConfig (pConfig) +{ +} + +CMIDIDevice::~CMIDIDevice (void) +{ + m_pSynthesizer = 0; +} + +void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable) +{ + assert (m_pSynthesizer != 0); + + // The packet contents are just normal MIDI data - see + // https://www.midi.org/specifications/item/table-1-summary-of-midi-message + + if (m_pConfig->GetMIDIDumpEnabled ()) + { + switch (nLength) + { + case 1: + printf ("MIDI %u: %02X\n", nCable, (unsigned) pMessage[0]); + break; + + case 2: + printf ("MIDI %u: %02X %02X\n", nCable, + (unsigned) pMessage[0], (unsigned) pMessage[1]); + break; + + case 3: + printf ("MIDI %u: %02X %02X %02X\n", nCable, + (unsigned) pMessage[0], (unsigned) pMessage[1], + (unsigned) pMessage[2]); + break; + } + } + + if (nLength < 2) + { + return; + } + + u8 ucStatus = pMessage[0]; + // TODO: u8 ucChannel = ucStatus & 0x0F; + u8 ucType = ucStatus >> 4; + u8 ucKeyNumber = pMessage[1]; + u8 ucVelocity = pMessage[2]; + + switch (ucType) + { + case MIDI_NOTE_ON: + if (nLength < 3) + { + break; + } + + if (ucVelocity > 0) + { + if (ucVelocity <= 127) + { + m_pSynthesizer->keydown (ucKeyNumber, ucVelocity); + } + } + else + { + m_pSynthesizer->keyup (ucKeyNumber); + } + break; + + case MIDI_NOTE_OFF: + if (nLength < 3) + { + break; + } + + m_pSynthesizer->keyup (ucKeyNumber); + break; + + case MIDI_CONTROL_CHANGE: + if (nLength < 3) + { + break; + } + + switch (pMessage[1]) + { + case MIDI_CC_BANK_SELECT_LSB: + m_pSynthesizer->BankSelectLSB (pMessage[2]); + break; + } + break; + + case MIDI_PROGRAM_CHANGE: + m_pSynthesizer->ProgramChange (pMessage[1]); + break; + + case MIDI_PITCH_BEND: + m_pSynthesizer->setPitchbend (pMessage[1]); + break; + + default: + break; + } +} diff --git a/src/mididevice.h b/src/mididevice.h new file mode 100644 index 0000000..a595d1e --- /dev/null +++ b/src/mididevice.h @@ -0,0 +1,45 @@ +// +// mididevice.h +// +// 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 +// (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 _mididevice_h +#define _mididevice_h + +#include "config.h" +#include + +class CMiniDexed; + +class CMIDIDevice +{ +public: + CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig); + ~CMIDIDevice (void); + +protected: + void MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable = 0); + +private: + CMiniDexed *m_pSynthesizer; + CConfig *m_pConfig; +}; + +#endif diff --git a/src/midikeyboard.cpp b/src/midikeyboard.cpp new file mode 100644 index 0000000..baf39ba --- /dev/null +++ b/src/midikeyboard.cpp @@ -0,0 +1,71 @@ +// +// midikeyboard.cpp +// +// 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 +// (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 "midikeyboard.h" +#include +#include + +CMIDIKeyboard *CMIDIKeyboard::s_pThis = 0; + +CMIDIKeyboard::CMIDIKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig) +: CMIDIDevice (pSynthesizer, pConfig), + m_pMIDIDevice (0) +{ + s_pThis = this; +} + +CMIDIKeyboard::~CMIDIKeyboard (void) +{ + s_pThis = 0; +} + +void CMIDIKeyboard::Process (boolean bPlugAndPlayUpdated) +{ + if (!bPlugAndPlayUpdated) + { + return; + } + + if (m_pMIDIDevice == 0) + { + m_pMIDIDevice = + (CUSBMIDIDevice *) CDeviceNameService::Get ()->GetDevice ("umidi1", FALSE); + if (m_pMIDIDevice != 0) + { + m_pMIDIDevice->RegisterPacketHandler (MIDIPacketHandler); + + m_pMIDIDevice->RegisterRemovedHandler (DeviceRemovedHandler); + } + } +} + +void CMIDIKeyboard::MIDIPacketHandler (unsigned nCable, u8 *pPacket, unsigned nLength) +{ + assert (s_pThis != 0); + s_pThis->MIDIMessageHandler (pPacket, nLength, nCable); +} + +void CMIDIKeyboard::DeviceRemovedHandler (CDevice *pDevice, void *pContext) +{ + assert (s_pThis != 0); + s_pThis->m_pMIDIDevice = 0; +} diff --git a/src/midikeyboard.h b/src/midikeyboard.h new file mode 100644 index 0000000..da9ae76 --- /dev/null +++ b/src/midikeyboard.h @@ -0,0 +1,53 @@ +// +// midikeyboard.h +// +// 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 +// (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 _midikeyboard_h +#define _midikeyboard_h + +#include "mididevice.h" +#include "config.h" +#include +#include +#include + +class CMiniDexed; + +class CMIDIKeyboard : public CMIDIDevice +{ +public: + CMIDIKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig); + ~CMIDIKeyboard (void); + + void Process (boolean bPlugAndPlayUpdated); + +private: + static void MIDIPacketHandler (unsigned nCable, u8 *pPacket, unsigned nLength); + + static void DeviceRemovedHandler (CDevice *pDevice, void *pContext); + +private: + CUSBMIDIDevice * volatile m_pMIDIDevice; + + static CMIDIKeyboard *s_pThis; +}; + +#endif diff --git a/src/minidexed.cpp b/src/minidexed.cpp index bbb34e2..d35bffd 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2,19 +2,10 @@ // minidexed.cpp // #include "minidexed.h" -#include +#include #include -#define MIDI_NOTE_OFF 0b1000 -#define MIDI_NOTE_ON 0b1001 -#define MIDI_AFTERTOUCH 0xA0 -#define MIDI_CONTROL_CHANGE 0xB0 - #define MIDI_CC_BANK_SELECT_MSB 0 // TODO: not supported - #define MIDI_CC_BANK_SELECT_LSB 32 -#define MIDI_PROGRAM_CHANGE 0xC0 -#define MIDI_PITCH_BEND 0xE0 - -CMiniDexed *CMiniDexed::s_pThis = 0; +LOGMODULE ("minidexed"); bool CMiniDexed::Initialize (void) { @@ -25,17 +16,17 @@ bool CMiniDexed::Initialize (void) m_SysExFileLoader.Load (); - if (!m_Serial.Initialize(m_pConfig->GetMIDIBaudRate ())) + if (m_SerialMIDI.Initialize ()) { - return false; - } + LOGNOTE ("Serial MIDI interface enabled"); - m_bUseSerial = true; + m_bUseSerial = true; + } activate(); - s_pThis->ChangeProgram(0); - s_pThis->setTranspose(24); + ProgramChange (0); + setTranspose (24); return true; } @@ -49,160 +40,33 @@ void CMiniDexed::Process(boolean bPlugAndPlayUpdated) m_UI.Process (); - if (m_pMIDIDevice != 0) - { - return; - } - - if (bPlugAndPlayUpdated) - { - m_pMIDIDevice = - (CUSBMIDIDevice *) CDeviceNameService::Get ()->GetDevice ("umidi1", FALSE); - if (m_pMIDIDevice != 0) - { - m_pMIDIDevice->RegisterRemovedHandler (USBDeviceRemovedHandler); - m_pMIDIDevice->RegisterPacketHandler (MIDIPacketHandler); - - return; - } - } + m_MIDIKeyboard.Process (bPlugAndPlayUpdated); m_PCKeyboard.Process (bPlugAndPlayUpdated); - if (!m_bUseSerial) + if (m_bUseSerial) { - return; - } - - // Read serial MIDI data - u8 Buffer[20]; - int nResult = m_Serial.Read (Buffer, sizeof Buffer); - if (nResult <= 0) - { - return; - } - - // Process MIDI messages - // See: https://www.midi.org/specifications/item/table-1-summary-of-midi-message - for (int i = 0; i < nResult; i++) - { - u8 uchData = Buffer[i]; - - switch (m_nSerialState) - { - case 0: - MIDIRestart: - if ((uchData & 0xE0) == 0x80) // Note on or off, all channels - { - m_SerialMessage[m_nSerialState++] = uchData; - } - break; - - case 1: - case 2: - if (uchData & 0x80) // got status when parameter expected - { - m_nSerialState = 0; - - goto MIDIRestart; - } - - m_SerialMessage[m_nSerialState++] = uchData; - - if (m_nSerialState == 3) // message is complete - { - MIDIPacketHandler (0, m_SerialMessage, sizeof m_SerialMessage); - - m_nSerialState = 0; - } - break; - - default: - assert (0); - break; - } + m_SerialMIDI.Process (); } } -void CMiniDexed::MIDIPacketHandler (unsigned nCable, u8 *pPacket, unsigned nLength) +void CMiniDexed::BankSelectLSB (unsigned nBankLSB) { - assert (s_pThis != 0); - - // The packet contents are just normal MIDI data - see - // https://www.midi.org/specifications/item/table-1-summary-of-midi-message - - if (s_pThis->m_pConfig->GetMIDIDumpEnabled ()) - { - switch (nLength) - { - case 1: - printf ("MIDI %u: %02X\n", nCable, (unsigned) pPacket[0]); - break; - - case 2: - printf ("MIDI %u: %02X %02X\n", nCable, - (unsigned) pPacket[0], (unsigned) pPacket[1]); - break; - - case 3: - printf ("MIDI %u: %02X %02X %02X\n", nCable, - (unsigned) pPacket[0], (unsigned) pPacket[1], (unsigned) pPacket[2]); - break; - } - } - - if (pPacket[0] == MIDI_CONTROL_CHANGE) + if (nBankLSB > 127) { - if (pPacket[1] == MIDI_CC_BANK_SELECT_LSB) - { - if (pPacket[2] > 127) - { - return; - } - - printf ("Select voice bank %u\n", (unsigned) pPacket[2]+1); // MIDI numbering starts with 0, user interface with 1 - s_pThis->m_SysExFileLoader.SelectVoiceBank (pPacket[2]); - } - - return; - } - - if (pPacket[0] == MIDI_PROGRAM_CHANGE) - { - s_pThis->ChangeProgram(pPacket[1]); return; } - if (pPacket[0] == MIDI_PITCH_BEND) - { - s_pThis->setPitchbend((unsigned) pPacket[1]); - - return; - } + // MIDI numbering starts with 0, user interface with 1 + printf ("Select voice bank %u\n", nBankLSB+1); - if (nLength < 3) - { - return; - } - - u8 ucStatus = pPacket[0]; - //u8 ucChannel = ucStatus & 0x0F; - u8 ucType = ucStatus >> 4; - u8 ucKeyNumber = pPacket[1]; - u8 ucVelocity = pPacket[2]; - - if (ucType == MIDI_NOTE_ON) - { - s_pThis->keydown(ucKeyNumber,ucVelocity); - } - else if (ucType == MIDI_NOTE_OFF) - { - s_pThis->keyup(ucKeyNumber); - } + m_SysExFileLoader.SelectVoiceBank (nBankLSB); } -void CMiniDexed::ChangeProgram(unsigned program) { - if(program > 31) { +void CMiniDexed::ProgramChange (unsigned program) +{ + if (program > 31) + { return; } @@ -213,14 +77,6 @@ void CMiniDexed::ChangeProgram(unsigned program) { m_UI.ProgramChanged (program); } -void CMiniDexed::USBDeviceRemovedHandler (CDevice *pDevice, void *pContext) -{ - if (s_pThis->m_pMIDIDevice == (CUSBMIDIDevice *) pDevice) - { - s_pThis->m_pMIDIDevice = 0; - } -} - bool CMiniDexedPWM::Initialize (void) { if (!CMiniDexed::Initialize()) diff --git a/src/minidexed.h b/src/minidexed.h index dc0626b..e15c8e9 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -9,15 +9,15 @@ #include #include #include -#include -#include #include #include #include #include #include "config.h" #include "sysexfileloader.h" +#include "midikeyboard.h" #include "pckeyboard.h" +#include "serialmididevice.h" #include "perftimer.h" #include "userinterface.h" @@ -26,38 +26,33 @@ class CMiniDexed : public CDexedAdapter public: CMiniDexed(CConfig *pConfig, CInterruptSystem *pInterrupt) : CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()), - m_pMIDIDevice (0), + m_MIDIKeyboard (this, pConfig), m_PCKeyboard (this), - m_Serial (pInterrupt, TRUE), + m_SerialMIDI (this, pInterrupt, pConfig), m_bUseSerial (FALSE), - m_nSerialState (0), - m_GetChunkTimer ("GetChunk", 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), m_pConfig (pConfig), - m_UI (this, pConfig) + m_UI (this, pConfig), + m_GetChunkTimer ("GetChunk", 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()) { - s_pThis = this; }; virtual bool Initialize (void); void Process(boolean bPlugAndPlayUpdated); - protected: - static void MIDIPacketHandler (unsigned nCable, u8 *pPacket, unsigned nLength); - static void KeyStatusHandlerRaw (unsigned char ucModifiers, const unsigned char RawKeys[6]); - void ChangeProgram(unsigned program); - static void USBDeviceRemovedHandler (CDevice *pDevice, void *pContext); - CUSBMIDIDevice * volatile m_pMIDIDevice; - CPCKeyboard m_PCKeyboard; - CSerialDevice m_Serial; + + void BankSelectLSB (unsigned nBankLSB); + void ProgramChange (unsigned program); + + private: + CMIDIKeyboard m_MIDIKeyboard; + CPCKeyboard m_PCKeyboard; + CSerialMIDIDevice m_SerialMIDI; boolean m_bUseSerial; - unsigned m_nSerialState; - u8 m_SerialMessage[3]; CSysExFileLoader m_SysExFileLoader; - CPerformanceTimer m_GetChunkTimer; - private: CConfig *m_pConfig; CUserInterface m_UI; - static CMiniDexed *s_pThis; + protected: + CPerformanceTimer m_GetChunkTimer; }; class CMiniDexedPWM : public CMiniDexed, public CPWMSoundBaseDevice diff --git a/src/serialmididevice.cpp b/src/serialmididevice.cpp new file mode 100644 index 0000000..cd7ad47 --- /dev/null +++ b/src/serialmididevice.cpp @@ -0,0 +1,98 @@ +// +// serialmididevice.cpp +// +// 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 +// (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 "serialmididevice.h" +#include + +CSerialMIDIDevice::CSerialMIDIDevice (CMiniDexed *pSynthesizer, CInterruptSystem *pInterrupt, + CConfig *pConfig) +: CMIDIDevice (pSynthesizer, pConfig), + m_pConfig (pConfig), + m_Serial (pInterrupt, TRUE), + m_nSerialState (0) +{ +} + +CSerialMIDIDevice::~CSerialMIDIDevice (void) +{ + m_nSerialState = 255; +} + +boolean CSerialMIDIDevice::Initialize (void) +{ + assert (m_pConfig); + return m_Serial.Initialize (m_pConfig->GetMIDIBaudRate ()); +} + +void CSerialMIDIDevice::Process (void) +{ + // Read serial MIDI data + u8 Buffer[100]; + int nResult = m_Serial.Read (Buffer, sizeof Buffer); + if (nResult <= 0) + { + return; + } + + // Process MIDI messages + // See: https://www.midi.org/specifications/item/table-1-summary-of-midi-message + for (int i = 0; i < nResult; i++) + { + u8 uchData = Buffer[i]; + + switch (m_nSerialState) + { + case 0: + MIDIRestart: + if ( (uchData & 0x80) == 0x80 // status byte, all channels + && (uchData & 0xF0) != 0xF0) // ignore system messages + { + m_SerialMessage[m_nSerialState++] = uchData; + } + break; + + case 1: + case 2: + if (uchData & 0x80) // got status when parameter expected + { + m_nSerialState = 0; + + goto MIDIRestart; + } + + m_SerialMessage[m_nSerialState++] = uchData; + + if ( (m_SerialMessage[0] & 0xE0) == 0xC0 + || m_nSerialState == 3) // message is complete + { + MIDIMessageHandler (m_SerialMessage, m_nSerialState); + + m_nSerialState = 0; + } + break; + + default: + assert (0); + break; + } + } +} diff --git a/src/serialmididevice.h b/src/serialmididevice.h new file mode 100644 index 0000000..82c2618 --- /dev/null +++ b/src/serialmididevice.h @@ -0,0 +1,52 @@ +// +// serialmididevice.h +// +// 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 +// (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 _serialmididevice_h +#define _serialmididevice_h + +#include "mididevice.h" +#include "config.h" +#include +#include +#include + +class CMiniDexed; + +class CSerialMIDIDevice : public CMIDIDevice +{ +public: + CSerialMIDIDevice (CMiniDexed *pSynthesizer, CInterruptSystem *pInterrupt, CConfig *pConfig); + ~CSerialMIDIDevice (void); + + boolean Initialize (void); + + void Process (void); + +private: + CConfig *m_pConfig; + + CSerialDevice m_Serial; + unsigned m_nSerialState; + u8 m_SerialMessage[3]; +}; + +#endif