diff --git a/src/tangara/audio/audio_fsm.cpp b/src/tangara/audio/audio_fsm.cpp index ad60ab86..dbf1954c 100644 --- a/src/tangara/audio/audio_fsm.cpp +++ b/src/tangara/audio/audio_fsm.cpp @@ -43,6 +43,7 @@ #include "sample.hpp" #include "system_fsm/service_locator.hpp" #include "system_fsm/system_events.hpp" +#include "tts/player.hpp" namespace audio { @@ -369,6 +370,11 @@ void Uninitialised::react(const system_fsm::BootComplete& ev) { sBtOutput.reset(new BluetoothAudioOutput( sServices->bluetooth(), *sDrainBuffers, sServices->bg_worker())); + auto& tts_provider = sServices->tts(); + auto tts_player = std::make_unique( + sServices->bg_worker(), sDrainBuffers->second, *sStreamFactory); + tts_provider.player(std::move(tts_player)); + auto& nvs = sServices->nvs(); sI2SOutput->SetMaxVolume(nvs.AmpMaxVolume()); sI2SOutput->SetVolume(nvs.AmpCurrentVolume()); diff --git a/src/tangara/tts/player.cpp b/src/tangara/tts/player.cpp new file mode 100644 index 00000000..70959992 --- /dev/null +++ b/src/tangara/tts/player.cpp @@ -0,0 +1,24 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "tts/player.hpp" + +#include "esp_log.h" + +namespace tts { + +[[maybe_unused]] static constexpr char kTag[] = "ttsplay"; + +Player::Player(tasks::WorkerPool& worker, + drivers::PcmBuffer& output, + audio::FatfsStreamFactory& factory) + : bg_(worker), stream_factory_(factory), output_(output) {} + +auto Player::playFile(const std::string& path) -> void { + ESP_LOGI(kTag, "playing '%s'", path.c_str()); +} + +} // namespace tts diff --git a/src/tangara/tts/player.hpp b/src/tangara/tts/player.hpp new file mode 100644 index 00000000..a132b9cd --- /dev/null +++ b/src/tangara/tts/player.hpp @@ -0,0 +1,38 @@ +/* + * Copyright 2024 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include + +#include "audio/fatfs_stream_factory.hpp" +#include "drivers/pcm_buffer.hpp" +#include "tasks.hpp" + +namespace tts { + +/* + * A TTS Player is the output stage of the TTS pipeline. It receives a stream + * of filenames that should be played, and handles decoding these files and + * sending them to the output buffer. + */ +class Player { + public: + Player(tasks::WorkerPool&, drivers::PcmBuffer&, audio::FatfsStreamFactory&); + + auto playFile(const std::string& path) -> void; + + // Not copyable or movable. + Player(const Player&) = delete; + Player& operator=(const Player&) = delete; + + private: + tasks::WorkerPool& bg_; + audio::FatfsStreamFactory& stream_factory_; + drivers::PcmBuffer& output_; +}; + +} // namespace tts diff --git a/src/tangara/tts/provider.cpp b/src/tangara/tts/provider.cpp index 7d33bae6..24229233 100644 --- a/src/tangara/tts/provider.cpp +++ b/src/tangara/tts/provider.cpp @@ -5,21 +5,40 @@ */ #include "tts/provider.hpp" +#include +#include #include +#include #include #include +#include "drivers/storage.hpp" #include "esp_log.h" +#include "komihash.h" #include "tts/events.hpp" namespace tts { [[maybe_unused]] static constexpr char kTag[] = "tts"; +static const char* kTtsPath = "/.tangara-tts/"; + +static auto textToFile(const std::string& text) -> std::optional { + uint64_t hash = komihash(text.data(), text.size(), 0); + std::stringstream stream; + stream << drivers::kStoragePath << kTtsPath; + stream << std::hex << hash; + return stream.str(); +} + Provider::Provider() {} +auto Provider::player(std::unique_ptr p) -> void { + player_ = std::move(p); +} + auto Provider::feed(const Event& e) -> void { if (std::holds_alternative(e)) { // ESP_LOGI(kTag, "context changed"); @@ -31,6 +50,10 @@ auto Provider::feed(const Event& e) -> void { // ESP_LOGI(kTag, "new selection: '%s', interactive? %i", // ev.new_selection->description.value_or("").c_str(), // ev.new_selection->is_interactive); + std::string new_desc = ev.new_selection->description.value_or(""); + if (player_) { + player_->playFile(textToFile(new_desc).value_or("")); + } } } } diff --git a/src/tangara/tts/provider.hpp b/src/tangara/tts/provider.hpp index 59f61a6c..8fe143cc 100644 --- a/src/tangara/tts/provider.hpp +++ b/src/tangara/tts/provider.hpp @@ -6,18 +6,35 @@ #pragma once +#include #include #include #include #include "tts/events.hpp" +#include "tts/player.hpp" namespace tts { +/* + * A TTS Provider is responsible for receiving system events that may be + * relevant to TTS, and digesting them into discrete 'utterances' that can be + * used to generate audio feedback. + */ class Provider { public: Provider(); + + auto player(std::unique_ptr) -> void; + auto feed(const Event&) -> void; + + // Not copyable or movable. + Provider(const Provider&) = delete; + Provider& operator=(const Provider&) = delete; + + private: + std::unique_ptr player_; }; } // namespace tts