From e35dcd918acb0347a2bb16fa3abcaa1b85c7b38c Mon Sep 17 00:00:00 2001
From: Steve Lascos <steve@blackaddr.com>
Date: Tue, 28 Jul 2020 14:31:30 -0400
Subject: [PATCH] Added noise measurements

---
 examples/Tests/MeasureNoise/MeasureNoise.ino |  99 ++++++++++++++++++
 src/AudioEffectRmsMeasure.h                  |  94 +++++++++++++++++
 src/BAEffects.h                              |   1 +
 src/effects/AudioEffectDelayExternal.cpp     |   8 +-
 src/effects/AudioEffectRmsMeasure.cpp        | 102 +++++++++++++++++++
 src/peripherals/BAAudioControlWM8731.cpp     |   3 +
 6 files changed, 306 insertions(+), 1 deletion(-)
 create mode 100644 examples/Tests/MeasureNoise/MeasureNoise.ino
 create mode 100644 src/AudioEffectRmsMeasure.h
 create mode 100644 src/effects/AudioEffectRmsMeasure.cpp

diff --git a/examples/Tests/MeasureNoise/MeasureNoise.ino b/examples/Tests/MeasureNoise/MeasureNoise.ino
new file mode 100644
index 0000000..19da702
--- /dev/null
+++ b/examples/Tests/MeasureNoise/MeasureNoise.ino
@@ -0,0 +1,99 @@
+/*************************************************************************
+ * This demo uses the BALibrary library to provide enhanced control of
+ * the TGA Pro board.
+ * 
+ * The latest copy of the BA Guitar library can be obtained from
+ * https://github.com/Blackaddr/BALibrary
+ * 
+ * This demo measures the input noise of the TGA Pro. Make sure nothing is physically plugged
+ * into the INPUT jack when you run this program. This allows the switching-input jack to ground
+ * the input to zero signal.
+ * 
+ * The test will measure RMS noise and average sample value while toggling the CODEC HPF
+ * every few seconds. the CODEC HPF attempts to maximize headroom by modulating it's digital
+ * HPF filter offset. This results in near-zero low-frequncy (DC and sub-sonic) content which can
+ * be useful for frequency detection but is not appropriate for when sound quality is desired as
+ * the modulation of the filter will result in audible artifacts.
+ * 
+ */
+#include <Wire.h>
+#include <Audio.h>
+#include "BALibrary.h"
+#include "BAEffects.h"
+
+using namespace BALibrary;
+using namespace BAEffects;
+
+#define MEASURE_CODEC_PERFORMANCE // uncomment this line to measure internal codec performance, comment the line to measure TGA analog input circuitry performance.
+
+BAAudioControlWM8731     codecControl;
+AudioInputI2S            i2sIn;
+AudioOutputI2S           i2sOut;
+AudioEffectRmsMeasure    rmsModule;
+
+// Audio Connections
+AudioConnection      patchInL(i2sIn,0, rmsModule, 0); // route the input to the delay
+AudioConnection      patchInR(i2sIn,1, rmsModule, 1); // route the input to the delay
+
+AudioConnection      patchOutL(rmsModule, 0, i2sOut, 0); // connect the cab filter to the output.
+AudioConnection      patchOutR(rmsModule, 0, i2sOut, 1); // connect the cab filter to the output.
+
+void setup() {
+
+  delay(5); // wait a few ms to make sure the GTA Pro is fully powered up
+  AudioMemory(48);
+
+  // If the codec was already powered up (due to reboot) power itd own first
+  codecControl.disable();
+  delay(100);
+  codecControl.enable();
+  delay(100);
+
+#if defined(MEASURE_CODEC_PERFORMANCE)
+  // Measure TGA board performance with input unplugged, and 0 gain. Please set
+  // the gain switch on the TGA Pro to 0 dB.
+  Serial.println("Measuring CODEC internal performance");
+  codecControl.setLeftInMute(true); // mute the input signal completely
+  codecControl.setRightInMute(true);
+  codecControl.setHPFDisable(false); // Start with the HPF enabled
+#else
+  // Measure TGA board performance with input unplugged, and 0 gain. Please set
+  // the gain switch on the TGA Pro to 0 dB.
+  Serial.println("Measuring TGA Pro analog performance");
+  codecControl.setLeftInputGain(23); // 23 = 10111 = 0 dB of CODEC analog gain
+  codecControl.setRightInputGain(23);
+  codecControl.setLeftInMute(false);
+  codecControl.setRightInMute(false);
+  codecControl.setHPFDisable(false); // Start with the HPF enabled.
+#endif
+
+  rmsModule.enable();
+  rmsModule.bypass(false);
+  
+  
+}
+
+unsigned loopCount = 0;
+bool isHpfEnabled = true;
+
+void loop() {  
+
+  // The audio flows automatically through the Teensy Audio Library
+
+  if (loopCount > 100000000) {
+    if (isHpfEnabled) {
+        Serial.println("Setting HPF disable to true");
+        codecControl.setHPFDisable(true);
+
+        isHpfEnabled = false;
+    } else {
+        Serial.println("Setting HPF disable to false");
+        codecControl.setHPFDisable(false);
+        isHpfEnabled = true;
+    }
+    loopCount = 0;
+  }
+
+  loopCount++;
+
+}
diff --git a/src/AudioEffectRmsMeasure.h b/src/AudioEffectRmsMeasure.h
new file mode 100644
index 0000000..70270ab
--- /dev/null
+++ b/src/AudioEffectRmsMeasure.h
@@ -0,0 +1,94 @@
+/**************************************************************************//**
+ *  @file
+ *  @author Steve Lascos
+ *  @company Blackaddr Audio
+ *
+ *  @brief Measure the RMS noise of a channel
+ *
+ *  @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__BAEFFECTS_AUDIOEFFECTDELAYEXTERNAL_H; 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 <http://www.gnu.org/licenses/>.
+ *****************************************************************************/
+
+#ifndef __BAEFFECTS_AUDIOEFFECTRMSMEASURE_H
+#define __BAEFFECTS_AUDIOEFFECTRMSMEASURE_H
+
+#include <stdint.h>
+#include <Audio.h>
+
+namespace BAEffects {
+
+/**************************************************************************//**
+ * AudioEffectRmsMeasure
+ *****************************************************************************/
+class AudioEffectRmsMeasure : public AudioStream {
+public:
+
+	// *** CONSTRUCTORS ***
+	/// Create the measurement object. Default is to measure and calculate over
+	/// approximate 1 second when rate is roughly 44.1 KHz
+	/// @param numBlockMeasurements specifies how many audio blocks to calculate the noise
+	/// over.
+	AudioEffectRmsMeasure(unsigned numBlockMeasurements = 345);
+
+	virtual ~AudioEffectRmsMeasure(); ///< Destructor
+
+	/// Get the most recently calculated RMS value
+	/// @returns the non-normalized RMS
+	float getRms(void) { return m_rms; }
+
+	/// Get the most recently calculated dBFS value
+	/// @returns the RMS as dB with respecdt to dBFS
+	float getDb(void) { return m_dbfs; }
+
+	/// Bypass the effect.
+	/// @param byp when true, bypass wil disable the effect, when false, effect is enabled.
+    /// Note that audio still passes through when bypass is enabled.
+	void bypass(bool byp) { m_bypass = byp; }
+
+	/// Get if the effect is bypassed
+	/// @returns true if bypassed, false if not bypassed
+	bool isBypass() { return m_bypass; }
+
+	/// Toggle the bypass effect
+	void toggleBypass() { m_bypass = !m_bypass; }
+
+	/// Set the output volume.
+	/// @details The default is 1.0.
+	/// @param vol Sets the output volume between -1.0 and +1.0
+	void volume(float vol) {m_volume = vol; }
+
+	/// Enables audio processing. Note: when not enabled, CPU load is nearly zero.
+	void enable() { m_enable = true; }
+
+	/// Disables audio process. When disabled, CPU load is nearly zero.
+	void disable() { m_enable = false; }
+
+	virtual void update(void); ///< update automatically called by the Teesny Audio Library
+
+private:
+	audio_block_t *m_inputQueueArray[1];
+
+	bool     m_bypass               = true;
+	bool     m_enable               = false;
+	float    m_volume               = 1.0f;
+	unsigned m_numBlockMeasurements = 0;
+	unsigned m_accumulatorCount     = 0;
+	int64_t  m_sum                  = 0;
+	float    m_rms                  = 0.0f;
+	float    m_dbfs                 = 0.0f;
+
+};
+
+}
+
+#endif /* __BAEFFECTS_AUDIOEFFECTRMSMEASURE_H */
diff --git a/src/BAEffects.h b/src/BAEffects.h
index 50b92b0..454e53d 100644
--- a/src/BAEffects.h
+++ b/src/BAEffects.h
@@ -26,5 +26,6 @@
 #include "AudioEffectAnalogDelay.h"
 #include "AudioEffectSOS.h"
 #include "AudioEffectTremolo.h"
+#include "AudioEffectRmsMeasure.h"
 
 #endif /* __BAEFFECTS_H */
diff --git a/src/effects/AudioEffectDelayExternal.cpp b/src/effects/AudioEffectDelayExternal.cpp
index f81e16a..e21d947 100644
--- a/src/effects/AudioEffectDelayExternal.cpp
+++ b/src/effects/AudioEffectDelayExternal.cpp
@@ -67,6 +67,7 @@ void BAAudioEffectDelayExternal::delay(uint8_t channel, float milliseconds) {
 	unsigned mask = m_activeMask;
 	if (m_activeMask == 0) m_startUsingSPI(m_spiChannel);
 	m_activeMask = mask | (1<<channel);
+	Serial.print("DelayLengthInt: "); Serial.println(m_requestedDelayLength);
 }
 
 void BAAudioEffectDelayExternal::disable(uint8_t channel) {
@@ -202,7 +203,12 @@ void BAAudioEffectDelayExternal::initialize(void)
 
 	avail = memsize - m_allocated[m_mem];
 
-	if (m_requestedDelayLength > avail) samples = avail;
+	if (m_requestedDelayLength > avail) {
+	    samples = avail;
+	} else {
+	    samples = m_requestedDelayLength;
+	}
+
 	m_memoryStart = m_allocated[m_mem];
 	m_allocated[m_mem] += samples;
 	m_memoryLength = samples;
diff --git a/src/effects/AudioEffectRmsMeasure.cpp b/src/effects/AudioEffectRmsMeasure.cpp
new file mode 100644
index 0000000..bae6439
--- /dev/null
+++ b/src/effects/AudioEffectRmsMeasure.cpp
@@ -0,0 +1,102 @@
+/*
+ * AudioEffectRmsMeasure.cpp
+ *
+ *  Created on: March 7, 2020
+ *      Author: slascos
+ */
+#include <arm_math.h>
+#include "BALibrary.h"
+#include "AudioEffectRmsMeasure.h"
+
+using namespace BALibrary;
+
+namespace BAEffects {
+
+AudioEffectRmsMeasure::AudioEffectRmsMeasure(unsigned numBlockMeasurements)
+:   AudioStream(1, m_inputQueueArray),
+  m_numBlockMeasurements(numBlockMeasurements),
+  m_accumulatorCount(0),
+  m_sum(0),
+  m_rms(0.0f),
+  m_dbfs(0.0f)
+{
+}
+
+AudioEffectRmsMeasure::~AudioEffectRmsMeasure()
+{
+}
+
+void AudioEffectRmsMeasure::update(void)
+{
+	audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples
+
+	// Check is block is disabled
+	if (m_enable == false) {
+		// do not transmit or process any audio, return as quickly as possible.
+		if (inputAudioBlock) release(inputAudioBlock);
+		return;
+	}
+
+	// Check is block is bypassed, if so either transmit input directly or create silence
+	if (m_bypass == true) {
+		// transmit the input directly
+		if (!inputAudioBlock) {
+			// create silence
+			inputAudioBlock = allocate();
+			if (!inputAudioBlock) { return; } // failed to allocate
+			else {
+				clearAudioBlock(inputAudioBlock);
+			}
+		}
+		transmit(inputAudioBlock, 0);
+		release(inputAudioBlock);
+		return;
+	}
+
+	// Calculate the RMS noise over the specified number of audio blocks.
+	// Once the necessary blocks have been accumulated, calculate and print the
+	// RMS noise figure.
+	// RMS noise = sqrt((1/N) * (x1*x1 + x2*x2 + ...) )
+
+	// First create the square sum of the input audio block and add it to the accumulator
+	int64_t dotProduct = 0;
+	//arm_dot_prod_q15(inputAudioBlock->data, inputAudioBlock->data, AUDIO_BLOCK_SAMPLES, &dotProduct);
+	for (int i=0; i<AUDIO_BLOCK_SAMPLES; i++) {
+	    dotProduct += ((int64_t)inputAudioBlock->data[i] * (int64_t)inputAudioBlock->data[i]);
+	    if (dotProduct < 0) {
+	        Serial.println("DOT ERROR!");
+	        Serial.println(inputAudioBlock->data[i], HEX);
+	    }
+	}
+	m_sum += (int64_t)(dotProduct);
+
+	m_accumulatorCount++;
+
+	// Check if we have enough samples accumulated.
+	if (m_accumulatorCount == m_numBlockMeasurements) {
+	    // Calculate and print the RMS figure
+	    float div = (float)m_sum / (float)(m_accumulatorCount * AUDIO_BLOCK_SAMPLES);
+	    arm_sqrt_f32(div, &m_rms);
+	    // dbfs = 20*log10(abs(rmsFigure)/32768.0f);
+	    m_dbfs = 20.0f * log10(m_rms/32768.0f);
+
+	    Serial.print("Accumulator: "); Serial.println((int)(m_sum >> 32), HEX);
+	    Serial.print("RAW RMS: "); Serial.println(m_rms);
+
+	    Serial.print("AudioEffectRmsMeasure: the RMS figure is "); Serial.print(m_dbfs);
+	    Serial.print(" dBFS over "); Serial.print(m_accumulatorCount); Serial.println(" audio blocks");
+
+	    m_sum = 0;
+	    m_accumulatorCount = 0;
+
+	}
+
+	transmit(inputAudioBlock);
+	release(inputAudioBlock);
+}
+
+
+}
+
+
+
diff --git a/src/peripherals/BAAudioControlWM8731.cpp b/src/peripherals/BAAudioControlWM8731.cpp
index 1600088..5b421cd 100644
--- a/src/peripherals/BAAudioControlWM8731.cpp
+++ b/src/peripherals/BAAudioControlWM8731.cpp
@@ -95,6 +95,9 @@ constexpr int WM8731_DAC_MUTE_SHIFT = 3;
 constexpr int WM8731_HPF_DISABLE_ADDR = 5;
 constexpr int WM8731_HPF_DISABLE_MASK = 0x1;
 constexpr int WM8731_HPF_DISABLE_SHIFT = 0;
+constexpr int WM8731_HPF_STORE_OFFSET_ADDR = 5;
+constexpr int WM8731_HPF_STORE_OFFSET_MASK = 0x10;
+constexpr int WM8731_HPF_STORE_OFFSET_SHIFT = 4;
 // Register 7
 constexpr int WM8731_LRSWAP_ADDR = 5;
 constexpr int WM8731_LRSWAP_MASK = 0x20;