Audio support for Microphone + Berry (#19677)

This commit is contained in:
s-hadinger 2023-10-05 21:47:07 +02:00 committed by GitHub
parent 69a3573f7e
commit 11aad19800
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 165 additions and 11 deletions

View File

@ -139,6 +139,14 @@ BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_lowpass_alpha, "f", ".");
extern int be_audio_input_i2s_set_lowpass_alpha(void* in, float alpha);
BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_set_lowpass_alpha, "b", ".f");
// AudioInputI2S.rms_bytes(int16*:bytes) -> int
extern int be_audio_input_i2s_rms_bytes(void *buf, size_t len);
BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_rms_bytes, "i", "(bytes)~");
// AudioInputI2S.sqrt_fast(int) -> int
extern int be_audio_input_i2s_sqrt_fast(int in);
BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_sqrt_fast, "i", "i");
#include "be_fixed_be_class_AudioOutputI2S.h"
#include "be_fixed_be_class_AudioGenerator.h"
#include "be_fixed_be_class_AudioGeneratorWAV.h"
@ -201,6 +209,7 @@ class be_class_AudioFileSourceFS (scope: global, name: AudioFileSourceFS, super:
class be_class_AudioInputI2S (scope: global, name: AudioInputI2S, strings: weak) {
.p, var
peak, var
init, ctype_func(be_audio_input_i2s_init)
deinit, ctype_func(be_audio_input_i2s_deinit)
@ -218,6 +227,9 @@ class be_class_AudioInputI2S (scope: global, name: AudioInputI2S, strings: weak)
get_lowpass_alpha, ctype_func(be_audio_input_i2s_get_lowpass_alpha)
set_lowpass_alpha, ctype_func(be_audio_input_i2s_set_lowpass_alpha)
rms_bytes, static_ctype_func(be_audio_input_i2s_rms_bytes)
sqrt_fast, static_ctype_func(be_audio_input_i2s_sqrt_fast)
}
@const_object_info_end */

View File

@ -4,6 +4,7 @@
* To use: `import tasmota`
*******************************************************************/
#include "be_constobj.h"
#include "be_mapping.h"
#ifdef USE_LIGHT
extern int l_getlight(bvm *vm);
@ -13,6 +14,10 @@ extern int l_gamma8(bvm *vm);
extern int l_gamma10(bvm *vm);
extern int l_rev_gamma10(bvm *vm);
// light.set_bri(bri:int) -> nil
extern void l_set_bri(int bri);
BE_FUNC_CTYPE_DECLARE(l_set_bri, "", "i");
/* @const_object_info_begin
module light (scope: global) {
get, func(l_getlight)
@ -21,6 +26,8 @@ module light (scope: global) {
gamma8, func(l_gamma8)
gamma10, func(l_gamma10)
reverse_gamma10, func(l_rev_gamma10)
set_bri, ctype_func(l_set_bri)
}
@const_object_info_end */
#include "be_fixed_light.h"

View File

@ -0,0 +1,71 @@
# This is sample code for audio SLM (Sound Level Meter)
#
# It uses typical cheap mems microphone in PDM mode,
# and uses a DC block filter (high pass), a fixed gain
# and a low pass filter (currently cutting at 3KHz)
#
# add `import audio_slm` in `autoexec.be``
#
class Audio_SLM
var audio_input, fast_loop_closure
var buffer
var rms
var peak
var noise_gate # ignore below this level
def init()
# tasmota.cmd("SaveData 0")
self.buffer = bytes(1024) # resuse same buffer at each iteration
self.audio_input = AudioInputI2S()
self.audio_input.set_gain(30)
self.rms = 0
self.peak = 0
self.noise_gate = 150
self.audio_input.begin()
tasmota.delay(300) # wait for 300 ms for mems microphone to stabilize
self.fast_loop_closure = def () self.fast_loop() end
#tasmota.add_fast_loop(self.fast_loop_closure)
tasmota.add_driver(self)
tasmota.set_timer(100, /-> tasmota.add_fast_loop(self.fast_loop_closure)) # delay start by 1 second
end
def stop()
self.audio_input.stop()
tasmota.remove_fast_loop(self.fast_loop_closure)
tasmota.remove_driver(self)
end
def fast_loop()
var b = self.audio_input.read_bytes(self.buffer)
if b != nil
import light
var rms = AudioInputI2S.rms_bytes(b)
if rms < self.noise_gate
rms = 0
end
if rms > self.rms
self.rms = rms
end
var new_peak = self.audio_input.peak
if new_peak == nil new_peak = 0 end # failsafe
if new_peak > self.peak self.peak = new_peak end
# light.set_bri(rms / 80 #-range 0..255-#, true #-no save-#)
end
end
#- display sensor value in the web UI -#
def web_sensor()
var msg = format(
"{s}SLM Gain %ix{m}RMS: %i (peak %i%%){e}",
self.audio_input.get_gain(),
self.rms,
self.peak / 32767)
tasmota.web_send(msg)
self.peak = 0
self.rms = 0
end
end
audio_slm = Audio_SLM()
return audio_slm

View File

@ -169,7 +169,7 @@ public:
bool startI2SChannel(bool tx, bool rx);
int updateClockConfig(void);
int32_t readMic(uint8_t *buffer, uint32_t size, bool dc_block, bool apply_gain, bool lowpass);
int32_t readMic(uint8_t *buffer, uint32_t size, bool dc_block, bool apply_gain, bool lowpass, uint32_t *peak_ptr);
// The following is now the preferred function
// and allows to send multiple samples at once
@ -558,7 +558,11 @@ void TasmotaI2S::micDeinit(void) {
// n<0: error occured
// 0: no data available
// n>0: number of bytes read
int32_t TasmotaI2S::readMic(uint8_t *buffer, uint32_t size, bool dc_block, bool apply_gain, bool lowpass) {
// peak: peak value of the mic (can go above 32767 to indicate clipping)
int32_t TasmotaI2S::readMic(uint8_t *buffer, uint32_t size, bool dc_block, bool apply_gain, bool lowpass, uint32_t *peak_ptr) {
uint32_t peak = 0;
if (peak_ptr) { *peak_ptr = peak; }
if (!audio_i2s.in->getRxRunning()) { return -1; }
size_t btr = 0;
@ -587,14 +591,23 @@ int32_t TasmotaI2S::readMic(uint8_t *buffer, uint32_t size, bool dc_block, bool
}
// apply gain
if (apply_gain) {
for (uint32_t i=0; i<samples_count; i++) {
int32_t val = (((int32_t)samples[i]) * _rx_gain) / 0x10;
// clipping if overflow
if (val > 0x7FFF) { val = 0x7FFF; }
if (val < -0x8000) { val = -0x8000; }
samples[i] = val;
for (uint32_t i=0; i<samples_count; i++) {
int32_t val = samples[i];
if (apply_gain) {
val = (val * _rx_gain) / 0x10;
}
// clipping if overflow, and compute peak value
if (val > 0x7FFF) {
if (val > peak) { peak = val; } // clipping
val = 0x7FFF;
} else if (val < -0x8000) {
val = -0x8000;
if (-val > peak) { peak = -val; } // clipping
} else {
if ((val > 0) && (val > peak)) { peak = val; }
if ((val < 0) && (-val > peak)) { peak = -val; }
}
samples[i] = val;
}
// lowpass filter
@ -605,6 +618,7 @@ int32_t TasmotaI2S::readMic(uint8_t *buffer, uint32_t size, bool dc_block, bool
}
}
if (peak_ptr) { *peak_ptr = peak; }
return btr;
}

View File

@ -826,7 +826,7 @@ void CmndI2SMic(void) {
uint8_t buf[128];
size_t bytes_read = 0;
int32_t btr = audio_i2s.in->readMic(buf, sizeof(buf), true /*dc_block*/, false /*apply_gain*/, true /*lowpass*/);
int32_t btr = audio_i2s.in->readMic(buf, sizeof(buf), true /*dc_block*/, false /*apply_gain*/, true /*lowpass*/, nullptr /*peak_ptr*/);
if (btr < 0) {
AddLog(LOG_LEVEL_INFO, "I2S: Mic (err:%i)", -btr);
ResponseCmndChar("I2S Mic read error");

View File

@ -397,9 +397,15 @@ extern "C" {
if (!in->getRxRunning()) { be_return_nil(vm); }
uint16_t buf_audio[512];
int32_t btr = in->readMic((uint8_t*)buf_audio, sizeof(buf_audio), true /*dc_block*/, true /*apply_gain*/, true /*lowpass*/);
uint32_t peak;
int32_t btr = in->readMic((uint8_t*)buf_audio, sizeof(buf_audio), true /*dc_block*/, true /*apply_gain*/, true /*lowpass*/, &peak);
if (btr <= 0) { be_return_nil(vm); }
// update peak attribute
be_pushint(vm, peak);
be_setmember(vm, 1, "peak");
be_pop(vm, 1);
// we received some data
if (argc >= 2 && be_isbytes(vm, 2)) {
// we have already a bytes() buffer
@ -427,6 +433,45 @@ extern "C" {
be_return(vm); /* return code */
}
// Fast int sqrt from https://stackoverflow.com/questions/65986056/is-there-a-non-looping-unsigned-32-bit-integer-square-root-function-c
// Time spend is 5-7 microseconds
uint16_t be_isqrt32(uint32_t x) {
int lz = __builtin_clz(x | 1) & 30;
x <<= lz;
uint32_t y = 1 + (x >> 30);
y = (y << 1) + (x >> 27) / y;
y = (y << 3) + (x >> 21) / y;
y = (y << 7) + (x >> 9) / y;
y -= x < (uint32_t)y * y;
return y >> (lz >> 1);
}
// AudioInputI2S.sqrt_fast(int) -> int
// debug testing - fast sqrt
int be_audio_input_i2s_sqrt_fast(int in) {
uint32_t now = micros();
int32_t ret = be_isqrt32(in);
uint32_t now_after = micros();
AddLog(LOG_LEVEL_DEBUG, "sqrtf: %d -> %d (%d us)", in, ret, now_after - now);
return ret;
}
// AudioInputI2S.rms_bytes(int16*:bytes) -> int
// compute RMS of a bytes buffer
int be_audio_input_i2s_rms_bytes(void *buf, size_t len_bytes) {
size_t len = len_bytes / 2;
if (buf == NULL || len == 0) { return 0; } // failsafe
int16_t * buf16 = (int16_t*) buf;
uint32_t sum = 0;
for (int i = 0; i < len; i++) {
sum += (buf16[i] * buf16[i]) / 256;
// AddLog(LOG_LEVEL_DEBUG, "I2S: buf16[%i]:%i sum:%i", i, buf16[i], sum);
}
int32_t rms = be_isqrt32(sum / len * 256);
// AddLog(LOG_LEVEL_DEBUG, "I2S: buf:%p rms sum/len:%i sum:%i len:%i ret:%i", buf16, sum/len, sum, len, rms);
return rms;
}
}
#endif // USE_I2S_AUDIO_BERRY

View File

@ -320,6 +320,11 @@ extern "C" {
}
be_raise(vm, kTypeError, nullptr);
}
// light.set_bri(bri:int) -> nil
void l_set_bri(int bri) {
light_controller.changeBri(bri);
}
}
#endif // USE_LIGHT