Skip to content

Commit d0bdb61

Browse files
committed
add option to use esp-dsp for IIR filters
1 parent 01c712b commit d0bdb61

File tree

9 files changed

+95
-39
lines changed

9 files changed

+95
-39
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
name: ${{ matrix.name }}
1212
runs-on: ubuntu-latest
1313
env:
14-
ESPHOME_VERSION: 2025.7.2
14+
ESPHOME_VERSION: 2025.7.4
1515
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
1616
strategy:
1717
fail-fast: false

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@ sound_level_meter:
8383
# additional offset if needed
8484
offset: 0dB # default: empty
8585

86+
# if you have many filters, you might consider using the IIR filter
87+
# implementation from the esp-dsp library. it's written in assembly
88+
# and includes several optimized versions, particularly performant on
89+
# ESP32 or ESP32-S3, and can provide up to 2x faster processing compared
90+
# to my C++ implementation. esp-dsp version uses direct form II, whereas
91+
# the C++ version uses the direct form II transposed. the latter may offer
92+
# slightly better numerical stability, but in most cases, the difference
93+
# is likely negligible.
94+
use_esp_dsp: false # default: false
95+
8696
# under dsp_filters section you can define multiple filters,
8797
# which can be referenced later by each sensor
8898

components/sound_level_meter/__init__.py

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from esphome import automation, core
66
from esphome.automation import maybe_simple_id
77
from esphome.components import sensor, microphone
8+
from esphome.components.esp32 import add_idf_component
89
from esphome.const import (
910
CONF_ID,
1011
CONF_SENSORS,
@@ -62,6 +63,7 @@
6263
CONF_HIGH_FREQ = "high_freq"
6364
CONF_DSP_FILTERS = "dsp_filters"
6465
CONF_AUTO_START = "auto_start"
66+
CONF_USE_ESP_DSP = "use_esp_dsp"
6567

6668
ICON_WAVEFORM = "mdi:waveform"
6769

@@ -147,28 +149,42 @@
147149
}
148150
)
149151

150-
CONFIG_SCHEMA = cv.Schema(
151-
{
152-
cv.GenerateID(): cv.declare_id(SoundLevelMeter),
153-
cv.Optional(CONF_MICROPHONE, default={}): microphone.microphone_source_schema(
154-
min_bits_per_sample=16,
155-
max_bits_per_sample=32,
156-
),
157-
cv.Optional(CONF_UPDATE_INTERVAL, default="60s"): cv.positive_time_period_milliseconds,
158-
cv.Optional(CONF_AUTO_START, default=True): cv.boolean,
159-
cv.Optional(CONF_HIGH_FREQ, default=False): cv.boolean,
160-
cv.Optional(CONF_RING_BUFFER_SIZE, default="100ms"): cv.positive_time_period_milliseconds,
161-
cv.Optional(CONF_WARMUP_INTERVAL, default="0ms"): cv.positive_time_period_milliseconds,
162-
cv.Optional(CONF_TASK_STACK_SIZE, default=4096): cv.positive_not_null_int,
163-
cv.Optional(CONF_TASK_PRIORITY, default=2): cv.uint8_t,
164-
cv.Optional(CONF_TASK_CORE, default=1): cv.int_range(0, 1),
165-
cv.Optional(CONF_MIC_SENSITIVITY): cv.decibel,
166-
cv.Optional(CONF_MIC_SENSITIVITY_REF): cv.decibel,
167-
cv.Optional(CONF_OFFSET): cv.decibel,
168-
cv.Optional(CONF_DSP_FILTERS, default=[]): [CONFIG_DSP_FILTER_SCHEMA],
169-
cv.Optional(CONF_SENSORS, default=[]): [CONFIG_SENSOR_SCHEMA],
170-
}
171-
).extend(cv.COMPONENT_SCHEMA)
152+
CONFIG_SCHEMA = cv.All(
153+
cv.Schema(
154+
{
155+
cv.GenerateID(): cv.declare_id(SoundLevelMeter),
156+
cv.Optional(
157+
CONF_MICROPHONE, default={}
158+
): microphone.microphone_source_schema(
159+
min_bits_per_sample=16,
160+
max_bits_per_sample=32,
161+
),
162+
cv.Optional(
163+
CONF_UPDATE_INTERVAL, default="60s"
164+
): cv.positive_time_period_milliseconds,
165+
cv.Optional(CONF_AUTO_START, default=True): cv.boolean,
166+
cv.Optional(CONF_HIGH_FREQ, default=False): cv.boolean,
167+
cv.Optional(
168+
CONF_RING_BUFFER_SIZE, default="100ms"
169+
): cv.positive_time_period_milliseconds,
170+
cv.Optional(
171+
CONF_WARMUP_INTERVAL, default="0ms"
172+
): cv.positive_time_period_milliseconds,
173+
cv.Optional(CONF_TASK_STACK_SIZE, default=4096): cv.positive_not_null_int,
174+
cv.Optional(CONF_TASK_PRIORITY, default=2): cv.uint8_t,
175+
cv.Optional(CONF_TASK_CORE, default=1): cv.int_range(0, 1),
176+
cv.Optional(CONF_MIC_SENSITIVITY): cv.decibel,
177+
cv.Optional(CONF_MIC_SENSITIVITY_REF): cv.decibel,
178+
cv.Optional(CONF_OFFSET): cv.decibel,
179+
cv.Optional(CONF_DSP_FILTERS, default=[]): [CONFIG_DSP_FILTER_SCHEMA],
180+
cv.Optional(CONF_SENSORS, default=[]): [CONFIG_SENSOR_SCHEMA],
181+
cv.Optional(CONF_USE_ESP_DSP, default=False): cv.All(
182+
cv.boolean, cv.only_with_esp_idf
183+
),
184+
}
185+
).extend(cv.COMPONENT_SCHEMA),
186+
cv.only_on_esp32,
187+
)
172188

173189
SOUND_LEVEL_METER_ACTION_SCHEMA = maybe_simple_id(
174190
{cv.GenerateID(): cv.use_id(SoundLevelMeter)}
@@ -223,6 +239,9 @@ async def to_code(config):
223239
cg.add(var.set_mic_sensitivity_ref(config[CONF_MIC_SENSITIVITY_REF]))
224240
if CONF_OFFSET in config:
225241
cg.add(var.set_offset(config[CONF_OFFSET]))
242+
if config[CONF_USE_ESP_DSP]:
243+
add_idf_component(name="espressif/esp-dsp", ref="1.7.0")
244+
cg.add_define("USE_ESP_DSP")
226245

227246
for fc in config[CONF_DSP_FILTERS]:
228247
await add_dsp_filter(fc, var)

components/sound_level_meter/sound_level_meter.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,17 @@ void SOS_Filter::process(std::vector<float> &data) {
444444
int n = data.size();
445445
int m = this->coeffs_.size();
446446
for (int j = 0; j < m; j++) {
447+
#ifdef USE_ESP_DSP
448+
#if defined(USE_ESP32_VARIANT_ESP32)
449+
dsps_biquad_f32_ae32(&data[0], &data[0], data.size(), &this->coeffs_[j][0], &this->state_[j][0]);
450+
#elif defined(USE_ESP32_VARIANT_ESP32S3)
451+
dsps_biquad_f32_aes3(&data[0], &data[0], data.size(), &this->coeffs_[j][0], &this->state_[j][0]);
452+
#elif defined(USE_ESP32_VARIANT_ESP32P4)
453+
dsps_biquad_f32_arp4(&data[0], &data[0], data.size(), &this->coeffs_[j][0], &this->state_[j][0]);
454+
#else
455+
dsps_biquad_f32_ansi(&data[0], &data[0], data.size(), &this->coeffs_[j][0], &this->state_[j][0]);
456+
#endif
457+
#else
447458
for (int i = 0; i < n; i++) {
448459
// y[i] = b0 * x[i] + s0
449460
float yi = this->coeffs_[j][0] * data[i] + this->state_[j][0];
@@ -454,6 +465,7 @@ void SOS_Filter::process(std::vector<float> &data) {
454465

455466
data[i] = yi;
456467
}
468+
#endif
457469
}
458470
}
459471

components/sound_level_meter/sound_level_meter.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
#include "esphome/components/sensor/sensor.h"
1313
#include "esphome/components/microphone/microphone_source.h"
1414

15+
#ifdef USE_ESP_DSP
16+
#include "dsps_biquad.h"
17+
#endif
18+
1519
namespace esphome::sound_level_meter {
1620
class SoundLevelMeterSensor;
1721
class Filter;

configs/10-bands-spectrum-analyzer-example-config.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ external_components:
66
- source: github://stas-sl/esphome-sound-level-meter
77

88
esp32:
9-
board: esp32-s3-devkitc-1
10-
variant: esp32s3
9+
board: esp32dev
1110
cpu_frequency: 240MHz
1211
framework:
1312
type: esp-idf

configs/advanced-example-config.yaml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ esp32:
1010
board: esp32dev
1111
cpu_frequency: 240MHz
1212
framework:
13-
type: arduino
13+
type: esp-idf
1414

1515
logger:
1616
level: DEBUG
@@ -75,6 +75,16 @@ sound_level_meter:
7575
# additional offset if needed
7676
offset: 0dB # default: empty
7777

78+
# if you have many filters, you might consider using the IIR filter
79+
# implementation from the esp-dsp library. it's written in assembly
80+
# and includes several optimized versions, particularly performant on
81+
# ESP32 or ESP32-S3, and can provide up to 2x faster processing compared
82+
# to my C++ implementation. esp-dsp version uses direct form II, whereas
83+
# the C++ version uses the direct form II transposed. the latter may offer
84+
# slightly better numerical stability, but in most cases, the difference
85+
# is likely negligible.
86+
use_esp_dsp: false # default: false
87+
7888
# under dsp_filters section you can define multiple filters,
7989
# which can be referenced later by each sensor
8090

configs/minimal-example-config.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ external_components:
77

88
esp32:
99
board: esp32dev
10+
cpu_frequency: 240MHz
1011
framework:
11-
type: arduino
12+
type: esp-idf
1213

1314
logger:
1415
level: DEBUG

configs/sensor-community-example-config.yaml

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ external_components:
77

88
esp32:
99
board: esp32dev
10+
cpu_frequency: 240MHz
1011
framework:
11-
type: arduino
12+
type: esp-idf
1213

1314
logger:
1415
level: DEBUG
@@ -102,17 +103,17 @@ interval:
102103
X-Sensor: esp32-... # replace with your sensor ID
103104
Content-Type: application/json
104105
json: |-
105-
root["software_version"] = "ESPHome " ESPHOME_VERSION;
106-
auto values = root.createNestedArray("sensordatavalues");
106+
root["software_version"] = "ESPHome " ESPHOME_VERSION;
107+
auto values = root["sensordatavalues"].to<JsonArray>();
107108
108-
auto LA_eq = values.createNestedObject();
109-
LA_eq["value_type"] = "noise_LAeq";
110-
LA_eq["value"] = id(LAeq_1min).state;
109+
auto LA_eq = values.add<JsonObject>();
110+
LA_eq["value_type"] = "noise_LAeq";
111+
LA_eq["value"] = id(LAeq_1min).state;
111112
112-
auto LA_min = values.createNestedObject();
113-
LA_min["value_type"] = "noise_LA_min";
114-
LA_min["value"] = id(LAmin_125ms_1min).state;
113+
auto LA_min = values.add<JsonObject>();
114+
LA_min["value_type"] = "noise_LA_min";
115+
LA_min["value"] = id(LAmin_125ms_1min).state;
115116
116-
auto LA_max = values.createNestedObject();
117-
LA_max["value_type"] = "noise_LA_max";
118-
LA_max["value"] = id(LAmax_125ms_1min).state;
117+
auto LA_max = values.add<JsonObject>();
118+
LA_max["value_type"] = "noise_LA_max";
119+
LA_max["value"] = id(LAmax_125ms_1min).state;

0 commit comments

Comments
 (0)