8d874ae31b
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
185 lines
6.5 KiB
C++
185 lines
6.5 KiB
C++
// hardware_manager.h - WS2812 + microphone + battery helpers for Freenove board.
|
|
#pragma once
|
|
|
|
#include <Arduino.h>
|
|
|
|
#include <Adafruit_NeoPixel.h>
|
|
#include <driver/i2s.h>
|
|
|
|
class HardwareManager {
|
|
public:
|
|
static constexpr uint8_t kMicWaveformCapacity = 16U;
|
|
static constexpr uint8_t kMicSpectrumBinCount = 5U;
|
|
|
|
enum class LedRuntimeMode : uint8_t {
|
|
kPalette = 0,
|
|
kBroken,
|
|
kTuner,
|
|
kSingleRandomBlink,
|
|
kDominantBandSingle,
|
|
};
|
|
|
|
struct LedPaletteEntry {
|
|
const char* scene_id;
|
|
uint8_t r;
|
|
uint8_t g;
|
|
uint8_t b;
|
|
uint8_t brightness;
|
|
bool pulse;
|
|
};
|
|
|
|
struct Snapshot {
|
|
bool ready = false;
|
|
bool ws2812_ready = false;
|
|
bool mic_ready = false;
|
|
bool battery_ready = false;
|
|
bool charging = false;
|
|
bool led_manual = false;
|
|
uint8_t led_r = 0U;
|
|
uint8_t led_g = 0U;
|
|
uint8_t led_b = 0U;
|
|
uint8_t led_brightness = 0U;
|
|
bool led_one_at_a_time = false;
|
|
uint8_t mic_level_percent = 0U;
|
|
uint16_t mic_peak = 0U;
|
|
uint16_t mic_noise_floor = 0U;
|
|
uint16_t mic_gain_percent = 100U;
|
|
uint16_t mic_freq_hz = 0U;
|
|
int16_t mic_pitch_cents = 0;
|
|
uint8_t mic_pitch_confidence = 0U;
|
|
uint8_t mic_waveform_count = 0U;
|
|
uint8_t mic_waveform_head = 0U;
|
|
uint8_t mic_waveform[kMicWaveformCapacity] = {0};
|
|
uint8_t mic_spectrum[kMicSpectrumBinCount] = {0};
|
|
uint16_t mic_spectrum_peak_hz = 0U;
|
|
uint16_t battery_mv = 0U;
|
|
uint16_t battery_cell_mv = 0U;
|
|
uint8_t battery_percent = 0U;
|
|
uint8_t last_button = 0U;
|
|
bool last_button_long = false;
|
|
uint32_t last_button_ms = 0U;
|
|
uint32_t button_count = 0U;
|
|
char led_mode[24] = "palette";
|
|
char scene_id[24] = "SCENE_READY";
|
|
};
|
|
|
|
HardwareManager();
|
|
~HardwareManager() = default;
|
|
HardwareManager(const HardwareManager&) = delete;
|
|
HardwareManager& operator=(const HardwareManager&) = delete;
|
|
HardwareManager(HardwareManager&&) = delete;
|
|
HardwareManager& operator=(HardwareManager&&) = delete;
|
|
|
|
bool begin();
|
|
void update(uint32_t now_ms);
|
|
void noteButton(uint8_t key, bool long_press, uint32_t now_ms);
|
|
void setSceneHint(const char* scene_id);
|
|
bool setManualLed(uint8_t r, uint8_t g, uint8_t b, uint8_t brightness, bool pulse);
|
|
void clearManualLed();
|
|
Snapshot snapshot() const;
|
|
const Snapshot& snapshotRef() const;
|
|
void setMicRuntimeEnabled(bool enabled);
|
|
bool micRuntimeEnabled() const;
|
|
void setSceneSingleRandomBlink(bool enabled,
|
|
uint8_t r,
|
|
uint8_t g,
|
|
uint8_t b,
|
|
uint8_t brightness,
|
|
bool dominant_band_sync = false);
|
|
|
|
private:
|
|
bool beginMic();
|
|
void endMic();
|
|
void updateMic(uint32_t now_ms);
|
|
void updateBattery(uint32_t now_ms);
|
|
void updateLed(uint32_t now_ms);
|
|
bool isBrokenSceneHint() const;
|
|
bool isTunerSceneHint() const;
|
|
void applyBrokenLedPattern(uint32_t now_ms, uint8_t base_r, uint8_t base_g, uint8_t base_b, uint8_t brightness);
|
|
void applyTunerLedPattern(uint32_t now_ms,
|
|
uint8_t base_r,
|
|
uint8_t base_g,
|
|
uint8_t base_b,
|
|
uint8_t brightness);
|
|
void applySingleRandomBlinkPattern(uint32_t now_ms,
|
|
uint8_t base_r,
|
|
uint8_t base_g,
|
|
uint8_t base_b,
|
|
uint8_t brightness);
|
|
void applyDominantBandSinglePattern(uint32_t now_ms, uint8_t brightness);
|
|
void estimatePitch(uint16_t& freq_hz, int16_t& cents, uint8_t& confidence, uint16_t& peak_for_window);
|
|
void estimatePitchFromSamples(const int16_t* samples,
|
|
size_t sample_count,
|
|
uint16_t& out_freq,
|
|
int16_t& out_cents,
|
|
uint8_t& out_confidence);
|
|
void applyPitchSmoothing(uint32_t now_ms,
|
|
uint16_t raw_freq,
|
|
int16_t raw_cents,
|
|
uint8_t raw_confidence,
|
|
uint16_t& smoothed_freq,
|
|
int16_t& smoothed_cents,
|
|
uint8_t& smoothed_confidence);
|
|
void setScenePalette(const char* scene_id);
|
|
const LedPaletteEntry* findPaletteForScene(const char* scene_id) const;
|
|
uint8_t batteryPercentFromMv(uint16_t cell_mv) const;
|
|
static uint8_t clampColor(int value);
|
|
|
|
static constexpr uint16_t kMicSampleRate = 16000U;
|
|
static constexpr uint16_t kMicReadSamples = 256U;
|
|
static constexpr uint32_t kMicPeriodMs = 40U;
|
|
static constexpr uint32_t kBatteryPeriodMs = 1200U;
|
|
static constexpr uint32_t kLedPeriodMs = 33U;
|
|
static constexpr uint32_t kButtonFlashMs = 180U;
|
|
static constexpr i2s_port_t kMicPort = I2S_NUM_1;
|
|
|
|
Snapshot snapshot_;
|
|
Adafruit_NeoPixel strip_;
|
|
|
|
bool mic_driver_ready_ = false;
|
|
bool led_pulse_ = false;
|
|
uint32_t next_mic_ms_ = 0U;
|
|
uint32_t next_battery_ms_ = 0U;
|
|
uint32_t next_led_ms_ = 0U;
|
|
uint32_t button_flash_until_ms_ = 0U;
|
|
|
|
uint8_t scene_r_ = 0U;
|
|
uint8_t scene_g_ = 0U;
|
|
uint8_t scene_b_ = 0U;
|
|
uint8_t scene_brightness_ = 0U;
|
|
|
|
bool manual_led_ = false;
|
|
bool manual_pulse_ = false;
|
|
uint8_t manual_r_ = 0U;
|
|
uint8_t manual_g_ = 0U;
|
|
uint8_t manual_b_ = 0U;
|
|
uint8_t manual_brightness_ = 0U;
|
|
bool scene_single_random_blink_ = false;
|
|
uint8_t scene_single_blink_r_ = 0U;
|
|
uint8_t scene_single_blink_g_ = 0U;
|
|
uint8_t scene_single_blink_b_ = 0U;
|
|
uint8_t scene_single_blink_brightness_ = 0U;
|
|
bool scene_single_blink_dominant_band_ = false;
|
|
LedRuntimeMode led_runtime_mode_ = LedRuntimeMode::kPalette;
|
|
uint16_t mic_agc_gain_q8_ = 256U;
|
|
uint16_t mic_noise_floor_raw_ = 120U;
|
|
uint32_t mic_last_signal_ms_ = 0U;
|
|
float pitch_confidence_ema_ = 0.0f;
|
|
uint8_t pitch_smoothing_count_ = 0U;
|
|
uint8_t pitch_smoothing_index_ = 0U;
|
|
uint32_t pitch_smoothing_last_ms_ = 0U;
|
|
static constexpr uint8_t kPitchSmoothingSamples = 3U;
|
|
static constexpr uint16_t kPitchSmoothingStaleMs = 260U;
|
|
uint16_t pitch_freq_window_[kPitchSmoothingSamples] = {0U};
|
|
int16_t pitch_cents_window_[kPitchSmoothingSamples] = {0};
|
|
uint8_t pitch_conf_window_[kPitchSmoothingSamples] = {0U};
|
|
bool mic_enabled_runtime_ = true;
|
|
|
|
// Keep DSP buffers off the loop task stack to avoid canary overflows.
|
|
int32_t mic_raw_samples_[kMicReadSamples] = {};
|
|
int16_t mic_samples_[kMicReadSamples] = {};
|
|
float pitch_centered_[kMicReadSamples] = {};
|
|
float pitch_energy_prefix_[kMicReadSamples + 1U] = {};
|
|
float pitch_corr_by_lag_[kMicReadSamples + 1U] = {};
|
|
};
|