// // mixing_console.hpp // // 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 . // // Implementation of the MixingConsole class defined in mixing_console.h #pragma once #include "mixing_console_constants.h" #include "fx_tube.h" #include "fx_chorus.h" #include "fx_flanger.h" #include "fx_orbitone.h" #include "fx_phaser.h" #include "fx_delay.h" #include "effect_platervbstereo.h" #include "fx_shimmer_reverb.h" #include "fx_dry.h" #include "fx_unit2.hpp" template class MixingConsole : public FXBase { DISALLOW_COPY_AND_ASSIGN(MixingConsole); public: MixingConsole(float32_t sampling_rate, size_t buffer_size) : FXBase(sampling_rate), BufferSize(buffer_size) { for(size_t i = 0; i < nb_inputs; ++i) { this->input_sample_buffer_[StereoChannels::Left ][i] = new float32_t[this->BufferSize]; this->input_sample_buffer_[StereoChannels::Right][i] = new float32_t[this->BufferSize]; memset(this->input_sample_buffer_[StereoChannels::Left ][i], 0, this->BufferSize); memset(this->input_sample_buffer_[StereoChannels::Right][i], 0, this->BufferSize); } memset(this->fx_, 0, MixerOutput::kFXCount * sizeof(FXElement*)); this->fx_[MixerOutput::FX_Tube] = this->tube_ = new FXUnit2(sampling_rate); this->fx_[MixerOutput::FX_Chorus] = this->chorus_ = new FXUnit2(sampling_rate); this->fx_[MixerOutput::FX_Flanger] = this->flanger_ = new FXUnit2(sampling_rate); this->fx_[MixerOutput::FX_Orbitone] = this->orbitone_ = new FXUnit2(sampling_rate); this->fx_[MixerOutput::FX_Phaser] = this->phaser_ = new FXUnit2(sampling_rate); this->fx_[MixerOutput::FX_Delay] = this->delay_ = new FXUnit2(sampling_rate); this->fx_[MixerOutput::FX_PlateReverb] = this->plate_reverb_ = new FXUnit2(sampling_rate); this->fx_[MixerOutput::FX_ShimmerReverb] = this->shimmer_reverb_ = new FXUnit2(sampling_rate); this->fx_[MixerOutput::MainOutput] = this->dry_ = new FXUnit2(sampling_rate); this->init(); } ~MixingConsole() { for(size_t i = 0; i < nb_inputs; ++i) { delete this->input_sample_buffer_[StereoChannels::Left ][i]; delete this->input_sample_buffer_[StereoChannels::Right][i]; } for(size_t i = 0; i < MixerOutput::kFXCount; ++i) { delete this->fx_[i]; } } // Send section void 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(); } void setPan(size_t in, float32_t pan) { assert(in < nb_inputs); pan = constrain(pan, 0.0f, 1.0f); if(pan == this->pan_[StereoChannels::kNumChannels][in]) return; this->pan_[StereoChannels::kNumChannels][in] = pan; this->updatePan(in); } void setSendLevel(size_t in, MixerOutput fx, float32_t lvl) { assert(in < nb_inputs); assert(fx < kFXCount); this->setLevel(in, fx, lvl); } void setInputSample(size_t in, float32_t sampleL, float32_t sampleR) { assert(in < nb_inputs); this->setSample(in, sampleL, sampleR); } void setInputSampleBuffer(size_t in, float32_t* samples) { assert(in < nb_inputs); if(samples != nullptr) { arm_scale_f32(samples, this->pan_[StereoChannels::Left ][in], this->input_sample_buffer_[StereoChannels::Left ][in], this->BufferSize); arm_scale_f32(samples, this->pan_[StereoChannels::Right][in], this->input_sample_buffer_[StereoChannels::Right][in], this->BufferSize); } 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)); } } void setInputSampleBuffer(size_t in, float32_t* samplesL, float32_t* samplesR) { 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)); } } // Return section void setReturnLevel(MixerOutput ret, MixerOutput dest, float32_t lvl) { assert(ret < (kFXCount - 1)); assert(dest < kFXCount); if(ret == dest) { // An FX cannot feedback on itself return; } this->setLevel(nb_inputs + ret, dest, lvl); } void setReturnSample(MixerOutput ret, float32_t sampleL, float32_t sampleR) { assert(ret < (kFXCount - 1)); this->setSample(nb_inputs + ret, sampleL, sampleR); } // Get FX FXElement* getFX(size_t fx) { assert(fx < MixerOutput::kFXCount); return this->fx_[fx]; } FXUnit2* getTube() { return this->tube_; } FXUnit2* getChorus() { return this->chorus_; } FXUnit2* getFlanger() { return this->flanger_; } FXUnit2* getOrbitone() { return this->orbitone_; } FXUnit2* getPhaser() { return this->phaser_; } FXUnit2* getDelay() { return this->delay_; } FXUnit2* getPlateReverb() { return this->plate_reverb_; } FXUnit2* getShimmerReverb() { return this->shimmer_reverb_; } FXUnit2* getDry() { return this->dry_; } // Processing void 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) memset(this->levels_[i], 0, (nb_inputs + MixerOutput::kFXCount - 1) * sizeof(float32_t)); for(size_t i = 0; i < StereoChannels::kNumChannels; ++i) memset(this->input_samples_[i], 0, (nb_inputs + MixerOutput::kFXCount - 1) * sizeof(float32_t)); this->reset(); } void reset() { for(size_t i = 0; i < nb_inputs; ++i) { memset(this->input_sample_buffer_[StereoChannels::Left ][i], 0, this->BufferSize); memset(this->input_sample_buffer_[StereoChannels::Right][i], 0, this->BufferSize); } for(size_t i = 0; i < MixerOutput::kFXCount; ++i) { this->fx_[i]->reset(); } for(size_t i = 0; i < MixerOutput::MainOutput; ++i) { this->setReturnSample(static_cast(i), 0.0f, 0.0f); } } void processSample(float32_t& outL, float32_t& outR) { 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) { // 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], nb_inputs + MixerOutput::kFXCount - 1); fx_inputs_[i][StereoChannels::Right] = arm_weighted_sum_f32(this->input_samples_[StereoChannels::Right], this->levels_[i], nb_inputs + MixerOutput::kFXCount - 1); // 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] ); if(i != MixerOutput::MainOutput) { // Feedback the resulting samples except for the main output this->setReturnSample( static_cast(i), fx_outputs_[i][StereoChannels::Left], fx_outputs_[i][StereoChannels::Right] ); } } // Return this main output sample outL = fx_inputs_[MixerOutput::MainOutput][StereoChannels::Left]; outR = fx_inputs_[MixerOutput::MainOutput][StereoChannels::Right]; } void prepare() { for(size_t i = 0; i < MixerOutput::kFXCount; ++i) { this->fx_[i]->prepare(); } } void process(float32_t* outL, float32_t* outR) { this->prepare(); for(size_t s = 0; s < this->BufferSize; ++s) { for(size_t in = 0; in < nb_inputs; ++in) { this->setSample( in, this->input_sample_buffer_[StereoChannels::Left ][in][s], this->input_sample_buffer_[StereoChannels::Right][in][s] ); } this->processSample(*outL, *outR); ++outL; ++outR; } } protected: void updatePan(size_t in) { float32_t pan = mapfloat(this->pan_[StereoChannels::kNumChannels][in], 0.0f, 1.0f, 0.0, Constants::MPI_2); this->pan_[StereoChannels::Left ][in] = arm_sin_f32(pan) * this->channel_level_[in]; this->pan_[StereoChannels::Right][in] = arm_cos_f32(pan) * this->channel_level_[in]; } void setLevel(size_t in, MixerOutput fx, float32_t lvl) { assert(in < (nb_inputs + MixerOutput::kFXCount - 1)); assert(fx < MixerOutput::kFXCount); this->levels_[fx][in] = constrain(lvl, 0.0f, 1.0f); } void setSample(size_t in, float32_t sampleL, float32_t sampleR) { assert(in < (nb_inputs + MixerOutput::kFXCount - 1)); this->input_samples_[StereoChannels::Left ][in] = sampleL; this->input_samples_[StereoChannels::Right][in] = sampleR; } private: const size_t BufferSize; float32_t channel_level_[nb_inputs]; float32_t pan_[StereoChannels::kNumChannels + 1][nb_inputs]; float32_t* input_sample_buffer_[StereoChannels::kNumChannels][nb_inputs]; float32_t input_samples_[StereoChannels::kNumChannels][nb_inputs + MixerOutput::kFXCount - 1]; float32_t levels_[MixerOutput::kFXCount][nb_inputs + MixerOutput::kFXCount - 1]; FXElement* fx_[MixerOutput::kFXCount]; FXUnit2* tube_; FXUnit2* chorus_; FXUnit2* flanger_; FXUnit2* orbitone_; FXUnit2* phaser_; FXUnit2* delay_; FXUnit2* plate_reverb_; FXUnit2* shimmer_reverb_; FXUnit2* dry_; #if defined(DEBUG) public: void dump(std::ostream& out, const std::string& key = "") const { std::stringstream ss; ss.fill(' '); ss.precision(4); ss << std::fixed; out << "\t" << "MixingConsole dump - START - " << key.c_str() << std::endl; out << "\t" << "Input levels & Pan:" << std::endl; ss.str(""); ss << std::left; ss << "\t"; ss.width(11); ss << ""; ss.width(8); ss << "Level"; ss.width(8); ss << "Pan L"; ss.width(8); ss << "Pan R"; ss.width(8); ss << "Pan"; out << ss.str() << std::endl; ss.str(""); for(size_t i = 0; i < nb_inputs; ++i) { ss << std::left; ss << "\t" << "* Input " << (i + 1) << ": "; ss.precision(4); ss << std::showpos; ss << std::fixed; ss.width(8); ss << this->channel_level_[i]; ss.width(8); ss << this->pan_[StereoChannels::Left][i]; ss.width(8); ss << this->pan_[StereoChannels::Right][i]; ss.width(8); ss << this->pan_[StereoChannels::kNumChannels][i]; ss << std::endl; } out << ss.str() << std::endl; out << "\t" << "Mixing Console input samples:" << std::endl; ss.str(""); ss.fill(' '); ss << std::left; ss << "\t"; ss.width(11); ss << ""; for(size_t i = 0; i < nb_inputs; ++i) { ss << "Input " << (i + 1) << " "; } for(size_t i = 0; i < (MixerOutput::kFXCount - 1); ++i) { ss.width(7); ss << toString(static_cast(i)) << " "; } out << ss.str() << std::endl; const char* LR = "LR"; ss.str(""); for(size_t c = 0; c < StereoChannels::kNumChannels; ++c) { ss << std::left; ss << "\t" << "* Input " << LR[c] << ": "; ss.precision(4); ss << std::showpos; ss << std::fixed; for(size_t i = 0; i < (nb_inputs + MixerOutput::kFXCount - 1); ++i) { ss.width(8); ss << this->input_samples_[c][i]; } ss << std::endl; } out << ss.str() << std::endl; out << "\t" << "Mixing Console levels:" << std::endl; ss.str(""); ss << std::left; ss << "\t"; ss.width(11); ss << " "; for(size_t i = 0; i < nb_inputs; ++i) { ss << "Input " << (i + 1) << " "; } for(size_t i = 0; i < (MixerOutput::kFXCount - 1); ++i) { ss.width(7); ss << toString(static_cast(i)) << " "; } out << ss.str() << std::endl; ss.str(""); for(size_t c = 0; c < MixerOutput::kFXCount; ++c) { ss << std::left; ss << "\t"; ss.width(9); ss << toString(static_cast(c)); ss << ": "; ss.precision(4); ss << std::showpos; ss << std::fixed; for(size_t i = 0; i < (nb_inputs + MixerOutput::kFXCount - 1); ++i) { ss.width(8); ss << this->levels_[c][i]; } ss << std::endl; } out << ss.str() << std::endl; out << "MixingConsole dump - END - " << key.c_str() << std::endl; } #endif };