From 47919fc1400bded69fcfb446efa3f16729b53f14 Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Wed, 24 Aug 2022 14:37:44 +0900 Subject: [PATCH] Important change: initial channel volume value from 64 to 255. ( #33 ) Change I2S port, I2S0 is used when DAC and ADC are used, I2S1 is used otherwise --- .../Bluetooth_with_ESP32A2DP.ino | 6 +- .../MP3_with_ESP8266Audio.ino | 6 +- .../WebRadio_with_ESP8266Audio.ino | 6 +- examples/Basic/Speaker/Speaker.ino | 16 +- src/M5Unified.cpp | 25 +- src/utility/MPU6886_Class.cpp | 4 +- src/utility/Mic_Class.cpp | 37 +- src/utility/Mic_Class.hpp | 13 +- src/utility/SH200Q_Class.cpp | 2 + src/utility/Speaker_Class.cpp | 413 ++++++++++-------- src/utility/Speaker_Class.hpp | 6 +- 11 files changed, 303 insertions(+), 231 deletions(-) diff --git a/examples/Advanced/Bluetooth_with_ESP32A2DP/Bluetooth_with_ESP32A2DP.ino b/examples/Advanced/Bluetooth_with_ESP32A2DP/Bluetooth_with_ESP32A2DP.ino index ab9674d..e656505 100644 --- a/examples/Advanced/Bluetooth_with_ESP32A2DP/Bluetooth_with_ESP32A2DP.ino +++ b/examples/Advanced/Bluetooth_with_ESP32A2DP/Bluetooth_with_ESP32A2DP.ino @@ -405,7 +405,7 @@ void gfxLoop(LGFX_Device* gfx) if (!gfx->displayBusy()) { // draw volume bar static int px; - uint8_t v = M5.Speaker.getChannelVolume(m5spk_virtual_channel); + uint8_t v = M5.Speaker.getVolume(); int x = v * (gfx->width()) >> 8; if (px != x) { @@ -634,7 +634,7 @@ void loop(void) } if (M5.BtnA.isHolding() || M5.BtnB.isPressed() || M5.BtnC.isPressed()) { - size_t v = M5.Speaker.getChannelVolume(m5spk_virtual_channel); + size_t v = M5.Speaker.getVolume(); int add = (M5.BtnB.isPressed()) ? -1 : 1; if (M5.BtnA.isHolding()) { @@ -643,7 +643,7 @@ void loop(void) v += add; if (v <= 255) { - M5.Speaker.setChannelVolume(m5spk_virtual_channel, v); + M5.Speaker.setVolume(v); } } } diff --git a/examples/Advanced/MP3_with_ESP8266Audio/MP3_with_ESP8266Audio.ino b/examples/Advanced/MP3_with_ESP8266Audio/MP3_with_ESP8266Audio.ino index a07fa85..d3bd21a 100644 --- a/examples/Advanced/MP3_with_ESP8266Audio/MP3_with_ESP8266Audio.ino +++ b/examples/Advanced/MP3_with_ESP8266Audio/MP3_with_ESP8266Audio.ino @@ -283,7 +283,7 @@ void gfxLoop(LGFX_Device* gfx) if (!gfx->displayBusy()) { // draw volume bar static int px; - uint8_t v = M5.Speaker.getChannelVolume(m5spk_virtual_channel); + uint8_t v = M5.Speaker.getVolume(); int x = v * (gfx->width()) >> 8; if (px != x) { @@ -490,11 +490,11 @@ void loop(void) else if (M5.BtnA.isHolding() || M5.BtnB.isPressed() || M5.BtnC.isPressed()) { - size_t v = M5.Speaker.getChannelVolume(m5spk_virtual_channel); + size_t v = M5.Speaker.getVolume(); if (M5.BtnB.isPressed()) { --v; } else { ++v; } if (v <= 255 || M5.BtnA.isHolding()) { - M5.Speaker.setChannelVolume(m5spk_virtual_channel, v); + M5.Speaker.setVolume(v); } } } diff --git a/examples/Advanced/WebRadio_with_ESP8266Audio/WebRadio_with_ESP8266Audio.ino b/examples/Advanced/WebRadio_with_ESP8266Audio/WebRadio_with_ESP8266Audio.ino index 8f84f9e..4e4e02d 100644 --- a/examples/Advanced/WebRadio_with_ESP8266Audio/WebRadio_with_ESP8266Audio.ino +++ b/examples/Advanced/WebRadio_with_ESP8266Audio/WebRadio_with_ESP8266Audio.ino @@ -552,7 +552,7 @@ void gfxLoop(LGFX_Device* gfx) if (!gfx->displayBusy()) { // draw volume bar static int px; - uint8_t v = M5.Speaker.getChannelVolume(m5spk_virtual_channel); + uint8_t v = M5.Speaker.getVolume(); int x = v * (gfx->width()) >> 8; if (px != x) { @@ -654,7 +654,7 @@ void loop(void) } if (M5.BtnA.isHolding() || M5.BtnB.isPressed() || M5.BtnC.isPressed()) { - size_t v = M5.Speaker.getChannelVolume(m5spk_virtual_channel); + size_t v = M5.Speaker.getVolume(); int add = (M5.BtnB.isPressed()) ? -1 : 1; if (M5.BtnA.isHolding()) { @@ -663,7 +663,7 @@ void loop(void) v += add; if (v <= 255) { - M5.Speaker.setChannelVolume(m5spk_virtual_channel, v); + M5.Speaker.setVolume(v); } } } diff --git a/examples/Basic/Speaker/Speaker.ino b/examples/Basic/Speaker/Speaker.ino index 2b43c3e..d9818ef 100644 --- a/examples/Basic/Speaker/Speaker.ino +++ b/examples/Basic/Speaker/Speaker.ino @@ -191,14 +191,14 @@ void setup(void) M5.Display.fillScreen(TFT_DARKGRAY); M5.Display.print("SOUND TEST"); - /// The setVolume function can be set the master volume in the range of 0-255. + /// The setVolume function can be set the master volume in the range of 0-255. (default : 64) M5.Speaker.setVolume(64); - /// The setAllChannelVolume function can be set the all virtual channel volume in the range of 0-255. - M5.Speaker.setAllChannelVolume(64); + /// The setAllChannelVolume function can be set the all virtual channel volume in the range of 0-255. (default : 255) + M5.Speaker.setAllChannelVolume(255); - /// The setChannelVolume function can be set the specified virtual channel volume in the range of 0-255. - M5.Speaker.setChannelVolume(0, 64); + /// The setChannelVolume function can be set the specified virtual channel volume in the range of 0-255. (default : 255) + M5.Speaker.setChannelVolume(0, 255); /// play 2000Hz tone sound, 100 msec. M5.Speaker.tone(2000, 100); @@ -236,7 +236,7 @@ void setup(void) delay(500); M5.Speaker.setVolume(0); - M5.Speaker.tone(220); // tone 220Hz sound output. (Keeps output until it stops.) + M5.Speaker.tone(880); // tone 880Hz sound output. (Keeps output until it stops.) for (int i = 0; i <= 64; i++) { M5.Speaker.setVolume(i); // Volume can be changed during sound output. @@ -296,8 +296,8 @@ void loop(void) { if (!M5.Display.displayBusy()) { - static uint8_t prev_channelvolume; - static uint8_t prev_mastervolume; + static int32_t prev_channelvolume; + static int32_t prev_mastervolume; int32_t m_vol = (M5.Speaker.getVolume() * (M5.Display.height() - menu_y)) >> 8; int32_t c_vol = (M5.Speaker.getChannelVolume(0) * (M5.Display.height() - menu_y)) >> 8; if (prev_mastervolume != m_vol diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 4afe35e..6ca7080 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -47,7 +47,19 @@ namespace m5 #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) case board_t::board_M5StackCore2: case board_t::board_M5Tough: - self->Power.Axp192.setGPIO2(enabled); + { + auto cfg = self->Speaker.config(); + if (cfg.pin_bck == GPIO_NUM_12) + { + self->Power.Axp192.setGPIO2(false); + if (enabled) + { // To prevent I2S mis-synchronization, turn off the setting once and set the BCLK pin low. + m5gfx::pinMode(GPIO_NUM_12, m5gfx::pin_mode_t::output); + m5gfx::gpio_lo(GPIO_NUM_12); + self->Power.Axp192.setGPIO2(true); + } + } + } break; case board_t::board_M5StickC: @@ -314,6 +326,7 @@ namespace m5 auto mic_cfg = Mic.config(); mic_cfg.over_sampling = 2; + mic_cfg.i2s_port = I2S_NUM_1; switch (_board) { #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) @@ -321,6 +334,7 @@ namespace m5 if (_cfg.internal_mic) { mic_cfg.pin_data_in = GPIO_NUM_34; // M5GO bottom MIC + mic_cfg.i2s_port = I2S_NUM_0; mic_cfg.use_adc = true; // use ADC analog input mic_cfg.input_offset = 192; mic_cfg.over_sampling = 4; @@ -368,6 +382,7 @@ namespace m5 auto spk_cfg = Speaker.config(); // set default speaker gain. spk_cfg.magnification = 16; + spk_cfg.i2s_port = I2S_NUM_1; switch (_board) { #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) @@ -376,6 +391,7 @@ namespace m5 { m5gfx::gpio_lo(GPIO_NUM_25); m5gfx::pinMode(GPIO_NUM_25, m5gfx::pin_mode_t::output); + spk_cfg.i2s_port = I2S_NUM_0; spk_cfg.use_dac = true; spk_cfg.pin_data_out = GPIO_NUM_25; spk_cfg.magnification = 8; @@ -388,7 +404,7 @@ namespace m5 { spk_cfg.buzzer = true; spk_cfg.pin_data_out = GPIO_NUM_2; - spk_cfg.magnification = 32; + spk_cfg.magnification = 48; } NON_BREAK; case board_t::board_M5StickC: @@ -400,6 +416,7 @@ namespace m5 m5gfx::gpio_lo(GPIO_NUM_26); m5gfx::pinMode(GPIO_NUM_26, m5gfx::pin_mode_t::output); spk_cfg.pin_data_out = GPIO_NUM_26; + spk_cfg.i2s_port = I2S_NUM_0; spk_cfg.use_dac = true; spk_cfg.buzzer = false; spk_cfg.magnification = 32; @@ -407,8 +424,8 @@ namespace m5 break; case board_t::board_M5Tough: - // The gain is set higher than Core2 here because the waterproof case reduces the sound.; - spk_cfg.magnification = 32; + // The magnification is set higher than Core2 here because the waterproof case reduces the sound.; + spk_cfg.magnification = 24; NON_BREAK; case board_t::board_M5StackCore2: if (_cfg.internal_spk) diff --git a/src/utility/MPU6886_Class.cpp b/src/utility/MPU6886_Class.cpp index 0e3bc71..1f880d1 100644 --- a/src/utility/MPU6886_Class.cpp +++ b/src/utility/MPU6886_Class.cpp @@ -2,8 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. #include "MPU6886_Class.hpp" - -#include +#include +#include namespace m5 { diff --git a/src/utility/Mic_Class.cpp b/src/utility/Mic_Class.cpp index 1d528cd..389f950 100644 --- a/src/utility/Mic_Class.cpp +++ b/src/utility/Mic_Class.cpp @@ -22,6 +22,7 @@ namespace m5 { +#if defined ( ESP_PLATFORM ) #if defined (ESP_IDF_VERSION_VAL) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) #define COMM_FORMAT_I2S (I2S_COMM_FORMAT_STAND_I2S) @@ -44,14 +45,11 @@ namespace m5 uint32_t Mic_Class::_calc_rec_rate(void) const { int rate = (_cfg.sample_rate * _cfg.over_sampling); - if (_cfg.use_adc) { rate *= 1.004f; } return rate; } esp_err_t Mic_Class::_setup_i2s(void) { - i2s_driver_uninstall(_cfg.i2s_port); - if (_cfg.pin_data_in < 0) { return ESP_FAIL; } SAMPLE_RATE_TYPE sample_rate = _calc_rec_rate(); @@ -61,27 +59,39 @@ namespace m5 ・I2S_MODE_ADC_BUILT_INを使用せずに初期化を行う ・最後にI2S0のレジスタを操作してADCモードを有効にする。 */ - if (_cfg.use_adc) { sample_rate >>= 4; } - + bool use_pdm = (_cfg.pin_bck < 0); + if (_cfg.use_adc) { sample_rate >>= 4; use_pdm = false;} +ESP_LOGV("Mic","sampling rate:%d", sample_rate); i2s_config_t i2s_config; memset(&i2s_config, 0, sizeof(i2s_config_t)); - i2s_config.mode = _cfg.use_adc - ? (i2s_mode_t)( I2S_MODE_MASTER | I2S_MODE_RX ) - : (i2s_mode_t)( I2S_MODE_MASTER | I2S_MODE_RX | (0x1 << 6) ); // 0x1<<6 is I2S_MODE_PDM - // : (i2s_mode_t)( I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM ); + i2s_config.mode = use_pdm + // ? (i2s_mode_t)( I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM ); + ? (i2s_mode_t)( I2S_MODE_MASTER | I2S_MODE_RX | (0x1 << 6) ) // 0x1<<6 is I2S_MODE_PDM + : (i2s_mode_t)( I2S_MODE_MASTER | I2S_MODE_RX ); i2s_config.sample_rate = sample_rate; i2s_config.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT; - i2s_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; + i2s_config.channel_format = _cfg.stereo ? I2S_CHANNEL_FMT_RIGHT_LEFT : I2S_CHANNEL_FMT_ONLY_RIGHT; i2s_config.communication_format = (i2s_comm_format_t)( COMM_FORMAT_I2S ); i2s_config.dma_buf_count = _cfg.dma_buf_count; i2s_config.dma_buf_len = _cfg.dma_buf_len; i2s_pin_config_t pin_config; memset(&pin_config, ~0u, sizeof(i2s_pin_config_t)); /// all pin set to I2S_PIN_NO_CHANGE +#if defined (ESP_IDF_VERSION_VAL) + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 1) + pin_config.mck_io_num = _cfg.pin_mck; + #endif +#endif + pin_config.bck_io_num = _cfg.pin_bck; pin_config.ws_io_num = _cfg.pin_ws; pin_config.data_in_num = _cfg.pin_data_in; - esp_err_t err = i2s_driver_install(_cfg.i2s_port, &i2s_config, 0, nullptr); + esp_err_t err; + if (ESP_OK != (err = i2s_driver_install(_cfg.i2s_port, &i2s_config, 0, nullptr))) + { + i2s_driver_uninstall(_cfg.i2s_port); + err = i2s_driver_install(_cfg.i2s_port, &i2s_config, 0, nullptr); + } if (err != ESP_OK) { return err; } #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) @@ -151,6 +161,7 @@ namespace m5 void Mic_Class::mic_task(void* args) { auto self = (Mic_Class*)args; + i2s_start(self->_cfg.i2s_port); int oversampling = self->_cfg.over_sampling; if ( oversampling < 1) { oversampling = 1; } @@ -308,6 +319,7 @@ namespace m5 } if (_cb_set_enabled) { _cb_set_enabled(_cb_set_enabled_args, false); } + i2s_driver_uninstall(_cfg.i2s_port); } bool Mic_Class::_rec_raw(void* recdata, size_t array_len, bool flg_16bit, uint32_t sample_rate) @@ -329,4 +341,5 @@ namespace m5 } return true; } -} +#endif +} \ No newline at end of file diff --git a/src/utility/Mic_Class.hpp b/src/utility/Mic_Class.hpp index bf6f732..97765ad 100644 --- a/src/utility/Mic_Class.hpp +++ b/src/utility/Mic_Class.hpp @@ -17,12 +17,21 @@ namespace m5 /// i2s_data_in (for mic) int pin_data_in = -1; - /// i2s_ws - int pin_ws = -1; + /// i2s_bclk + int pin_bck = I2S_PIN_NO_CHANGE; + + /// i2s_mclk + int pin_mck = I2S_PIN_NO_CHANGE; + + /// i2s_ws (lrck) + int pin_ws = I2S_PIN_NO_CHANGE; /// input sampling rate (Hz) uint32_t sample_rate = 16000; + /// use stereo output + bool stereo = false; + /// offset correction value of ADC input value int input_offset = 0; diff --git a/src/utility/SH200Q_Class.cpp b/src/utility/SH200Q_Class.cpp index b49b018..89506f6 100644 --- a/src/utility/SH200Q_Class.cpp +++ b/src/utility/SH200Q_Class.cpp @@ -2,6 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. #include "SH200Q_Class.hpp" +#include +#include namespace m5 { diff --git a/src/utility/Speaker_Class.cpp b/src/utility/Speaker_Class.cpp index 3566c1f..7c3b07e 100644 --- a/src/utility/Speaker_Class.cpp +++ b/src/utility/Speaker_Class.cpp @@ -93,7 +93,7 @@ namespace m5 if (_cfg.i2s_port == I2S_NUM_0) { /// レジスタを操作してDACモードの設定を有効にする(I2S0のみ。I2S1はDAC,ADC非対応) ; I2S0.conf2.lcd_en = true; - I2S0.conf.tx_right_first = true; + I2S0.conf.tx_right_first = false; I2S0.conf.tx_msb_shift = 0; I2S0.conf.tx_short_sync = 0; } @@ -169,21 +169,25 @@ namespace m5 const i2s_port_t i2s_port = self->_cfg.i2s_port; const bool out_stereo = self->_cfg.stereo; - static constexpr uint32_t base = 160*1000*1000; // 160 MHz - uint32_t bits = (self->_cfg.use_dac) ? 2 : 32; /// 1サンプリング当たりの出力ビット数 (DACは2ch分の出力で2回、I2Sは16bit x2chで32回); + static constexpr uint32_t PLL_D2_CLK = 80*1000*1000; // 80 MHz + uint32_t bits = (self->_cfg.use_dac) ? 1 : 16; /// 1サンプリング当たりの出力ビット数; uint32_t div_a, div_b, div_n; - uint32_t div_m = 64 / bits; /// MCLKを使用しないので、サンプリングレート誤差が少なくなるようにdiv_mを調整する; + uint32_t div_m = 32 / bits; /// MCLKを使用しない場合、サンプリングレート誤差が少なくなるようにdiv_mを調整する; // MCLKを使用するデバイスに対応する場合には、div_mを使用してBCKとMCKの比率を調整する; - calcClockDiv(&div_a, &div_b, &div_n, base, div_m * bits * self->_cfg.sample_rate); + calcClockDiv(&div_a, &div_b, &div_n, PLL_D2_CLK, div_m * bits * self->_cfg.sample_rate); /// 実際に設定されたサンプリングレートの算出を行う; - const int32_t spk_sample_rate_x256 = (float)base * SAMPLERATE_MUL / ((float)(div_b * div_m * bits) / (float)div_a + (div_n * div_m * bits)); -// ESP_EARLY_LOGW("Speaker_Class", "sample rate:%d Hz = %d MHz/(%d+(%d/%d))/%d/%d = %d Hz", self->_cfg.sample_rate, base / 1000000, div_n, div_b, div_a, div_m, bits, spk_sample_rate_x256 / SAMPLERATE_MUL); + const int32_t spk_sample_rate_x256 = (float)PLL_D2_CLK * SAMPLERATE_MUL / ((float)(div_b * div_m * bits) / (float)div_a + (div_n * div_m * bits)); +// ESP_EARLY_LOGW("Speaker_Class", "sample rate:%d Hz = %d MHz/(%d+(%d/%d))/%d/%d = %d Hz", self->_cfg.sample_rate, PLL_D2_CLK / 1000000, div_n, div_b, div_a, div_m, bits, spk_sample_rate_x256 / SAMPLERATE_MUL); -#if defined ( CONFIG_IDF_TARGET_ESP32C3 ) +#if defined ( CONFIG_IDF_TARGET_ESP32C3 ) || defined ( CONFIG_IDF_TARGET_ESP32S3 ) +#if defined ( I2S1I_BCK_OUT_IDX ) + auto dev = (i2s_port == i2s_port_t::I2S_NUM_1) ? &I2S1 : &I2S0; +#else auto dev = &I2S0; +#endif dev->tx_conf1.tx_bck_div_num = div_m - 1; dev->tx_clkm_conf.tx_clkm_div_num = div_n; bool yn1 = (div_b > (div_a >> 1)) ? 1 : 0; @@ -215,35 +219,64 @@ namespace m5 #endif - const float magnification = (float)self->_cfg.magnification / spk_sample_rate_x256 - / (~0u >> ((self->_cfg.use_dac || self->_cfg.buzzer) ? 0 : 8)); + // ステレオ出力の場合は倍率を2倍する + const float magnification = (float)(self->_cfg.magnification << out_stereo) / spk_sample_rate_x256 / (1 << 28); + const size_t dma_buf_len = self->_cfg.dma_buf_len & ~1; - int nodata_count = 0; - int32_t dac_offset = self->_cfg.dac_zero_level << 8; - bool flg_i2s_started = false; + int32_t dac_offset = std::min(INT16_MAX-255, self->_cfg.dac_zero_level << 8); - union + bool flg_nodata = false; + + enum spk_i2s_state { - int16_t surplus16 = 0; - uint8_t surplus[2]; + spk_i2s_stop, + spk_i2s_mute, + spk_i2s_run, }; + spk_i2s_state flg_i2s_started = spk_i2s_stop; union { - float* float_buf; - int16_t* sound_buf; - int32_t* sound_buf32; + int16_t surplus16 = 0; + uint8_t surplus[2]; }; - float_buf = (float*)alloca(dma_buf_len * sizeof(float)); + + int32_t* sound_buf32 = (int32_t*)alloca(dma_buf_len * sizeof(int32_t)); while (self->_task_running) { - if (nodata_count) + if (flg_nodata) { - if (nodata_count > self->_cfg.dma_buf_count) + if (self->_cfg.use_dac && dac_offset) + { // Gradual transition of DAC output to 0; + flg_i2s_started = spk_i2s_mute; + size_t idx = 0; + do + { + auto tmp = (uint32_t)((1.0f + cosf(idx * M_PI / dma_buf_len)) * (dac_offset >> 1)); + sound_buf32[idx] = tmp | tmp << 16; + } while (++idx < dma_buf_len); + size_t write_bytes; + i2s_write(i2s_port, sound_buf32, dma_buf_len * sizeof(int32_t), &write_bytes, portMAX_DELAY); + if (self->_cfg.dac_zero_level == 0) + { + dac_offset = 0; + } + } + + // バッファ全てゼロになるまで出力を繰返す; + memset(sound_buf32, 0, dma_buf_len * sizeof(uint32_t)); + // DAC使用時は長めに設定する + size_t retry = (self->_cfg.dma_buf_count << self->_cfg.use_dac) + 1; + while (!ulTaskNotifyTake( pdTRUE, 0 ) && --retry) + { + size_t write_bytes; + i2s_write(i2s_port, sound_buf32, dma_buf_len * sizeof(int32_t), &write_bytes, portMAX_DELAY); + } + + if (!retry) { - nodata_count = 0; - flg_i2s_started = false; + flg_i2s_started = spk_i2s_stop; i2s_stop(i2s_port); #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) if (self->_cfg.use_dac) @@ -251,54 +284,60 @@ namespace m5 i2s_set_dac_mode(i2s_dac_mode_t::I2S_DAC_CHANNEL_DISABLE); } #endif + // 新しいデータが届くまで待機; ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); - continue; } - ulTaskNotifyTake( pdTRUE, 0 ); } + ulTaskNotifyTake( pdTRUE, 0 ); - memset(float_buf, 0, dma_buf_len * sizeof(float)); - ++nodata_count; + flg_nodata = true; - if (!flg_i2s_started) + if (flg_i2s_started != spk_i2s_run) { - flg_i2s_started = true; -#if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) - if (self->_cfg.use_dac) + if (flg_i2s_started == spk_i2s_stop) { - i2s_dac_mode_t dac_mode = i2s_dac_mode_t::I2S_DAC_CHANNEL_BOTH_EN; - if (!self->_cfg.stereo) +#if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) + if (self->_cfg.use_dac) { - dac_mode = (self->_cfg.pin_data_out == GPIO_NUM_25) - ? i2s_dac_mode_t::I2S_DAC_CHANNEL_RIGHT_EN // for GPIO 25 - : i2s_dac_mode_t::I2S_DAC_CHANNEL_LEFT_EN; // for GPIO 26 + i2s_dac_mode_t dac_mode = i2s_dac_mode_t::I2S_DAC_CHANNEL_BOTH_EN; + if (!out_stereo) + { + dac_mode = (self->_cfg.pin_data_out == GPIO_NUM_25) + ? i2s_dac_mode_t::I2S_DAC_CHANNEL_RIGHT_EN // for GPIO 25 + : i2s_dac_mode_t::I2S_DAC_CHANNEL_LEFT_EN; // for GPIO 26 + } + i2s_set_dac_mode(dac_mode); } - i2s_set_dac_mode(dac_mode); - } #endif - i2s_zero_dma_buffer(i2s_port); - i2s_start(i2s_port); + i2s_start(i2s_port); + } + if (self->_cfg.use_dac && self->_cfg.dac_zero_level != 0) { size_t idx = 0; do { - sound_buf[idx^1] = dac_offset * idx / dma_buf_len; + auto tmp = (uint32_t)((1.0f - cosf(idx * M_PI / dma_buf_len)) * (dac_offset >> 1)); + sound_buf32[idx] = tmp | tmp << 16; } while (++idx < dma_buf_len); size_t write_bytes; - i2s_write(i2s_port, sound_buf, dma_buf_len * sizeof(int16_t), &write_bytes, portMAX_DELAY); - memset(float_buf, 0, dma_buf_len * sizeof(float)); + i2s_write(i2s_port, sound_buf32, dma_buf_len * sizeof(int32_t), &write_bytes, portMAX_DELAY); } + flg_i2s_started = spk_i2s_run; } + memset(sound_buf32, 0, dma_buf_len * sizeof(int32_t)); float volume = magnification * (self->_master_volume * self->_master_volume); for (size_t ch = 0; ch < sound_channel_max; ++ch) { if (0 == (self->_play_channel_bits.load() & (1 << ch))) { continue; } - nodata_count = 0; + flg_nodata = false; auto ch_info = &(self->_ch_info[ch]); + int ch_diff = ch_info->diff; + size_t ch_index = ch_info->index; + wav_info_t* current_wav = &(ch_info->wavinfo[!ch_info->flip]); wav_info_t* next_wav = &(ch_info->wavinfo[ ch_info->flip]); @@ -317,101 +356,102 @@ namespace m5 if (clear_idx) { - ch_info->index = 0; + ch_index = 0; if (current_wav->repeat == 0) { self->_play_channel_bits.fetch_and(~(1 << ch)); if (current_wav->repeat == 0) { ch_info->diff = 0; + ch_info->index = 0; continue; } self->_play_channel_bits.fetch_or(1 << ch); } } } - const void* data = current_wav->data; + auto data = (const uint8_t*)current_wav->data; const bool in_stereo = current_wav->is_stereo; const int32_t in_rate = current_wav->sample_rate_x256; int32_t tmp = ch_info->volume; tmp *= tmp; + // 8bitのデータの場合は倍率を256倍する if (!current_wav->is_16bit) { tmp <<= 8; } - if (self->_cfg.stereo) { tmp <<= 1; } const float ch_v = volume * tmp; - bool liner_flip = ch_info->liner_flip; - auto liner_base = ch_info->liner_buf[ liner_flip]; - auto liner_prev = ch_info->liner_buf[!liner_flip]; + auto liner_base = ch_info->liner_buf[0]; + auto liner_prev = ch_info->liner_buf[1]; - int ch_diff = ch_info->diff; if (ch_diff < 0) { goto label_continue_sample; } + if (ch_index >= current_wav->length) + { +label_wav_end: + ch_index -= current_wav->length; + auto repeat = current_wav->repeat; + if (repeat != ~0u) + { + current_wav->repeat = --repeat; + if (repeat == 0) + { + goto label_next_wav; + } + } + } + do { + do { - size_t ch_index = ch_info->index; - do + if (ch_index >= current_wav->length) { - if (ch_index >= current_wav->length) - { - ch_index -= current_wav->length; - auto repeat = current_wav->repeat; - if (repeat != ~0u) - { - current_wav->repeat = --repeat; - if (repeat == 0) - { - ch_info->index = ch_index; - ch_info->liner_flip = (liner_prev < liner_base); - - goto label_next_wav; - } - } - } + goto label_wav_end; + } - int32_t l, r; - if (current_wav->is_16bit) + int32_t l, r; + if (current_wav->is_16bit) + { + auto wav = (const int16_t*)data; + l = wav[ch_index]; + r = wav[ch_index += in_stereo]; + ch_index++; + if (!current_wav->is_signed) { - auto wav = (const int16_t*)data; - l = wav[ch_index + in_stereo]; - r = wav[ch_index]; - ch_index += in_stereo + 1; - if (!current_wav->is_signed) - { - l = (l & 0xFFFF) + INT16_MIN; - r = (r & 0xFFFF) + INT16_MIN; - } + l = (l & 0xFFFF) + INT16_MIN; + r = (r & 0xFFFF) + INT16_MIN; } - else + } + else + { + l = data[ch_index]; + r = data[ch_index += in_stereo]; + ch_index++; + if (current_wav->is_signed) { - auto wav = (const uint8_t*)data; - l = wav[ch_index + in_stereo]; - r = wav[ch_index]; - ch_index += in_stereo + 1; - if (current_wav->is_signed) - { - l = (int8_t)l; - r = (int8_t)r; - } - else - { - l += INT8_MIN; - r += INT8_MIN; - } + l = (int8_t)l; + r = (int8_t)r; } - std::swap(liner_base, liner_prev); - - if (!out_stereo) { l += r; } else { - liner_base[1] = r * ch_v; + l += INT8_MIN; + r += INT8_MIN; } - liner_base[0] = l * ch_v; + } - ch_diff -= spk_sample_rate_x256; - } while (ch_diff >= 0); - ch_info->index = ch_index; - } + liner_prev[0] = liner_base[0]; + if (out_stereo) + { + liner_prev[1] = liner_base[1]; + liner_base[1] = l * ch_v; + } + else + { + r += l; + } + liner_base[0] = r * ch_v; + + ch_diff -= spk_sample_rate_x256; + } while (ch_diff >= 0); label_continue_sample: @@ -421,6 +461,8 @@ namespace m5 base_l *= spk_sample_rate_x256; base_l += step_l * ch_diff; step_l *= in_rate; + int32_t b_l = base_l; + int32_t s_l = step_l; if (out_stereo) { float base_r = liner_base[1]; @@ -428,12 +470,14 @@ namespace m5 base_r *= spk_sample_rate_x256; base_r += step_r * ch_diff; step_r *= in_rate; + int32_t b_r = base_r; + int32_t s_r = step_r; do { - float_buf[ idx] += base_l; - float_buf[++idx] += base_r; - base_l += step_l; - base_r += step_r; + sound_buf32[ idx] += b_l; + sound_buf32[++idx] += b_r; + b_l += s_l; + b_r += s_r; ch_diff += in_rate; } while (++idx < dma_buf_len && ch_diff < 0); } @@ -441,113 +485,100 @@ namespace m5 { do { - float_buf[idx] += base_l; - base_l += step_l; + sound_buf32[idx] += b_l; + b_l += s_l; ch_diff += in_rate; } while (++idx < dma_buf_len && ch_diff < 0); } - ch_info->diff = ch_diff; } while (idx < dma_buf_len); - ch_info->liner_flip = (liner_prev < liner_base); + ch_info->diff = ch_diff; + ch_info->index = ch_index; } - if (self->_cfg.use_dac) + if (!flg_nodata) { -/// DAC出力は cfg.dac_zero_levelが0に設定されている場合、振幅のオフセットを動的に変更する。; -/// これはESP32のDAC出力は高ければ高いほどノイズも増加するため、なるべく低いDAC出力を用いてノイズを低減することを目的とする。; - const bool zero_bias = (self->_cfg.dac_zero_level == 0); - if (nodata_count == 0) + if (self->_cfg.use_dac) { - if (zero_bias) { dac_offset -= (dac_offset * dma_buf_len) >> 15; } + /// DAC出力は cfg.dac_zero_levelが0に設定されている場合、振幅のオフセットを動的に変更する。; + /// DAC出力が低いほどノイズ音が減るため、なるべくDAC出力を下げてノイズを低減することを目的とする。; + const bool zero_bias = (self->_cfg.dac_zero_level == 0); + bool biasing = zero_bias; size_t idx = 0; do { - int32_t v = float_buf[idx]; - if (zero_bias) + int32_t v1 = sound_buf32[ idx] >> 8; + int32_t v2 = sound_buf32[++idx] >> 8; + int32_t vabs = std::max(abs(v1), abs(v2)); + if (dac_offset <= vabs) { - int32_t vabs = abs(v); - if (dac_offset < vabs) + if (zero_bias) { - dac_offset = (INT16_MAX < vabs) ? INT16_MAX : vabs; + dac_offset = (INT16_MAX-255 < vabs) ? INT16_MAX-255 : vabs; + biasing = false; } + v1 += dac_offset; + v2 += dac_offset; + if (v1 < 0) { v1 = 0; } + else if (v1 > UINT16_MAX) { v1 = UINT16_MAX; } + if (v2 < 0) { v2 = 0; } + else if (v2 > UINT16_MAX) { v2 = UINT16_MAX; } + } + else + { + v1 += dac_offset + surplus[0]; + surplus[0] = v1; + v2 += dac_offset + surplus[out_stereo]; + surplus[out_stereo] = v2; } - v += dac_offset; - auto s = &surplus[idx & out_stereo]; - v += *s; - *s = v; - - if (v < 0) { v = 0; } - else if (v > UINT16_MAX) { v = UINT16_MAX; } - sound_buf[idx ^ 1] = v; + sound_buf32[idx >> 1] = v1 << 16 | v2; } while (++idx < dma_buf_len); + if (biasing) { dac_offset -= (dac_offset * dma_buf_len) >> 15; } } - else + else if (self->_cfg.buzzer) { - if (nodata_count == 1) - { - surplus16 = dac_offset; - } - uint_fast16_t offset = surplus16; - if (offset) + /// ブザー出力は 1bit ΔΣ方式。 I2Sデータ出力をブザーの駆動信号として利用する; + /// 出力はモノラル限定だが、I2Sへはステレオ扱いで出力する。; + /// (I2Sをモノラル設定にした場合は同じデータが2チャンネル分送信されてしまうため、敢えてステレオ扱いとしている); + int32_t tmp = (uint16_t)surplus16; + size_t idx = 0; + do { - nodata_count = 1; - if (--offset > 16) + int32_t v = sound_buf32[idx] >> 8; + v = INT16_MIN - v; + uint32_t bitdata = 0; + uint32_t bit = 0x80000000; + do { - size_t idx = 0; - do + if ((tmp += v) < 0) { - offset = (offset * 255) >> 8; - sound_buf[idx] = offset; - } while (++idx < dma_buf_len); - if (offset <= 16) { offset = 16; } - } - surplus16 = offset; - if (zero_bias) { dac_offset = offset; } - } + tmp += 0x10000; + bitdata |= bit; + } + } while (bit >>= 1); + sound_buf32[idx] = bitdata; + } while (++idx < dma_buf_len); + surplus16 = flg_nodata ? 0x8000 : tmp; } - } - else if (self->_cfg.buzzer) - { -/// ブザー出力は 1bit ΔΣ方式。 I2Sデータ出力をブザーの駆動信号として利用する; -/// 出力はモノラル限定だが、I2Sへはステレオ扱いで出力する。; -/// (I2Sをモノラル設定にした場合は同じデータが2チャンネル分送信されてしまうため、敢えてステレオ扱いとしている); - int32_t tmp = (uint16_t)surplus16; - size_t idx = 0; - do + else { - int32_t v = float_buf[idx]; - v = INT16_MIN - v; - uint32_t bitdata = 0; - uint32_t bit = 0x80000000; + size_t idx = 0; do { - if ((tmp += v) < 0) - { - tmp += 0x10000; - bitdata |= bit; - } - } while (bit >>= 1); - sound_buf32[idx] = bitdata; - } while (++idx < dma_buf_len); - surplus16 = nodata_count ? 0x8000 : tmp; - } - else - { - size_t idx = 0; - do - { - int32_t v = float_buf[idx]; - v += surplus[idx & out_stereo]; - surplus[idx & out_stereo] = v; - v >>= 8; - if (v < INT16_MIN) { v = INT16_MIN; } - else if (v > INT16_MAX) { v = INT16_MAX; } - sound_buf[idx ^ 1] = v; - } while (++idx < dma_buf_len); - } + int32_t v1 = sound_buf32[idx] >> 8; + if (v1 < INT16_MIN) { v1 = INT16_MIN; } + else if (v1 > INT16_MAX) { v1 = INT16_MAX; } - size_t write_bytes; - i2s_write(i2s_port, sound_buf, dma_buf_len * sizeof(int16_t) << self->_cfg.buzzer, &write_bytes, portMAX_DELAY); + int32_t v2 = sound_buf32[++idx] >> 8; + if (v2 < INT16_MIN) { v2 = INT16_MIN; } + else if (v2 > INT16_MAX) { v2 = INT16_MAX; } + + sound_buf32[idx >> 1] = v1 << 16 | (uint16_t)v2; + } while (++idx < dma_buf_len); + } + + size_t write_bytes; + i2s_write(i2s_port, sound_buf32, dma_buf_len * sizeof(int16_t) << self->_cfg.buzzer, &write_bytes, portMAX_DELAY); + } } i2s_stop(i2s_port); #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) @@ -574,7 +605,7 @@ namespace m5 res = (ESP_OK == _setup_i2s()) && res; if (res) { - size_t stack_size = 1024 + (_cfg.dma_buf_len * sizeof(float)); + size_t stack_size = 1024 + (_cfg.dma_buf_len * sizeof(uint32_t)); _task_running = true; #if portNUM_PROCESSORS > 1 if (((size_t)_cfg.task_pinned_core) < portNUM_PROCESSORS) diff --git a/src/utility/Speaker_Class.hpp b/src/utility/Speaker_Class.hpp index ae37b99..503bf1a 100644 --- a/src/utility/Speaker_Class.hpp +++ b/src/utility/Speaker_Class.hpp @@ -33,7 +33,8 @@ namespace m5 /// use single gpio buzzer, ( need only pin_data_out ) bool buzzer = false; - /// use DAC speaker, ( need only pin_data_out ) ( only GPIO_NUM_25 or GPIO_NUM_26 ) + /// use DAC speaker, ( need only pin_data_out ) ( for ESP32, only GPIO_NUM_25 or GPIO_NUM_26 ) + /// ※ for ESP32, need `i2s_port = I2S_NUM_0`. ( DAC+I2S_NUM_1 is not available ) bool use_dac = false; /// Zero level reference value when using DAC ( 0=Dynamic change ) @@ -237,10 +238,9 @@ namespace m5 wav_info_t wavinfo[2]; // current/next flip info. size_t index = 0; int diff = 0; - volatile uint8_t volume = 64; // channel volume (not master volume) + volatile uint8_t volume = 255; // channel volume (not master volume) volatile bool flip = false; - bool liner_flip = false; float liner_buf[2][2] = { { 0, 0 }, { 0, 0 } }; };