diff --git a/include/effects/matrix/PatternStocks.h b/include/effects/matrix/PatternStocks.h index 5ac79411..d1f56b9f 100644 --- a/include/effects/matrix/PatternStocks.h +++ b/include/effects/matrix/PatternStocks.h @@ -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) diff --git a/include/effects/matrix/spectrumeffects.h b/include/effects/matrix/spectrumeffects.h index 4a579daf..ce96ec8a 100644 --- a/include/effects/matrix/spectrumeffects.h +++ b/include/effects/matrix/spectrumeffects.h @@ -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); diff --git a/include/effects/strip/particles.h b/include/effects/strip/particles.h index b393a509..f328eb6c 100644 --- a/include/effects/strip/particles.h +++ b/include/effects/strip/particles.h @@ -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); diff --git a/include/globals.h b/include/globals.h index 9153daaa..002d3ee1 100644 --- a/include/globals.h +++ b/include/globals.h @@ -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 @@ -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 @@ -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) @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/include/soundanalyzer.h b/include/soundanalyzer.h index 2d61fdf7..a2c8a35b 100644 --- a/include/soundanalyzer.h +++ b/include/soundanalyzer.h @@ -36,14 +36,6 @@ #include #include #include -// #include - -#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 @@ -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 *); @@ -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) @@ -155,26 +145,26 @@ class PeakData case MESMERIZERMIC: { static constexpr std::array 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 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 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 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 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; } } @@ -202,13 +192,13 @@ class PeakData class SoundAnalyzer : public AudioVariables { static constexpr size_t MAX_SAMPLES = 256; - std::unique_ptr ptrSampleBuffer; + std::unique_ptr 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); @@ -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) @@ -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) @@ -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]); } } @@ -489,7 +479,10 @@ class SoundAnalyzer : public AudioVariables SoundAnalyzer() { - ptrSampleBuffer = make_unique_psram_array(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])); @@ -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 = { @@ -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 @@ -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 diff --git a/src/audio.cpp b/src/audio.cpp index a8c87d44..4ad5dd65 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -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 diff --git a/src/screen.cpp b/src/screen.cpp index 5a62e479..d3f789d2 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -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++) { @@ -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 }