Skip to content

Commit

Permalink
Merge pull request #646 from davepl/main
Browse files Browse the repository at this point in the history
Make Audio Great Again
  • Loading branch information
rbergen committed Jul 28, 2024
2 parents a96ab44 + 3087273 commit 411d9cd
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 76 deletions.
6 changes: 5 additions & 1 deletion include/effects/matrix/PatternStocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,11 @@ class PatternStocks : public LEDStripEffect
// We have the high and low data in the stock, but let's not trust it and calculate it ourselves
// If this works, Davepl wrote it. If not, Robert made me do it!

auto [minpoint, maxpoint] = std::minmax_element(currentStock.points.begin(), currentStock.points.end(), [](const StockPoint& a, const StockPoint& b) { return a.val < b.val; });
auto [minpoint, maxpoint] =
std::minmax_element(currentStock.points.begin(), currentStock.points.end(), [](const StockPoint& a, const StockPoint& b)
{
return a.val < b.val;
});
float min = minpoint->val, max = maxpoint->val, range = max - min;

if (range > 0.0f)
Expand Down
2 changes: 1 addition & 1 deletion include/effects/matrix/spectrumeffects.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ class VUMeter
const int MAX_FADE = 256;

int xHalf = pGFXChannel->width()/2-1;
int bars = g_Analyzer._VURatioFade / 2.0 * xHalf; // map(g_Analyzer._VU, 0, MAX_VU/8, 1, xHalf);
int bars = g_Analyzer._VURatioFade / 2.0 * xHalf;
bars = min(bars, xHalf);

EraseVUMeter(pGFXChannel, bars, yVU);
Expand Down
2 changes: 1 addition & 1 deletion include/effects/strip/particles.h
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ class ColorBeatOverRed : public LEDStripEffect, public BeatEffectBase, public Pa
// also have to update and render the particle system, which does the actual pixel drawing. We clear the scene ever
// pass and rely on the fade effects of the particles to blend the

float amount = g_Analyzer._VU / MAX_VU;
float amount = g_Analyzer._VU / 4096;

_baseColor = CRGB(500 * amount, 0, 0);
setAllOnAllChannels(_baseColor.r, _baseColor.g, _baseColor.b);
Expand Down
35 changes: 14 additions & 21 deletions include/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,6 @@ extern RemoteDebug Debug; // Let everyone in the project know about it
#define ENABLE_REMOTE 0 // IR Remote Control
#define ENABLE_AUDIO 1 // Listen for audio from the microphone and process it
#define COLORDATA_SERVER_ENABLED 0
#define MIN_VU 20
#define NOISE_CUTOFF 1000

#if USE_PSRAM
#define MAX_BUFFERS 500
Expand Down Expand Up @@ -429,10 +427,6 @@ extern RemoteDebug Debug; // Let everyone in the project know about it

#define LED_PIN0 32

#define MIN_VU 280
#define NOISE_CUTOFF 1000
#define NOISE_FLOOR 2000

// The webserver serves files that are baked into the device firmware. When running you should be able to
// see/select the list of effects by visiting the chip's IP in a browser. You can get the chip's IP by
// watching the serial output or checking your router for the DHCP given to a new device; often they're
Expand Down Expand Up @@ -560,7 +554,6 @@ extern RemoteDebug Debug; // Let everyone in the project know about it
#define ENABLE_REMOTE 1 // IR Remote Control
#define ENABLE_AUDIO 1 // Listen for audio from the microphone and process it
#define SCALE_AUDIO_EXPONENTIAL 0
#define ENABLE_AUDIO_SMOOTHING 1
#define EFFECT_PERSISTENCE_CRITICAL 1 // Require effects serialization to succeed

#define DEFAULT_EFFECT_INTERVAL (MILLIS_PER_SECOND * 60 * 2)
Expand All @@ -581,9 +574,12 @@ extern RemoteDebug Debug; // Let everyone in the project know about it

#define TOGGLE_BUTTON_1 0

#define COLOR_ORDER EOrder::RGB
// The mesmerizer mic isn't quite as sensitive as the M5 mic that the code was originally written for
// so we adjust by a scalar to get the same effect.

#define AUDIO_MIC_SCALAR 1.5

#define MIN_VU 80
#define COLOR_ORDER EOrder::RGB

#elif TTGO

Expand Down Expand Up @@ -983,10 +979,9 @@ extern RemoteDebug Debug; // Let everyone in the project know about it
#define NUM_LEDS (MATRIX_WIDTH*MATRIX_HEIGHT)
#define LED_FAN_OFFSET_BU 6

// The mic in the M5 is not quite the same as the Mesmerizer, so it gets a different minimum VU than default

#define MIN_VU 150
#define NOISE_CUTOFF 100
//#define MIN_VU 400
//#define NOISE_FLOOR 30
//#define NOISE_CUTOFF 5

#if !(ELECROW)
#define TOGGLE_BUTTON_1 37
Expand Down Expand Up @@ -1036,11 +1031,6 @@ extern RemoteDebug Debug; // Let everyone in the project know about it
#define NUM_LEDS (MATRIX_WIDTH*MATRIX_HEIGHT)
#define LED_FAN_OFFSET_BU 6

// The mic in the M5 is not quite the same as the Mesmerizer, so it gets a different minimum VU than default

#define MIN_VU 280
#define NOISE_CUTOFF 1000

#define TOGGLE_BUTTON_1 39
#define TOGGLE_BUTTON_2 37

Expand Down Expand Up @@ -1289,16 +1279,19 @@ extern RemoteDebug Debug; // Let everyone in the project know about it
#define NUM_BANDS 16
#endif
#ifndef NOISE_FLOOR
#define NOISE_FLOOR 4000
#define NOISE_FLOOR 30
#endif
#ifndef NOISE_CUTOFF
#define NOISE_CUTOFF 1000
#define NOISE_CUTOFF 10
#endif
#ifndef AUDIO_MIC_SCALAR
#define AUDIO_MIC_SCALAR 1.0
#endif
#ifndef AUDIO_PEAK_REMOTE_TIMEOUT
#define AUDIO_PEAK_REMOTE_TIMEOUT 1000.0f // How long after remote PeakData before local microphone is used again
#endif
#ifndef ENABLE_AUDIO_SMOOTHING
#define ENABLE_AUDIO_SMOOTHING 0
#define ENABLE_AUDIO_SMOOTHING 1
#endif
#ifndef BARBEAT_ENHANCE
#define BARBEAT_ENHANCE 0.3 // How much the SpectrumAnalyzer "pulses" with the music
Expand Down
81 changes: 35 additions & 46 deletions include/soundanalyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,6 @@
#include <arduinoFFT.h>
#include <driver/i2s.h>
#include <driver/adc.h>
// #include <driver/adc_deprecated.h>

#define SUPERSAMPLES 1 // How many supersamples to take
#define SAMPLE_BITS 12 // Sample resolution (0-4095)
#define MAX_ANALOG_IN ((1 << SAMPLE_BITS) * SUPERSAMPLES) // What our max analog input value is on all analog pins (4096 is default 12 bit resolution)
#ifndef MAX_VU
#define MAX_VU (MAX_ANALOG_IN / 2)
#endif

#define MS_PER_SECOND 1000

Expand Down Expand Up @@ -77,8 +69,6 @@ class SoundAnalyzer : public AudioVariables // Non-audio case. Inherits only th

#define EXAMPLE_I2S_NUM (I2S_NUM_0)
#define EXAMPLE_I2S_FORMAT (I2S_CHANNEL_FMT_RIGHT_LEFT) // I2S data format
#define I2S_ADC_UNIT ADC_UNIT_1 // I2S built-in ADC unit
#define I2S_ADC_CHANNEL ADC1_CHANNEL_0 // I2S built-in ADC channel

void IRAM_ATTR AudioSamplerTaskEntry(void *);
void IRAM_ATTR AudioSerialTaskEntry(void *);
Expand All @@ -89,7 +79,7 @@ void IRAM_ATTR AudioSerialTaskEntry(void *);
// results are simplified down to this small class of band peaks.

#ifndef MIN_VU
#define MIN_VU 180 // Minimum VU value to use for the span when computing VURatio. Contributes to
#define MIN_VU 2 // Minimum VU value to use for the span when computing VURatio. Contributes to
#endif // how dynamic the music is (smaller values == more dynamic)


Expand Down Expand Up @@ -155,26 +145,26 @@ class PeakData
case MESMERIZERMIC:
{
static constexpr std::array<float, 16> Scalars16 = {0.4, .5, 0.75, 1.0, 0.6, 0.6, 0.8, 0.8, 1.2, 1.5, 3.0, 3.0, 3.0, 3.0, 3.5, 2.5}; // {0.08, 0.12, 0.3, 0.35, 0.35, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.4, 1.4, 1.0, 1.0, 1.0};
float result = (NUM_BANDS == 16) ? Scalars16[i] : map(i, 0, NUM_BANDS - 1, 1.0, 1.0);
float result = (NUM_BANDS == 16) ? Scalars16[i] : 1.0;
return result;
}
case PCREMOTE:
{

static constexpr std::array<float, 16> Scalars16 = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
float result = (NUM_BANDS == 16) ? Scalars16[i] : map(i, 0, NUM_BANDS - 1, 1.0, 1.0);
float result = (NUM_BANDS == 16) ? Scalars16[i] : 1.0;
return result;
}
case M5PLUS2:
{
static constexpr std::array<float, 16> Scalars16 = {0.3, .5, 0.8, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.7, 0.7, 0.7};
float result = (NUM_BANDS == 16) ? Scalars16[i] : map(i, 0, NUM_BANDS - 1, 1.0, 1.0);
static constexpr std::array<float, 16> Scalars16 = {0.5, 1.0, 2.5, 2.2, 1.5, 2.0, 2.0, 2.0, 1.5, 1.5, 1.5, 1.5, 1.0, 0.8, 1.0, 1.0};
float result = (NUM_BANDS == 16) ? Scalars16[i] : 1.0;
return result;
}
default:
{
static constexpr std::array<float, 16> Scalars16 = {0.5, .5, 0.8, 1.0, 1.5, 1.2, 1.5, 1.6, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 5.0, 2.5};
float result = (NUM_BANDS == 16) ? Scalars16[i] : map(i, 0, NUM_BANDS - 1, 1.0, 1.0);
float result = (NUM_BANDS == 16) ? Scalars16[i] : 1.0;
return result;
}
}
Expand Down Expand Up @@ -202,13 +192,13 @@ class PeakData
class SoundAnalyzer : public AudioVariables
{
static constexpr size_t MAX_SAMPLES = 256;
std::unique_ptr<uint16_t[]> ptrSampleBuffer;
std::unique_ptr<int16_t[]> ptrSampleBuffer;

// I'm old enough I can only hear up to about 12K, but feel free to adjust. Remember from
// school that you need to sample at double the frequency you want to process, so 24000 is 12K

static constexpr size_t SAMPLING_FREQUENCY = 20000;
static constexpr size_t LOWEST_FREQ = 40;
static constexpr size_t LOWEST_FREQ = 100;
static constexpr size_t HIGHEST_FREQ = SAMPLING_FREQUENCY / 2;

static constexpr size_t _sampling_period_us = PERIOD_FROM_FREQ(SAMPLING_FREQUENCY);
Expand Down Expand Up @@ -288,9 +278,9 @@ class SoundAnalyzer : public AudioVariables
if (M5.Mic.record((int16_t *)ptrSampleBuffer.get(), MAX_SAMPLES, SAMPLING_FREQUENCY, false))
bytesRead = bytesExpected;
#else
ESP_ERROR_CHECK(i2s_start(EXAMPLE_I2S_NUM));
ESP_ERROR_CHECK(i2s_read(EXAMPLE_I2S_NUM, (void *) ptrSampleBuffer.get(), bytesExpected, &bytesRead, 100 / portTICK_RATE_MS));
ESP_ERROR_CHECK(i2s_stop(EXAMPLE_I2S_NUM));
ESP_ERROR_CHECK(i2s_start(I2S_NUM_0));
ESP_ERROR_CHECK(i2s_read(I2S_NUM_0, (void *) ptrSampleBuffer.get(), bytesExpected, &bytesRead, 100 / portTICK_PERIOD_MS));
ESP_ERROR_CHECK(i2s_stop(I2S_NUM_0));
#endif

if (bytesRead != bytesExpected)
Expand Down Expand Up @@ -364,10 +354,7 @@ class SoundAnalyzer : public AudioVariables

for (int i = 2; i < MAX_SAMPLES / 2; i++)
{
#if USE_M5
// The M5 Mic returns some large vales, so we normalize here
_vReal[i] = _vReal[i] / MAX_SAMPLES;
#endif
_vReal[i] = _vReal[i] / MAX_SAMPLES * AUDIO_MIC_SCALAR;

int freq = GetBucketFrequency(i-2);
if (freq >= LOWEST_FREQ)
Expand Down Expand Up @@ -468,16 +455,19 @@ class SoundAnalyzer : public AudioVariables
}
else
{
// uses geometric spacing to calculate the upper frequency for each of the 12 bands, starting with a frequency of 200 Hz
// and ending with a frequency of 12.5 kHz. The spacing ratio r is calculated as the 11th root of the ratio of the maximum
// frequency to the minimum frequency, and each upper frequency is calculated as f1 * r^(i+1).

// Calculate the logarithmic spacing for the frequency bands
float f1 = LOWEST_FREQ;
float f2 = HIGHEST_FREQ;
float r = pow(f2 / f1, 1.0 / (NUM_BANDS - 1));

// Calculate the ratio based on logarithmic scale
float log_f1 = log10(f1);
float log_f2 = log10(f2);
float delta = (log_f2 - log_f1) / (NUM_BANDS - 1);

for (int i = 0; i < NUM_BANDS; i++)
{
_cutOffsBand[i] = round(f1 * pow(r, i + 1));
// Calculate the upper frequency for each band
_cutOffsBand[i] = round(pow(10, log_f1 + delta * (i + 1)));
debugV("BAND %d: %d\n", i, _cutOffsBand[i]);
}
}
Expand All @@ -489,7 +479,10 @@ class SoundAnalyzer : public AudioVariables

SoundAnalyzer()
{
ptrSampleBuffer = make_unique_psram_array<uint16_t>(MAX_SAMPLES);
ptrSampleBuffer.reset( (int16_t *)heap_caps_malloc(MAX_SAMPLES * sizeof(int16_t), MALLOC_CAP_8BIT) );
if (!ptrSampleBuffer)
throw std::runtime_error("Failed to allocate sample buffer");

_vReal = (double *)PreferPSRAMAlloc(MAX_SAMPLES * sizeof(_vReal[0]));
_vImaginary = (double *)PreferPSRAMAlloc(MAX_SAMPLES * sizeof(_vImaginary[0]));
_vPeaks = (double *)PreferPSRAMAlloc(NUM_BANDS * sizeof(_vPeaks[0]));
Expand Down Expand Up @@ -530,17 +523,13 @@ class SoundAnalyzer : public AudioVariables

#if USE_M5

auto miccfg = M5.Mic.config();
miccfg.over_sampling = 4;
miccfg.magnification = 1;
miccfg.dma_buf_count = 2;
miccfg.dma_buf_len = MAX_SAMPLES;
miccfg.sample_rate = SAMPLING_FREQUENCY;
miccfg.use_adc = false;
M5.Mic.config(miccfg);


// Can't use speaker and mic at the same time, and speaker defaults on, so turn it off

M5.Speaker.setVolume(255);
M5.Speaker.end();
M5.Mic.begin();

#elif ELECROW

const i2s_config_t i2s_config = {
Expand Down Expand Up @@ -582,8 +571,8 @@ class SoundAnalyzer : public AudioVariables

ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_12));
ESP_ERROR_CHECK(adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_0));
ESP_ERROR_CHECK(i2s_driver_install(EXAMPLE_I2S_NUM, &i2s_config, 0, NULL));
ESP_ERROR_CHECK(i2s_set_adc_mode(I2S_ADC_UNIT, I2S_ADC_CHANNEL));
ESP_ERROR_CHECK(i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL));
ESP_ERROR_CHECK(i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_0));

#else

Expand All @@ -600,8 +589,8 @@ class SoundAnalyzer : public AudioVariables

ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_12));
ESP_ERROR_CHECK(adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_0));
ESP_ERROR_CHECK(i2s_driver_install(EXAMPLE_I2S_NUM, &i2s_config, 0, NULL));
ESP_ERROR_CHECK(i2s_set_adc_mode(I2S_ADC_UNIT, I2S_ADC_CHANNEL));
ESP_ERROR_CHECK(i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL));
ESP_ERROR_CHECK(i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_0));

#endif

Expand Down
3 changes: 3 additions & 0 deletions src/audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ void IRAM_ATTR AudioSamplerTaskEntry(void *)
0.0 :
(g_Analyzer._VU - g_Analyzer._MinVU) / std::max(g_Analyzer._PeakVU - g_Analyzer._MinVU, (float) MIN_VU) * 2.0f;

debugV("VU: %f\n", g_Analyzer._VU);
debugV("PeakVU: %f\n", g_Analyzer._PeakVU);
debugV("MinVU: %f\n", g_Analyzer._MinVU);
debugV("VURatio: %f\n", g_Analyzer._VURatio);

// Delay enough time to yield 60fps max
Expand Down
12 changes: 6 additions & 6 deletions src/screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,8 @@ void CurrentEffectSummary(bool bRedraw)

// Draw the spectrum analyzer bars

int spectrumTop = topMargin + ySizeVU + 1; // Start at the bottom of the VU meter
int bandHeight = display.height() - spectrumTop - display.BottomMargin;
const int spectrumTop = topMargin + ySizeVU + 1; // Start at the bottom of the VU meter
const int bandHeight = display.height() - spectrumTop - display.BottomMargin;

for (int iBand = 0; iBand < NUM_BANDS; iBand++)
{
Expand All @@ -363,14 +363,14 @@ void CurrentEffectSummary(bool bRedraw)
auto val = min(1.0f, g_Analyzer._peak2Decay[iBand]);
assert(bandHeight * val <= bandHeight);
display.fillRect(iBand * bandWidth, spectrumTop + topSection, bandWidth - 1, bandHeight - topSection, color16);
for (int iLine = spectrumTop; iLine <= spectrumTop + bandHeight; iLine += display.width() / 40)
display.drawFastHLine(iBand * bandWidth, iLine, bandWidth, BLACK16);
}

display.EndFrame();

// Draw horizontal lines so the bars look like they are made of segments

// for (int iLine = spectrumTop; iLine <= spectrumTop + bandHeight; iLine += display.height() / 25)
// display.drawLine(0, iLine, display.width()-1, iLine, BLACK16);
display.EndFrame();

#endif
}

Expand Down

0 comments on commit 411d9cd

Please sign in to comment.