From 98b5274cf3889577d7a402abc5ddce529e233350 Mon Sep 17 00:00:00 2001 From: Kevin <68612569+diyelectromusic@users.noreply.github.com> Date: Sat, 29 Jun 2024 22:44:52 +0100 Subject: [PATCH] Add support for 8 channel I2S mono audio output on RPi 5 (#657) * First implementation of using four PCM5102 I2S sound devices for 8-channel mono sound output on a Raspberry Pi 5. Requires latest develop branch of circle. * Update to required develop branch of circle * Adjusted default chunk sizes to correctly support number of channels. * Update queue size as per Rene's suggestion. --------- Co-authored-by: probonopd --- src/config.cpp | 18 +++- src/config.h | 2 + src/minidexed.cpp | 222 ++++++++++++++++++++++++++++++---------------- src/minidexed.h | 1 + submod.sh | 2 +- 5 files changed, 167 insertions(+), 78 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index d456c58..ea6bde3 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -41,15 +41,22 @@ void CConfig::Load (void) m_SoundDevice = m_Properties.GetString ("SoundDevice", "pwm"); m_nSampleRate = m_Properties.GetNumber ("SampleRate", 48000); + m_bQuadDAC8Chan = m_Properties.GetNumber ("QuadDAC8Chan", 0) != 0; + if (m_SoundDevice == "hdmi") { + m_nChunkSize = m_Properties.GetNumber ("ChunkSize", 384*6); + } + else + { #ifdef ARM_ALLOW_MULTI_CORE - m_nChunkSize = m_Properties.GetNumber ("ChunkSize", m_SoundDevice == "hdmi" ? 384*6 : 256); + m_nChunkSize = m_Properties.GetNumber ("ChunkSize", m_bQuadDAC8Chan ? 1024 : 256); // 128 per channel #else - m_nChunkSize = m_Properties.GetNumber ("ChunkSize", m_SoundDevice == "hdmi" ? 384*6 : 1024); + m_nChunkSize = m_Properties.GetNumber ("ChunkSize", 1024); #endif + } m_nDACI2CAddress = m_Properties.GetNumber ("DACI2CAddress", 0); m_bChannelsSwapped = m_Properties.GetNumber ("ChannelsSwapped", 0) != 0; - unsigned newEngineType = m_Properties.GetNumber ("EngineType", 1); + unsigned newEngineType = m_Properties.GetNumber ("EngineType", 1); if (newEngineType == 2) { m_EngineType = MKI; } else if (newEngineType == 3) { @@ -243,6 +250,11 @@ bool CConfig::GetExpandPCAcrossBanks (void) const return m_bExpandPCAcrossBanks; } +bool CConfig::GetQuadDAC8Chan (void) const +{ + return m_bQuadDAC8Chan; +} + bool CConfig::GetLCDEnabled (void) const { return m_bLCDEnabled; diff --git a/src/config.h b/src/config.h index 71bb5ad..5944dd2 100644 --- a/src/config.h +++ b/src/config.h @@ -87,6 +87,7 @@ public: bool GetMIDIAutoVoiceDumpOnPC (void) const; // false if not specified bool GetHeaderlessSysExVoices (void) const; // false if not specified bool GetExpandPCAcrossBanks (void) const; // true if not specified + bool GetQuadDAC8Chan (void) const; // false if not specified // HD44780 LCD // GPIO pin numbers are chip numbers, not header positions @@ -208,6 +209,7 @@ private: bool m_bMIDIAutoVoiceDumpOnPC; bool m_bHeaderlessSysExVoices; bool m_bExpandPCAcrossBanks; + bool m_bQuadDAC8Chan; bool m_bLCDEnabled; unsigned m_nLCDPinEnable; diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 02d8fd6..429fddc 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -42,6 +42,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_PCKeyboard (this, pConfig, &m_UI), m_SerialMIDI (this, pInterrupt, pConfig, &m_UI), m_bUseSerial (false), + m_bQuadDAC8Chan (false), m_pSoundDevice (0), m_bChannelsSwapped (pConfig->GetChannelsSwapped ()), #ifdef ARM_ALLOW_MULTI_CORE @@ -125,10 +126,26 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, if (strcmp (pDeviceName, "i2s") == 0) { LOGNOTE ("I2S mode"); - - m_pSoundDevice = new CI2SSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), - pConfig->GetChunkSize (), false, - pI2CMaster, pConfig->GetDACI2CAddress ()); +#if RASPPI==5 + // Quad DAC 8-channel mono only an option for RPI 5 + m_bQuadDAC8Chan = pConfig->GetQuadDAC8Chan (); +#endif + if (m_bQuadDAC8Chan) { + LOGNOTE ("Configured for Quad DAC 8-channel Mono audio"); + m_pSoundDevice = new CI2SSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), + pConfig->GetChunkSize (), false, + pI2CMaster, pConfig->GetDACI2CAddress (), + CI2SSoundBaseDevice::DeviceModeTXOnly, + 8); // 8 channels - L+R x4 across 4 I2S lanes + } + else + { + m_pSoundDevice = new CI2SSoundBaseDevice (pInterrupt, pConfig->GetSampleRate (), + pConfig->GetChunkSize (), false, + pI2CMaster, pConfig->GetDACI2CAddress (), + CI2SSoundBaseDevice::DeviceModeTXOnly, + 2); // 2 channels - L+R + } } else if (strcmp (pDeviceName, "hdmi") == 0) { @@ -251,18 +268,30 @@ bool CMiniDexed::Initialize (void) } // setup and start the sound device - if (!m_pSoundDevice->AllocateQueueFrames (m_pConfig->GetChunkSize ())) + int Channels = 1; // 16-bit Mono +#ifdef ARM_ALLOW_MULTI_CORE + if (m_bQuadDAC8Chan) + { + Channels = 8; // 16-bit 8-channel mono + } + else + { + Channels = 2; // 16-bit Stereo + } +#endif + // Need 2 x ChunkSize / Channel queue frames as the audio driver uses + // two DMA channels each of ChunkSize and one single single frame + // contains a sample for each of all the channels. + // + // See discussion here: https://github.com/rsta2/circle/discussions/453 + if (!m_pSoundDevice->AllocateQueueFrames (2 * m_pConfig->GetChunkSize () / Channels)) { 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_pSoundDevice->SetWriteFormat (SoundFormatSigned16, Channels); m_nQueueSizeFrames = m_pSoundDevice->GetQueueSizeFrames (); @@ -1128,85 +1157,130 @@ void CMiniDexed::ProcessSound (void) assert (CConfig::ToneGenerators == 8); - uint8_t indexL=0, indexR=1; - - // BEGIN TG mixing - float32_t tmp_float[nFrames*2]; - int16_t tmp_int[nFrames*2]; + if (m_bQuadDAC8Chan) { + // No mixing is performed by MiniDexed, sound is output in 8 channels. + // Note: one TG per audio channel; output=mono; no processing. + const int Channels = 8; // One TG per channel + float32_t tmp_float[nFrames*Channels]; + int16_t tmp_int[nFrames*Channels]; - if(nMasterVolume > 0.0) - { - for (uint8_t i = 0; i < CConfig::ToneGenerators; i++) + if(nMasterVolume > 0.0) { - tg_mixer->doAddMix(i,m_OutputLevel[i]); - reverb_send_mixer->doAddMix(i,m_OutputLevel[i]); + // Convert dual float array (8 chan) to single int16 array (8 chan) + for(uint16_t i=0; i0.0 && nMasterVolume <1.0) + { + tmp_float[(i*Channels)+tg]=m_OutputLevel[tg][i] * nMasterVolume; + } + else if(nMasterVolume == 1.0) + { + tmp_float[(i*Channels)+tg]=m_OutputLevel[tg][i]; + } + } + } + arm_float_to_q15(tmp_float,tmp_int,nFrames*Channels); } - // END TG mixing - - // BEGIN create SampleBuffer for holding audio data - float32_t SampleBuffer[2][nFrames]; - // END create SampleBuffer for holding audio data - - // get the mix of all TGs - tg_mixer->getMix(SampleBuffer[indexL], SampleBuffer[indexR]); - - // BEGIN adding reverb - if (m_nParameter[ParameterReverbEnable]) + else { - float32_t ReverbBuffer[2][nFrames]; - float32_t ReverbSendBuffer[2][nFrames]; - - arm_fill_f32(0.0f, ReverbBuffer[indexL], nFrames); - arm_fill_f32(0.0f, ReverbBuffer[indexR], nFrames); - arm_fill_f32(0.0f, ReverbSendBuffer[indexR], nFrames); - arm_fill_f32(0.0f, ReverbSendBuffer[indexL], nFrames); - - m_ReverbSpinLock.Acquire (); - - reverb_send_mixer->getMix(ReverbSendBuffer[indexL], ReverbSendBuffer[indexR]); - reverb->doReverb(ReverbSendBuffer[indexL],ReverbSendBuffer[indexR],ReverbBuffer[indexL], ReverbBuffer[indexR],nFrames); - - // scale down and add left reverb buffer by reverb level - arm_scale_f32(ReverbBuffer[indexL], reverb->get_level(), ReverbBuffer[indexL], nFrames); - arm_add_f32(SampleBuffer[indexL], ReverbBuffer[indexL], SampleBuffer[indexL], nFrames); - // scale down and add right reverb buffer by reverb level - arm_scale_f32(ReverbBuffer[indexR], reverb->get_level(), ReverbBuffer[indexR], nFrames); - arm_add_f32(SampleBuffer[indexR], ReverbBuffer[indexR], SampleBuffer[indexR], nFrames); - - m_ReverbSpinLock.Release (); + arm_fill_q15(0, tmp_int, nFrames*Channels); } - // END adding reverb - - // swap stereo channels if needed prior to writing back out - if (m_bChannelsSwapped) + + if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) { - indexL=1; - indexR=0; + LOGERR ("Sound data dropped"); } + } + else + { + // Mix everything down to stereo + uint8_t indexL=0, indexR=1; + + // BEGIN TG mixing + float32_t tmp_float[nFrames*2]; + int16_t tmp_int[nFrames*2]; - // Convert dual float array (left, right) to single int16 array (left/right) - for(uint16_t i=0; i 0.0) { - if(nMasterVolume >0.0 && nMasterVolume <1.0) + for (uint8_t i = 0; i < CConfig::ToneGenerators; i++) { - tmp_float[i*2]=SampleBuffer[indexL][i] * nMasterVolume; - tmp_float[(i*2)+1]=SampleBuffer[indexR][i] * nMasterVolume; + tg_mixer->doAddMix(i,m_OutputLevel[i]); + reverb_send_mixer->doAddMix(i,m_OutputLevel[i]); } - else if(nMasterVolume == 1.0) + // END TG mixing + + // BEGIN create SampleBuffer for holding audio data + float32_t SampleBuffer[2][nFrames]; + // END create SampleBuffer for holding audio data + + // get the mix of all TGs + tg_mixer->getMix(SampleBuffer[indexL], SampleBuffer[indexR]); + + // BEGIN adding reverb + if (m_nParameter[ParameterReverbEnable]) { - tmp_float[i*2]=SampleBuffer[indexL][i]; - tmp_float[(i*2)+1]=SampleBuffer[indexR][i]; + float32_t ReverbBuffer[2][nFrames]; + float32_t ReverbSendBuffer[2][nFrames]; + + arm_fill_f32(0.0f, ReverbBuffer[indexL], nFrames); + arm_fill_f32(0.0f, ReverbBuffer[indexR], nFrames); + arm_fill_f32(0.0f, ReverbSendBuffer[indexR], nFrames); + arm_fill_f32(0.0f, ReverbSendBuffer[indexL], nFrames); + + m_ReverbSpinLock.Acquire (); + + reverb_send_mixer->getMix(ReverbSendBuffer[indexL], ReverbSendBuffer[indexR]); + reverb->doReverb(ReverbSendBuffer[indexL],ReverbSendBuffer[indexR],ReverbBuffer[indexL], ReverbBuffer[indexR],nFrames); + + // scale down and add left reverb buffer by reverb level + arm_scale_f32(ReverbBuffer[indexL], reverb->get_level(), ReverbBuffer[indexL], nFrames); + arm_add_f32(SampleBuffer[indexL], ReverbBuffer[indexL], SampleBuffer[indexL], nFrames); + // scale down and add right reverb buffer by reverb level + arm_scale_f32(ReverbBuffer[indexR], reverb->get_level(), ReverbBuffer[indexR], nFrames); + arm_add_f32(SampleBuffer[indexR], ReverbBuffer[indexR], SampleBuffer[indexR], nFrames); + + m_ReverbSpinLock.Release (); } + // END adding reverb + + // swap stereo channels if needed prior to writing back out + if (m_bChannelsSwapped) + { + indexL=1; + indexR=0; + } + + // Convert dual float array (left, right) to single int16 array (left/right) + for(uint16_t i=0; i0.0 && nMasterVolume <1.0) + { + tmp_float[i*2]=SampleBuffer[indexL][i] * nMasterVolume; + tmp_float[(i*2)+1]=SampleBuffer[indexR][i] * nMasterVolume; + } + else if(nMasterVolume == 1.0) + { + 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); + } + else + { + arm_fill_q15(0, tmp_int, nFrames * 2); } - arm_float_to_q15(tmp_float,tmp_int,nFrames*2); - } - else - arm_fill_q15(0, tmp_int, nFrames * 2); - if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) - { - LOGERR ("Sound data dropped"); - } + if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) + { + LOGERR ("Sound data dropped"); + } + } // End of Stereo mixing if (m_bProfileEnabled) { diff --git a/src/minidexed.h b/src/minidexed.h index 8ca74c8..6e5e012 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -298,6 +298,7 @@ private: CPCKeyboard m_PCKeyboard; CSerialMIDIDevice m_SerialMIDI; bool m_bUseSerial; + bool m_bQuadDAC8Chan; CSoundBaseDevice *m_pSoundDevice; bool m_bChannelsSwapped; diff --git a/submod.sh b/submod.sh index c13dca3..f9524a3 100755 --- a/submod.sh +++ b/submod.sh @@ -12,7 +12,7 @@ cd - # # Optional update submodules explicitly cd circle-stdlib/libs/circle -git checkout 4155f43 +git checkout fff3764 cd - cd circle-stdlib/libs/circle-newlib #git checkout develop