/* * LibBasicFunctions.cpp * * Created on: Dec 23, 2017 * Author: slascos */ #include "Audio.h" #include "LibBasicFunctions.h" namespace BAGuitar { size_t calcAudioSamples(float milliseconds) { return (size_t)((milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f); } QueuePosition calcQueuePosition(size_t numSamples) { QueuePosition queuePosition; queuePosition.index = (int)(numSamples / AUDIO_BLOCK_SAMPLES); queuePosition.offset = numSamples % AUDIO_BLOCK_SAMPLES; return queuePosition; } QueuePosition calcQueuePosition(float milliseconds) { size_t numSamples = (int)((milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f); return calcQueuePosition(numSamples); } size_t calcOffset(QueuePosition position) { return (position.index*AUDIO_BLOCK_SAMPLES) + position.offset; } void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix) { //Non-optimized version for illustrative purposes // for (int i=0; i< AUDIO_BLOCK_SAMPLES; i++) { // out->data[i] = (dry->data[i] * (1 - mix)) + (wet->data[i] * mix); // } // return; // ARM DSP optimized int16_t wetBuffer[AUDIO_BLOCK_SAMPLES]; int16_t dryBuffer[AUDIO_BLOCK_SAMPLES]; int16_t scaleFractWet = (int16_t)(mix * 32767.0f); int16_t scaleFractDry = 32767-scaleFractWet; arm_scale_q15(dry->data, scaleFractDry, 0, dryBuffer, AUDIO_BLOCK_SAMPLES); arm_scale_q15(wet->data, scaleFractWet, 0, wetBuffer, AUDIO_BLOCK_SAMPLES); arm_add_q15(wetBuffer, dryBuffer, out->data, AUDIO_BLOCK_SAMPLES); } void clearAudioBlock(audio_block_t *block) { memset(block->data, 0, sizeof(int16_t)*AUDIO_BLOCK_SAMPLES); } //////////////////////////////////////////////////// // AudioDelay //////////////////////////////////////////////////// AudioDelay::AudioDelay(size_t maxSamples) : m_slot(nullptr) { m_type = (MemType::MEM_INTERNAL); // INTERNAL memory consisting of audio_block_t data structures. QueuePosition pos = calcQueuePosition(maxSamples); m_ringBuffer = new RingBuffer<audio_block_t *>(pos.index+2); // If the delay is in queue x, we need to overflow into x+1, thus x+2 total buffers. } AudioDelay::AudioDelay(float maxDelayTimeMs) : AudioDelay(calcAudioSamples(maxDelayTimeMs)) { } AudioDelay::AudioDelay(ExtMemSlot *slot) { m_type = (MemType::MEM_EXTERNAL); m_slot = slot; } AudioDelay::~AudioDelay() { if (m_ringBuffer) delete m_ringBuffer; } audio_block_t* AudioDelay::addBlock(audio_block_t *block) { audio_block_t *blockToRelease = nullptr; if (m_type == (MemType::MEM_INTERNAL)) { // INTERNAL memory // purposefully don't check if block is valid, the ringBuffer can support nullptrs if ( m_ringBuffer->size() >= m_ringBuffer->max_size() ) { // pop before adding blockToRelease = m_ringBuffer->front(); m_ringBuffer->pop_front(); } // add the new buffer m_ringBuffer->push_back(block); return blockToRelease; } else { // EXTERNAL memory if (!m_slot) { Serial.println("addBlock(): m_slot is not valid"); } if (block) { // Audio is stored in reverse in block so we need to write it backwards to external memory // to maintain temporal coherency. // int16_t *srcPtr = block->data + AUDIO_BLOCK_SAMPLES - 1; // for (int i=0; i<AUDIO_BLOCK_SAMPLES; i++) { // m_slot->writeAdvance16(*srcPtr); // srcPtr--; // } int16_t *srcPtr = block->data; for (int i=0; i<AUDIO_BLOCK_SAMPLES; i++) { m_slot->writeAdvance16(*srcPtr); srcPtr++; } } blockToRelease = block; } return blockToRelease; } audio_block_t* AudioDelay::getBlock(size_t index) { audio_block_t *ret = nullptr; if (m_type == (MemType::MEM_INTERNAL)) { ret = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); } return ret; } bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSamples) { if (!dest) { Serial.println("getSamples(): dest is invalid"); return false; } if (m_type == (MemType::MEM_INTERNAL)) { QueuePosition position = calcQueuePosition(offset); size_t index = position.index; audio_block_t *currentQueue0 = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); // The latest buffer is at the back. We need index+1 counting from the back. audio_block_t *currentQueue1 = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index+1)); // check if either queue is invalid, if so just zero the destination buffer if ( (!currentQueue0) || (!currentQueue0) ) { // a valid entry is not in all queue positions while it is filling, use zeros memset(static_cast<void*>(dest->data), 0, numSamples * sizeof(int16_t)); return true; } if (position.offset == 0) { // single transfer memcpy(static_cast<void*>(dest->data), static_cast<void*>(currentQueue0->data), numSamples * sizeof(int16_t)); return true; } // Otherwise we need to break the transfer into two memcpy because it will go across two source queues. // Audio is stored in reverse order. That means the first sample (in time) goes in the last location in the audio block. int16_t *destStart = dest->data; int16_t *srcStart; // Break the transfer into two. Copy the 'older' data first then the 'newer' data with respect to current time. //currentQueue = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index+1)); // The latest buffer is at the back. We need index+1 counting from the back. srcStart = (currentQueue1->data + AUDIO_BLOCK_SAMPLES - position.offset); size_t numData = position.offset; memcpy(static_cast<void*>(destStart), static_cast<void*>(srcStart), numData * sizeof(int16_t)); //currentQueue = m_ringBuffer->at(m_ringBuffer->get_index_from_back(index)); // now grab the queue where the 'first' data sample was destStart += numData; // we already wrote numData so advance by this much. srcStart = (currentQueue0->data); numData = AUDIO_BLOCK_SAMPLES - numData; memcpy(static_cast<void*>(destStart), static_cast<void*>(srcStart), numData * sizeof(int16_t)); return true; } else { // EXTERNAL Memory if (numSamples*sizeof(int16_t) <= m_slot->size() ) { int currentPositionBytes = (int)m_slot->getWritePosition() - (int)(AUDIO_BLOCK_SAMPLES*sizeof(int16_t)); size_t offsetBytes = offset * sizeof(int16_t); if ((int)offsetBytes <= currentPositionBytes) { m_slot->setReadPosition(currentPositionBytes - offsetBytes); } else { // It's going to wrap around to the end of the slot int readPosition = (int)m_slot->size() + currentPositionBytes - offsetBytes; m_slot->setReadPosition((size_t)readPosition); } //m_slot->printStatus(); // write the data to the destination block in reverse // int16_t *destPtr = dest->data + AUDIO_BLOCK_SAMPLES-1; // for (int i=0; i<AUDIO_BLOCK_SAMPLES; i++) { // *destPtr = m_slot->readAdvance16(); // destPtr--; // } int16_t *destPtr = dest->data; for (int i=0; i<AUDIO_BLOCK_SAMPLES; i++) { *destPtr = m_slot->readAdvance16(); destPtr++; } return true; } else { // numSampmles is > than total slot size Serial.println("getSamples(): ERROR numSamples > total slot size"); return false; } } } //////////////////////////////////////////////////// // IirBiQuadFilter //////////////////////////////////////////////////// IirBiQuadFilter::IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift) : NUM_STAGES(numStages) { m_coeffs = new int32_t[5*numStages]; memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t)); m_state = new int32_t[4*numStages]; arm_biquad_cascade_df1_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift); } IirBiQuadFilter::~IirBiQuadFilter() { if (m_coeffs) delete [] m_coeffs; if (m_state) delete [] m_state; } bool IirBiQuadFilter::process(int16_t *output, int16_t *input, size_t numSamples) { if (!output) return false; if (!input) { // send zeros memset(output, 0, numSamples * sizeof(int16_t)); } else { // create convertion buffers on teh stack int32_t input32[numSamples]; int32_t output32[numSamples]; for (int i=0; i<numSamples; i++) { input32[i] = (int32_t)(input[i]); } arm_biquad_cascade_df1_fast_q31(&m_iirCfg, input32, output32, numSamples); for (int i=0; i<numSamples; i++) { output[i] = (int16_t)(output32[i] & 0xffff); } } return true; } // HIGH QUALITY IirBiQuadFilterHQ::IirBiQuadFilterHQ(unsigned numStages, const int32_t *coeffs, int coeffShift) : NUM_STAGES(numStages) { m_coeffs = new int32_t[5*numStages]; memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t)); m_state = new int64_t[4*numStages];; arm_biquad_cas_df1_32x64_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift); } IirBiQuadFilterHQ::~IirBiQuadFilterHQ() { if (m_coeffs) delete [] m_coeffs; if (m_state) delete [] m_state; } bool IirBiQuadFilterHQ::process(int16_t *output, int16_t *input, size_t numSamples) { if (!output) return false; if (!input) { // send zeros memset(output, 0, numSamples * sizeof(int16_t)); } else { // create convertion buffers on teh stack int32_t input32[numSamples]; int32_t output32[numSamples]; for (int i=0; i<numSamples; i++) { input32[i] = (int32_t)(input[i]); } arm_biquad_cas_df1_32x64_q31(&m_iirCfg, input32, output32, numSamples); for (int i=0; i<numSamples; i++) { output[i] = (int16_t)(output32[i] & 0xffff); } } return true; } // FLOAT IirBiQuadFilterFloat::IirBiQuadFilterFloat(unsigned numStages, const float *coeffs) : NUM_STAGES(numStages) { m_coeffs = new float[5*numStages]; memcpy(m_coeffs, coeffs, 5*numStages * sizeof(float)); m_state = new float[4*numStages];; arm_biquad_cascade_df2T_init_f32(&m_iirCfg, numStages, m_coeffs, m_state); } IirBiQuadFilterFloat::~IirBiQuadFilterFloat() { if (m_coeffs) delete [] m_coeffs; if (m_state) delete [] m_state; } bool IirBiQuadFilterFloat::process(float *output, float *input, size_t numSamples) { if (!output) return false; if (!input) { // send zeros memset(output, 0, numSamples * sizeof(float)); } else { arm_biquad_cascade_df2T_f32(&m_iirCfg, input, output, numSamples); } return true; } }