mirror of https://github.com/probonopd/MiniDexed
Signal chain in float32_t and fixes for float signal path (#73)
* Signal chain is now float32_t with much more support of CMSIS5. * Fixes for float signal path. * Several fixes for float signal path. * Manual merge of changes from upstream. * Fixes for wrong panning calculation. Co-authored-by: Holger Wirtz <wirtz@parasitstudio.de>pull/84/head
parent
075e17407d
commit
f98f5db10d
@ -1 +1 @@ |
|||||||
Subproject commit 70293ae5998643c706244b090504dde8b4097851 |
Subproject commit e414a8718300815aefc3fe0acd8df5c12ad0b58a |
@ -0,0 +1,21 @@ |
|||||||
|
|
||||||
|
#ifndef _common_h |
||||||
|
#define _common_h |
||||||
|
|
||||||
|
inline long maplong(long x, long in_min, long in_max, long out_min, long out_max) { |
||||||
|
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; |
||||||
|
} |
||||||
|
|
||||||
|
inline float32_t mapfloat(float32_t val, float32_t in_min, float32_t in_max, float32_t out_min, float32_t out_max) |
||||||
|
{ |
||||||
|
return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; |
||||||
|
} |
||||||
|
|
||||||
|
#define constrain(amt, low, high) ({ \ |
||||||
|
__typeof__(amt) _amt = (amt); \
|
||||||
|
__typeof__(low) _low = (low); \
|
||||||
|
__typeof__(high) _high = (high); \
|
||||||
|
(_amt < _low) ? _low : ((_amt > _high) ? _high : _amt); \
|
||||||
|
}) |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,853 @@ |
|||||||
|
// |
||||||
|
// minidexed.cpp |
||||||
|
// |
||||||
|
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi |
||||||
|
// Copyright (C) 2022 The MiniDexed Team |
||||||
|
// |
||||||
|
// 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 "minidexed.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 <assert.h> |
||||||
|
|
||||||
|
LOGMODULE ("minidexed"); |
||||||
|
|
||||||
|
CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, |
||||||
|
CGPIOManager *pGPIOManager, CI2CMaster *pI2CMaster, FATFS *pFileSystem) |
||||||
|
: |
||||||
|
#ifdef ARM_ALLOW_MULTI_CORE |
||||||
|
CMultiCoreSupport (CMemorySystem::Get ()), |
||||||
|
#endif |
||||||
|
m_pConfig (pConfig), |
||||||
|
m_UI (this, pGPIOManager, pConfig), |
||||||
|
m_PerformanceConfig (pFileSystem), |
||||||
|
m_PCKeyboard (this, pConfig), |
||||||
|
m_SerialMIDI (this, pInterrupt, pConfig), |
||||||
|
m_bUseSerial (false), |
||||||
|
m_pSoundDevice (0), |
||||||
|
m_bChannelsSwapped (pConfig->GetChannelsSwapped ()), |
||||||
|
#ifdef ARM_ALLOW_MULTI_CORE |
||||||
|
m_nActiveTGsLog2 (0), |
||||||
|
#endif |
||||||
|
m_GetChunkTimer ("GetChunk", |
||||||
|
1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), |
||||||
|
m_bProfileEnabled (m_pConfig->GetProfileEnabled ()) |
||||||
|
{ |
||||||
|
assert (m_pConfig); |
||||||
|
|
||||||
|
for (unsigned i = 0; i < CConfig::ToneGenerators; i++) |
||||||
|
{ |
||||||
|
m_nVoiceBankID[i] = 0; |
||||||
|
m_nProgram[i] = 0; |
||||||
|
m_nVolume[i] = 100; |
||||||
|
m_nPan[i] = 64; |
||||||
|
pan_float[i]=0.0f; |
||||||
|
m_nMasterTune[i] = 0; |
||||||
|
m_nMIDIChannel[i] = CMIDIDevice::Disabled; |
||||||
|
|
||||||
|
m_nNoteLimitLow[i] = 0; |
||||||
|
m_nNoteLimitHigh[i] = 127; |
||||||
|
m_nNoteShift[i] = 0; |
||||||
|
|
||||||
|
m_pTG[i] = new CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()); |
||||||
|
assert (m_pTG[i]); |
||||||
|
|
||||||
|
m_pTG[i]->activate (); |
||||||
|
} |
||||||
|
|
||||||
|
for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) |
||||||
|
{ |
||||||
|
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 ()); |
||||||
|
|
||||||
|
// The channels are swapped by default in the HDMI sound driver. |
||||||
|
// TODO: Remove this line, when this has been fixed in the driver. |
||||||
|
m_bChannelsSwapped = !m_bChannelsSwapped; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
LOGNOTE ("PWM mode"); |
||||||
|
|
||||||
|
m_pSoundDevice = new CPWMSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), |
||||||
|
pConfig->GetChunkSize ()); |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef ARM_ALLOW_MULTI_CORE |
||||||
|
for (unsigned nCore = 0; nCore < CORES; nCore++) |
||||||
|
{ |
||||||
|
m_CoreStatus[nCore] = CoreStatusInit; |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
// BEGIN setup tg_mixer |
||||||
|
//tg_mixer = new AudioStereoMixer<8>(); |
||||||
|
// END setup tg_mixer |
||||||
|
|
||||||
|
SetParameter (ParameterCompressorEnable, 1); |
||||||
|
|
||||||
|
// BEGIN setup reverb |
||||||
|
reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate()); |
||||||
|
SetParameter (ParameterReverbEnable, 1); |
||||||
|
SetParameter (ParameterReverbSize, 70); |
||||||
|
SetParameter (ParameterReverbHighDamp, 50); |
||||||
|
SetParameter (ParameterReverbLowDamp, 50); |
||||||
|
SetParameter (ParameterReverbLowPass, 30); |
||||||
|
SetParameter (ParameterReverbDiffusion, 20); |
||||||
|
SetParameter (ParameterReverbLevel, 80); |
||||||
|
// END setup reverb |
||||||
|
}; |
||||||
|
|
||||||
|
bool CMiniDexed::Initialize (void) |
||||||
|
{ |
||||||
|
assert (m_pConfig); |
||||||
|
assert (m_pSoundDevice); |
||||||
|
|
||||||
|
if (!m_UI.Initialize ()) |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
m_SysExFileLoader.Load (); |
||||||
|
|
||||||
|
if (m_SerialMIDI.Initialize ()) |
||||||
|
{ |
||||||
|
LOGNOTE ("Serial MIDI interface enabled"); |
||||||
|
|
||||||
|
m_bUseSerial = true; |
||||||
|
} |
||||||
|
|
||||||
|
for (unsigned i = 0; i < CConfig::ToneGenerators; i++) |
||||||
|
{ |
||||||
|
assert (m_pTG[i]); |
||||||
|
|
||||||
|
SetVolume (100, i); |
||||||
|
ProgramChange (0, i); |
||||||
|
|
||||||
|
m_pTG[i]->setTranspose (24); |
||||||
|
|
||||||
|
m_pTG[i]->setPBController (12, 1); |
||||||
|
m_pTG[i]->setMWController (99, 7, 0); |
||||||
|
} |
||||||
|
|
||||||
|
if (m_PerformanceConfig.Load ()) |
||||||
|
{ |
||||||
|
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) |
||||||
|
{ |
||||||
|
BankSelectLSB (m_PerformanceConfig.GetBankNumber (nTG), nTG); |
||||||
|
ProgramChange (m_PerformanceConfig.GetVoiceNumber (nTG), nTG); |
||||||
|
SetMIDIChannel (m_PerformanceConfig.GetMIDIChannel (nTG), nTG); |
||||||
|
SetVolume (m_PerformanceConfig.GetVolume (nTG), nTG); |
||||||
|
SetPan (m_PerformanceConfig.GetPan (nTG), nTG); |
||||||
|
SetMasterTune (m_PerformanceConfig.GetDetune (nTG), nTG); |
||||||
|
|
||||||
|
m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG); |
||||||
|
m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG); |
||||||
|
m_nNoteShift[nTG] = m_PerformanceConfig.GetNoteShift (nTG); |
||||||
|
} |
||||||
|
|
||||||
|
// Effects |
||||||
|
SetParameter (ParameterCompressorEnable, m_PerformanceConfig.GetCompressorEnable () ? 1 : 0); |
||||||
|
SetParameter (ParameterReverbEnable, m_PerformanceConfig.GetReverbEnable () ? 1 : 0); |
||||||
|
SetParameter (ParameterReverbSize, m_PerformanceConfig.GetReverbSize ()); |
||||||
|
SetParameter (ParameterReverbHighDamp, m_PerformanceConfig.GetReverbHighDamp ()); |
||||||
|
SetParameter (ParameterReverbLowDamp, m_PerformanceConfig.GetReverbLowDamp ()); |
||||||
|
SetParameter (ParameterReverbLowPass, m_PerformanceConfig.GetReverbLowPass ()); |
||||||
|
SetParameter (ParameterReverbDiffusion, m_PerformanceConfig.GetReverbDiffusion ()); |
||||||
|
SetParameter (ParameterReverbLevel, m_PerformanceConfig.GetReverbLevel ()); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
SetMIDIChannel (CMIDIDevice::OmniMode, 0); |
||||||
|
} |
||||||
|
|
||||||
|
// setup and start the sound device |
||||||
|
if (!m_pSoundDevice->AllocateQueueFrames (m_pConfig->GetChunkSize ())) |
||||||
|
{ |
||||||
|
LOGERR ("Cannot allocate sound queue"); |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
#ifndef ARM_ALLOW_MULTI_CORE |
||||||
|
m_pSoundDevice->SetWriteFormat (SoundFormatSigned16, 1); // 16-bit Mono |
||||||
|
#else |
||||||
|
m_pSoundDevice->SetWriteFormat (SoundFormatSigned16, 2); // 16-bit Stereo |
||||||
|
#endif |
||||||
|
|
||||||
|
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]); |
||||||
|
m_pMIDIKeyboard[i]->Process (bPlugAndPlayUpdated); |
||||||
|
} |
||||||
|
|
||||||
|
m_PCKeyboard.Process (bPlugAndPlayUpdated); |
||||||
|
|
||||||
|
if (m_bUseSerial) |
||||||
|
{ |
||||||
|
m_SerialMIDI.Process (); |
||||||
|
} |
||||||
|
|
||||||
|
m_UI.Process (); |
||||||
|
|
||||||
|
if (m_bProfileEnabled) |
||||||
|
{ |
||||||
|
m_GetChunkTimer.Dump (); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef ARM_ALLOW_MULTI_CORE |
||||||
|
|
||||||
|
void CMiniDexed::Run (unsigned nCore) |
||||||
|
{ |
||||||
|
assert (1 <= nCore && nCore < CORES); |
||||||
|
|
||||||
|
if (nCore == 1) |
||||||
|
{ |
||||||
|
m_CoreStatus[nCore] = CoreStatusIdle; // core 1 ready |
||||||
|
|
||||||
|
// wait for cores 2 and 3 to be ready |
||||||
|
for (unsigned nCore = 2; nCore < CORES; nCore++) |
||||||
|
{ |
||||||
|
while (m_CoreStatus[nCore] != CoreStatusIdle) |
||||||
|
{ |
||||||
|
// just wait |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
while (m_CoreStatus[nCore] != CoreStatusExit) |
||||||
|
{ |
||||||
|
ProcessSound (); |
||||||
|
} |
||||||
|
} |
||||||
|
else // core 2 and 3 |
||||||
|
{ |
||||||
|
while (1) |
||||||
|
{ |
||||||
|
m_CoreStatus[nCore] = CoreStatusIdle; // ready to be kicked |
||||||
|
while (m_CoreStatus[nCore] == CoreStatusIdle) |
||||||
|
{ |
||||||
|
// just wait |
||||||
|
} |
||||||
|
|
||||||
|
// now kicked from core 1 |
||||||
|
|
||||||
|
if (m_CoreStatus[nCore] == CoreStatusExit) |
||||||
|
{ |
||||||
|
m_CoreStatus[nCore] = CoreStatusUnknown; |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
assert (m_CoreStatus[nCore] == CoreStatusBusy); |
||||||
|
|
||||||
|
// process the TGs, assigned to this core (2 or 3) |
||||||
|
|
||||||
|
assert (m_nFramesToProcess <= CConfig::MaxChunkSize); |
||||||
|
unsigned nTG = CConfig::TGsCore1 + (nCore-2)*CConfig::TGsCore23; |
||||||
|
for (unsigned i = 0; i < CConfig::TGsCore23; i++, nTG++) |
||||||
|
{ |
||||||
|
assert (m_pTG[nTG]); |
||||||
|
m_pTG[nTG]->getSamples (m_OutputLevel[nTG], m_nFramesToProcess); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#endif |
||||||
|
|
||||||
|
CSysExFileLoader *CMiniDexed::GetSysExFileLoader (void) |
||||||
|
{ |
||||||
|
return &m_SysExFileLoader; |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::BankSelectLSB (unsigned nBankLSB, unsigned nTG) |
||||||
|
{ |
||||||
|
if (nBankLSB > 127) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
m_nVoiceBankID[nTG] = nBankLSB; |
||||||
|
|
||||||
|
m_UI.ParameterChanged (); |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) |
||||||
|
{ |
||||||
|
if (nProgram > 31) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
m_nProgram[nTG] = nProgram; |
||||||
|
|
||||||
|
uint8_t Buffer[156]; |
||||||
|
m_SysExFileLoader.GetVoice (m_nVoiceBankID[nTG], nProgram, Buffer); |
||||||
|
|
||||||
|
assert (m_pTG[nTG]); |
||||||
|
m_pTG[nTG]->loadVoiceParameters (Buffer); |
||||||
|
|
||||||
|
m_UI.ParameterChanged (); |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG) |
||||||
|
{ |
||||||
|
if (nVolume > 127) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
m_nVolume[nTG] = nVolume; |
||||||
|
|
||||||
|
assert (m_pTG[nTG]); |
||||||
|
m_pTG[nTG]->setGain (nVolume / 127.0); |
||||||
|
|
||||||
|
m_UI.ParameterChanged (); |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::SetPan (unsigned nPan, unsigned nTG) |
||||||
|
{ |
||||||
|
constrain(nPan,-1.0f,1.0f); |
||||||
|
|
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
m_nPan[nTG] = nPan; |
||||||
|
pan_float[nTG]=mapfloat(nPan,0,127,-1.0,1.0); |
||||||
|
|
||||||
|
m_UI.ParameterChanged (); |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::SetMasterTune (int nMasterTune, unsigned nTG) |
||||||
|
{ |
||||||
|
if (!(-99 <= nMasterTune && nMasterTune <= 99)) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
m_nMasterTune[nTG] = nMasterTune; |
||||||
|
|
||||||
|
assert (m_pTG[nTG]); |
||||||
|
m_pTG[nTG]->setMasterTune ((int8_t) nMasterTune); |
||||||
|
|
||||||
|
m_UI.ParameterChanged (); |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) |
||||||
|
{ |
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
m_nMIDIChannel[nTG] = uchChannel; |
||||||
|
|
||||||
|
for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) |
||||||
|
{ |
||||||
|
assert (m_pMIDIKeyboard[i]); |
||||||
|
m_pMIDIKeyboard[i]->SetChannel (uchChannel, nTG); |
||||||
|
} |
||||||
|
|
||||||
|
m_PCKeyboard.SetChannel (uchChannel, nTG); |
||||||
|
|
||||||
|
if (m_bUseSerial) |
||||||
|
{ |
||||||
|
m_SerialMIDI.SetChannel (uchChannel, nTG); |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef ARM_ALLOW_MULTI_CORE |
||||||
|
unsigned nActiveTGs = 0; |
||||||
|
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) |
||||||
|
{ |
||||||
|
if (m_nMIDIChannel[nTG] != CMIDIDevice::Disabled) |
||||||
|
{ |
||||||
|
nActiveTGs++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
assert (nActiveTGs <= 8); |
||||||
|
static const unsigned Log2[] = {0, 0, 1, 2, 2, 3, 3, 3, 3}; |
||||||
|
m_nActiveTGsLog2 = Log2[nActiveTGs]; |
||||||
|
#endif |
||||||
|
|
||||||
|
m_UI.ParameterChanged (); |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::keyup (int16_t pitch, unsigned nTG) |
||||||
|
{ |
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
assert (m_pTG[nTG]); |
||||||
|
|
||||||
|
pitch = ApplyNoteLimits (pitch, nTG); |
||||||
|
if (pitch >= 0) |
||||||
|
{ |
||||||
|
m_pTG[nTG]->keyup (pitch); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::keydown (int16_t pitch, uint8_t velocity, unsigned nTG) |
||||||
|
{ |
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
assert (m_pTG[nTG]); |
||||||
|
|
||||||
|
pitch = ApplyNoteLimits (pitch, nTG); |
||||||
|
if (pitch >= 0) |
||||||
|
{ |
||||||
|
m_pTG[nTG]->keydown (pitch, velocity); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int16_t CMiniDexed::ApplyNoteLimits (int16_t pitch, unsigned nTG) |
||||||
|
{ |
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
|
||||||
|
if ( pitch < (int16_t) m_nNoteLimitLow[nTG] |
||||||
|
|| pitch > (int16_t) m_nNoteLimitHigh[nTG]) |
||||||
|
{ |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
pitch += m_nNoteShift[nTG]; |
||||||
|
|
||||||
|
if ( pitch < 0 |
||||||
|
|| pitch > 127) |
||||||
|
{ |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
return pitch; |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::setSustain(bool sustain, unsigned nTG) |
||||||
|
{ |
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
assert (m_pTG[nTG]); |
||||||
|
m_pTG[nTG]->setSustain (sustain); |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::setModWheel (uint8_t value, unsigned nTG) |
||||||
|
{ |
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
assert (m_pTG[nTG]); |
||||||
|
m_pTG[nTG]->setModWheel (value); |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::setPitchbend (int16_t value, unsigned nTG) |
||||||
|
{ |
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
assert (m_pTG[nTG]); |
||||||
|
m_pTG[nTG]->setPitchbend (value); |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::ControllersRefresh (unsigned nTG) |
||||||
|
{ |
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
assert (m_pTG[nTG]); |
||||||
|
m_pTG[nTG]->ControllersRefresh (); |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::SetParameter (TParameter Parameter, int nValue) |
||||||
|
{ |
||||||
|
assert (reverb); |
||||||
|
|
||||||
|
assert (Parameter < ParameterUnknown); |
||||||
|
m_nParameter[Parameter] = nValue; |
||||||
|
|
||||||
|
switch (Parameter) |
||||||
|
{ |
||||||
|
<<<<<<< HEAD |
||||||
|
case ParameterReverbSize: reverb->size (fValue); break; |
||||||
|
case ParameterReverbHighDamp: reverb->hidamp (fValue); break; |
||||||
|
case ParameterReverbLowDamp: reverb->lodamp (fValue); break; |
||||||
|
case ParameterReverbLowPass: reverb->lowpass (fValue); break; |
||||||
|
case ParameterReverbDiffusion: reverb->diffusion (fValue); break; |
||||||
|
case ParameterReverbLevel: reverb->level (fValue); break; |
||||||
|
======= |
||||||
|
case ParameterCompressorEnable: |
||||||
|
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) |
||||||
|
{ |
||||||
|
assert (m_pTG[nTG]); |
||||||
|
m_pTG[nTG]->setCompressor (!!nValue); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case ParameterReverbEnable: |
||||||
|
m_ReverbSpinLock.Acquire (); |
||||||
|
reverb->set_bypass (!nValue); |
||||||
|
m_ReverbSpinLock.Release (); |
||||||
|
break; |
||||||
|
|
||||||
|
case ParameterReverbSize: |
||||||
|
m_ReverbSpinLock.Acquire (); |
||||||
|
reverb->size (nValue / 99.0); |
||||||
|
m_ReverbSpinLock.Release (); |
||||||
|
break; |
||||||
|
|
||||||
|
case ParameterReverbHighDamp: |
||||||
|
m_ReverbSpinLock.Acquire (); |
||||||
|
reverb->hidamp (nValue / 99.0); |
||||||
|
m_ReverbSpinLock.Release (); |
||||||
|
break; |
||||||
|
|
||||||
|
case ParameterReverbLowDamp: |
||||||
|
m_ReverbSpinLock.Acquire (); |
||||||
|
reverb->lodamp (nValue / 99.0); |
||||||
|
m_ReverbSpinLock.Release (); |
||||||
|
break; |
||||||
|
|
||||||
|
case ParameterReverbLowPass: |
||||||
|
m_ReverbSpinLock.Acquire (); |
||||||
|
reverb->lowpass (nValue / 99.0); |
||||||
|
m_ReverbSpinLock.Release (); |
||||||
|
break; |
||||||
|
|
||||||
|
case ParameterReverbDiffusion: |
||||||
|
m_ReverbSpinLock.Acquire (); |
||||||
|
reverb->diffusion (nValue / 99.0); |
||||||
|
m_ReverbSpinLock.Release (); |
||||||
|
break; |
||||||
|
|
||||||
|
case ParameterReverbLevel: |
||||||
|
m_ReverbSpinLock.Acquire (); |
||||||
|
reverb->level (nValue / 99.0); |
||||||
|
m_ReverbSpinLock.Release (); |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
assert (0); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int CMiniDexed::GetParameter (TParameter Parameter) |
||||||
|
{ |
||||||
|
assert (Parameter < ParameterUnknown); |
||||||
|
return m_nParameter[Parameter]; |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nTG) |
||||||
|
{ |
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
|
||||||
|
switch (Parameter) |
||||||
|
{ |
||||||
|
case TGParameterVoiceBank: BankSelectLSB (nValue, nTG); break; |
||||||
|
case TGParameterProgram: ProgramChange (nValue, nTG); break; |
||||||
|
case TGParameterVolume: SetVolume (nValue, nTG); break; |
||||||
|
case TGParameterPan: SetPan (nValue, nTG); break; |
||||||
|
case TGParameterMasterTune: SetMasterTune (nValue, nTG); break; |
||||||
|
|
||||||
|
case TGParameterMIDIChannel: |
||||||
|
assert (0 <= nValue && nValue <= 255); |
||||||
|
SetMIDIChannel ((uint8_t) nValue, nTG); |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
assert (0); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) |
||||||
|
{ |
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
|
||||||
|
switch (Parameter) |
||||||
|
{ |
||||||
|
case TGParameterVoiceBank: return m_nVoiceBankID[nTG]; |
||||||
|
case TGParameterProgram: return m_nProgram[nTG]; |
||||||
|
case TGParameterVolume: return m_nVolume[nTG]; |
||||||
|
case TGParameterPan: return m_nPan[nTG]; |
||||||
|
case TGParameterMasterTune: return m_nMasterTune[nTG]; |
||||||
|
case TGParameterMIDIChannel: return m_nMIDIChannel[nTG]; |
||||||
|
|
||||||
|
default: |
||||||
|
assert (0); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void CMiniDexed::SetVoiceParameter (uint8_t uchOffset, uint8_t uchValue, unsigned nOP, unsigned nTG) |
||||||
|
{ |
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
assert (m_pTG[nTG]); |
||||||
|
assert (nOP <= 6); |
||||||
|
|
||||||
|
if (nOP < 6) |
||||||
|
{ |
||||||
|
nOP = 5 - nOP; // OPs are in reverse order |
||||||
|
} |
||||||
|
|
||||||
|
uchOffset += nOP * 21; |
||||||
|
assert (uchOffset < 156); |
||||||
|
|
||||||
|
m_pTG[nTG]->setVoiceDataElement (uchOffset, uchValue); |
||||||
|
} |
||||||
|
|
||||||
|
uint8_t CMiniDexed::GetVoiceParameter (uint8_t uchOffset, unsigned nOP, unsigned nTG) |
||||||
|
{ |
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
assert (m_pTG[nTG]); |
||||||
|
assert (nOP <= 6); |
||||||
|
|
||||||
|
if (nOP < 6) |
||||||
|
{ |
||||||
|
nOP = 5 - nOP; // OPs are in reverse order |
||||||
|
} |
||||||
|
|
||||||
|
uchOffset += nOP * 21; |
||||||
|
assert (uchOffset < 156); |
||||||
|
|
||||||
|
return m_pTG[nTG]->getVoiceDataElement (uchOffset); |
||||||
|
} |
||||||
|
|
||||||
|
std::string CMiniDexed::GetVoiceName (unsigned nTG) |
||||||
|
{ |
||||||
|
char VoiceName[11]; |
||||||
|
memset (VoiceName, 0, sizeof VoiceName); |
||||||
|
|
||||||
|
assert (nTG < CConfig::ToneGenerators); |
||||||
|
assert (m_pTG[nTG]); |
||||||
|
m_pTG[nTG]->setName (VoiceName); |
||||||
|
|
||||||
|
std::string Result (VoiceName); |
||||||
|
|
||||||
|
return Result; |
||||||
|
} |
||||||
|
|
||||||
|
#ifndef ARM_ALLOW_MULTI_CORE |
||||||
|
|
||||||
|
void CMiniDexed::ProcessSound (void) |
||||||
|
{ |
||||||
|
assert (m_pSoundDevice); |
||||||
|
|
||||||
|
unsigned nFrames = m_nQueueSizeFrames - m_pSoundDevice->GetQueueFramesAvail (); |
||||||
|
if (nFrames >= m_nQueueSizeFrames/2) |
||||||
|
{ |
||||||
|
if (m_bProfileEnabled) |
||||||
|
{ |
||||||
|
m_GetChunkTimer.Start (); |
||||||
|
} |
||||||
|
|
||||||
|
//int16_t SampleBuffer[nFrames]; // TODO float->int |
||||||
|
m_pTG[0]->getSamples (SampleBuffer, nFrames); |
||||||
|
|
||||||
|
if ( m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer) |
||||||
|
!= (int) sizeof SampleBuffer) |
||||||
|
{ |
||||||
|
LOGERR ("Sound data dropped"); |
||||||
|
} |
||||||
|
|
||||||
|
if (m_bProfileEnabled) |
||||||
|
{ |
||||||
|
m_GetChunkTimer.Stop (); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#else // #ifdef ARM_ALLOW_MULTI_CORE |
||||||
|
|
||||||
|
void CMiniDexed::ProcessSound (void) |
||||||
|
{ |
||||||
|
assert (m_pSoundDevice); |
||||||
|
|
||||||
|
unsigned nFrames = m_nQueueSizeFrames - m_pSoundDevice->GetQueueFramesAvail (); |
||||||
|
if (nFrames >= m_nQueueSizeFrames/2) |
||||||
|
{ |
||||||
|
if (m_bProfileEnabled) |
||||||
|
{ |
||||||
|
m_GetChunkTimer.Start (); |
||||||
|
} |
||||||
|
|
||||||
|
m_nFramesToProcess = nFrames; |
||||||
|
|
||||||
|
// kick secondary cores |
||||||
|
for (unsigned nCore = 2; nCore < CORES; nCore++) |
||||||
|
{ |
||||||
|
assert (m_CoreStatus[nCore] == CoreStatusIdle); |
||||||
|
m_CoreStatus[nCore] = CoreStatusBusy; |
||||||
|
} |
||||||
|
|
||||||
|
// process the TGs assigned to core 1 |
||||||
|
assert (nFrames <= CConfig::MaxChunkSize); |
||||||
|
for (unsigned i = 0; i < CConfig::TGsCore1; i++) |
||||||
|
{ |
||||||
|
assert (m_pTG[i]); |
||||||
|
m_pTG[i]->getSamples (m_OutputLevel[i], nFrames); |
||||||
|
} |
||||||
|
|
||||||
|
// wait for cores 2 and 3 to complete their work |
||||||
|
for (unsigned nCore = 2; nCore < CORES; nCore++) |
||||||
|
{ |
||||||
|
while (m_CoreStatus[nCore] != CoreStatusIdle) |
||||||
|
{ |
||||||
|
// just wait |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// |
||||||
|
// Audio signal path after tone generators starts here |
||||||
|
// |
||||||
|
|
||||||
|
// now mix the output of all TGs |
||||||
|
float32_t SampleBuffer[2][nFrames]; |
||||||
|
uint8_t indexL=0, indexR=1; |
||||||
|
|
||||||
|
if (m_bChannelsSwapped) |
||||||
|
{ |
||||||
|
indexL=1; |
||||||
|
indexR=0; |
||||||
|
} |
||||||
|
|
||||||
|
// init left sum output |
||||||
|
assert (SampleBuffer[0]!=NULL); |
||||||
|
arm_fill_f32(0.0, SampleBuffer[0], nFrames); |
||||||
|
// init right sum output |
||||||
|
assert (SampleBuffer[1]!=NULL); |
||||||
|
arm_fill_f32(0.0, SampleBuffer[1], nFrames); |
||||||
|
|
||||||
|
assert (CConfig::ToneGenerators == 8); |
||||||
|
|
||||||
|
// BEGIN stereo panorama |
||||||
|
for (uint8_t i = 0; i < CConfig::ToneGenerators; i++) |
||||||
|
{ |
||||||
|
float32_t tmpBuffer[nFrames]; |
||||||
|
|
||||||
|
m_PanoramaSpinLock.Acquire (); |
||||||
|
// calculate left panorama of this TG |
||||||
|
arm_scale_f32(m_OutputLevel[i], 1.0f-pan_float[i], tmpBuffer, nFrames); |
||||||
|
// add left panorama output of this TG to sum output |
||||||
|
arm_add_f32(SampleBuffer[indexL], tmpBuffer, SampleBuffer[indexL], nFrames); |
||||||
|
|
||||||
|
// calculate right panorama of this TG |
||||||
|
arm_scale_f32(m_OutputLevel[i], pan_float[i], tmpBuffer, nFrames); |
||||||
|
// add right panaorama output of this TG to sum output |
||||||
|
arm_add_f32(SampleBuffer[indexR], tmpBuffer, SampleBuffer[indexR], nFrames); |
||||||
|
|
||||||
|
m_PanoramaSpinLock.Release (); |
||||||
|
} |
||||||
|
// END stereo panorama |
||||||
|
|
||||||
|
// BEGIN adding reverb |
||||||
|
if (m_nParameter[ParameterReverbEnable]) |
||||||
|
{ |
||||||
|
float32_t ReverbBuffer[2][nFrames]; |
||||||
|
|
||||||
|
m_ReverbSpinLock.Acquire (); |
||||||
|
reverb->doReverb(SampleBuffer[indexL],SampleBuffer[indexR],ReverbBuffer[0], ReverbBuffer[1],nFrames); |
||||||
|
m_ReverbSpinLock.Release (); |
||||||
|
|
||||||
|
// scale down and add left reverb buffer by reverb level |
||||||
|
arm_scale_f32(ReverbBuffer[0], reverb->get_level(), ReverbBuffer[0], nFrames); |
||||||
|
arm_add_f32(SampleBuffer[indexL], ReverbBuffer[0], SampleBuffer[indexL], nFrames); |
||||||
|
// scale down and add right reverb buffer by reverb level |
||||||
|
arm_scale_f32(ReverbBuffer[1], reverb->get_level(), ReverbBuffer[1], nFrames); |
||||||
|
arm_add_f32(SampleBuffer[indexR], ReverbBuffer[1], SampleBuffer[indexR], nFrames); |
||||||
|
} |
||||||
|
// END adding reverb |
||||||
|
|
||||||
|
// Convert dual float array (left, right) to single int16 array (left/right) |
||||||
|
float32_t tmp_float[nFrames*2]; |
||||||
|
int16_t tmp_int[nFrames*2]; |
||||||
|
for(uint16_t i=0; i<nFrames;i++) |
||||||
|
{ |
||||||
|
tmp_float[i*2]=SampleBuffer[indexL][i]; |
||||||
|
tmp_float[(i*2)+1]=SampleBuffer[indexR][i]; |
||||||
|
} |
||||||
|
arm_float_to_q15(tmp_float,tmp_int,nFrames*2); |
||||||
|
|
||||||
|
if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) |
||||||
|
{ |
||||||
|
LOGERR ("Sound data dropped"); |
||||||
|
} |
||||||
|
|
||||||
|
if (m_bProfileEnabled) |
||||||
|
{ |
||||||
|
m_GetChunkTimer.Stop (); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#endif |
||||||
|
|
||||||
|
bool CMiniDexed::SavePerformance (void) |
||||||
|
{ |
||||||
|
for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) |
||||||
|
{ |
||||||
|
m_PerformanceConfig.SetBankNumber (m_nVoiceBankID[nTG], nTG); |
||||||
|
m_PerformanceConfig.SetVoiceNumber (m_nProgram[nTG], nTG); |
||||||
|
m_PerformanceConfig.SetMIDIChannel (m_nMIDIChannel[nTG], nTG); |
||||||
|
m_PerformanceConfig.SetVolume (m_nVolume[nTG], nTG); |
||||||
|
m_PerformanceConfig.SetPan (m_nPan[nTG], nTG); |
||||||
|
m_PerformanceConfig.SetDetune (m_nMasterTune[nTG], nTG); |
||||||
|
|
||||||
|
m_PerformanceConfig.SetNoteLimitLow (m_nNoteLimitLow[nTG], nTG); |
||||||
|
m_PerformanceConfig.SetNoteLimitHigh (m_nNoteLimitHigh[nTG], nTG); |
||||||
|
m_PerformanceConfig.SetNoteShift (m_nNoteShift[nTG], nTG); |
||||||
|
} |
||||||
|
|
||||||
|
m_PerformanceConfig.SetCompressorEnable (!!m_nParameter[ParameterCompressorEnable]); |
||||||
|
m_PerformanceConfig.SetReverbEnable (!!m_nParameter[ParameterReverbEnable]); |
||||||
|
m_PerformanceConfig.SetReverbSize (m_nParameter[ParameterReverbSize]); |
||||||
|
m_PerformanceConfig.SetReverbHighDamp (m_nParameter[ParameterReverbHighDamp]); |
||||||
|
m_PerformanceConfig.SetReverbLowDamp (m_nParameter[ParameterReverbLowDamp]); |
||||||
|
m_PerformanceConfig.SetReverbLowPass (m_nParameter[ParameterReverbLowPass]); |
||||||
|
m_PerformanceConfig.SetReverbDiffusion (m_nParameter[ParameterReverbDiffusion]); |
||||||
|
m_PerformanceConfig.SetReverbLevel (m_nParameter[ParameterReverbLevel]); |
||||||
|
|
||||||
|
return m_PerformanceConfig.Save (); |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
// Taken from https://github.com/manicken/Audio/tree/templateMixer
|
||||||
|
// Adapted for MiniDexed by Holger Wirtz <dcoredump@googlemail.com>
|
||||||
|
|
||||||
|
/* Audio Library for Teensy 3.X
|
||||||
|
* Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com |
||||||
|
* |
||||||
|
* Development of this audio library was funded by PJRC.COM, LLC by sales of |
||||||
|
* Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop |
||||||
|
* open source software by purchasing Teensy or other PJRC products. |
||||||
|
* |
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
* of this software and associated documentation files (the "Software"), to deal |
||||||
|
* in the Software without restriction, including without limitation the rights |
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
* copies of the Software, and to permit persons to whom the Software is |
||||||
|
* furnished to do so, subject to the following conditions: |
||||||
|
* |
||||||
|
* The above copyright notice, development funding notice, and this permission |
||||||
|
* notice shall be included in all copies or substantial portions of the Software. |
||||||
|
* |
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
* THE SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <cstdlib> |
||||||
|
#include <stdint.h> |
||||||
|
#include <assert.h> |
||||||
|
#include "arm_math.h" |
||||||
|
#include "mixer.h" |
||||||
|
|
||||||
|
template <int NN> void AudioMixer<NN>::gain(uint8_t channel, float32_t gain) |
||||||
|
{ |
||||||
|
if (channel >= NN) return; |
||||||
|
|
||||||
|
if (gain > MAX_GAIN) |
||||||
|
gain = MAX_GAIN; |
||||||
|
else if (gain < MIN_GAIN) |
||||||
|
gain = MIN_GAIN; |
||||||
|
multiplier[channel] = gain; |
||||||
|
} |
||||||
|
|
||||||
|
template <int NN> void AudioMixer<NN>::gain(float32_t gain) |
||||||
|
{ |
||||||
|
for (uint8_t i = 0; i < NN; i++) |
||||||
|
{ |
||||||
|
if (gain > MAX_GAIN) |
||||||
|
gain = MAX_GAIN; |
||||||
|
else if (gain < MIN_GAIN) |
||||||
|
gain = MIN_GAIN; |
||||||
|
multiplier[i] = gain; |
||||||
|
}
|
||||||
|
} |
||||||
|
|
||||||
|
template <int NN> void AudioMixer<NN>::doAddMix(uint8_t channel, float32_t* in, float32_t* out, uint16_t len) |
||||||
|
{ |
||||||
|
float32_t* tmp=malloc(sizeof(float32_t)*len); |
||||||
|
|
||||||
|
assert(tmp!=NULL); |
||||||
|
|
||||||
|
arm_scale_f32(in,multiplier[channel],tmp,len); |
||||||
|
arm_add_f32(out, tmp, out, len); |
||||||
|
|
||||||
|
free(tmp); |
||||||
|
} |
||||||
|
|
||||||
|
template <int NN> void AudioStereoMixer<NN>::doAddMix(uint8_t channel, float32_t* in[], float32_t* out[], uint16_t len) |
||||||
|
{ |
||||||
|
float32_t* tmp=malloc(sizeof(float32_t)*len); |
||||||
|
|
||||||
|
assert(tmp!=NULL); |
||||||
|
|
||||||
|
// panorama
|
||||||
|
for(uint16_t i=0;i<len;i++) |
||||||
|
{ |
||||||
|
// left
|
||||||
|
arm_scale_f32(in+(i*2),multiplier[channel],tmp+(i*2),len); |
||||||
|
arm_add_f32(out(i*2), tmp(i*2), out(i*2), len*2); |
||||||
|
// right
|
||||||
|
} |
||||||
|
|
||||||
|
free(tmp); |
||||||
|
} |
@ -0,0 +1,79 @@ |
|||||||
|
// Taken from https://github.com/manicken/Audio/tree/templateMixer
|
||||||
|
// Adapted for MiniDexed by Holger Wirtz <dcoredump@googlemail.com>
|
||||||
|
|
||||||
|
/* Audio Library for Teensy 3.X
|
||||||
|
* Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com |
||||||
|
* |
||||||
|
* Development of this audio library was funded by PJRC.COM, LLC by sales of |
||||||
|
* Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop |
||||||
|
* open source software by purchasing Teensy or other PJRC products. |
||||||
|
* |
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
* of this software and associated documentation files (the "Software"), to deal |
||||||
|
* in the Software without restriction, including without limitation the rights |
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
* copies of the Software, and to permit persons to whom the Software is |
||||||
|
* furnished to do so, subject to the following conditions: |
||||||
|
* |
||||||
|
* The above copyright notice, development funding notice, and this permission |
||||||
|
* notice shall be included in all copies or substantial portions of the Software. |
||||||
|
* |
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
* THE SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef template_mixer_h_ |
||||||
|
#define template_mixer_h_ |
||||||
|
|
||||||
|
#include "arm_math.h" |
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
#define UNITYGAIN 1.0f |
||||||
|
#define MAX_GAIN 1.0f |
||||||
|
#define MIN_GAIN 0.0f |
||||||
|
|
||||||
|
template <int NN> class AudioMixer |
||||||
|
{ |
||||||
|
public: |
||||||
|
AudioMixer(void) |
||||||
|
{ |
||||||
|
for (uint8_t i=0; i<NN; i++) |
||||||
|
multiplier[i] = UNITYGAIN; |
||||||
|
}
|
||||||
|
void doAddMix(uint8_t channel, float32_t* in, float32_t* out, uint16_t len); |
||||||
|
/**
|
||||||
|
* this sets the individual gains |
||||||
|
* @param channel |
||||||
|
* @param gain |
||||||
|
*/ |
||||||
|
void gain(uint8_t channel, float32_t gain); |
||||||
|
/**
|
||||||
|
* set all channels to specified gain |
||||||
|
* @param gain |
||||||
|
*/ |
||||||
|
void gain(float32_t gain); |
||||||
|
|
||||||
|
protected: |
||||||
|
float32_t multiplier[NN]; |
||||||
|
}; |
||||||
|
|
||||||
|
template <int NN> class AudioStereoMixer : public AudioMixer<NN> |
||||||
|
{ |
||||||
|
public: |
||||||
|
AudioStereoMixer(void) |
||||||
|
{ |
||||||
|
AudioMixer<NN>(); |
||||||
|
for (uint8_t i=0; i<NN; i++) |
||||||
|
panorama[i] = 0.0; |
||||||
|
} |
||||||
|
void doAddMix(uint8_t channel, float32_t* in[], float32_t* out[], uint16_t len); |
||||||
|
protected: |
||||||
|
float32_t panorama[NN]; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
Loading…
Reference in new issue