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 <probonopd@users.noreply.github.com>
pull/49/head
Rene Stange 3 years ago committed by GitHub
parent 715212fc14
commit 6f3f2e12da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      build.sh
  2. 2
      circle-stdlib
  3. 2
      src/Makefile
  4. 1
      src/Rules.mk
  5. 25
      src/kernel.cpp
  6. 2
      src/kernel.h
  7. 315
      src/ky040.cpp
  8. 153
      src/ky040.h
  9. 5
      src/mididevice.cpp
  10. 227
      src/minidexed.cpp
  11. 63
      src/minidexed.h
  12. 85
      src/userinterface.cpp
  13. 13
      src/userinterface.h

@ -14,19 +14,25 @@ else
export TOOLCHAIN_PREFIX="arm-none-eabi-" export TOOLCHAIN_PREFIX="arm-none-eabi-"
fi 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 # Build circle-stdlib library
cd circle-stdlib/ cd circle-stdlib/
make mrproper || true make mrproper || true
./configure -r ${RPI} --prefix "${TOOLCHAIN_PREFIX}" ./configure -r ${RPI} --prefix "${TOOLCHAIN_PREFIX}" ${OPTIONS}
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
make -j make -j
# Build additional libraries # Build additional libraries
cd libs/circle/addon/display/ cd libs/circle/addon/display/
make clean || true make clean || true
make -j make -j
cd ../sensor/
make clean || true
make -j
cd ../Properties/ cd ../Properties/
make clean || true make clean || true
make -j make -j

@ -1 +1 @@
Subproject commit 47d9deb580f3d91201c2e275d3412abfbafdf156 Subproject commit 61cf3a47bf93628039078b7c840e44432e52343e

@ -8,7 +8,7 @@ CMSIS_DIR = ../CMSIS_5/CMSIS
OBJS = main.o kernel.o minidexed.o config.o userinterface.o \ OBJS = main.o kernel.o minidexed.o config.o userinterface.o \
mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \
sysexfileloader.o perftimer.o ky040.o sysexfileloader.o perftimer.o
include ./Synth_Dexed.mk include ./Synth_Dexed.mk
include ./Rules.mk include ./Rules.mk

@ -18,6 +18,7 @@ LIBS += \
$(NEWLIBDIR)/lib/libc.a \ $(NEWLIBDIR)/lib/libc.a \
$(NEWLIBDIR)/lib/libcirclenewlib.a \ $(NEWLIBDIR)/lib/libcirclenewlib.a \
$(CIRCLEHOME)/addon/display/libdisplay.a \ $(CIRCLEHOME)/addon/display/libdisplay.a \
$(CIRCLEHOME)/addon/sensor/libsensor.a \
$(CIRCLEHOME)/addon/Properties/libproperties.a \ $(CIRCLEHOME)/addon/Properties/libproperties.a \
$(CIRCLEHOME)/addon/SDCard/libsdcard.a \ $(CIRCLEHOME)/addon/SDCard/libsdcard.a \
$(CIRCLEHOME)/lib/usb/libusb.a \ $(CIRCLEHOME)/lib/usb/libusb.a \

@ -18,7 +18,6 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
#include "kernel.h" #include "kernel.h"
#include <string.h>
#include <circle/logger.h> #include <circle/logger.h>
#include <circle/synchronize.h> #include <circle/synchronize.h>
#include <assert.h> #include <assert.h>
@ -65,27 +64,7 @@ bool CKernel::Initialize (void)
m_Config.Load (); m_Config.Load ();
// select the sound device m_pDexed = new CMiniDexed (&m_Config, &mInterrupt, &m_GPIOManager, &m_I2CMaster);
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);
}
assert (m_pDexed); assert (m_pDexed);
if (!m_pDexed->Initialize ()) if (!m_pDexed->Initialize ())
@ -110,6 +89,8 @@ CStdlibApp::TShutdownMode CKernel::Run (void)
{ {
mScreen.Update (); mScreen.Update ();
} }
m_CPUThrottle.Update ();
} }
return ShutdownHalt; return ShutdownHalt;

@ -21,6 +21,7 @@
#define _kernel_h #define _kernel_h
#include "circle_stdlib_app.h" #include "circle_stdlib_app.h"
#include <circle/cputhrottle.h>
#include <circle/gpiomanager.h> #include <circle/gpiomanager.h>
#include <circle/i2cmaster.h> #include <circle/i2cmaster.h>
#include "config.h" #include "config.h"
@ -49,6 +50,7 @@ private:
private: private:
// do not change this order // do not change this order
CConfig m_Config; CConfig m_Config;
CCPUThrottle m_CPUThrottle;
CGPIOManager m_GPIOManager; CGPIOManager m_GPIOManager;
CI2CMaster m_I2CMaster; CI2CMaster m_I2CMaster;
CMiniDexed *m_pDexed; CMiniDexed *m_pDexed;

@ -1,315 +0,0 @@
//
// ky040.cpp
//
// Circle - A C++ bare metal environment for Raspberry Pi
// Copyright (C) 2022 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 "ky040.h"
#include <assert.h>
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<CKY040 *> (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<CKY040 *> (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<CKY040 *> (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<CKY040 *> (pParam);
assert (pThis != 0);
pThis->m_hTickTimer = CTimer::Get ()->StartKernelTimer (MSEC2HZ (SwitchTickDelayMillis),
SwitchTickHandler, pThis, 0);
pThis->HandleSwitchEvent (SwitchEventTick);
}

@ -1,153 +0,0 @@
//
// ky040.h
//
// Circle - A C++ bare metal environment for Raspberry Pi
// Copyright (C) 2022 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 _sensor_ky040_h
#define _sensor_ky040_h
#include <circle/gpiomanager.h>
#include <circle/gpiopin.h>
#include <circle/timer.h>
#include <circle/types.h>
/// \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

@ -31,6 +31,7 @@
#define MIDI_AFTERTOUCH 0b1010 // TODO #define MIDI_AFTERTOUCH 0b1010 // TODO
#define MIDI_CONTROL_CHANGE 0b1011 #define MIDI_CONTROL_CHANGE 0b1011
#define MIDI_CC_BANK_SELECT_MSB 0 // TODO #define MIDI_CC_BANK_SELECT_MSB 0 // TODO
#define MIDI_CC_VOLUME 7
#define MIDI_CC_BANK_SELECT_LSB 32 #define MIDI_CC_BANK_SELECT_LSB 32
#define MIDI_PROGRAM_CHANGE 0b1100 #define MIDI_PROGRAM_CHANGE 0b1100
#define MIDI_PITCH_BEND 0b1110 #define MIDI_PITCH_BEND 0b1110
@ -130,6 +131,10 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
switch (pMessage[1]) switch (pMessage[1])
{ {
case MIDI_CC_VOLUME:
m_pSynthesizer->SetVolume (pMessage[2]);
break;
case MIDI_CC_BANK_SELECT_LSB: case MIDI_CC_BANK_SELECT_LSB:
m_pSynthesizer->BankSelectLSB (pMessage[2]); m_pSynthesizer->BankSelectLSB (pMessage[2]);
break; break;

@ -19,18 +19,28 @@
// //
#include "minidexed.h" #include "minidexed.h"
#include <circle/logger.h> #include <circle/logger.h>
#include <circle/memory.h>
#include <circle/pwmsoundbasedevice.h>
#include <circle/i2ssoundbasedevice.h>
#include <circle/hdmisoundbasedevice.h>
#include <string.h>
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
LOGMODULE ("minidexed"); 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 ()), : CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()),
#ifdef ARM_ALLOW_MULTI_CORE
CMultiCoreSupport (CMemorySystem::Get ()),
#endif
m_pConfig (pConfig), m_pConfig (pConfig),
m_UI (this, pGPIOManager, pConfig), m_UI (this, pGPIOManager, pConfig),
m_PCKeyboard (this), m_PCKeyboard (this),
m_SerialMIDI (this, pInterrupt, pConfig), m_SerialMIDI (this, pInterrupt, pConfig),
m_bUseSerial (false), m_bUseSerial (false),
m_pSoundDevice (0),
m_GetChunkTimer ("GetChunk", m_GetChunkTimer ("GetChunk",
1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()),
m_bProfileEnabled (m_pConfig->GetProfileEnabled ()) m_bProfileEnabled (m_pConfig->GetProfileEnabled ())
@ -40,10 +50,38 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, CGPIOMan
m_pMIDIKeyboard[i] = new CMIDIKeyboard (this, pConfig, i); m_pMIDIKeyboard[i] = new CMIDIKeyboard (this, pConfig, i);
assert (m_pMIDIKeyboard[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) bool CMiniDexed::Initialize (void)
{ {
assert (m_pConfig);
assert (m_pSoundDevice);
if (!m_UI.Initialize ()) if (!m_UI.Initialize ())
{ {
return false; return false;
@ -60,14 +98,41 @@ bool CMiniDexed::Initialize (void)
activate (); activate ();
SetVolume (100);
ProgramChange (0); ProgramChange (0);
setTranspose (24); 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; return true;
} }
void CMiniDexed::Process (bool bPlugAndPlayUpdated) void CMiniDexed::Process (bool bPlugAndPlayUpdated)
{ {
#ifndef ARM_ALLOW_MULTI_CORE
ProcessSound ();
#endif
for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++)
{ {
assert (m_pMIDIKeyboard[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) CSysExFileLoader *CMiniDexed::GetSysExFileLoader (void)
{ {
return &m_SysExFileLoader; return &m_SysExFileLoader;
@ -120,159 +200,42 @@ void CMiniDexed::ProgramChange (unsigned nProgram)
m_UI.ProgramChanged (nProgram); m_UI.ProgramChanged (nProgram);
} }
//// PWM ////////////////////////////////////////////////////////////////////// void CMiniDexed::SetVolume (unsigned nVolume)
CMiniDexedPWM::CMiniDexedPWM (CConfig *pConfig, CInterruptSystem *pInterrupt,
CGPIOManager *pGPIOManager)
: CMiniDexed (pConfig, pInterrupt, pGPIOManager),
CPWMSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (),
pConfig->GetChunkSize ())
{
}
bool CMiniDexedPWM::Initialize (void)
{
if (!CMiniDexed::Initialize ())
{
return false;
}
return Start ();
}
unsigned CMiniDexedPWM::GetChunk (u32 *pBuffer, unsigned nChunkSize)
{ {
if (m_bProfileEnabled) if (nVolume > 127)
{
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++]; return;
nSample += 32768;
nSample *= GetRangeMax()/2;
nSample /= 32768;
*pBuffer++ = nSample; // 2 stereo channels
*pBuffer++ = nSample;
} }
if (m_bProfileEnabled) setGain (nVolume / 127.0);
{
m_GetChunkTimer.Stop ();
}
return nResult; m_UI.VolumeChanged (nVolume);
};
//// 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) void CMiniDexed::ProcessSound (void)
{
if (!CMiniDexed::Initialize ())
{ {
return false; assert (m_pSoundDevice);
}
return Start ();
}
unsigned CMiniDexedI2S::GetChunk (u32 *pBuffer, unsigned nChunkSize) unsigned nFrames = m_nQueueSizeFrames - m_pSoundDevice->GetQueueFramesAvail ();
if (nFrames >= m_nQueueSizeFrames/2)
{ {
if (m_bProfileEnabled) if (m_bProfileEnabled)
{ {
m_GetChunkTimer.Start (); m_GetChunkTimer.Start ();
} }
unsigned nResult = nChunkSize; int16_t SampleBuffer[nFrames];
getSamples (nFrames, SampleBuffer);
int16_t SampleBuffer[nChunkSize/2];
getSamples (nChunkSize/2, SampleBuffer);
for (unsigned i = 0; nChunkSize > 0; nChunkSize -= 2) // fill the whole buffer if ( m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer)
!= (int) sizeof SampleBuffer)
{ {
s32 nSample = SampleBuffer[i++]; LOGERR ("Sound data dropped");
nSample <<= 8;
*pBuffer++ = nSample; // 2 stereo channels
*pBuffer++ = nSample;
} }
if (m_bProfileEnabled) if (m_bProfileEnabled)
{ {
m_GetChunkTimer.Stop (); m_GetChunkTimer.Stop ();
} }
return nResult;
};
//// HDMI /////////////////////////////////////////////////////////////////////
CMiniDexedHDMI::CMiniDexedHDMI (CConfig *pConfig, CInterruptSystem *pInterrupt,
CGPIOManager *pGPIOManager)
: CMiniDexed (pConfig, pInterrupt, pGPIOManager),
CHDMISoundBaseDevice (pInterrupt, pConfig->GetSampleRate (),
pConfig->GetChunkSize ())
{
} }
bool CMiniDexedHDMI::Initialize (void)
{
if (!CMiniDexed::Initialize ())
{
return false;
}
return Start ();
} }
unsigned CMiniDexedHDMI::GetChunk(u32 *pBuffer, unsigned nChunkSize)
{
if (m_bProfileEnabled)
{
m_GetChunkTimer.Start ();
}
unsigned nResult = nChunkSize;
int16_t SampleBuffer[nChunkSize/2];
getSamples (nChunkSize/2, SampleBuffer);
unsigned nFrame = 0;
for (unsigned i = 0; nChunkSize > 0; nChunkSize -= 2) // fill the whole buffer
{
s32 nSample = SampleBuffer[i++];
nSample <<= 8;
nSample = ConvertIEC958Sample (nSample, nFrame);
if (++nFrame == IEC958_FRAMES_PER_BLOCK)
{
nFrame = 0;
}
*pBuffer++ = nSample; // 2 stereo channels
*pBuffer++ = nSample;
}
if (m_bProfileEnabled)
{
m_GetChunkTimer.Stop();
}
return nResult;
};

@ -33,24 +33,34 @@
#include <circle/interrupt.h> #include <circle/interrupt.h>
#include <circle/gpiomanager.h> #include <circle/gpiomanager.h>
#include <circle/i2cmaster.h> #include <circle/i2cmaster.h>
#include <circle/pwmsoundbasedevice.h> #include <circle/multicore.h>
#include <circle/i2ssoundbasedevice.h> #include <circle/soundbasedevice.h>
#include <circle/hdmisoundbasedevice.h>
class CMiniDexed : public CDexedAdapter class CMiniDexed : public CDexedAdapter
#ifdef ARM_ALLOW_MULTI_CORE
, public CMultiCoreSupport
#endif
{ {
public: public:
CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
CGPIOManager *pGPIOManager); CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster);
virtual bool Initialize (void); bool Initialize (void);
void Process (bool bPlugAndPlayUpdated); void Process (bool bPlugAndPlayUpdated);
#ifdef ARM_ALLOW_MULTI_CORE
void Run (unsigned nCore);
#endif
CSysExFileLoader *GetSysExFileLoader (void); CSysExFileLoader *GetSysExFileLoader (void);
void BankSelectLSB (unsigned nBankLSB); void BankSelectLSB (unsigned nBankLSB);
void ProgramChange (unsigned nProgram); void ProgramChange (unsigned nProgram);
void SetVolume (unsigned nVolume);
private:
void ProcessSound (void);
private: private:
CConfig *m_pConfig; CConfig *m_pConfig;
@ -63,48 +73,11 @@ private:
CSerialMIDIDevice m_SerialMIDI; CSerialMIDIDevice m_SerialMIDI;
bool m_bUseSerial; bool m_bUseSerial;
protected: CSoundBaseDevice *m_pSoundDevice;
unsigned m_nQueueSizeFrames;
CPerformanceTimer m_GetChunkTimer; CPerformanceTimer m_GetChunkTimer;
bool m_bProfileEnabled; 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 #endif

@ -38,7 +38,8 @@ CUserInterface::CUserInterface (CMiniDexed *pMiniDexed, CGPIOManager *pGPIOManag
m_pRotaryEncoder (0), m_pRotaryEncoder (0),
m_UIMode (UIModeVoiceSelect), m_UIMode (UIModeVoiceSelect),
m_nBank (0), 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); m_pLCDBuffered = new CWriteBufferDevice (m_pLCD);
assert (m_pLCDBuffered); assert (m_pLCDBuffered);
LCDWrite ("\x1B[?25l"); // cursor off LCDWrite ("\x1B[?25l\x1B""d+"); // cursor off, autopage mode
LOGDBG ("LCD initialized"); LOGDBG ("LCD initialized");
} }
@ -121,9 +122,9 @@ void CUserInterface::BankSelected (unsigned nBankLSB)
if (m_UIMode == UIModeBankSelect) if (m_UIMode == UIModeBankSelect)
{ {
CString String; 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) if (m_UIMode == UIModeVoiceSelect)
{ {
CString String; 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) void CUserInterface::LCDWrite (const char *pString)
{ {
if (m_pLCDBuffered) if (m_pLCDBuffered)
@ -210,6 +267,22 @@ void CUserInterface::EncoderEventHandler (CKY040::TEvent Event)
} }
break; 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: default:
break; break;
} }

@ -21,7 +21,7 @@
#define _userinterface_h #define _userinterface_h
#include "config.h" #include "config.h"
#include "ky040.h" #include <sensor/ky040.h>
#include <display/hd44780device.h> #include <display/hd44780device.h>
#include <circle/gpiomanager.h> #include <circle/gpiomanager.h>
#include <circle/writebuffer.h> #include <circle/writebuffer.h>
@ -40,8 +40,17 @@ public:
void BankSelected (unsigned nBankLSB); // 0 .. 127 void BankSelected (unsigned nBankLSB); // 0 .. 127
void ProgramChanged (unsigned nProgram); // 0 .. 127 void ProgramChanged (unsigned nProgram); // 0 .. 127
void VolumeChanged (unsigned nVolume); // 0 .. 127
private: 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 LCDWrite (const char *pString); // Print to optional HD44780 display
void EncoderEventHandler (CKY040::TEvent Event); void EncoderEventHandler (CKY040::TEvent Event);
@ -53,6 +62,7 @@ private:
UIModeStart, UIModeStart,
UIModeVoiceSelect = UIModeStart, UIModeVoiceSelect = UIModeStart,
UIModeBankSelect, UIModeBankSelect,
UIModeVolume,
UIModeUnknown UIModeUnknown
}; };
@ -70,6 +80,7 @@ private:
unsigned m_nBank; unsigned m_nBank;
unsigned m_nProgram; unsigned m_nProgram;
unsigned m_nVolume;
}; };
#endif #endif

Loading…
Cancel
Save