Introduce support for MiSTer I2C control interface

This commit is contained in:
Dale Whinham committed 2020-12-19 15:02:47 +00:00
1 parent 005a9f2593
commit 51f86d6fda
10 files changed
+449 -3

No files matched your search

+1
View File
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for the SSD1309 using the `ssd1306_i2c` driver - many thanks to @flynnsbit for testing!
- Support for the [Blokas Pisound](https://blokas.io/pisound/) - many thanks to @sigkill for providing the device for development!
- Support for configuration using [MiSTer FPGA](https://github.com/MiSTer-devel/Main_MiSTer/wiki)'s OSD via an I²C control interface in certain cores - many thanks to @sorgelig for collaborating and implementing the MiSTer side!
### Fixed
+2
View File
@@ -5,6 +5,7 @@
include Config.mk
OBJS := src/config.o \
src/control/mister.o \
src/kernel.o \
src/lcd/hd44780.o \
src/lcd/hd44780fourbit.o \
@@ -22,6 +23,7 @@ OBJS := src/config.o \
src/synth/soundfontsynth.o
EXTRACLEAN += src/*.d src/*.o \
src/control/*.d src/control/*.o \
src/lcd/*.d src/lcd/*.o \
src/synth/*.d src/synth/*.o
+4
View File
@@ -33,6 +33,10 @@ CFG(i2c_dac_address, int, AudioI2CDACAddress, 0x4c, true )
CFG(i2c_dac_init, TAudioI2CDACInit, AudioI2CDACInit, TAudioI2CDACInit::None )
END_SECTION
BEGIN_SECTION(control)
CFG(mister, bool, ControlMister, false )
END_SECTION
BEGIN_SECTION(mt32emu)
CFG(resampler_quality, TMT32EmuResamplerQuality, MT32EmuResamplerQuality, TMT32EmuResamplerQuality::Good )
CFG(midi_channels, TMT32EmuMIDIChannels, MT32EmuMIDIChannels, TMT32EmuMIDIChannels::Standard )
+52
View File
@@ -0,0 +1,52 @@
//
// mister.h
//
// mt32-pi - A bare-metal Roland MT-32 emulator for Raspberry Pi
// Copyright (C) 2020 Dale Whinham <daleyo@gmail.com>
//
// 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 _mister_h
#define _mister_h
#include <circle/i2cmaster.h>
#include <circle/types.h>
#include "event.h"
#include "misterstatus.h"
#include "synth/synth.h"
class CMisterControl
{
public:
CMisterControl(CI2CMaster* pI2CMaster, TEventQueue& EventQueue);
void Update(const TMisterStatus& SystemStatus);
private:
bool WriteConfigToMister(const TMisterStatus& NewStatus);
void ResetState();
void ApplyConfig(const TMisterStatus& NewStatus, const TMisterStatus& SystemStatus);
void EnqueueAllSoundOffEvent();
CI2CMaster* m_pI2CMaster;
TEventQueue* m_pEventQueue;
bool bMisterActive;
TMisterStatus m_LastSystemStatus;
TMisterStatus m_LastMisterStatus;
};
#endif
+54
View File
@@ -0,0 +1,54 @@
//
// misterstatus.h
//
// mt32-pi - A bare-metal Roland MT-32 emulator for Raspberry Pi
// Copyright (C) 2020 Dale Whinham <daleyo@gmail.com>
//
// 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 _misterstatus_h
#define _misterstatus_h
#include <circle/types.h>
#include "synth/synth.h"
enum class TMisterSynth : u8
{
Mute = 0xA0,
MT32 = 0xA1,
SoundFont = 0xA2,
Unknown = 0xFF,
};
struct TMisterStatus
{
TMisterSynth Synth;
u8 MT32ROMSet;
u8 SoundFontIndex;
}
PACKED;
inline bool operator==(const TMisterStatus& LHS, const TMisterStatus& RHS)
{
return LHS.Synth == RHS.Synth && LHS.MT32ROMSet == RHS.MT32ROMSet && LHS.SoundFontIndex == RHS.SoundFontIndex;
}
inline bool operator!=(const TMisterStatus& LHS, const TMisterStatus& RHS)
{
return !operator==(LHS, RHS);
}
#endif
+71
View File
@@ -0,0 +1,71 @@
//
// event.h
//
// mt32-pi - A bare-metal Roland MT-32 emulator for Raspberry Pi
// Copyright (C) 2020 Dale Whinham <daleyo@gmail.com>
//
// 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 _event_h
#define _event_h
#include "ringbuffer.h"
#include "synth/mt32romset.h"
#include "synth/synth.h"
struct TSwitchSynthEvent
{
TSynth Synth;
};
struct TSwitchMT32ROMSetEvent
{
TMT32ROMSet ROMSet;
};
struct TSwitchSoundFontEvent
{
size_t Index;
};
struct TAllSoundOffEvent
{
// No payload
};
enum class TEventType {
SwitchSynth,
SwitchMT32ROMSet,
SwitchSoundFont,
AllSoundOff
};
struct TEvent
{
TEventType Type;
union
{
TSwitchSynthEvent SwitchSynth;
TSwitchMT32ROMSetEvent SwitchMT32ROMSet;
TSwitchSoundFontEvent SwitchSoundFont;
TAllSoundOffEvent AllSoundOff;
};
};
constexpr size_t EventQueueSize = 32;
using TEventQueue = CRingBuffer<TEvent, EventQueueSize>;
#endif
+12
View File
@@ -37,6 +37,8 @@
#include <circle/usb/usbhcidevice.h>
#include "config.h"
#include "control/mister.h"
#include "event.h"
#include "lcd/synthlcd.h"
#include "midiparser.h"
#include "pisound.h"
@@ -89,6 +91,8 @@ private:
size_t ReceiveSerialMIDI(u8* pOutData, size_t nSize);
bool ParseCustomSysEx(const u8* pData, size_t nSize);
void ProcessEventQueue();
// Actions that can be triggered via events
void SwitchSynth(TSynth Synth);
void SwitchMT32ROMSet(TMT32ROMSet ROMSet);
@@ -112,6 +116,10 @@ private:
CSynthLCD* m_pLCD;
unsigned m_nLCDUpdateTime;
// MiSTer control interface
CMisterControl m_MisterControl;
unsigned m_nMisterUpdateTime;
// Serial GPIO MIDI
bool m_bSerialMIDIEnabled;
@@ -137,6 +145,10 @@ private:
// MIDI receive buffer
CRingBuffer<u8, MIDIRxBufferSize> m_MIDIRxBuffer;
// Event handling
TEventQueue m_EventQueue;
static void EventHandler(const TEvent& Event);
static void USBMIDIPacketHandler(unsigned nCable, u8* pPacket, unsigned nLength);
static void MIDIReceiveHandler(const u8* pData, size_t nSize);
+14
View File
@@ -142,6 +142,20 @@ i2c_dac_address = 4c
# pcm51xx: for DACs based on PCM5121 or similar (e.g. PCM5141, PCM5242)
i2c_dac_init = none
# -----------------------------------------------------------------------------
# Control options
# -----------------------------------------------------------------------------
[control]
# Enable or disable the I2C MiSTer control interface.
#
# If using mt32-pi with a MiSTer FPGA system and custom hardware to interface
# with MiSTer's User Port, enable this option to allow controlling mt32-pi via
# the MiSTer's on-screen display.
#
# Values: on, off*
mister = off
# -----------------------------------------------------------------------------
# MT-32 emulator options
# -----------------------------------------------------------------------------
+162
View File
@@ -0,0 +1,162 @@
//
// mister.cpp
//
// mt32-pi - A bare-metal Roland MT-32 emulator for Raspberry Pi
// Copyright (C) 2020 Dale Whinham <daleyo@gmail.com>
//
// 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 <circle/logger.h>
#include "control/mister.h"
const char MisterControlName[] = "mistercontrol";
constexpr u8 MisterI2CAddress = 0x45;
CMisterControl::CMisterControl(CI2CMaster* pI2CMaster, TEventQueue& EventQueue)
: m_pI2CMaster(pI2CMaster),
m_pEventQueue(&EventQueue),
bMisterActive(false),
m_LastSystemStatus{TMisterSynth::Unknown, 0xFF, 0xFF},
m_LastMisterStatus{TMisterSynth::Unknown, 0xFF, 0xFF}
{
}
void CMisterControl::Update(const TMisterStatus& SystemStatus)
{
assert(m_pI2CMaster != nullptr);
// Read current status from MiSTer
TMisterStatus MisterStatus;
if (m_pI2CMaster->Read(MisterI2CAddress, &MisterStatus, sizeof(MisterStatus)) < 0)
{
ResetState();
return;
}
//CLogger::Get()->Write(MisterControlName, LogDebug, "MiSTer Rx: 0x%02x 0x%02x 0x%02x", static_cast<u8>(MisterStatus.Synth), MisterStatus.MT32ROMSet, MisterStatus.SoundFontIndex);
// Core was reset or "Reset Hanging Notes" was selected from OSD; turn off all sound
if (MisterStatus.Synth == TMisterSynth::Mute)
{
CLogger::Get()->Write(MisterControlName, LogNotice, "Stopping synth activity");
EnqueueAllSoundOffEvent();
WriteConfigToMister(SystemStatus);
return;
}
if (bMisterActive)
{
// If the state has been changed by user controls/SysEx, we just need to update the MiSTer status
if (SystemStatus != m_LastSystemStatus)
{
// Write config back to MiSTer
if (!WriteConfigToMister(SystemStatus))
{
ResetState();
return;
}
m_LastSystemStatus = SystemStatus;
}
else if (MisterStatus != m_LastMisterStatus)
{
// The state has been changed by MiSTer; apply it
ApplyConfig(MisterStatus, SystemStatus);
// Write config back to MiSTer
if (!WriteConfigToMister(MisterStatus))
{
ResetState();
return;
}
m_LastMisterStatus = MisterStatus;
}
}
else
{
// First valid reply from MiSTer; apply its config
ApplyConfig(MisterStatus, SystemStatus);
// Write config back to MiSTer
if (!WriteConfigToMister(MisterStatus))
return;
m_LastMisterStatus = MisterStatus;
bMisterActive = true;
}
}
void CMisterControl::ApplyConfig(const TMisterStatus& NewStatus, const TMisterStatus& CurrentStatus)
{
TEvent Event;
if (NewStatus.Synth != CurrentStatus.Synth)
{
Event.Type = TEventType::SwitchSynth;
Event.SwitchSynth.Synth = NewStatus.Synth == TMisterSynth::MT32 ? TSynth::MT32 : TSynth::SoundFont;
m_pEventQueue->Enqueue(Event);
}
if (NewStatus.MT32ROMSet != CurrentStatus.MT32ROMSet)
{
Event.Type = TEventType::SwitchMT32ROMSet;
Event.SwitchMT32ROMSet.ROMSet = static_cast<TMT32ROMSet>(NewStatus.MT32ROMSet);
m_pEventQueue->Enqueue(Event);
}
if (NewStatus.SoundFontIndex != CurrentStatus.SoundFontIndex)
{
Event.Type = TEventType::SwitchSoundFont;
Event.SwitchSoundFont.Index = NewStatus.SoundFontIndex;
m_pEventQueue->Enqueue(Event);
}
}
bool CMisterControl::WriteConfigToMister(const TMisterStatus& NewStatus)
{
// Write config back to MiSTer
if (m_pI2CMaster->Write(MisterI2CAddress, &NewStatus, sizeof(NewStatus)) < 0)
{
CLogger::Get()->Write(MisterControlName, LogError, "MiSTer write failed");
return false;
}
//CLogger::Get()->Write(MisterControlName, LogDebug, "MiSTer Tx: 0x%02x 0x%02x 0x%02x", static_cast<u8>(NewStatus.Synth), NewStatus.MT32ROMSet, NewStatus.SoundFontIndex);
return true;
}
void CMisterControl::ResetState()
{
if (bMisterActive)
{
// MiSTer has just stopped responding; dispatch an All Sound Off event
CLogger::Get()->Write(MisterControlName, LogNotice, "MiSTer stopped responding; turning notes off");
EnqueueAllSoundOffEvent();
bMisterActive = false;
m_LastSystemStatus = {TMisterSynth::Unknown, 0xFF, 0xFF};
m_LastMisterStatus = {TMisterSynth::Unknown, 0xFF, 0xFF};
}
}
void CMisterControl::EnqueueAllSoundOffEvent()
{
TEvent Event;
Event.Type = TEventType::AllSoundOff;
m_pEventQueue->Enqueue(Event);
}
+77 -3
View File
@@ -30,9 +30,10 @@
const char MT32PiName[] = "mt32-pi";
constexpr u32 LCDUpdatePeriodMillis = 16;
constexpr u32 LEDTimeoutMillis = 50;
constexpr u32 ActiveSenseTimoutMillis = 330;
constexpr u32 LCDUpdatePeriodMillis = 16;
constexpr u32 MisterUpdatePeriodMillis = 50;
constexpr u32 LEDTimeoutMillis = 50;
constexpr u32 ActiveSenseTimoutMillis = 330;
constexpr float Sample16BitMax = (1 << 16 - 1) - 1;
constexpr float Sample24BitMax = (1 << 24 - 1) - 1;
@@ -64,6 +65,9 @@ CMT32Pi::CMT32Pi(CI2CMaster* pI2CMaster, CSPIMaster* pSPIMaster, CInterruptSyste
m_pLCD(nullptr),
m_nLCDUpdateTime(0),
m_MisterControl(pI2CMaster, m_EventQueue),
m_nMisterUpdateTime(0),
m_bSerialMIDIEnabled(false),
m_bActiveSenseFlag(false),
@@ -255,6 +259,9 @@ void CMT32Pi::MainTask()
// Process MIDI data
UpdateMIDI();
// Process events
ProcessEventQueue();
unsigned ticks = m_pTimer->GetTicks();
// Update activity LED
@@ -295,6 +302,8 @@ void CMT32Pi::UITask()
// Display current MT-32 ROM version/SoundFont
m_pCurrentSynth->ReportStatus();
const bool bMisterEnabled = CConfig::Get()->ControlMister;
while (m_bRunning)
{
unsigned ticks = m_pTimer->GetTicks();
@@ -308,6 +317,26 @@ void CMT32Pi::UITask()
m_pLCD->Update(*m_pSoundFontSynth);
m_nLCDUpdateTime = ticks;
}
// Poll MiSTer interface
if (bMisterEnabled && (ticks - m_nMisterUpdateTime) >= MSEC2HZ(MisterUpdatePeriodMillis))
{
TMisterStatus Status{TMisterSynth::Unknown, 0xFF, 0xFF};
if (m_pCurrentSynth == m_pMT32Synth)
Status.Synth = TMisterSynth::MT32;
else if (m_pCurrentSynth == m_pSoundFontSynth)
Status.Synth = TMisterSynth::SoundFont;
if (m_pMT32Synth)
Status.MT32ROMSet = static_cast<u8>(m_pMT32Synth->GetROMSet());
if (m_pSoundFontSynth)
Status.SoundFontIndex = m_pSoundFontSynth->GetSoundFontIndex();
m_MisterControl.Update(Status);
m_nMisterUpdateTime = ticks;
}
}
// Clear screen
@@ -577,6 +606,43 @@ size_t CMT32Pi::ReceiveSerialMIDI(u8* pOutData, size_t nSize)
return static_cast<size_t>(nResult);
}
void CMT32Pi::ProcessEventQueue()
{
TEvent Buffer[EventQueueSize];
const size_t nEvents = m_EventQueue.Dequeue(Buffer, sizeof(Buffer));
// We got some events, wake up
if (nEvents > 0)
Awaken();
for (size_t i = 0; i < nEvents; ++i)
{
const TEvent& Event = Buffer[i];
switch (Event.Type)
{
case TEventType::SwitchSynth:
SwitchSynth(Event.SwitchSynth.Synth);
break;
case TEventType::SwitchMT32ROMSet:
SwitchMT32ROMSet(Event.SwitchMT32ROMSet.ROMSet);
break;
case TEventType::SwitchSoundFont:
SwitchSoundFont(Event.SwitchSoundFont.Index);
break;
case TEventType::AllSoundOff:
if (m_pMT32Synth)
m_pMT32Synth->AllSoundOff();
if (m_pSoundFontSynth)
m_pSoundFontSynth->AllSoundOff();
break;
}
}
}
void CMT32Pi::SwitchSynth(TSynth NewSynth)
{
CSynthBase* pNewSynth = nullptr;
@@ -674,6 +740,14 @@ bool CMT32Pi::InitPCM51xx(u8 nAddress)
return true;
}
void CMT32Pi::EventHandler(const TEvent& Event)
{
assert(s_pThis != nullptr);
// Enqueue event
s_pThis->m_EventQueue.Enqueue(Event);
}
// The following handlers are called from interrupt context, enqueue into ring buffer for main thread
void CMT32Pi::USBMIDIPacketHandler(unsigned nCable, u8* pPacket, unsigned nLength)
{