mirror of https://github.com/arendst/Tasmota.git
Audio support for Microphone + Berry (#19677)
This commit is contained in:
parent
69a3573f7e
commit
11aad19800
|
@ -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 */
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue