Support changing max volume, persisted to nvs
This commit is contained in:
+18
-2
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "audio_fsm.hpp"
|
||||
#include <stdint.h>
|
||||
|
||||
#include <future>
|
||||
#include <memory>
|
||||
@@ -29,6 +30,7 @@
|
||||
#include "system_events.hpp"
|
||||
#include "track.hpp"
|
||||
#include "track_queue.hpp"
|
||||
#include "wm8523.hpp"
|
||||
|
||||
namespace audio {
|
||||
|
||||
@@ -39,6 +41,7 @@ std::shared_ptr<system_fsm::ServiceLocator> AudioState::sServices;
|
||||
std::shared_ptr<FatfsAudioInput> AudioState::sFileSource;
|
||||
std::unique_ptr<Decoder> AudioState::sDecoder;
|
||||
std::shared_ptr<SampleConverter> AudioState::sSampleConverter;
|
||||
std::shared_ptr<I2SAudioOutput> AudioState::sI2SOutput;
|
||||
std::shared_ptr<IAudioOutput> AudioState::sOutput;
|
||||
|
||||
std::optional<database::TrackId> AudioState::sCurrentTrack;
|
||||
@@ -65,6 +68,13 @@ void AudioState::react(const system_fsm::HasPhonesChanged& ev) {
|
||||
}
|
||||
}
|
||||
|
||||
void AudioState::react(const ChangeMaxVolume& ev) {
|
||||
ESP_LOGI(kTag, "new max volume %u db",
|
||||
(ev.new_max - drivers::wm8523::kLineLevelReferenceVolume) / 4);
|
||||
sI2SOutput->SetMaxVolume(ev.new_max);
|
||||
sServices->nvs().AmpMaxVolume(ev.new_max);
|
||||
}
|
||||
|
||||
namespace states {
|
||||
|
||||
void Uninitialised::react(const system_fsm::BootComplete& ev) {
|
||||
@@ -78,8 +88,14 @@ void Uninitialised::react(const system_fsm::BootComplete& ev) {
|
||||
}
|
||||
|
||||
sFileSource.reset(new FatfsAudioInput(sServices->tag_parser()));
|
||||
sOutput.reset(new I2SAudioOutput(sServices->gpios(),
|
||||
std::unique_ptr<drivers::I2SDac>{*dac}));
|
||||
sI2SOutput.reset(new I2SAudioOutput(sServices->gpios(),
|
||||
std::unique_ptr<drivers::I2SDac>{*dac}));
|
||||
|
||||
auto& nvs = sServices->nvs();
|
||||
sI2SOutput->SetMaxVolume(nvs.AmpMaxVolume().get());
|
||||
sI2SOutput->SetVolumeDb(nvs.AmpCurrentVolume().get());
|
||||
|
||||
sOutput = sI2SOutput;
|
||||
// sOutput.reset(new BluetoothAudioOutput(bluetooth));
|
||||
|
||||
sSampleConverter.reset(new SampleConverter());
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "i2s_audio_output.hpp"
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
@@ -48,9 +49,8 @@ I2SAudioOutput::I2SAudioOutput(drivers::IGpios& expander,
|
||||
dac_(std::move(dac)),
|
||||
current_config_(),
|
||||
left_difference_(0),
|
||||
current_volume_(kDefaultVolume),
|
||||
max_volume_(kLineLevelVolume) {
|
||||
SetVolume(GetVolume());
|
||||
current_volume_(0),
|
||||
max_volume_(0) {
|
||||
dac_->SetSource(stream());
|
||||
}
|
||||
|
||||
@@ -72,6 +72,18 @@ auto I2SAudioOutput::SetVolumeImbalance(int_fast8_t balance) -> void {
|
||||
SetVolume(GetVolume());
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::SetMaxVolume(uint16_t max) -> void {
|
||||
max_volume_ = std::clamp(max, drivers::wm8523::kAbsoluteMinVolume,
|
||||
drivers::wm8523::kAbsoluteMaxVolume);
|
||||
SetVolume(GetVolume());
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::SetVolumeDb(uint16_t vol) -> void {
|
||||
current_volume_ =
|
||||
std::clamp(vol, drivers::wm8523::kAbsoluteMinVolume, max_volume_);
|
||||
SetVolume(GetVolume());
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::SetVolume(uint_fast8_t percent) -> void {
|
||||
percent = std::min<uint_fast8_t>(percent, 100);
|
||||
float new_value = static_cast<float>(max_volume_) / 100 * percent;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
@@ -36,6 +37,9 @@ struct PlayFile : tinyfsm::Event {
|
||||
};
|
||||
|
||||
struct VolumeChanged : tinyfsm::Event {};
|
||||
struct ChangeMaxVolume : tinyfsm::Event {
|
||||
uint16_t new_max;
|
||||
};
|
||||
|
||||
struct TogglePlayPause : tinyfsm::Event {};
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
|
||||
void react(const system_fsm::KeyUpChanged&);
|
||||
void react(const system_fsm::KeyDownChanged&);
|
||||
void react(const system_fsm::HasPhonesChanged&);
|
||||
void react(const ChangeMaxVolume&);
|
||||
|
||||
virtual void react(const system_fsm::BootComplete&) {}
|
||||
|
||||
@@ -63,6 +64,7 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
|
||||
static std::shared_ptr<FatfsAudioInput> sFileSource;
|
||||
static std::unique_ptr<Decoder> sDecoder;
|
||||
static std::shared_ptr<SampleConverter> sSampleConverter;
|
||||
static std::shared_ptr<I2SAudioOutput> sI2SOutput;
|
||||
static std::shared_ptr<IAudioOutput> sOutput;
|
||||
|
||||
static std::optional<database::TrackId> sCurrentTrack;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -25,6 +26,9 @@ class I2SAudioOutput : public IAudioOutput {
|
||||
|
||||
auto SetInUse(bool) -> void override;
|
||||
|
||||
auto SetMaxVolume(uint16_t) -> void;
|
||||
auto SetVolumeDb(uint16_t) -> void;
|
||||
|
||||
auto SetVolumeImbalance(int_fast8_t balance) -> void override;
|
||||
auto SetVolume(uint_fast8_t percent) -> void override;
|
||||
auto GetVolume() -> uint_fast8_t override;
|
||||
|
||||
@@ -37,6 +37,12 @@ class NvsStorage {
|
||||
auto ScreenBrightness() -> std::future<uint_fast8_t>;
|
||||
auto ScreenBrightness(uint_fast8_t) -> std::future<bool>;
|
||||
|
||||
auto AmpMaxVolume() -> std::future<uint16_t>;
|
||||
auto AmpMaxVolume(uint16_t) -> std::future<bool>;
|
||||
|
||||
auto AmpCurrentVolume() -> std::future<uint16_t>;
|
||||
auto AmpCurrentVolume(uint16_t) -> std::future<bool>;
|
||||
|
||||
explicit NvsStorage(std::unique_ptr<tasks::Worker>, nvs_handle_t);
|
||||
~NvsStorage();
|
||||
|
||||
|
||||
@@ -5,12 +5,32 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace drivers {
|
||||
namespace wm8523 {
|
||||
|
||||
extern const uint16_t kAbsoluteMaxVolume;
|
||||
|
||||
extern const uint16_t kAbsoluteMinVolume;
|
||||
|
||||
extern const uint16_t kMaxVolumeBeforeClipping;
|
||||
|
||||
extern const uint16_t kLineLevelReferenceVolume;
|
||||
|
||||
extern const uint16_t kDefaultVolume;
|
||||
extern const uint16_t kDefaultMaxVolume;
|
||||
|
||||
constexpr auto VolumeToDb(uint16_t vol) -> int_fast8_t {
|
||||
return (vol - kLineLevelReferenceVolume) / 4;
|
||||
}
|
||||
|
||||
constexpr auto DbToVolume(int_fast8_t db) -> uint16_t {
|
||||
return (db * 4) + kLineLevelReferenceVolume;
|
||||
}
|
||||
|
||||
enum class Register : uint8_t {
|
||||
kReset = 0,
|
||||
kRevision = 1,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "tasks.hpp"
|
||||
#include "wm8523.hpp"
|
||||
|
||||
namespace drivers {
|
||||
|
||||
@@ -27,6 +28,8 @@ static constexpr char kKeyVersion[] = "ver";
|
||||
static constexpr char kKeyBluetooth[] = "bt";
|
||||
static constexpr char kKeyOutput[] = "out";
|
||||
static constexpr char kKeyBrightness[] = "bright";
|
||||
static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max";
|
||||
static constexpr char kKeyAmpCurrentVolume[] = "hp_vol";
|
||||
|
||||
auto NvsStorage::OpenSync() -> NvsStorage* {
|
||||
esp_err_t err = nvs_flash_init();
|
||||
@@ -154,4 +157,34 @@ auto NvsStorage::ScreenBrightness(uint_fast8_t val) -> std::future<bool> {
|
||||
});
|
||||
}
|
||||
|
||||
auto NvsStorage::AmpMaxVolume() -> std::future<uint16_t> {
|
||||
return writer_->Dispatch<uint16_t>([&]() -> uint16_t {
|
||||
uint16_t out = wm8523::kDefaultMaxVolume;
|
||||
nvs_get_u16(handle_, kKeyAmpMaxVolume, &out);
|
||||
return out;
|
||||
});
|
||||
}
|
||||
|
||||
auto NvsStorage::AmpMaxVolume(uint16_t val) -> std::future<bool> {
|
||||
return writer_->Dispatch<bool>([&]() {
|
||||
nvs_set_u16(handle_, kKeyAmpMaxVolume, val);
|
||||
return nvs_commit(handle_) == ESP_OK;
|
||||
});
|
||||
}
|
||||
|
||||
auto NvsStorage::AmpCurrentVolume() -> std::future<uint16_t> {
|
||||
return writer_->Dispatch<uint16_t>([&]() -> uint16_t {
|
||||
uint16_t out = wm8523::kDefaultVolume;
|
||||
nvs_get_u16(handle_, kKeyAmpCurrentVolume, &out);
|
||||
return out;
|
||||
});
|
||||
}
|
||||
|
||||
auto NvsStorage::AmpCurrentVolume(uint16_t val) -> std::future<bool> {
|
||||
return writer_->Dispatch<bool>([&]() {
|
||||
nvs_set_u16(handle_, kKeyAmpCurrentVolume, val);
|
||||
return nvs_commit(handle_) == ESP_OK;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace drivers
|
||||
|
||||
@@ -15,6 +15,23 @@
|
||||
namespace drivers {
|
||||
namespace wm8523 {
|
||||
|
||||
const uint16_t kAbsoluteMaxVolume = 0x1ff;
|
||||
const uint16_t kAbsoluteMinVolume = 0b0;
|
||||
|
||||
// This is 3dB below what the DAC considers to be '0dB', and 9.5dB above line
|
||||
// level reference.
|
||||
const uint16_t kMaxVolumeBeforeClipping = 0x184;
|
||||
|
||||
// This is 12.5 dB below what the DAC considers to be '0dB'.
|
||||
const uint16_t kLineLevelReferenceVolume = 0x15E;
|
||||
|
||||
// Default to -24 dB, which I will claim is 'arbitrarily chosen to be safe but
|
||||
// audible', but is in fact just a nice value for my headphones in particular.
|
||||
const uint16_t kDefaultVolume = kLineLevelReferenceVolume - 96;
|
||||
|
||||
// Default to +6dB == 2Vrms == 'CD Player'
|
||||
const uint16_t kDefaultMaxVolume = kLineLevelReferenceVolume + 12;
|
||||
|
||||
static const uint8_t kAddress = 0b0011010;
|
||||
|
||||
auto ReadRegister(Register reg) -> std::optional<uint16_t> {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -32,7 +33,20 @@ class Bluetooth : public MenuScreen {
|
||||
|
||||
class Headphones : public MenuScreen {
|
||||
public:
|
||||
Headphones();
|
||||
Headphones(drivers::NvsStorage& nvs);
|
||||
|
||||
auto ChangeMaxVolume(uint8_t index) -> void;
|
||||
auto ChangeCustomVolume(int8_t diff) -> void;
|
||||
|
||||
private:
|
||||
auto UpdateCustomVol(uint16_t) -> void;
|
||||
|
||||
drivers::NvsStorage& nvs_;
|
||||
lv_obj_t* custom_vol_container_;
|
||||
lv_obj_t* custom_vol_label_;
|
||||
|
||||
std::vector<uint16_t> index_to_level_;
|
||||
uint16_t custom_limit_;
|
||||
};
|
||||
|
||||
class Appearance : public MenuScreen {
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
*/
|
||||
|
||||
#include "screen_settings.hpp"
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
#include "audio_events.hpp"
|
||||
#include "core/lv_event.h"
|
||||
#include "core/lv_obj.h"
|
||||
#include "display.hpp"
|
||||
@@ -18,6 +20,7 @@
|
||||
#include "extra/layouts/flex/lv_flex.h"
|
||||
#include "extra/widgets/list/lv_list.h"
|
||||
#include "extra/widgets/menu/lv_menu.h"
|
||||
#include "extra/widgets/spinbox/lv_spinbox.h"
|
||||
#include "extra/widgets/spinner/lv_spinner.h"
|
||||
#include "hal/lv_hal_disp.h"
|
||||
#include "index.hpp"
|
||||
@@ -34,6 +37,7 @@
|
||||
#include "widgets/lv_label.h"
|
||||
#include "widgets/lv_slider.h"
|
||||
#include "widgets/lv_switch.h"
|
||||
#include "wm8523.hpp"
|
||||
|
||||
namespace ui {
|
||||
namespace screens {
|
||||
@@ -120,19 +124,68 @@ Bluetooth::Bluetooth() : MenuScreen("Bluetooth") {
|
||||
"this one has a really long name"));
|
||||
}
|
||||
|
||||
Headphones::Headphones() : MenuScreen("Headphones") {
|
||||
static void change_vol_limit_cb(lv_event_t* ev) {
|
||||
int selected_index = lv_dropdown_get_selected(ev->target);
|
||||
Headphones* instance = reinterpret_cast<Headphones*>(ev->user_data);
|
||||
instance->ChangeMaxVolume(selected_index);
|
||||
}
|
||||
|
||||
static void increase_vol_limit_cb(lv_event_t* ev) {
|
||||
Headphones* instance = reinterpret_cast<Headphones*>(ev->user_data);
|
||||
instance->ChangeCustomVolume(2);
|
||||
}
|
||||
|
||||
static void decrease_vol_limit_cb(lv_event_t* ev) {
|
||||
Headphones* instance = reinterpret_cast<Headphones*>(ev->user_data);
|
||||
instance->ChangeCustomVolume(-2);
|
||||
}
|
||||
|
||||
Headphones::Headphones(drivers::NvsStorage& nvs)
|
||||
: MenuScreen("Headphones"), nvs_(nvs), custom_limit_(0) {
|
||||
uint16_t reference = drivers::wm8523::kLineLevelReferenceVolume;
|
||||
index_to_level_.push_back(reference - (10 * 4));
|
||||
index_to_level_.push_back(reference + (6 * 4));
|
||||
index_to_level_.push_back(reference + (9.5 * 4));
|
||||
|
||||
lv_obj_t* vol_label = lv_label_create(content_);
|
||||
lv_label_set_text(vol_label, "Volume Limit");
|
||||
lv_obj_t* vol_dropdown = lv_dropdown_create(content_);
|
||||
lv_dropdown_set_options(vol_dropdown,
|
||||
"Line Level (-10 dBV)\nPro Level (+4 dBu)\nMax "
|
||||
"before clipping\nUnlimited\nCustom");
|
||||
"Line Level (-10 dB)\nCD Level (+6 dB)\nMax "
|
||||
"before clipping (+10dB)\nCustom");
|
||||
lv_group_add_obj(group_, vol_dropdown);
|
||||
|
||||
lv_obj_t* warning_label = label_pair(
|
||||
content_, "!!", "Changing volume limit is for advanced users.");
|
||||
lv_label_set_long_mode(warning_label, LV_LABEL_LONG_WRAP);
|
||||
lv_obj_set_flex_grow(warning_label, 1);
|
||||
uint16_t level = nvs.AmpMaxVolume().get();
|
||||
for (int i = 0; i < index_to_level_.size() + 1; i++) {
|
||||
if (i == index_to_level_.size() || index_to_level_[i] == level) {
|
||||
lv_dropdown_set_selected(vol_dropdown, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lv_obj_add_event_cb(vol_dropdown, change_vol_limit_cb, LV_EVENT_VALUE_CHANGED,
|
||||
this);
|
||||
|
||||
custom_vol_container_ = settings_container(content_);
|
||||
|
||||
lv_obj_t* decrease_btn = lv_btn_create(custom_vol_container_);
|
||||
lv_obj_t* btn_label = lv_label_create(decrease_btn);
|
||||
lv_label_set_text(btn_label, "-");
|
||||
lv_obj_add_event_cb(decrease_btn, decrease_vol_limit_cb, LV_EVENT_CLICKED,
|
||||
this);
|
||||
|
||||
custom_vol_label_ = lv_label_create(custom_vol_container_);
|
||||
UpdateCustomVol(level);
|
||||
|
||||
lv_obj_t* increase_btn = lv_btn_create(custom_vol_container_);
|
||||
btn_label = lv_label_create(increase_btn);
|
||||
lv_label_set_text(btn_label, "+");
|
||||
lv_obj_add_event_cb(increase_btn, increase_vol_limit_cb, LV_EVENT_CLICKED,
|
||||
this);
|
||||
|
||||
if (lv_dropdown_get_selected(vol_dropdown) != index_to_level_.size()) {
|
||||
lv_obj_add_flag(custom_vol_container_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
lv_obj_t* balance_label = lv_label_create(content_);
|
||||
lv_label_set_text(balance_label, "Left/Right Balance");
|
||||
@@ -147,6 +200,41 @@ Headphones::Headphones() : MenuScreen("Headphones") {
|
||||
lv_obj_set_size(current_balance_label, lv_pct(100), LV_SIZE_CONTENT);
|
||||
}
|
||||
|
||||
auto Headphones::ChangeMaxVolume(uint8_t index) -> void {
|
||||
if (index >= index_to_level_.size()) {
|
||||
lv_obj_clear_flag(custom_vol_container_, LV_OBJ_FLAG_HIDDEN);
|
||||
return;
|
||||
}
|
||||
auto vol = index_to_level_[index];
|
||||
lv_obj_add_flag(custom_vol_container_, LV_OBJ_FLAG_HIDDEN);
|
||||
UpdateCustomVol(vol);
|
||||
events::Audio().Dispatch(audio::ChangeMaxVolume{.new_max = vol});
|
||||
}
|
||||
|
||||
auto Headphones::ChangeCustomVolume(int8_t diff) -> void {
|
||||
UpdateCustomVol(custom_limit_ + diff);
|
||||
}
|
||||
|
||||
auto Headphones::UpdateCustomVol(uint16_t level) -> void {
|
||||
custom_limit_ = level;
|
||||
int16_t db = (static_cast<int32_t>(level) -
|
||||
drivers::wm8523::kLineLevelReferenceVolume) /
|
||||
4;
|
||||
int16_t db_parts = (static_cast<int32_t>(level) -
|
||||
drivers::wm8523::kLineLevelReferenceVolume) %
|
||||
4;
|
||||
|
||||
std::ostringstream builder;
|
||||
if (db >= 0) {
|
||||
builder << "+";
|
||||
}
|
||||
builder << db << ".";
|
||||
builder << (db_parts * 100 / 4);
|
||||
builder << " dBV";
|
||||
|
||||
lv_label_set_text(custom_vol_label_, builder.str().c_str());
|
||||
}
|
||||
|
||||
static void change_brightness_cb(lv_event_t* ev) {
|
||||
Appearance* instance = reinterpret_cast<Appearance*>(ev->user_data);
|
||||
instance->ChangeBrightness(lv_slider_get_value(ev->target));
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ void Browse::react(const internal::ShowSettingsPage& ev) {
|
||||
screen.reset(new screens::Bluetooth());
|
||||
break;
|
||||
case internal::ShowSettingsPage::Page::kHeadphones:
|
||||
screen.reset(new screens::Headphones());
|
||||
screen.reset(new screens::Headphones(sServices->nvs()));
|
||||
break;
|
||||
case internal::ShowSettingsPage::Page::kAppearance:
|
||||
screen.reset(new screens::Appearance(sServices->nvs(), *sDisplay));
|
||||
|
||||
Reference in New Issue
Block a user