diff --git a/esp/speaker-netsender/components/tas5805/include/tas5805.hpp b/esp/speaker-netsender/components/tas5805/include/tas5805.hpp index dcbd26f..81bf933 100644 --- a/esp/speaker-netsender/components/tas5805/include/tas5805.hpp +++ b/esp/speaker-netsender/components/tas5805/include/tas5805.hpp @@ -76,7 +76,7 @@ class TAS5805 { /** * @brief sets the digital volume - * @param vol volume to set the speaker at (0-100). + * @param vol volume to set the speaker at (0 = mute, 100 = 0 dB). */ esp_err_t set_volume(uint8_t vol); diff --git a/esp/speaker-netsender/components/tas5805/tas5805.cpp b/esp/speaker-netsender/components/tas5805/tas5805.cpp index b9c3c7a..db81f6a 100644 --- a/esp/speaker-netsender/components/tas5805/tas5805.cpp +++ b/esp/speaker-netsender/components/tas5805/tas5805.cpp @@ -29,6 +29,7 @@ #include "freertos/FreeRTOS.h" // IWYU pragma: keep +#include #include #include #include @@ -262,15 +263,26 @@ esp_err_t TAS5805::set_volume(uint8_t vol) write_reg(TAS5805_CHANGE_BOOK_REG, BOOK::ZERO); // Go to book 0. // Bound volume. - if (vol < 0) { - vol = 0; - } else if (vol > 100) { + if (vol > 100) { vol = 100; } - // Scale and invert volume from 0-100 to 255-0. - // TODO: Make this a more linear sounding scale. - uint8_t cmd = 255 - ((uint16_t)vol * 255 / 100); + // The digital volume register is 0x00 = +24 dB, 0x30 = 0 dB, with 0.5 dB + // steps down to 0xFF = -103.5 dB. A linear register mapping makes the + // lower half of the range almost inaudible while the top end is very loud. + // Use a square-law amplitude curve (40 * log10(vol / 100) dB), which gives + // a much more natural sounding taper, and cap the output at 0 dB. + uint8_t cmd = 0xFF; // Mute. + if (vol > 0) { + const auto db = 40.0f * std::log10(static_cast(vol) / 100.0f); + auto reg = std::lround(48.0f - 2.0f * db); // 48 == 0 dB. + if (reg < 48) { + reg = 48; + } else if (reg > 0xFF) { + reg = 0xFF; + } + cmd = static_cast(reg); + } // Write the new volume set point. write_reg(TAS5805_DIG_VOL_CTRL_REG, cmd);