From 6f3f2e12dabd8b8743e09d14a8ea77e9b9108a38 Mon Sep 17 00:00:00 2001 From: Rene Stange Date: Mon, 14 Mar 2022 23:32:35 +0100 Subject: [PATCH] Multi-core support (#47) * Cleanup Makefile * Update submodule circle-stdlib to v15.12 * System options can be defined cleaner using the "-o" option * Include KY-040 driver from Circle (removed from MiniDexed) * Render sound on secondary CPU core 1 * Enable multi-core support on Raspberry Pi 2-4 * Does still work on the Raspberry Pi 1 with restrictions * Use CSoundBaseDevice::Write() instead overriding GetChunk() * CMiniDexed is not derived from the sound device classes any more * Add option SCREEN_DMA_BURST_LENGTH=1 to relieve bus congestion * Add volume control to MIDI CC and UI * Add CPU full speed support Normally the CPU runs at a reduced speed in bare metal applications. With this update and the setting "fast=true" in the file cmdline.txt, it runs at the full speed. Co-authored-by: probonopd --- build.sh | 14 +- circle-stdlib | 2 +- src/Makefile | 2 +- src/Rules.mk | 1 + src/kernel.cpp | 25 +--- src/kernel.h | 2 + src/ky040.cpp | 315 ------------------------------------------ src/ky040.h | 153 -------------------- src/mididevice.cpp | 5 + src/minidexed.cpp | 245 ++++++++++++++------------------ src/minidexed.h | 63 +++------ src/userinterface.cpp | 85 +++++++++++- src/userinterface.h | 13 +- 13 files changed, 236 insertions(+), 689 deletions(-) delete mode 100644 src/ky040.cpp delete mode 100644 src/ky040.h diff --git a/build.sh b/build.sh index ef9ce68..30772b1 100755 --- a/build.sh +++ b/build.sh @@ -14,19 +14,25 @@ else export TOOLCHAIN_PREFIX="arm-none-eabi-" fi +# Define system options +OPTIONS="-o USE_PWM_AUDIO_ON_ZERO -o SAVE_VFP_REGS_ON_IRQ -o REALTIME -o SCREEN_DMA_BURST_LENGTH=1" +if [ "${RPI}" -gt "1" ]; then + OPTIONS="${OPTIONS} -o ARM_ALLOW_MULTI_CORE" +fi + # Build circle-stdlib library cd circle-stdlib/ make mrproper || true -./configure -r ${RPI} --prefix "${TOOLCHAIN_PREFIX}" -echo "DEFINE += -DUSE_PWM_AUDIO_ON_ZERO" >> libs/circle/Config.mk -echo "DEFINE += -DSAVE_VFP_REGS_ON_IRQ" >> libs/circle/Config.mk -echo "DEFINE += -DREALTIME" >> libs/circle/Config.mk +./configure -r ${RPI} --prefix "${TOOLCHAIN_PREFIX}" ${OPTIONS} make -j # Build additional libraries cd libs/circle/addon/display/ make clean || true make -j +cd ../sensor/ +make clean || true +make -j cd ../Properties/ make clean || true make -j diff --git a/circle-stdlib b/circle-stdlib index 47d9deb..61cf3a4 160000 --- a/circle-stdlib +++ b/circle-stdlib @@ -1 +1 @@ -Subproject commit 47d9deb580f3d91201c2e275d3412abfbafdf156 +Subproject commit 61cf3a47bf93628039078b7c840e44432e52343e diff --git a/src/Makefile b/src/Makefile index f826c48..a5010e6 100644 --- a/src/Makefile +++ b/src/Makefile @@ -8,7 +8,7 @@ CMSIS_DIR = ../CMSIS_5/CMSIS OBJS = main.o kernel.o minidexed.o config.o userinterface.o \ mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ - sysexfileloader.o perftimer.o ky040.o + sysexfileloader.o perftimer.o include ./Synth_Dexed.mk include ./Rules.mk diff --git a/src/Rules.mk b/src/Rules.mk index 3552503..39b9f25 100644 --- a/src/Rules.mk +++ b/src/Rules.mk @@ -18,6 +18,7 @@ LIBS += \ $(NEWLIBDIR)/lib/libc.a \ $(NEWLIBDIR)/lib/libcirclenewlib.a \ $(CIRCLEHOME)/addon/display/libdisplay.a \ + $(CIRCLEHOME)/addon/sensor/libsensor.a \ $(CIRCLEHOME)/addon/Properties/libproperties.a \ $(CIRCLEHOME)/addon/SDCard/libsdcard.a \ $(CIRCLEHOME)/lib/usb/libusb.a \ diff --git a/src/kernel.cpp b/src/kernel.cpp index 2ad706d..1c9ed77 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -18,7 +18,6 @@ // along with this program. If not, see . // #include "kernel.h" -#include #include #include #include @@ -65,27 +64,7 @@ bool CKernel::Initialize (void) m_Config.Load (); - // select the sound device - const char *pSoundDevice = m_Config.GetSoundDevice (); - if (strcmp (pSoundDevice, "i2s") == 0) - { - LOGNOTE ("I2S mode"); - - m_pDexed = new CMiniDexedI2S (&m_Config, &mInterrupt, &m_GPIOManager, &m_I2CMaster); - } - else if (strcmp (pSoundDevice, "hdmi") == 0) - { - LOGNOTE ("HDMI mode"); - - m_pDexed = new CMiniDexedHDMI (&m_Config, &mInterrupt, &m_GPIOManager); - } - else - { - LOGNOTE ("PWM mode"); - - m_pDexed = new CMiniDexedPWM (&m_Config, &mInterrupt, &m_GPIOManager); - } - + m_pDexed = new CMiniDexed (&m_Config, &mInterrupt, &m_GPIOManager, &m_I2CMaster); assert (m_pDexed); if (!m_pDexed->Initialize ()) @@ -110,6 +89,8 @@ CStdlibApp::TShutdownMode CKernel::Run (void) { mScreen.Update (); } + + m_CPUThrottle.Update (); } return ShutdownHalt; diff --git a/src/kernel.h b/src/kernel.h index a2b5cc3..de9a5f0 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -21,6 +21,7 @@ #define _kernel_h #include "circle_stdlib_app.h" +#include #include #include #include "config.h" @@ -49,6 +50,7 @@ private: private: // do not change this order CConfig m_Config; + CCPUThrottle m_CPUThrottle; CGPIOManager m_GPIOManager; CI2CMaster m_I2CMaster; CMiniDexed *m_pDexed; diff --git a/src/ky040.cpp b/src/ky040.cpp deleted file mode 100644 index 24c040f..0000000 --- a/src/ky040.cpp +++ /dev/null @@ -1,315 +0,0 @@ -// -// ky040.cpp -// -// Circle - A C++ bare metal environment for Raspberry Pi -// Copyright (C) 2022 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 "ky040.h" -#include - -static const unsigned SwitchDebounceDelayMillis = 50; -static const unsigned SwitchTickDelayMillis = 500; - -CKY040::TState CKY040::s_NextState[StateUnknown][2][2] = -{ - // {{CLK=0/DT=0, CLK=0/DT=1}, {CLK=1/DT=0, CLK=1/DT=1}} - - {{StateInvalid, StateCWStart}, {StateCCWStart, StateStart}}, // StateStart - - {{StateCWBothLow, StateCWStart}, {StateInvalid, StateStart}}, // StateCWStart - {{StateCWBothLow, StateInvalid}, {StateCWFirstHigh, StateInvalid}}, // StateCWBothLow - {{StateInvalid, StateInvalid}, {StateCWFirstHigh, StateStart}}, // StateCWFirstHigh - - {{StateCCWBothLow, StateInvalid}, {StateCCWStart, StateStart}}, // StateCCWStart - {{StateCCWBothLow, StateCCWFirstHigh}, {StateInvalid, StateInvalid}}, // StateCCWBothLow - {{StateInvalid, StateCCWFirstHigh}, {StateInvalid, StateStart}}, // StateCCWFirstHigh - - {{StateInvalid, StateInvalid}, {StateInvalid, StateStart}} // StateInvalid -}; - -CKY040::TEvent CKY040::s_Output[StateUnknown][2][2] = -{ - // {{CLK=0/DT=0, CLK=0/DT=1}, {CLK=1/DT=0, CLK=1/DT=1}} - - {{EventUnknown, EventUnknown}, {EventUnknown, EventUnknown}}, // StateStart - - {{EventUnknown, EventUnknown}, {EventUnknown, EventUnknown}}, // StateCWStart - {{EventUnknown, EventUnknown}, {EventUnknown, EventUnknown}}, // StateCWBothLow - {{EventUnknown, EventUnknown}, {EventUnknown, EventClockwise}}, // StateCWFirstHigh - - {{EventUnknown, EventUnknown}, {EventUnknown, EventUnknown}}, // StateCCWStart - {{EventUnknown, EventUnknown}, {EventUnknown, EventUnknown}}, // StateCCWBothLow - {{EventUnknown, EventUnknown}, {EventUnknown, EventCounterclockwise}}, // StateCCWFirstHigh - - {{EventUnknown, EventUnknown}, {EventUnknown, EventUnknown}} // StateInvalid -}; - -CKY040::TSwitchState CKY040::s_NextSwitchState[SwitchStateUnknown][SwitchEventUnknown] = -{ - // {SwitchEventDown, SwitchEventUp, SwitchEventTick} - - {SwitchStateDown, SwitchStateStart, SwitchStateStart}, // SwitchStateStart - {SwitchStateDown, SwitchStateClick, SwitchStateHold}, // SwitchStateDown - {SwitchStateDown2, SwitchStateClick, SwitchStateStart}, // SwitchStateClick - {SwitchStateDown2, SwitchStateClick2, SwitchStateInvalid}, // SwitchStateDown2 - {SwitchStateDown3, SwitchStateClick2, SwitchStateStart}, // SwitchStateClick2 - {SwitchStateDown3, SwitchStateClick3, SwitchStateInvalid}, // SwitchStateDown3 - {SwitchStateInvalid, SwitchStateClick3, SwitchStateStart}, // SwitchStateClick3 - {SwitchStateHold, SwitchStateStart, SwitchStateHold}, // SwitchStateHold - {SwitchStateInvalid, SwitchStateStart, SwitchStateInvalid} // SwitchStateInvalid -}; - -CKY040::TEvent CKY040::s_SwitchOutput[SwitchStateUnknown][SwitchEventUnknown] = -{ - // {SwitchEventDown, SwitchEventUp, SwitchEventTick} - - {EventUnknown, EventUnknown, EventUnknown}, // SwitchStateStart - {EventUnknown, EventUnknown, EventSwitchHold}, // SwitchStateDown - {EventUnknown, EventUnknown, EventSwitchClick}, // SwitchStateClick - {EventUnknown, EventUnknown, EventUnknown}, // SwitchStateDown2 - {EventUnknown, EventUnknown, EventSwitchDoubleClick}, // SwitchStateClick2 - {EventUnknown, EventUnknown, EventUnknown}, // SwitchStateDown3 - {EventUnknown, EventUnknown, EventSwitchTripleClick}, // SwitchStateClick3 - {EventUnknown, EventUnknown, EventSwitchHold}, // SwitchStateHold - {EventUnknown, EventUnknown, EventUnknown} // SwitchStateInvalid -}; - -CKY040::CKY040 (unsigned nCLKPin, unsigned nDTPin, unsigned nSWPin, CGPIOManager *pGPIOManager) -: m_CLKPin (nCLKPin, GPIOModeInputPullUp, pGPIOManager), - m_DTPin (nDTPin, GPIOModeInputPullUp, pGPIOManager), - m_SWPin (nSWPin, GPIOModeInputPullUp, pGPIOManager), - m_bPollingMode (!pGPIOManager), - m_bInterruptConnected (FALSE), - m_pEventHandler (nullptr), - m_State (StateStart), - m_hDebounceTimer (0), - m_hTickTimer (0), - m_nLastSWLevel (HIGH), - m_bDebounceActive (FALSE), - m_SwitchState (SwitchStateStart), - m_nSwitchLastTicks (0) -{ -} - -CKY040::~CKY040 (void) -{ - if (m_bInterruptConnected) - { - m_pEventHandler = nullptr; - - m_CLKPin.DisableInterrupt2 (); - m_CLKPin.DisableInterrupt (); - m_CLKPin.DisconnectInterrupt (); - - m_DTPin.DisableInterrupt2 (); - m_DTPin.DisableInterrupt (); - m_DTPin.DisconnectInterrupt (); - - m_SWPin.DisableInterrupt2 (); - m_SWPin.DisableInterrupt (); - m_SWPin.DisconnectInterrupt (); - } - - if (m_hDebounceTimer) - { - CTimer::Get ()->CancelKernelTimer (m_hDebounceTimer); - } - - if (m_hTickTimer) - { - CTimer::Get ()->CancelKernelTimer (m_hTickTimer); - } -} - -boolean CKY040::Initialize (void) -{ - if (!m_bPollingMode) - { - assert (!m_bInterruptConnected); - m_bInterruptConnected = TRUE; - - m_CLKPin.ConnectInterrupt (EncoderInterruptHandler, this); - m_DTPin.ConnectInterrupt (EncoderInterruptHandler, this); - m_SWPin.ConnectInterrupt (SwitchInterruptHandler, this); - - m_CLKPin.EnableInterrupt (GPIOInterruptOnFallingEdge); - m_CLKPin.EnableInterrupt2 (GPIOInterruptOnRisingEdge); - - m_DTPin.EnableInterrupt (GPIOInterruptOnFallingEdge); - m_DTPin.EnableInterrupt2 (GPIOInterruptOnRisingEdge); - - m_SWPin.EnableInterrupt (GPIOInterruptOnFallingEdge); - m_SWPin.EnableInterrupt2 (GPIOInterruptOnRisingEdge); - } - - return TRUE; -} - -void CKY040::RegisterEventHandler (TEventHandler *pHandler, void *pParam) -{ - assert (!m_pEventHandler); - m_pEventHandler = pHandler; - assert (m_pEventHandler); - m_pEventParam = pParam; -} - -unsigned CKY040::GetHoldSeconds (void) const -{ - return m_nHoldCounter / 2; -} - -void CKY040::Update (void) -{ - assert (m_bPollingMode); - - EncoderInterruptHandler (this); - - // handle switch - unsigned nTicks = CTimer::GetClockTicks (); - unsigned nSW = m_SWPin.Read (); - - if (nSW != m_nLastSWLevel) - { - m_nLastSWLevel = nSW; - - m_bDebounceActive = TRUE; - m_nDebounceLastTicks = CTimer::GetClockTicks (); - } - else - { - if ( m_bDebounceActive - && nTicks - m_nDebounceLastTicks >= SwitchDebounceDelayMillis * (CLOCKHZ / 1000)) - { - m_bDebounceActive = FALSE; - m_nSwitchLastTicks = nTicks; - - if (m_pEventHandler) - { - (*m_pEventHandler) (nSW ? EventSwitchUp : EventSwitchDown, - m_pEventParam); - } - - HandleSwitchEvent (nSW ? SwitchEventUp : SwitchEventDown); - } - - if (nTicks - m_nSwitchLastTicks >= SwitchTickDelayMillis * (CLOCKHZ / 1000)) - { - m_nSwitchLastTicks = nTicks; - - HandleSwitchEvent (SwitchEventTick); - } - } -} - -// generates the higher level switch events -void CKY040::HandleSwitchEvent (TSwitchEvent SwitchEvent) -{ - assert (SwitchEvent < SwitchEventUnknown); - TEvent Event = s_SwitchOutput[m_SwitchState][SwitchEvent]; - TSwitchState NextState = s_NextSwitchState[m_SwitchState][SwitchEvent]; - - if (NextState == SwitchStateHold) - { - if (m_SwitchState != SwitchStateHold) - { - m_nHoldCounter = 0; - } - - m_nHoldCounter++; - } - - m_SwitchState = NextState; - - if ( Event != EventUnknown - && (Event != EventSwitchHold || !(m_nHoldCounter & 1)) // emit hold event each second - && m_pEventHandler) - { - (*m_pEventHandler) (Event, m_pEventParam); - } -} - -void CKY040::EncoderInterruptHandler (void *pParam) -{ - CKY040 *pThis = static_cast (pParam); - assert (pThis != 0); - - unsigned nCLK = pThis->m_CLKPin.Read (); - unsigned nDT = pThis->m_DTPin.Read (); - assert (nCLK <= 1); - assert (nDT <= 1); - - assert (pThis->m_State < StateUnknown); - TEvent Event = s_Output[pThis->m_State][nCLK][nDT]; - pThis->m_State = s_NextState[pThis->m_State][nCLK][nDT]; - - if ( Event != EventUnknown - && pThis->m_pEventHandler) - { - (*pThis->m_pEventHandler) (Event, pThis->m_pEventParam); - } -} - -void CKY040::SwitchInterruptHandler (void *pParam) -{ - CKY040 *pThis = static_cast (pParam); - assert (pThis != 0); - - if (pThis->m_hDebounceTimer) - { - CTimer::Get ()->CancelKernelTimer (pThis->m_hDebounceTimer); - } - - pThis->m_hDebounceTimer = - CTimer::Get ()->StartKernelTimer (MSEC2HZ (SwitchDebounceDelayMillis), - SwitchDebounceHandler, pThis, 0); -} - -void CKY040::SwitchDebounceHandler (TKernelTimerHandle hTimer, void *pParam, void *pContext) -{ - CKY040 *pThis = static_cast (pParam); - assert (pThis != 0); - - pThis->m_hDebounceTimer = 0; - - if (pThis->m_hTickTimer) - { - CTimer::Get ()->CancelKernelTimer (pThis->m_hTickTimer); - } - - pThis->m_hTickTimer = CTimer::Get ()->StartKernelTimer (MSEC2HZ (SwitchTickDelayMillis), - SwitchTickHandler, pThis, 0); - - unsigned nSW = pThis->m_SWPin.Read (); - - if (pThis->m_pEventHandler) - { - (*pThis->m_pEventHandler) (nSW ? EventSwitchUp : EventSwitchDown, - pThis->m_pEventParam); - } - - pThis->HandleSwitchEvent (nSW ? SwitchEventUp : SwitchEventDown); -} - -void CKY040::SwitchTickHandler (TKernelTimerHandle hTimer, void *pParam, void *pContext) -{ - CKY040 *pThis = static_cast (pParam); - assert (pThis != 0); - - pThis->m_hTickTimer = CTimer::Get ()->StartKernelTimer (MSEC2HZ (SwitchTickDelayMillis), - SwitchTickHandler, pThis, 0); - - pThis->HandleSwitchEvent (SwitchEventTick); -} diff --git a/src/ky040.h b/src/ky040.h deleted file mode 100644 index 052b06c..0000000 --- a/src/ky040.h +++ /dev/null @@ -1,153 +0,0 @@ -// -// ky040.h -// -// Circle - A C++ bare metal environment for Raspberry Pi -// Copyright (C) 2022 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 _sensor_ky040_h -#define _sensor_ky040_h - -#include -#include -#include -#include - -/// \note This driver supports an interrupt mode and a polling mode. - -class CKY040 /// Driver for KY-040 rotary encoder module -{ -public: - enum TEvent - { - EventClockwise, - EventCounterclockwise, - - EventSwitchDown, - EventSwitchUp, - EventSwitchClick, - EventSwitchDoubleClick, - EventSwitchTripleClick, - EventSwitchHold, ///< generated each second - - EventUnknown - }; - - typedef void TEventHandler (TEvent Event, void *pParam); - -public: - /// \param nCLKPin GPIO pin number of clock pin (encoder pin A) - /// \param nDTPin GPIO pin number of data pin (encoder pin B) - /// \param nSWPin GPIO pin number of switch pin - /// \param pGPIOManager Pointer to GPIO manager object (0 enables polling mode) - CKY040 (unsigned nCLKPin, unsigned nDTPin, unsigned nSWPin, CGPIOManager *pGPIOManager = 0); - - ~CKY040 (void); - - /// \brief Operation successful? - boolean Initialize (void); - - /// \brief Register a handler, to be called on an event from the encoder - /// \param pHandler Pointer to the handler - /// \param pParam Optional user parameter, handed over to the handler - void RegisterEventHandler (TEventHandler *pHandler, void *pParam = 0); - - /// \return Number of seconds, the switch is hold down - /// \note Only valid, when EventSwitchHold has been received. - unsigned GetHoldSeconds (void) const; - - /// \brief Has to be called very frequently in polling mode - void Update (void); - -private: - enum TState - { - StateStart, - StateCWStart, - StateCWBothLow, - StateCWFirstHigh, - StateCCWStart, - StateCCWBothLow, - StateCCWFirstHigh, - StateInvalid, - StateUnknown - }; - - enum TSwitchState - { - SwitchStateStart, - SwitchStateDown, - SwitchStateClick, - SwitchStateDown2, - SwitchStateClick2, - SwitchStateDown3, - SwitchStateClick3, - SwitchStateHold, - SwitchStateInvalid, - SwitchStateUnknown - }; - - enum TSwitchEvent - { - SwitchEventDown, - SwitchEventUp, - SwitchEventTick, - SwitchEventUnknown - }; - -private: - void HandleSwitchEvent (TSwitchEvent SwitchEvent); - - static void EncoderInterruptHandler (void *pParam); - static void SwitchInterruptHandler (void *pParam); - - static void SwitchDebounceHandler (TKernelTimerHandle hTimer, void *pParam, void *pContext); - static void SwitchTickHandler (TKernelTimerHandle hTimer, void *pParam, void *pContext); - -private: - CGPIOPin m_CLKPin; - CGPIOPin m_DTPin; - CGPIOPin m_SWPin; - - boolean m_bPollingMode; - boolean m_bInterruptConnected; - - TEventHandler *m_pEventHandler; - void *m_pEventParam; - - // encoder - TState m_State; - - static TState s_NextState[StateUnknown][2][2]; - static TEvent s_Output[StateUnknown][2][2]; - - // switch low level - TKernelTimerHandle m_hDebounceTimer; - TKernelTimerHandle m_hTickTimer; - - unsigned m_nLastSWLevel; - boolean m_bDebounceActive; - unsigned m_nDebounceLastTicks; - - // switch higher level - TSwitchState m_SwitchState; - unsigned m_nSwitchLastTicks; - unsigned m_nHoldCounter; - - static TSwitchState s_NextSwitchState[SwitchStateUnknown][SwitchEventUnknown]; - static TEvent s_SwitchOutput[SwitchStateUnknown][SwitchEventUnknown]; -}; - -#endif diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 8b0d570..4f92e61 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -31,6 +31,7 @@ #define MIDI_AFTERTOUCH 0b1010 // TODO #define MIDI_CONTROL_CHANGE 0b1011 #define MIDI_CC_BANK_SELECT_MSB 0 // TODO + #define MIDI_CC_VOLUME 7 #define MIDI_CC_BANK_SELECT_LSB 32 #define MIDI_PROGRAM_CHANGE 0b1100 #define MIDI_PITCH_BEND 0b1110 @@ -130,6 +131,10 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign switch (pMessage[1]) { + case MIDI_CC_VOLUME: + m_pSynthesizer->SetVolume (pMessage[2]); + break; + case MIDI_CC_BANK_SELECT_LSB: m_pSynthesizer->BankSelectLSB (pMessage[2]); break; diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 476a8f3..4217c35 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -19,18 +19,28 @@ // #include "minidexed.h" #include +#include +#include +#include +#include +#include #include #include LOGMODULE ("minidexed"); -CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, CGPIOManager *pGPIOManager) +CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, + CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster) : CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()), +#ifdef ARM_ALLOW_MULTI_CORE + CMultiCoreSupport (CMemorySystem::Get ()), +#endif m_pConfig (pConfig), m_UI (this, pGPIOManager, pConfig), m_PCKeyboard (this), m_SerialMIDI (this, pInterrupt, pConfig), m_bUseSerial (false), + m_pSoundDevice (0), m_GetChunkTimer ("GetChunk", 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), m_bProfileEnabled (m_pConfig->GetProfileEnabled ()) @@ -40,10 +50,38 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, CGPIOMan m_pMIDIKeyboard[i] = new CMIDIKeyboard (this, pConfig, i); assert (m_pMIDIKeyboard[i]); } + + // select the sound device + const char *pDeviceName = pConfig->GetSoundDevice (); + if (strcmp (pDeviceName, "i2s") == 0) + { + LOGNOTE ("I2S mode"); + + m_pSoundDevice = new CI2SSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), + pConfig->GetChunkSize (), false, + pI2CMaster, pConfig->GetDACI2CAddress ()); + } + else if (strcmp (pDeviceName, "hdmi") == 0) + { + LOGNOTE ("HDMI mode"); + + m_pSoundDevice = new CHDMISoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), + pConfig->GetChunkSize ()); + } + else + { + LOGNOTE ("PWM mode"); + + m_pSoundDevice = new CPWMSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), + pConfig->GetChunkSize ()); + } }; bool CMiniDexed::Initialize (void) { + assert (m_pConfig); + assert (m_pSoundDevice); + if (!m_UI.Initialize ()) { return false; @@ -60,14 +98,41 @@ bool CMiniDexed::Initialize (void) activate (); + SetVolume (100); ProgramChange (0); setTranspose (24); + // setup and start the sound device + if (!m_pSoundDevice->AllocateQueueFrames (m_pConfig->GetChunkSize ())) + { + LOGERR ("Cannot allocate sound queue"); + + return false; + } + + m_pSoundDevice->SetWriteFormat (SoundFormatSigned16, 1); // 16-bit Mono + + m_nQueueSizeFrames = m_pSoundDevice->GetQueueSizeFrames (); + + m_pSoundDevice->Start (); + +#ifdef ARM_ALLOW_MULTI_CORE + // start secondary cores + if (!CMultiCoreSupport::Initialize ()) + { + return false; + } +#endif + return true; } void CMiniDexed::Process (bool bPlugAndPlayUpdated) { +#ifndef ARM_ALLOW_MULTI_CORE + ProcessSound (); +#endif + for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) { assert (m_pMIDIKeyboard[i]); @@ -89,6 +154,21 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) } } +#ifdef ARM_ALLOW_MULTI_CORE + +void CMiniDexed::Run (unsigned nCore) +{ + if (nCore == 1) + { + while (1) + { + ProcessSound (); + } + } +} + +#endif + CSysExFileLoader *CMiniDexed::GetSysExFileLoader (void) { return &m_SysExFileLoader; @@ -120,159 +200,42 @@ void CMiniDexed::ProgramChange (unsigned nProgram) m_UI.ProgramChanged (nProgram); } -//// PWM ////////////////////////////////////////////////////////////////////// - -CMiniDexedPWM::CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt, - CGPIOManager *pGPIOManager) -: CMiniDexed (pConfig, pInterrupt, pGPIOManager), - CPWMSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), - pConfig->GetChunkSize ()) -{ -} - -bool CMiniDexedPWM::Initialize (void) +void CMiniDexed::SetVolume (unsigned nVolume) { - if (!CMiniDexed::Initialize ()) - { - return false; - } - - return Start (); -} - -unsigned CMiniDexedPWM::GetChunk (u32 *pBuffer, unsigned nChunkSize) -{ - if (m_bProfileEnabled) - { - m_GetChunkTimer.Start (); - } - - unsigned nResult = nChunkSize; - - int16_t SampleBuffer[nChunkSize/2]; - getSamples (nChunkSize/2, SampleBuffer); - - for (unsigned i = 0; nChunkSize > 0; nChunkSize -= 2) // fill the whole buffer - { - s32 nSample = SampleBuffer[i++]; - nSample += 32768; - nSample *= GetRangeMax()/2; - nSample /= 32768; - - *pBuffer++ = nSample; // 2 stereo channels - *pBuffer++ = nSample; - } - - if (m_bProfileEnabled) + if (nVolume > 127) { - m_GetChunkTimer.Stop (); - } - - return nResult; -}; - -//// I2S ////////////////////////////////////////////////////////////////////// - -CMiniDexedI2S::CMiniDexedI2S (CConfig *pConfig, CInterruptSystem *pInterrupt, - CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster) -: CMiniDexed (pConfig, pInterrupt, pGPIOManager), - CI2SSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), - pConfig->GetChunkSize (), false, pI2CMaster, - pConfig->GetDACI2CAddress ()) -{ -} - -bool CMiniDexedI2S::Initialize (void) -{ - if (!CMiniDexed::Initialize ()) - { - return false; - } - - return Start (); -} - -unsigned CMiniDexedI2S::GetChunk (u32 *pBuffer, unsigned nChunkSize) -{ - if (m_bProfileEnabled) - { - m_GetChunkTimer.Start (); - } - - unsigned nResult = nChunkSize; - - int16_t SampleBuffer[nChunkSize/2]; - getSamples (nChunkSize/2, SampleBuffer); - - for (unsigned i = 0; nChunkSize > 0; nChunkSize -= 2) // fill the whole buffer - { - s32 nSample = SampleBuffer[i++]; - nSample <<= 8; - - *pBuffer++ = nSample; // 2 stereo channels - *pBuffer++ = nSample; - } - - if (m_bProfileEnabled) - { - m_GetChunkTimer.Stop (); + return; } - return nResult; -}; - -//// HDMI ///////////////////////////////////////////////////////////////////// + setGain (nVolume / 127.0); -CMiniDexedHDMI::CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt, - CGPIOManager *pGPIOManager) -: CMiniDexed (pConfig, pInterrupt, pGPIOManager), - CHDMISoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), - pConfig->GetChunkSize ()) -{ + m_UI.VolumeChanged (nVolume); } -bool CMiniDexedHDMI::Initialize (void) +void CMiniDexed::ProcessSound (void) { - if (!CMiniDexed::Initialize ()) - { - return false; - } - - return Start (); -} + assert (m_pSoundDevice); -unsigned CMiniDexedHDMI::GetChunk(u32 *pBuffer, unsigned nChunkSize) -{ - if (m_bProfileEnabled) + unsigned nFrames = m_nQueueSizeFrames - m_pSoundDevice->GetQueueFramesAvail (); + if (nFrames >= m_nQueueSizeFrames/2) { - m_GetChunkTimer.Start (); - } - - unsigned nResult = nChunkSize; - - int16_t SampleBuffer[nChunkSize/2]; - getSamples (nChunkSize/2, SampleBuffer); + if (m_bProfileEnabled) + { + m_GetChunkTimer.Start (); + } - unsigned nFrame = 0; - for (unsigned i = 0; nChunkSize > 0; nChunkSize -= 2) // fill the whole buffer - { - s32 nSample = SampleBuffer[i++]; - nSample <<= 8; + int16_t SampleBuffer[nFrames]; + getSamples (nFrames, SampleBuffer); - nSample = ConvertIEC958Sample (nSample, nFrame); - if (++nFrame == IEC958_FRAMES_PER_BLOCK) + if ( m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer) + != (int) sizeof SampleBuffer) { - nFrame = 0; + LOGERR ("Sound data dropped"); } - *pBuffer++ = nSample; // 2 stereo channels - *pBuffer++ = nSample; - } - - if (m_bProfileEnabled) - { - m_GetChunkTimer.Stop(); + if (m_bProfileEnabled) + { + m_GetChunkTimer.Stop (); + } } - - return nResult; -}; +} diff --git a/src/minidexed.h b/src/minidexed.h index d817f87..17ca610 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -33,24 +33,34 @@ #include #include #include -#include -#include -#include +#include +#include class CMiniDexed : public CDexedAdapter +#ifdef ARM_ALLOW_MULTI_CORE + , public CMultiCoreSupport +#endif { public: CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, - CGPIOManager *pGPIOManager); + CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster); - virtual bool Initialize (void); + bool Initialize (void); void Process (bool bPlugAndPlayUpdated); +#ifdef ARM_ALLOW_MULTI_CORE + void Run (unsigned nCore); +#endif + CSysExFileLoader *GetSysExFileLoader (void); void BankSelectLSB (unsigned nBankLSB); void ProgramChange (unsigned nProgram); + void SetVolume (unsigned nVolume); + +private: + void ProcessSound (void); private: CConfig *m_pConfig; @@ -63,48 +73,11 @@ private: CSerialMIDIDevice m_SerialMIDI; bool m_bUseSerial; -protected: + CSoundBaseDevice *m_pSoundDevice; + unsigned m_nQueueSizeFrames; + CPerformanceTimer m_GetChunkTimer; bool m_bProfileEnabled; }; -//// PWM ////////////////////////////////////////////////////////////////////// - -class CMiniDexedPWM : public CMiniDexed, public CPWMSoundBaseDevice -{ -public: - CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt, - CGPIOManager *pGPIOManager); - - bool Initialize (void); - - unsigned GetChunk (u32 *pBuffer, unsigned nChunkSize); -}; - -//// I2S ////////////////////////////////////////////////////////////////////// - -class CMiniDexedI2S : public CMiniDexed, public CI2SSoundBaseDevice -{ -public: - CMiniDexedI2S (CConfig *pConfig, CInterruptSystem *pInterrupt, - CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster); - - bool Initialize (void); - - unsigned GetChunk (u32 *pBuffer, unsigned nChunkSize); -}; - -//// HDMI ///////////////////////////////////////////////////////////////////// - -class CMiniDexedHDMI : public CMiniDexed, public CHDMISoundBaseDevice -{ -public: - CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt, - CGPIOManager *pGPIOManager); - - bool Initialize (void); - - unsigned GetChunk (u32 *pBuffer, unsigned nChunkSize); -}; - #endif diff --git a/src/userinterface.cpp b/src/userinterface.cpp index 6cc5b90..6f573e2 100644 --- a/src/userinterface.cpp +++ b/src/userinterface.cpp @@ -38,7 +38,8 @@ CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManag m_pRotaryEncoder (0), m_UIMode (UIModeVoiceSelect), m_nBank (0), - m_nProgram (0) + m_nProgram (0), + m_nVolume (0) { } @@ -73,7 +74,7 @@ bool CUserInterface::Initialize (void) m_pLCDBuffered = new CWriteBufferDevice (m_pLCD); assert (m_pLCDBuffered); - LCDWrite ("\x1B[?25l"); // cursor off + LCDWrite ("\x1B[?25l\x1B""d+"); // cursor off, autopage mode LOGDBG ("LCD initialized"); } @@ -121,9 +122,9 @@ void CUserInterface::BankSelected (unsigned nBankLSB) if (m_UIMode == UIModeBankSelect) { CString String; - String.Format ("\n\r%-12uBANK%s", nBankLSB+1, BankName.c_str ()); + String.Format ("%u", nBankLSB+1); - LCDWrite (String); + DisplayWrite (String, "BANK", BankName.c_str ()); } } @@ -145,12 +146,68 @@ void CUserInterface::ProgramChanged (unsigned nProgram) if (m_UIMode == UIModeVoiceSelect) { CString String; - String.Format ("\n\r%-11uVOICE%s", nProgram, ProgramName); + String.Format ("%u", nProgram); - LCDWrite (String); + DisplayWrite (String, "VOICE", ProgramName); } } +void CUserInterface::VolumeChanged (unsigned nVolume) +{ + assert (nVolume < 128); + m_nVolume = nVolume; + + if (m_UIMode == UIModeVolume) + { + char VolumeBar[CConfig::LCDColumns+1]; + memset (VolumeBar, 0xFF, sizeof VolumeBar); // 0xFF is the block character + VolumeBar[nVolume * CConfig::LCDColumns / 127] = '\0'; + + DisplayWrite ("", "VOLUME", VolumeBar); + } +} + +void CUserInterface::DisplayWrite (const char *pInstance, const char *pMenu, + const char *pParam, const char *pValue) +{ + assert (pInstance); + assert (pMenu); + assert (pParam); + + CString Msg ("\x1B[H"); // cursor home + + // first line + Msg.Append (pInstance); + + size_t nLen = strlen (pInstance) + strlen (pMenu); + if (nLen < CConfig::LCDColumns) + { + for (unsigned i = CConfig::LCDColumns-nLen; i > 0; i--) + { + Msg.Append (" "); + } + } + + Msg.Append (pMenu); + + // second line + CString ParamValue (pParam); + if (pValue) + { + ParamValue.Append ("="); + ParamValue.Append (pValue); + } + + Msg.Append (ParamValue); + + if (ParamValue.GetLength () < CConfig::LCDColumns) + { + Msg.Append ("\x1B[K"); // clear end of line + } + + LCDWrite (Msg); +} + void CUserInterface::LCDWrite (const char *pString) { if (m_pLCDBuffered) @@ -210,6 +267,22 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event) } break; + case UIModeVolume: { + const int Increment = 128 / CConfig::LCDColumns; + + int nVolume = m_nVolume + nStep*Increment; + if (nVolume < 0) + { + nVolume = 0; + } + else if (nVolume > 127) + { + nVolume = 127; + } + + m_pMiniDexed->SetVolume (nVolume); + } break; + default: break; } diff --git a/src/userinterface.h b/src/userinterface.h index 0ce874c..6e34d99 100644 --- a/src/userinterface.h +++ b/src/userinterface.h @@ -21,7 +21,7 @@ #define _userinterface_h #include "config.h" -#include "ky040.h" +#include #include #include #include @@ -40,8 +40,17 @@ public: void BankSelected (unsigned nBankLSB); // 0 .. 127 void ProgramChanged (unsigned nProgram); // 0 .. 127 + void VolumeChanged (unsigned nVolume); // 0 .. 127 private: + // Print to display in this format: + // +----------------+ + // |INSTANCE MENU| + // |PARAM[=VALUE] | + // +----------------+ + void DisplayWrite (const char *pInstance, const char *pMenu, + const char *pParam, const char *pValue = nullptr); + void LCDWrite (const char *pString); // Print to optional HD44780 display void EncoderEventHandler (CKY040::TEvent Event); @@ -53,6 +62,7 @@ private: UIModeStart, UIModeVoiceSelect = UIModeStart, UIModeBankSelect, + UIModeVolume, UIModeUnknown }; @@ -70,6 +80,7 @@ private: unsigned m_nBank; unsigned m_nProgram; + unsigned m_nVolume; }; #endif