/**************************************************************************//** * @file * @author Steve Lascos * @company Blackaddr Audio * * LibBasicFunctions is a collection of helpful functions and classes that make * it easier to perform common tasks in Audio applications. * * @copyright 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 . *****************************************************************************/ #include #include #include #include "Arduino.h" #include "Audio.h" #include "BATypes.h" #include "LibMemoryManagement.h" #ifndef __LIBBASICFUNCTIONS_H #define __LIBBASICFUNCTIONS_H namespace BAGuitar { /**************************************************************************//** * QueuePosition is used for storing the index (in an array of queues) and the * offset within an audio_block_t data buffer. Useful for dealing with large * windows of audio spread across multiple audio data blocks. *****************************************************************************/ struct QueuePosition { int offset; ///< offset in samples within an audio_block_t data buffer int index; ///< index in an array of audio data blocks }; /// Calculate the exact sample position in an array of audio blocks that corresponds /// to a particular offset given as time. /// @param milliseconds length of the interval in milliseconds /// @returns a struct containing the index and offset QueuePosition calcQueuePosition(float milliseconds); /// Calculate the exact sample position in an array of audio blocks that corresponds /// to a particular offset given as a number of samples /// @param milliseconds length of the interval in milliseconds /// @returns a struct containing the index and offset QueuePosition calcQueuePosition(size_t numSamples); /// Calculate the number of audio samples (rounded up) that correspond to a /// given length of time. /// @param milliseconds length of the interval in milliseconds /// @returns the number of corresonding audio samples. size_t calcAudioSamples(float milliseconds); /// Calculate the number of audio samples (usually an offset) from /// a queue position. /// @param position specifies the index and offset within a queue /// @returns the number of samples from the start of the queue array to the /// specified position. size_t calcOffset(QueuePosition position); void clearAudioBlock(audio_block_t *block); void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix); template class RingBuffer; // forward declare so AudioDelay can use it. /**************************************************************************//** * Audio delays are a very common function in audio processing. In addition to * being used for simply create a delay effect, it can also be used for buffering * a sliding window in time of audio samples. This is useful when combining * several audio_block_t data buffers together to form one large buffer for * FFTs, etc. * @details The buffer works like a queue. You add new audio_block_t when available, * and the class will return an old buffer when it is to be discarded from the queue.
* Note that using INTERNAL memory means the class will only store a queue * of pointers to audio_block_t buffers, since the Teensy Audio uses a shared memory * approach. When using EXTERNAL memory, data is actually copyied to/from an external * SRAM device. *****************************************************************************/ class AudioDelay { public: AudioDelay() = delete; /// Construct an audio buffer using INTERNAL memory by specifying the max number /// of audio samples you will want. /// @param maxSamples equal or greater than your longest delay requirement AudioDelay(size_t maxSamples); /// Construct an audio buffer using INTERNAL memory by specifying the max amount of /// time you will want available in the buffer. /// @param maxDelayTimeMs max length of time you want in the buffer specified in milliseconds AudioDelay(float maxDelayTimeMs); /// Construct an audio buffer using a slot configured with the BAGuitar::ExternalSramManager /// @param slot a pointer to the slot representing the memory you wish to use for the buffer. AudioDelay(ExtMemSlot *slot, bool useDma=true); ~AudioDelay(); /// Add a new audio block into the buffer. When the buffer is filled, /// adding a new block will push out the oldest once which is returned. /// @param blockIn pointer to the most recent block of audio /// @returns the buffer to be discarded, or nullptr if not filled (INTERNAL), or /// not applicable (EXTERNAL). audio_block_t *addBlock(audio_block_t *blockIn); /// When using INTERNAL memory, returns the pointer for the specified index into buffer. /// @details, the most recent block is 0, 2nd most recent is 1, ..., etc. /// @param index the specifies how many buffers older than the current to retrieve /// @returns a pointer to the requested audio_block_t audio_block_t *getBlock(size_t index); /// Retrieve an audio block (or samples) from the buffer. /// @details when using INTERNAL memory, only supported size is AUDIO_BLOCK_SAMPLES. When using /// EXTERNAL, a size smaller than AUDIO_BLOCK_SAMPLES can be requested. /// @param dest pointer to the target audio block to write the samples to. /// @param offset data will start being transferred offset samples from the start of the audio buffer /// @param numSamples default value is AUDIO_BLOCK_SAMPLES, so typically you don't have to specify this parameter. /// @returns true on success, false on error. bool getSamples(audio_block_t *dest, size_t offset, size_t numSamples = AUDIO_BLOCK_SAMPLES); /// When using EXTERNAL memory, this function can return a pointer to the underlying ExtMemSlot object associated /// with the buffer. /// @returns pointer to the underlying ExtMemSlot. ExtMemSlot *getSlot() const { return m_slot; } void readDmaBufferContents(audio_block_t *dest, size_t numSamples = AUDIO_BLOCK_SAMPLES, size_t bufferOffset = 0); RingBuffer *getRingBuffer() const { return m_ringBuffer; } private: /// enumerates whether the underlying memory buffer uses INTERNAL or EXTERNAL memory enum class MemType : unsigned { MEM_INTERNAL = 0, ///< internal audio_block_t from the Teensy Audio Library is used MEM_EXTERNAL ///< external SPI based ram is used }; MemType m_type; ///< when 0, INTERNAL memory, when 1, external MEMORY. RingBuffer *m_ringBuffer = nullptr; ///< When using INTERNAL memory, a RingBuffer will be created. ExtMemSlot *m_slot = nullptr; ///< When using EXTERNAL memory, an ExtMemSlot must be provided. }; /**************************************************************************//** * IIR BiQuad Filter - Direct Form I
* y[n] = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2]
* Some design tools (like Matlab assume the feedback coefficients 'a' are negated. You * may have to negate your 'a' coefficients. * @details Note that the ARM CMSIS-DSP library requires an extra zero between first * and second 'b' coefficients. E.g.
* {b10, 0, b11, b12, a11, a12, b20, 0, b21, b22, a21, a22, ...} *****************************************************************************/ class IirBiQuadFilter { public: IirBiQuadFilter() = delete; IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); virtual ~IirBiQuadFilter(); bool process(int16_t *output, int16_t *input, size_t numSamples); private: const unsigned NUM_STAGES; int32_t *m_coeffs = nullptr; // ARM DSP Math library filter instance arm_biquad_casd_df1_inst_q31 m_iirCfg; int32_t *m_state = nullptr; }; class IirBiQuadFilterHQ { public: IirBiQuadFilterHQ() = delete; IirBiQuadFilterHQ(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); virtual ~IirBiQuadFilterHQ(); bool process(int16_t *output, int16_t *input, size_t numSamples); private: const unsigned NUM_STAGES; int32_t *m_coeffs = nullptr; // ARM DSP Math library filter instance arm_biquad_cas_df1_32x64_ins_q31 m_iirCfg; int64_t *m_state = nullptr; }; class IirBiQuadFilterFloat { public: IirBiQuadFilterFloat() = delete; IirBiQuadFilterFloat(unsigned numStages, const float *coeffs); virtual ~IirBiQuadFilterFloat(); bool process(float *output, float *input, size_t numSamples); private: const unsigned NUM_STAGES; float *m_coeffs = nullptr; // ARM DSP Math library filter instance arm_biquad_cascade_df2T_instance_f32 m_iirCfg; float *m_state = nullptr; }; ///**************************************************************************//** // * Customer RingBuffer with random access // *****************************************************************************/ //template //class RingBuffer { //public: // RingBuffer() = delete; // // /// Construct a RingBuffer of specified max size // /// @param maxSize number of entries in ring buffer // RingBuffer(const size_t maxSize) : m_maxSize(maxSize) { // m_buffer = new T[maxSize](); // } // virtual ~RingBuffer(){ // if (m_buffer) delete [] m_buffer; // } // // /// Add an element to the back of the queue // /// @param element element to add to queue // /// returns 0 if success, otherwise error // int push_back(T element) { // // //Serial.println(String("RingBuffer::push_back...") + m_head + String(":") + m_tail + String(":") + m_size); // if ( (m_head == m_tail) && (m_size > 0) ) { // // overflow // Serial.println("RingBuffer::push_back: overflow"); // return -1; // } // // m_buffer[m_head] = element; // if (m_head < (m_maxSize-1) ) { // m_head++; // } else { // m_head = 0; // } // m_size++; // // return 0; // } // // /// Remove the element at teh front of the queue // /// @returns 0 if success, otherwise error // int pop_front() { // // if (m_size == 0) { // // buffer is empty // //Serial.println("RingBuffer::pop_front: buffer is empty\n"); // return -1; // } // if (m_tail < m_maxSize-1) { // m_tail++; // } else { // m_tail = 0; // } // m_size--; // //Serial.println(String("RingBuffer::pop_front: ") + m_head + String(":") + m_tail + String(":") + m_size); // return 0; // } // // /// Get the element at the front of the queue // /// @returns element at front of queue // T front() const { // return m_buffer[m_tail]; // } // // /// get the element at the back of the queue // /// @returns element at the back of the queue // T back() const { // return m_buffer[m_head-1]; // } // // /// Get a previously pushed elememt // /// @param offset zero is last pushed, 1 is second last, etc. // /// @returns the absolute index corresponding to the requested offset. // size_t get_index_from_back(size_t offset = 0) const { // // the target at m_head - 1 - offset or m_maxSize + m_head -1 - offset; // size_t idx = (m_maxSize + m_head -1 - offset); // // if ( idx >= m_maxSize) { // idx -= m_maxSize; // } // // return idx; // } // // /// get the current size of the queue // /// @returns size of the queue // size_t size() const { // return m_size; // } // // /// get the maximum size the queue can hold // /// @returns maximum size of the queue // size_t max_size() const { // return m_maxSize; // } // // /// get the element at the specified absolute index // /// @param index element to retrieve from absolute queue position // /// @returns the request element // T& operator[] (size_t index) { // return m_buffer[index]; // } // // /// get the element at the specified absolute index // /// @param index element to retrieve from absolute queue position // /// @returns the request element // T at(size_t index) const { // return m_buffer[index]; // } // // /// DEBUG: Prints the status of the Ringbuffer. NOte using this much printing will usually cause audio glitches // void print() const { // for (int idx=0; idxdata, HEX); // } // } //private: // size_t m_head=0; ///< back of the queue // size_t m_tail=0; ///< front of the queue // size_t m_size=0; ///< current size of the qeueu // T *m_buffer = nullptr; ///< pointer to the allocated buffer array // const size_t m_maxSize; ///< maximum size of the queue //}; } #endif /* __LIBBASICFUNCTIONS_H */