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
pull/37/head
Rene Stange 3 years ago
parent ae0a2262c4
commit b0b62a7640
  1. 3
      src/Makefile
  2. 143
      src/mididevice.cpp
  3. 45
      src/mididevice.h
  4. 71
      src/midikeyboard.cpp
  5. 53
      src/midikeyboard.h
  6. 184
      src/minidexed.cpp
  7. 37
      src/minidexed.h
  8. 98
      src/serialmididevice.cpp
  9. 52
      src/serialmididevice.h

@ -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

@ -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 <rsta2@o2online.de>
//
// 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 <http://www.gnu.org/licenses/>.
//
#include "mididevice.h"
#include "minidexed.h"
#include "config.h"
#include <stdio.h>
#include <assert.h>
#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;
}
}

@ -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 <rsta2@o2online.de>
//
// 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 <http://www.gnu.org/licenses/>.
//
#ifndef _mididevice_h
#define _mididevice_h
#include "config.h"
#include <circle/types.h>
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

@ -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 <rsta2@o2online.de>
//
// 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 <http://www.gnu.org/licenses/>.
//
#include "midikeyboard.h"
#include <circle/devicenameservice.h>
#include <assert.h>
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;
}

@ -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 <rsta2@o2online.de>
//
// 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 <http://www.gnu.org/licenses/>.
//
#ifndef _midikeyboard_h
#define _midikeyboard_h
#include "mididevice.h"
#include "config.h"
#include <circle/usb/usbmidi.h>
#include <circle/device.h>
#include <circle/types.h>
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

@ -2,19 +2,10 @@
// minidexed.cpp
//
#include "minidexed.h"
#include <circle/devicenameservice.h>
#include <circle/logger.h>
#include <stdio.h>
#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)
{
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++)
if (m_bUseSerial)
{
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())

@ -9,15 +9,15 @@
#include <math.h>
#include <circle/interrupt.h>
#include <circle/i2cmaster.h>
#include <circle/usb/usbmidi.h>
#include <circle/serial.h>
#include <circle/types.h>
#include <circle/pwmsoundbasedevice.h>
#include <circle/i2ssoundbasedevice.h>
#include <circle/hdmisoundbasedevice.h>
#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

@ -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 <rsta2@o2online.de>
//
// 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 <http://www.gnu.org/licenses/>.
//
#include "serialmididevice.h"
#include <assert.h>
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;
}
}
}

@ -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 <rsta2@o2online.de>
//
// 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 <http://www.gnu.org/licenses/>.
//
#ifndef _serialmididevice_h
#define _serialmididevice_h
#include "mididevice.h"
#include "config.h"
#include <circle/interrupt.h>
#include <circle/serial.h>
#include <circle/types.h>
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
Loading…
Cancel
Save