diff --git a/src/CFileDevice.hpp b/src/CFileDevice.hpp index ccd6cd6..0cd4cf4 100644 --- a/src/CFileDevice.hpp +++ b/src/CFileDevice.hpp @@ -1,3 +1,24 @@ +// 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 . + +// +// CFileDevice.h +// +// Device implementation that writes into a file. +// This device is a singleton dedicated to allow the Circle CLogger class to output log event into the file instead of the internal buffer. +// Author: Vincent Gauché +// + #pragma once #include "extra_features.h" @@ -14,8 +35,8 @@ class CFileDevice : public CDevice DISALLOW_COPY_AND_ASSIGN(CFileDevice); private: - CFileDevice() - : m_file(new FIL()) + CFileDevice() : + m_file(new FIL()) { FRESULT res = f_open(this->m_file, "SD:/debuglog.txt", FA_WRITE | FA_CREATE_ALWAYS); assert(res == FR_OK); @@ -42,9 +63,12 @@ public: virtual int Write(const void* pBuffer, size_t nCount) override { - UINT nb; - f_write(this->m_file, pBuffer, nCount, &nb); - f_sync(this->m_file); + UINT nb = 0; + if(nCount > 0) + { + f_write(this->m_file, pBuffer, nCount, &nb); + f_sync(this->m_file); + } return (int)nb; } diff --git a/src/extra_features.h b/src/extra_features.h index d982388..a081577 100644 --- a/src/extra_features.h +++ b/src/extra_features.h @@ -15,6 +15,7 @@ // extra_features.h // // Header file that centralizes MACROS to enable / disable extra features +// Author: Vincent Gauché // #pragma once diff --git a/src/fx.h b/src/fx.h index 56ca858..bca0408 100644 --- a/src/fx.h +++ b/src/fx.h @@ -15,6 +15,7 @@ // fx.h // // Base classes for Stereo audio effects proposed in the context of the MiniDexed project +// Author: Vincent Gauché // #pragma once diff --git a/src/fx_base.h b/src/fx_base.h index 958e434..b57eb1c 100644 --- a/src/fx_base.h +++ b/src/fx_base.h @@ -1,3 +1,23 @@ +// 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 . + +// +// fx_base.h +// +// Base header file for the FX section of the MiniDexed project +// Author: Vincent Gauché +// + #pragma once #include "extra_features.h" diff --git a/src/fx_chorus.h b/src/fx_chorus.h index 03d0c8f..6a7d842 100644 --- a/src/fx_chorus.h +++ b/src/fx_chorus.h @@ -16,6 +16,7 @@ // // Stereo Chorus audio effects proposed in the context of the MiniDexed project // This implemelntation is based on the Chorus FX from the Rings Eurorack module from Mutable Instruments +// Ported by: Vincent Gauché // #pragma once diff --git a/src/fx_components.h b/src/fx_components.h index 1d23e87..1f62ae1 100644 --- a/src/fx_components.h +++ b/src/fx_components.h @@ -15,6 +15,7 @@ // fx_components.h // // Several tools and components used in the implemlentation of FX +// Quthor: Vincent Gauché // #pragma once diff --git a/src/fx_delay.h b/src/fx_delay.h index c81dee3..e4e5f1c 100644 --- a/src/fx_delay.h +++ b/src/fx_delay.h @@ -16,6 +16,7 @@ // fx_tape_delay.h // // Stereo Delay proposed in the context of the MiniDexed project +// Author: Vincent Gauché // #pragma once diff --git a/src/fx_diffuser.h b/src/fx_diffuser.h index 9f2ef44..a6fdfda 100644 --- a/src/fx_diffuser.h +++ b/src/fx_diffuser.h @@ -17,6 +17,7 @@ // // Stereo Diffuser proposed in the context of the MiniDexed project // It is adapted from the Diffuser that could be found on Cloud EuroRack module from Mutable Instrruments +// Ported by: Vincent Gauché // #pragma once diff --git a/src/fx_dry.h b/src/fx_dry.h index 0890f8a..42ea0ac 100644 --- a/src/fx_dry.h +++ b/src/fx_dry.h @@ -12,9 +12,10 @@ // along with this program. If not, see . // -// fx_tube.h +// fx_dry.h // -// Stereo Tube overdrive audio effects proposed in the context of the MiniDexed project +// An FX that does nothing but used to generalize the processing. +// Author: Vincent Gauché // #pragma once diff --git a/src/fx_engine.hpp b/src/fx_engine.hpp index 02f1166..4cef466 100644 --- a/src/fx_engine.hpp +++ b/src/fx_engine.hpp @@ -1,3 +1,24 @@ +// 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 . + +// +// fx_engine.h +// +// FX Engine used in some of the Mutable Instruments Eurorack modules. +// This version is ported for the MiniDexed project and brings some additional optimization to avoid unecessary multiplications. +// Ported by: Vincent Gauché +// + #pragma once #include diff --git a/src/fx_flanger.h b/src/fx_flanger.h index 86979a4..84b3fe3 100644 --- a/src/fx_flanger.h +++ b/src/fx_flanger.h @@ -15,6 +15,7 @@ // fx_flanger.h // // Stereo Flanger audio effects proposed in the context of the MiniDexed project +// Author: Vincent Gauché // #pragma once diff --git a/src/fx_orbitone.h b/src/fx_orbitone.h index fe2dd36..e901b68 100644 --- a/src/fx_orbitone.h +++ b/src/fx_orbitone.h @@ -16,6 +16,7 @@ // // Stereo Orbitone audio effects proposed in the context of the MiniDexed project // This audio effect is based on the Ensemble audio effect of the Rings Eurorack module by Mutable Instruments +// Ported by: Vincent Gauché // #pragma once diff --git a/src/fx_phaser.cpp b/src/fx_phaser.cpp index da34deb..eadbb77 100644 --- a/src/fx_phaser.cpp +++ b/src/fx_phaser.cpp @@ -72,8 +72,8 @@ void Phaser::reset() void Phaser::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) { - float32_t dL = this->dmin_ + (this->dmax_ - this->dmin_) * ((1.0f + this->lfo_[StereoChannels::Left ]->process()) / 2.0f); - float32_t dR = this->dmin_ + (this->dmax_ - this->dmin_) * ((1.0f + this->lfo_[StereoChannels::Right]->process()) / 2.0f); + float32_t dL = this->dmin_ + (this->dmax_ - this->dmin_) * this->lfo_[StereoChannels::Left ]->process(); + float32_t dR = this->dmin_ + (this->dmax_ - this->dmin_) * this->lfo_[StereoChannels::Right]->process(); float32_t sampleL = inL + this->feedback_ * this->z_[StereoChannels::Left ]; float32_t sampleR = inR + this->feedback_ * this->z_[StereoChannels::Right]; diff --git a/src/fx_phaser.h b/src/fx_phaser.h index 63712d4..537cdd4 100644 --- a/src/fx_phaser.h +++ b/src/fx_phaser.h @@ -15,6 +15,7 @@ // fx_phaser.h // // Stereo Phaser audio effects proposed in the context of the MiniDexed project +// Author: Vincent Gauché // #pragma once diff --git a/src/fx_pitch_shifter.h b/src/fx_pitch_shifter.h index 35ba366..18fb664 100644 --- a/src/fx_pitch_shifter.h +++ b/src/fx_pitch_shifter.h @@ -17,6 +17,7 @@ // // Stereo Pitch Shifter proposed in the context of the MiniDexed project // It is adapted from the Pitch Shifter that could be found on Cloud EuroRack module from Mutable Instrruments +// Ported by: Vincent Gauché // #pragma once diff --git a/src/fx_rack.h b/src/fx_rack.h index 2d3eac3..b018e7d 100644 --- a/src/fx_rack.h +++ b/src/fx_rack.h @@ -15,6 +15,7 @@ // fx_rack.h // // Rack of audio effects proposed in the context of the MiniDexed project +// Author: Vincent Gauché // #pragma once diff --git a/src/fx_reverberator.h b/src/fx_reverberator.h index c01cbff..2e9ac2f 100644 --- a/src/fx_reverberator.h +++ b/src/fx_reverberator.h @@ -17,6 +17,7 @@ // // Stereo Reverberator proposed in the context of the MiniDexed project // It is adapted from the Reverb that could be found on Cloud EuroRack module from Mutable Instrruments +// Ported by: Vincent Gauché // #pragma once diff --git a/src/fx_shimmer_helper.h b/src/fx_shimmer_helper.h index 20c42c9..378a7a9 100644 --- a/src/fx_shimmer_helper.h +++ b/src/fx_shimmer_helper.h @@ -1,3 +1,23 @@ +// 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 . + +// +// fx_shimmer_helper.h +// +// Helper class for the FX Shimmer FX that is ported from Mutable Instruments +// Ported by: Vincent Gauché +// + #pragma once #include "fx.h" diff --git a/src/fx_shimmer_reverb.h b/src/fx_shimmer_reverb.h index e0535e5..81e11eb 100644 --- a/src/fx_shimmer_reverb.h +++ b/src/fx_shimmer_reverb.h @@ -17,6 +17,7 @@ // // Stereo ShimmerReverb Reverb proposed in the context of the MiniDexed project // It is adapted from the ShimmerReverb Reverb that could be found on Cloud EuroRack module from Mutable Instrruments +// Ported by: Vincent Gauché // #pragma once diff --git a/src/fx_svf.h b/src/fx_svf.h index 3f8119b..edaea61 100644 --- a/src/fx_svf.h +++ b/src/fx_svf.h @@ -16,6 +16,7 @@ // fx_svf.h // // State Variable Filter used in Tape Delay +// Author: Vincent Gauché // #pragma once diff --git a/src/fx_tube.h b/src/fx_tube.h index b4cfdba..e749c23 100644 --- a/src/fx_tube.h +++ b/src/fx_tube.h @@ -15,6 +15,7 @@ // fx_tube.h // // Stereo Tube overdrive audio effects proposed in the context of the MiniDexed project +// Author: Vincent Gauché // #pragma once diff --git a/src/fx_unit.hpp b/src/fx_unit.hpp index 173a1c6..955569c 100644 --- a/src/fx_unit.hpp +++ b/src/fx_unit.hpp @@ -15,6 +15,7 @@ // fx_unit.h // // Unit of FX that handle enable and wet parameters +// Author: Vincent Gauché // #pragma once diff --git a/src/fx_unit2.hpp b/src/fx_unit2.hpp index ee5c85f..906bc15 100644 --- a/src/fx_unit2.hpp +++ b/src/fx_unit2.hpp @@ -15,6 +15,7 @@ // fx_unit2.h // // Unit of FX that handle the mute parameter +// Author: Vincent Gauché // #pragma once diff --git a/src/kernel.cpp b/src/kernel.cpp index a5d848a..4397cd9 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -22,8 +22,6 @@ #include #include -#include "CFileDevice.hpp" - LOGMODULE ("kernel"); CKernel *CKernel::s_pThis = 0; @@ -52,7 +50,6 @@ bool CKernel::Initialize (void) return FALSE; } - CFileDevice::UseMeForLogger(); mLogger.RegisterPanicHandler (PanicHandler); if (!m_GPIOManager.Initialize ()) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 465dc36..1a13925 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -152,7 +152,8 @@ CMiniDexed::CMiniDexed ( this->mixing_console_ = new Mixer(static_cast(pConfig->GetSampleRate()), CConfig::MaxChunkSize); for (uint8_t i = 0; i < CConfig::ToneGenerators; i++) { - this->mixing_console_->setInputSampleBuffer(i, m_OutputLevel[i]); + memset(this->m_OutputLevel[i], 0, CConfig::MaxChunkSize * sizeof(float32_t)); + this->mixing_console_->setInputSampleBuffer(i, this->m_OutputLevel[i]); } // Tube parameters @@ -277,10 +278,12 @@ bool CMiniDexed::Initialize (void) #if defined(MIXING_CONSOLE_ENABLE) // setup the mixer so that it remains identical to the initial version of the synth + this->mixing_console_->reset(); this->mixing_console_->setPan(i, this->m_nPan[i] / 127.0f); float32_t sendRev = this->m_nFXSendLevel[i][MixerOutput::FX_PlateReverb] / 99.0f; - this->mixing_console_->setSendLevel(i, MixerOutput::FX_PlateReverb, sendRev); + this->mixing_console_->setSendLevel(i, MixerOutput::FX_PlateReverb, 1.0f); this->mixing_console_->setSendLevel(i, MixerOutput::MainOutput, 1.0f - sendRev); + this->mixing_console_->setReturnLevel(MixerOutput::FX_PlateReverb, MixerOutput::MainOutput, sendRev); #elif defined(PLATE_REVERB_ENABLE) @@ -541,6 +544,10 @@ void CMiniDexed::SetVolume (unsigned nVolume, unsigned nTG) assert (m_pTG[nTG]); m_pTG[nTG]->setGain (nVolume / 127.0f); +#if defined(MIXING_CONSOLE_ENABLE) + this->mixing_console_->setChannelLevel(nTG, nVolume == 0 ? 0.0f : 1.0f); +#endif + m_UI.ParameterChanged (); } @@ -1780,6 +1787,8 @@ void CMiniDexed::ProcessSound (void) // // Audio signal path after tone generators starts here // + float32_t tmp_float[nFrames * 2]; + int16_t tmp_int[nFrames * 2]; #if defined(MIXING_CONSOLE_ENABLE) // // swap stereo channels if needed @@ -1792,17 +1801,12 @@ void CMiniDexed::ProcessSound (void) } // BEGIN TG mixing - float32_t tmp_float[nFrames * 2]; - int16_t tmp_int[nFrames * 2]; - - float32_t SampleBuffer[StereoChannels::kNumChannels][nFrames]; - if(this->nMasterVolume > 0.0f) { - this->m_FXSpinLock.Acquire (); - + float32_t SampleBuffer[StereoChannels::kNumChannels][nFrames]; + this->m_FXSpinLock.Acquire(); this->mixing_console_->process(SampleBuffer[indexL], SampleBuffer[indexR]); - this->m_FXSpinLock.Release (); + this->m_FXSpinLock.Release(); // Convert dual float array (left, right) to single int16 array (left/right) this->nMasterVolume = constrain(this->nMasterVolume, 0.0f, 1.0f); @@ -1818,17 +1822,16 @@ void CMiniDexed::ProcessSound (void) } arm_float_to_q15(tmp_float, tmp_int, nFrames * 2); } - else + else // this->nMasterVolume == 0.0f + { arm_fill_q15(0, tmp_int, nFrames * 2); + } #elif defined(PLATE_REVERB_ENABLE) uint8_t indexL=0, indexR=1; // BEGIN TG mixing - float32_t tmp_float[nFrames*2]; - int16_t tmp_int[nFrames*2]; - if(nMasterVolume > 0.0f) { for (uint8_t i = 0; i < CConfig::ToneGenerators; i++) diff --git a/src/mixing_console.hpp b/src/mixing_console.hpp index 72db76e..7210898 100644 --- a/src/mixing_console.hpp +++ b/src/mixing_console.hpp @@ -2,6 +2,7 @@ // mixing_console.hpp // // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Author: Vincent Gauché // Copyright (C) 2022 The MiniDexed Team // // This program is free software: you can redistribute it and/or modify @@ -43,12 +44,11 @@ public: ~MixingConsole(); // Send section + inline void setChannelLevel(size_t in, float32_t lvl); inline void setPan(size_t in, float32_t pan); inline void setSendLevel(size_t in, MixerOutput fx, float32_t lvl); inline void setInputSample(size_t in, float32_t sampleL, float32_t sampleR); inline void setInputSampleBuffer(size_t in, float32_t* samples); - inline void copyInputSampleBuffer(size_t in, float32_t* samplesL, float32_t* samplesR); - inline void preProcessInputSampleBuffer(size_t in, size_t nSamples); // Return section inline void setReturnLevel(MixerOutput ret, MixerOutput dest, float32_t lvl); @@ -69,16 +69,22 @@ public: // Processing inline void init(); inline void reset() override; + inline void preProcessInputSampleBuffer(size_t in, size_t nSamples); + inline void injectInputSamples(size_t in, float32_t* samplesL, float32_t* samplesR, size_t nSamples); inline void processSample(float32_t& outL, float32_t& outR); void process(float32_t* outL, float32_t* outR); protected: + inline void updatePan(size_t in); inline void setLevel(size_t in, MixerOutput fx, float32_t lvl); inline void setSample(size_t in, float32_t sampleL, float32_t sampleR); private: + static inline float32_t weighted_sum(const float32_t* data, const float32_t* weights, size_t size); + const size_t BufferSize; + float32_t channel_level_[nb_inputs]; float32_t pan_[StereoChannels::kNumChannels + 1][nb_inputs]; float32_t* tg_input_sample_buffer_[nb_inputs]; float32_t* input_sample_buffer_[StereoChannels::kNumChannels][nb_inputs]; @@ -109,6 +115,7 @@ private: { SS_RESET(ss, precision, std::left); SS_SPACE(ss, ' ', space, std::left, '|'); + SS__TEXT(ss, ' ', space, std::left, '|', "Level"); SS__TEXT(ss, ' ', space, std::left, '|', "Pan L"); SS__TEXT(ss, ' ', space, std::left, '|', "Pan R"); SS__TEXT(ss, ' ', space, std::left, '|', "Pan"); @@ -119,6 +126,7 @@ private: SS_SPACE(ss, '-', space, std::left, '+'); SS_SPACE(ss, '-', space, std::left, '+'); SS_SPACE(ss, '-', space, std::left, '+'); + SS_SPACE(ss, '-', space, std::left, '+'); out << "\t" << ss.str() << std::endl; for(size_t i = 0; i < nb_inputs; ++i) @@ -129,6 +137,7 @@ private: SS_RESET(ss, precision, std::left); SS__TEXT(ss, ' ', space, std::left, '|', s.str()); + SS__TEXT(ss, ' ', space - 1, std::right, " |", this->channel_level_[i]); SS__TEXT(ss, ' ', space - 1, std::right, " |", this->pan_[StereoChannels::Left][i]); SS__TEXT(ss, ' ', space - 1, std::right, " |", this->pan_[StereoChannels::Right][i]); SS__TEXT(ss, ' ', space - 1, std::right, " |", this->pan_[StereoChannels::kNumChannels][i]); @@ -256,6 +265,7 @@ private: for(size_t i = 0; i < nb_inputs; ++i) { + nb_errors += inspector(tag + ".level[ input #" + std::to_string(i) + " ]" , this->channel_level_[i], -1.0f, 1.0f, deepInspection); nb_errors += inspector(tag + ".pan[ L ][ input #" + std::to_string(i) + " ]", this->pan_[StereoChannels::Left][i], -1.0f, 1.0f, deepInspection); nb_errors += inspector(tag + ".pan[ R ][ input #" + std::to_string(i) + " ]", this->pan_[StereoChannels::Right][i], -1.0f, 1.0f, deepInspection); nb_errors += inspector(tag + ".pan[ input #" + std::to_string(i) + " ]", this->pan_[StereoChannels::kNumChannels][i], -1.0f, 1.0f, deepInspection); @@ -285,7 +295,7 @@ private: { for(size_t i = 0; i < nb_inputs; ++i) { - for(size_t k = 0; k < this->BufferSize; ++k) + for(size_t k = 0; k < this->m_nSamples; ++k) { nb_errors += inspector(tag + ".input_sample_buffer_[ L ][ " + std::to_string(i) + " ][ " + std::to_string(k) +" ] ", this->input_sample_buffer_[StereoChannels::Left ][i][k], -1.0f, 1.0f, deepInspection); nb_errors += inspector(tag + ".input_sample_buffer_[ R ][ " + std::to_string(i) + " ][ " + std::to_string(k) +" ] ", this->input_sample_buffer_[StereoChannels::Right][i][k], -1.0f, 1.0f, deepInspection); @@ -307,6 +317,13 @@ private: ) }; +template +float32_t MixingConsole::weighted_sum(const float32_t* data, const float32_t* weights, size_t size) +{ + float32_t res = arm_weighted_sum_f32(data, weights, size); + + return std::isnan(res) ? 0.0f : res; +} template MixingConsole::MixingConsole(float32_t sampling_rate, size_t buffer_size) : @@ -348,11 +365,26 @@ MixingConsole::~MixingConsole() { delete[] this->input_sample_buffer_[StereoChannels::Left ][i]; delete[] this->input_sample_buffer_[StereoChannels::Right][i]; + + // The tg_input_sample_buffer_ buffers are not freed as MixingConsole is not the creator + // They must be freed by the creator of the buffers this->tg_input_sample_buffer_[i] = nullptr; } } // Send section +template +void MixingConsole::setChannelLevel(size_t in, float32_t lvl) +{ + assert(in < nb_inputs); + + lvl = constrain(lvl, 0.0f, 1.0f); + if(lvl == this->channel_level_[in]) return; + + this->channel_level_[in] = lvl; + this->updatePan(in); +} + template void MixingConsole::setPan(size_t in, float32_t pan) { @@ -363,10 +395,7 @@ void MixingConsole::setPan(size_t in, float32_t pan) if(pan == this->pan_[StereoChannels::kNumChannels][in]) return; this->pan_[StereoChannels::kNumChannels][in] = pan; - - pan *= Constants::MPI_2; - this->pan_[StereoChannels::Left ][in] = InterpolatedSineOscillator::Cos(pan); - this->pan_[StereoChannels::Right][in] = InterpolatedSineOscillator::Sin(pan); + this->updatePan(in); } template @@ -394,67 +423,6 @@ void MixingConsole::setInputSampleBuffer(size_t in, float32_t* sample this->tg_input_sample_buffer_[in] = samples; } -template -void MixingConsole::copyInputSampleBuffer(size_t in, float32_t* samplesL, float32_t* samplesR) -{ - // Only used to input stereo samples - assert(in < nb_inputs); - if(samplesL != nullptr) - { - memcpy(this->input_sample_buffer_[StereoChannels::Left ][in], samplesL, this->BufferSize * sizeof(float32_t)); - } - else - { - memset(this->input_sample_buffer_[StereoChannels::Left ][in], 0, this->BufferSize * sizeof(float32_t)); - } - - if(samplesR != nullptr) - { - memcpy(this->input_sample_buffer_[StereoChannels::Right][in], samplesR, this->BufferSize * sizeof(float32_t)); - } - else - { - memset(this->input_sample_buffer_[StereoChannels::Right][in], 0, this->BufferSize * sizeof(float32_t)); - } -} - -template -void MixingConsole::preProcessInputSampleBuffer(size_t in, size_t nSamples) -{ - assert(in < nb_inputs); - assert(nSamples <= this->BufferSize); - - float32_t* samples = this->tg_input_sample_buffer_[in]; - if(samples == nullptr) return; - - this->m_nSamples = nSamples; - if(nSamples > 0) - { - if(this->pan_[StereoChannels::Left ][in] != 0.0f) - { - arm_scale_f32(samples, this->pan_[StereoChannels::Left ][in], this->input_sample_buffer_[StereoChannels::Left ][in], nSamples); - } - else - { - memset(this->input_sample_buffer_[StereoChannels::Left ][in], 0, nSamples * sizeof(float32_t)); - } - - if(this->pan_[StereoChannels::Right][in] != 0.0f) - { - arm_scale_f32(samples, this->pan_[StereoChannels::Right][in], this->input_sample_buffer_[StereoChannels::Right][in], nSamples); - } - else - { - memset(this->input_sample_buffer_[StereoChannels::Right][in], 0, nSamples * sizeof(float32_t)); - } - } - else - { - memset(this->input_sample_buffer_[StereoChannels::Left ][in], 0, this->BufferSize * sizeof(float32_t)); - memset(this->input_sample_buffer_[StereoChannels::Right][in], 0, this->BufferSize * sizeof(float32_t)); - } -} - // Return section template void MixingConsole::setReturnLevel(MixerOutput ret, MixerOutput dest, float32_t lvl) @@ -545,6 +513,7 @@ FXUnit2* MixingConsole::getDry() template void MixingConsole::init() { + memset(this->channel_level_, 0, nb_inputs * sizeof(float32_t)); for(size_t i = 0; i <= StereoChannels::kNumChannels; ++i) memset(this->pan_[i], 0, nb_inputs * sizeof(float32_t)); for(size_t i = 0; i < MixerOutput::kFXCount; ++i) @@ -576,41 +545,105 @@ void MixingConsole::reset() } } +template +void MixingConsole::injectInputSamples(size_t in, float32_t* samplesL, float32_t* samplesR, size_t nSamples) +{ + // Only used to input stereo samples + assert(in < nb_inputs); + this->m_nSamples = std::min(nSamples, this->BufferSize); + if(samplesL != nullptr) + { + memcpy(this->input_sample_buffer_[StereoChannels::Left ][in], samplesL, this->m_nSamples * sizeof(float32_t)); + } + else + { + memset(this->input_sample_buffer_[StereoChannels::Left ][in], 0, this->m_nSamples * sizeof(float32_t)); + } + + if(samplesR != nullptr) + { + memcpy(this->input_sample_buffer_[StereoChannels::Right][in], samplesR, this->m_nSamples * sizeof(float32_t)); + } + else + { + memset(this->input_sample_buffer_[StereoChannels::Right][in], 0, this->m_nSamples * sizeof(float32_t)); + } +} + +template +void MixingConsole::preProcessInputSampleBuffer(size_t in, size_t nSamples) +{ + assert(in < nb_inputs); + assert(nSamples <= this->BufferSize); + + float32_t* samples = this->tg_input_sample_buffer_[in]; + if(samples == nullptr) return; + + this->m_nSamples = nSamples; + if(nSamples > 0) + { + if(this->pan_[StereoChannels::Left ][in] != 0.0f) + { + arm_scale_f32(samples, this->pan_[StereoChannels::Left ][in], this->input_sample_buffer_[StereoChannels::Left ][in], nSamples); + } + else + { + memset(this->input_sample_buffer_[StereoChannels::Left ][in], 0, nSamples * sizeof(float32_t)); + } + + if(this->pan_[StereoChannels::Right][in] != 0.0f) + { + arm_scale_f32(samples, this->pan_[StereoChannels::Right][in], this->input_sample_buffer_[StereoChannels::Right][in], nSamples); + } + else + { + memset(this->input_sample_buffer_[StereoChannels::Right][in], 0, nSamples * sizeof(float32_t)); + } + } + else + { + memset(this->input_sample_buffer_[StereoChannels::Left ][in], 0, this->BufferSize * sizeof(float32_t)); + memset(this->input_sample_buffer_[StereoChannels::Right][in], 0, this->BufferSize * sizeof(float32_t)); + } +} + template void MixingConsole::processSample(float32_t& outL, float32_t& outR) { const size_t nBuffers = nb_inputs + MixerOutput::kFXCount - 1; - float32_t fx_inputs_[MixerOutput::kFXCount][StereoChannels::kNumChannels]; - float32_t fx_outputs_[MixerOutput::kFXCount][StereoChannels::kNumChannels]; - for(size_t i = 0; i < MixerOutput::kFXCount; ++i) + float32_t fx_input_[StereoChannels::kNumChannels]; + float32_t fx_output_[StereoChannels::kNumChannels]; + for(size_t fxId = 0; fxId < MixerOutput::kFXCount; ++fxId) { - // Compute the samples that will feed the MixerOutput and process MixerOutput - fx_inputs_[i][StereoChannels::Left ] = arm_weighted_sum_f32(this->input_samples_[StereoChannels::Left ], this->levels_[i], nBuffers); - fx_inputs_[i][StereoChannels::Right] = arm_weighted_sum_f32(this->input_samples_[StereoChannels::Right], this->levels_[i], nBuffers); + // Compute the samples that will feed the FX + fx_input_[StereoChannels::Left ] = MixingConsole::weighted_sum(this->input_samples_[StereoChannels::Left ], this->levels_[fxId], nBuffers); + fx_input_[StereoChannels::Right] = MixingConsole::weighted_sum(this->input_samples_[StereoChannels::Right], this->levels_[fxId], nBuffers); // Process the FX - this->fx_[i]->processSample( - fx_inputs_[i][StereoChannels::Left], - fx_inputs_[i][StereoChannels::Right], - fx_outputs_[i][StereoChannels::Left], - fx_outputs_[i][StereoChannels::Right] + this->fx_[fxId]->processSample( + fx_input_[StereoChannels::Left ], + fx_input_[StereoChannels::Right], + fx_output_[StereoChannels::Left ], + fx_output_[StereoChannels::Right] ); - if(i != MixerOutput::MainOutput) + if(fxId != MixerOutput::MainOutput) { - // Feedback the resulting samples except for the main output + // Feedback the processed samples except for the main output this->setReturnSample( - static_cast(i), - fx_outputs_[i][StereoChannels::Left], - fx_outputs_[i][StereoChannels::Right] + static_cast(fxId), + fx_output_[StereoChannels::Left], + fx_output_[StereoChannels::Right] ); } + else + { + // Returns the main output sample + outL = fx_output_[StereoChannels::Left]; + outR = fx_output_[StereoChannels::Right]; + } } - - // Return this main output sample - outL = fx_inputs_[MixerOutput::MainOutput][StereoChannels::Left]; - outR = fx_inputs_[MixerOutput::MainOutput][StereoChannels::Right]; } template @@ -636,6 +669,22 @@ void MixingConsole::process(float32_t* outL, float32_t* outR) this->m_nSamples = 0; } +template +void MixingConsole::updatePan(size_t in) +{ + float32_t pan = this->pan_[StereoChannels::kNumChannels][in] * Constants::MPI_2; + if(this->channel_level_[in] != 0.0f) + { + this->pan_[StereoChannels::Left ][in] = InterpolatedSineOscillator::Cos(pan) * this->channel_level_[in]; + this->pan_[StereoChannels::Right][in] = InterpolatedSineOscillator::Sin(pan) * this->channel_level_[in]; + } + else + { + this->pan_[StereoChannels::Left ][in] = + this->pan_[StereoChannels::Right][in] = 0.0f; + } +} + template void MixingConsole::setLevel(size_t in, MixerOutput fx, float32_t lvl) { diff --git a/src/test/arm_functions.cpp b/src/test/arm_functions.cpp index 84385ce..5ba4673 100644 --- a/src/test/arm_functions.cpp +++ b/src/test/arm_functions.cpp @@ -35,9 +35,16 @@ void arm_fill_f32(float32_t value, float32_t *pDst, uint32_t blockSize) float32_t arm_weighted_sum_f32(const float32_t *in, const float32_t *weights, uint32_t blockSize) { - float32_t m = 0.0f; - for(size_t i = 0; i < blockSize; ++i) m += in[i] * weights[i]; - return m; + float32_t s = 0.0f; + float32_t w = 0.0f; + + for(size_t i = 0; i < blockSize; ++i) + { + s += in[i] * weights[i]; + w += weights[i]; + } + + return s / w; } void arm_clip_f32(const float32_t *pSrc, float32_t *pDst, float32_t low, float32_t high, uint32_t numSamples) diff --git a/src/test/test_fx_helper.h b/src/test/test_fx_helper.h index e694a8f..c7383b0 100644 --- a/src/test/test_fx_helper.h +++ b/src/test/test_fx_helper.h @@ -1,3 +1,22 @@ +// 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 . + +// +// test_fx_helper.h +// +// Set og helpers dedicated to code rationalization for the elaboration on unit tests. +// Author: Vincent Gauché +// #pragma once #include diff --git a/src/test/test_fx_mixing_console.cpp b/src/test/test_fx_mixing_console.cpp index d92c806..bafbf5d 100644 --- a/src/test/test_fx_mixing_console.cpp +++ b/src/test/test_fx_mixing_console.cpp @@ -25,6 +25,7 @@ INSTANTIATE_TEST_SUITE_P(MixerOutputTest, MixingConsoleScenarioTest, testing::Ra void setupMixingConsoleFX(Mixer* mixer) { + mixer->setChannelLevel(0, 1.0f); mixer->setPan(0, 0.5f); mixer->getTube()->setMute(false); @@ -85,6 +86,7 @@ void setupMixingConsoleFX(Mixer* mixer, int scenarioId) ACTIVE_FX(scenarioId, FX_PlateReverb); ACTIVE_FX(scenarioId, FX_Reverberator); + mixer->setChannelLevel(0, 1.0f); mixer->setPan(0, 0.5f); size_t nbActiveFX = 0; @@ -217,6 +219,9 @@ TEST(MixingConsole, ZeroSamplesTest) setupMixingConsoleFX(&mixer); ASSERT_EQ(0, FULL_INSPECT((&mixer), true)); + mixer.setChannelLevel(0, 1.0f); + ASSERT_EQ(0, FULL_INSPECT((&mixer), true)); + mixer.setPan(0, 0.5f); ASSERT_EQ(0, FULL_INSPECT((&mixer), true)); @@ -252,6 +257,8 @@ TEST(MixingConsole, DryProcessing) Mixer mixer(SAMPLING_FREQUENCY, length); mixer.reset(); + mixer.setChannelLevel(0, 1.0f); + mixer.setPan(0, 0.5f); mixer.setSendLevel(0, MixerOutput::FX_Tube, 0.0f); mixer.setSendLevel(0, MixerOutput::FX_Chorus, 0.0f); @@ -267,7 +274,6 @@ TEST(MixingConsole, DryProcessing) mixer.setReturnLevel(static_cast(i), MixerOutput::MainOutput, 0.0f); } - mixer.setPan(0, 0.5f); mixer.setSendLevel(0, MixerOutput::MainOutput, 1.0f); ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); @@ -298,11 +304,12 @@ TEST(MixingConsole, ReverberatorProcessing) Mixer mixer(SAMPLING_FREQUENCY, length); mixer.reset(); + mixer.setChannelLevel(0, 1.0f); + mixer.setPan(0, 0.5f); mixer.setSendLevel(0, MixerOutput::MainOutput, 0.0f); mixer.setSendLevel(0, MixerOutput::FX_Reverberator, 1.0f); mixer.setReturnLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, 1.0f); - mixer.setPan(0, 0.5f); ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); float32_t in[length] = {0.1, 0.2}; @@ -343,11 +350,12 @@ TEST(MixingConsole, ReverberatorNoiseProcessing) Mixer mixer(SAMPLING_FREQUENCY, length); mixer.reset(); + mixer.setChannelLevel(0, 1.0f); + mixer.setPan(0, 0.5f); mixer.setSendLevel(0, MixerOutput::MainOutput, 0.0f); mixer.setSendLevel(0, MixerOutput::FX_Reverberator, 1.0f); mixer.setReturnLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, 1.0f); - mixer.setPan(0, 0.5f); ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); float32_t in[length]; @@ -366,7 +374,7 @@ TEST(MixingConsole, ReverberatorNoiseProcessing) ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); } -TEST(MixingConsole, StandardUsageProcessing) +TEST(MixingConsole, StandardUsageProcessingByInjection) { PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); @@ -390,8 +398,7 @@ TEST(MixingConsole, StandardUsageProcessing) mixer.setReturnLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, 0.3f); mixer.setReturnLevel(MixerOutput::FX_Delay, MixerOutput::MainOutput, 0.3f); - mixer.setInputSampleBuffer(0, inSamples[0]); - mixer.preProcessInputSampleBuffer(0, size); + mixer.injectInputSamples(0, inSamples[StereoChannels::Left], inSamples[StereoChannels::Right], size); mixer.process(outSamples[0], outSamples[1]); ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamples[0], outSamples[1], size, static_cast(SAMPLING_FREQUENCY), 16); @@ -399,6 +406,65 @@ TEST(MixingConsole, StandardUsageProcessing) CLEANUP_AUDIO_TEST(inSamples, outSamples); } +TEST(MixingConsole, StandardUsageProcessing) +{ + static const size_t MAX_BUFFER_SIZE = 4096; + static const size_t BUFFER_SIZE = 256; + + PREPARE_AUDIO_TEST(size, inSamples, outSamples, full_test_name); + + Mixer mixer(SAMPLING_FREQUENCY, MAX_BUFFER_SIZE); + + float32_t channelBuffer[MAX_BUFFER_SIZE]; + memset(channelBuffer, 0, MAX_BUFFER_SIZE * sizeof(float32_t)); + mixer.setInputSampleBuffer(0, channelBuffer); + + setupMixingConsoleFX(&mixer); + + mixer.getTube()->setOverdrive(0.15f); + + mixer.setSendLevel(0, MixerOutput::FX_Tube, 1.0f); + mixer.setSendLevel(0, MixerOutput::FX_Phaser, 1.0f); + // mixer.setReturnLevel(MixerOutput::FX_Tube, MixerOutput::MainOutput, 1.0f); + // mixer.setSendLevel(0, MixerOutput::FX_Chorus, 1.0f); + // mixer.setSendLevel(0, MixerOutput::FX_Reverberator, 1.0f); + mixer.setReturnLevel(MixerOutput::FX_Tube, MixerOutput::FX_Chorus, 1.0f); + mixer.setReturnLevel(MixerOutput::FX_Chorus, MixerOutput::FX_Reverberator, 1.0f); + mixer.setReturnLevel(MixerOutput::FX_Phaser, MixerOutput::FX_Delay, 1.0f); + + mixer.setSendLevel(0, MixerOutput::MainOutput, 0.25f); + mixer.setReturnLevel(MixerOutput::FX_Tube, MixerOutput::MainOutput, 0.1f); + mixer.setReturnLevel(MixerOutput::FX_Chorus, MixerOutput::MainOutput, 0.15f); + mixer.setReturnLevel(MixerOutput::FX_Reverberator, MixerOutput::MainOutput, 0.3f); + mixer.setReturnLevel(MixerOutput::FX_Delay, MixerOutput::MainOutput, 0.3f); + + float32_t* inS = inSamples[StereoChannels::Left]; + float32_t* outS[StereoChannels::kNumChannels]; + outS[StereoChannels::Left ] = outSamples[StereoChannels::Left ]; + outS[StereoChannels::Right] = outSamples[StereoChannels::Right]; + size_t s = size; + + while(s > 0) + { + size_t nb = (s > BUFFER_SIZE) ? BUFFER_SIZE : s; + + memcpy(channelBuffer, inS, nb * sizeof(float32_t)); + + mixer.preProcessInputSampleBuffer(0, nb); + mixer.process(outS[StereoChannels::Left ], outS[StereoChannels::Right]); + // ASSERT_EQ(0, INSPECT((&mixer), fullInspector)); + + inS += nb; + outS[StereoChannels::Left ] += nb; + outS[StereoChannels::Right] += nb; + s -= nb; + } + + saveWaveFile(getResultFile(full_test_name + ".wav", true), outSamples[0], outSamples[1], size, static_cast(SAMPLING_FREQUENCY), 16); + + CLEANUP_AUDIO_TEST(inSamples, outSamples); +} + // TEST_P(FXScenarioTest, FXProcessingScenario) // { // const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); diff --git a/src/test/test_fx_mixing_console_unitary.cpp b/src/test/test_fx_mixing_console_unitary.cpp index e7e7f83..d63e233 100644 --- a/src/test/test_fx_mixing_console_unitary.cpp +++ b/src/test/test_fx_mixing_console_unitary.cpp @@ -16,7 +16,8 @@ TEST(MixingConsole, ShortBuffer) const size_t size = 10; Mixer* mixer = new Mixer(SAMPLING_FREQUENCY, size); - + mixer->reset(); + mixer->setChannelLevel(0, 1.0f); mixer->setPan(0, 0.5f); mixer->setSendLevel(0, MixerOutput::MainOutput, 1.0f); @@ -51,7 +52,8 @@ TEST(MixingConsole, ReverberatorShortBuffer) const size_t size = 10; Mixer* mixer = new Mixer(SAMPLING_FREQUENCY, size); - + mixer->reset(); + mixer->setChannelLevel(0, 1.0f); mixer->setPan(0, 0.5f); mixer->getReverberator()->setInputGain(0.35f); @@ -93,6 +95,7 @@ TEST(MixingConsole, DrySamplesBoundariesTest) mixer->reset(); FULL_INSPECT2(mixer, true, "Mixer.reset"); + mixer->setChannelLevel(0, 1.0f); mixer->setPan(0, 0.5f); mixer->setSendLevel(0, MixerOutput::MainOutput, 1.0f); @@ -144,6 +147,7 @@ TEST(MixingConsole, ReverberatorSamplesBoundariesTest) Mixer* mixer = new Mixer(SAMPLING_FREQUENCY, size); mixer->reset(); + mixer->setChannelLevel(0, 1.0f); mixer->setPan(0, 0.5f); mixer->setSendLevel(0, MixerOutput::MainOutput, 0.4f); diff --git a/src/test/wave.h b/src/test/wave.h index 6f9c2c4..3388431 100644 --- a/src/test/wave.h +++ b/src/test/wave.h @@ -1,3 +1,22 @@ +// 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 . + +// +// wave.h +// +// Set of helpers to manipulate RIFF Wave files. These helpers are used in the unit tests. +// Author: Vincent Gauché +// #pragma once #include