Flesh out audio state machine for playback

Also fix mono playback
custom
jacqueline 2 years ago
parent 2a568846bd
commit 1f903accd9
  1. 10
      src/audio/audio_decoder.cpp
  2. 32
      src/audio/audio_fsm.cpp
  3. 10
      src/audio/audio_task.cpp
  4. 13
      src/audio/fatfs_audio_input.cpp
  5. 27
      src/audio/i2s_audio_output.cpp
  6. 3
      src/audio/include/audio_events.hpp
  7. 14
      src/audio/include/audio_fsm.hpp
  8. 2
      src/audio/include/audio_sink.hpp
  9. 2
      src/audio/include/i2s_audio_output.hpp
  10. 4
      src/audio/include/pipeline.hpp
  11. 3
      src/audio/stream_info.cpp
  12. 3
      src/codecs/include/codec.hpp
  13. 6
      src/codecs/include/mad.hpp
  14. 35
      src/codecs/mad.cpp
  15. 7
      src/drivers/display.cpp
  16. 16
      src/drivers/i2s_dac.cpp
  17. 4
      src/drivers/include/display.hpp
  18. 6
      src/drivers/include/i2s_dac.hpp

@ -98,14 +98,13 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
while (true) { while (true) {
if (has_samples_to_send_) { if (has_samples_to_send_) {
if (!current_output_format_) {
auto format = current_codec_->GetOutputFormat(); auto format = current_codec_->GetOutputFormat();
if (format.has_value()) {
current_output_format_ = StreamInfo::Pcm{ current_output_format_ = StreamInfo::Pcm{
.channels = format.num_channels, .channels = format->num_channels,
.bits_per_sample = format.bits_per_sample, .bits_per_sample = format->bits_per_sample,
.sample_rate = format.sample_rate_hz, .sample_rate = format->sample_rate_hz,
}; };
}
if (!output->prepare(*current_output_format_)) { if (!output->prepare(*current_output_format_)) {
break; break;
@ -121,6 +120,7 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
break; break;
} }
} }
}
auto res = current_codec_->ProcessNextFrame(); auto res = current_codec_->ProcessNextFrame();
if (res.has_error()) { if (res.has_error()) {

@ -6,6 +6,7 @@
#include "audio_fsm.hpp" #include "audio_fsm.hpp"
#include <memory> #include <memory>
#include <variant>
#include "audio_decoder.hpp" #include "audio_decoder.hpp"
#include "audio_events.hpp" #include "audio_events.hpp"
#include "audio_task.hpp" #include "audio_task.hpp"
@ -16,6 +17,8 @@
namespace audio { namespace audio {
static const char kTag[] = "audio_fsm";
drivers::GpioExpander* AudioState::sGpioExpander; drivers::GpioExpander* AudioState::sGpioExpander;
std::shared_ptr<drivers::I2SDac> AudioState::sDac; std::shared_ptr<drivers::I2SDac> AudioState::sDac;
std::shared_ptr<drivers::DigitalPot> AudioState::sPots; std::shared_ptr<drivers::DigitalPot> AudioState::sPots;
@ -25,6 +28,8 @@ std::unique_ptr<FatfsAudioInput> AudioState::sFileSource;
std::unique_ptr<I2SAudioOutput> AudioState::sI2SOutput; std::unique_ptr<I2SAudioOutput> AudioState::sI2SOutput;
std::vector<std::unique_ptr<IAudioElement>> AudioState::sPipeline; std::vector<std::unique_ptr<IAudioElement>> AudioState::sPipeline;
std::deque<AudioState::EnqueuedItem> AudioState::sSongQueue;
auto AudioState::Init(drivers::GpioExpander* gpio_expander, auto AudioState::Init(drivers::GpioExpander* gpio_expander,
std::weak_ptr<database::Database> database) -> bool { std::weak_ptr<database::Database> database) -> bool {
sGpioExpander = gpio_expander; sGpioExpander = gpio_expander;
@ -66,6 +71,33 @@ void Standby::react(const PlayFile& ev) {
} }
} }
void Playback::entry() {
ESP_LOGI(kTag, "beginning playback");
sI2SOutput->SetInUse(true);
}
void Playback::exit() {
ESP_LOGI(kTag, "finishing playback");
sI2SOutput->SetInUse(false);
}
void Playback::react(const InputFileFinished& ev) {
ESP_LOGI(kTag, "finished file");
if (sSongQueue.empty()) {
return;
}
EnqueuedItem next_item = sSongQueue.front();
sSongQueue.pop_front();
if (std::holds_alternative<std::string>(next_item)) {
sFileSource->OpenFile(std::get<std::string>(next_item));
}
}
void Playback::react(const AudioPipelineIdle& ev) {
transit<Standby>();
}
} // namespace states } // namespace states
} // namespace audio } // namespace audio

@ -15,6 +15,8 @@
#include <memory> #include <memory>
#include <variant> #include <variant>
#include "audio_events.hpp"
#include "audio_fsm.hpp"
#include "audio_sink.hpp" #include "audio_sink.hpp"
#include "cbor.h" #include "cbor.h"
#include "esp_err.h" #include "esp_err.h"
@ -60,6 +62,7 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) {
std::vector<Pipeline*> all_elements = pipeline->GetIterationOrder(); std::vector<Pipeline*> all_elements = pipeline->GetIterationOrder();
bool previously_had_work = false;
events::EventQueue& event_queue = events::EventQueue::GetInstance(); events::EventQueue& event_queue = events::EventQueue::GetInstance();
while (1) { while (1) {
// First, see if we actually have any pipeline work to do in this iteration. // First, see if we actually have any pipeline work to do in this iteration.
@ -75,6 +78,11 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) {
} }
} }
if (previously_had_work && !has_work) {
events::Dispatch<AudioPipelineIdle, AudioState>({});
}
previously_had_work = has_work;
// See if there's any new events. // See if there's any new events.
event_queue.ServiceAudio(has_work ? delay_ticks : portMAX_DELAY); event_queue.ServiceAudio(has_work ? delay_ticks : portMAX_DELAY);
@ -118,6 +126,7 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) {
if (sink_stream.info().bytes_in_stream == 0) { if (sink_stream.info().bytes_in_stream == 0) {
// No new bytes to sink, so skip sinking completely. // No new bytes to sink, so skip sinking completely.
ESP_LOGI(kTag, "no bytes to sink");
continue; continue;
} }
@ -130,6 +139,7 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) {
output_format = sink_stream.info().format; output_format = sink_stream.info().format;
sink->Configure(*output_format); sink->Configure(*output_format);
} else { } else {
ESP_LOGI(kTag, "waiting to reconfigure");
continue; continue;
} }
} }

@ -13,8 +13,11 @@
#include <variant> #include <variant>
#include "arena.hpp" #include "arena.hpp"
#include "audio_events.hpp"
#include "audio_fsm.hpp"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "esp_log.h" #include "esp_log.h"
#include "event_queue.hpp"
#include "ff.h" #include "ff.h"
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
@ -69,6 +72,10 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
} }
std::size_t max_size = output->data().size_bytes(); std::size_t max_size = output->data().size_bytes();
if (max_size < output->data().size_bytes() / 2) {
return;
}
std::size_t size = 0; std::size_t size = 0;
FRESULT result = FRESULT result =
f_read(&current_file_, output->data().data(), max_size, &size); f_read(&current_file_, output->data().data(), max_size, &size);
@ -83,6 +90,12 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
if (size < max_size || f_eof(&current_file_)) { if (size < max_size || f_eof(&current_file_)) {
f_close(&current_file_); f_close(&current_file_);
is_file_open_ = false; is_file_open_ = false;
// TODO(jacqueline): MP3 only
std::fill_n(output->data().begin(), 8, std::byte(0));
output->add(8);
events::Dispatch<InputFileFinished, AudioState>({});
} }
} }

@ -38,7 +38,6 @@ I2SAudioOutput::I2SAudioOutput(drivers::GpioExpander* expander,
attenuation_(pots_->GetMaxAttenuation()) { attenuation_(pots_->GetMaxAttenuation()) {
SetVolume(25); // For testing SetVolume(25); // For testing
dac_->SetSource(buffer()); dac_->SetSource(buffer());
dac_->Start();
} }
I2SAudioOutput::~I2SAudioOutput() { I2SAudioOutput::~I2SAudioOutput() {
@ -46,6 +45,15 @@ I2SAudioOutput::~I2SAudioOutput() {
dac_->SetSource(nullptr); dac_->SetSource(nullptr);
} }
auto I2SAudioOutput::SetInUse(bool in_use) -> void {
if (in_use) {
dac_->Start();
} else {
dac_->Stop();
}
pots_->SetZeroCrossDetect(in_use);
}
auto I2SAudioOutput::SetVolumeImbalance(int_fast8_t balance) -> void { auto I2SAudioOutput::SetVolumeImbalance(int_fast8_t balance) -> void {
int_fast8_t new_difference = balance - left_difference_; int_fast8_t new_difference = balance - left_difference_;
left_difference_ = balance; left_difference_ = balance;
@ -124,6 +132,19 @@ auto I2SAudioOutput::Configure(const StreamInfo::Format& format) -> bool {
ESP_LOGI(kTag, "incoming audio stream: %u bpp @ %lu Hz", pcm.bits_per_sample, ESP_LOGI(kTag, "incoming audio stream: %u bpp @ %lu Hz", pcm.bits_per_sample,
pcm.sample_rate); pcm.sample_rate);
drivers::I2SDac::Channels ch;
switch (pcm.channels) {
case 1:
ch = drivers::I2SDac::CHANNELS_MONO;
break;
case 2:
ch = drivers::I2SDac::CHANNELS_STEREO;
break;
default:
ESP_LOGE(kTag, "dropping stream with out of bounds channels");
return false;
}
drivers::I2SDac::BitsPerSample bps; drivers::I2SDac::BitsPerSample bps;
switch (pcm.bits_per_sample) { switch (pcm.bits_per_sample) {
case 16: case 16:
@ -153,9 +174,7 @@ auto I2SAudioOutput::Configure(const StreamInfo::Format& format) -> bool {
return false; return false;
} }
// TODO(jacqueline): probs do something with the channel hey dac_->Reconfigure(ch, bps, sample_rate);
dac_->Reconfigure(bps, sample_rate);
current_config_ = pcm; current_config_ = pcm;
return true; return true;

@ -24,4 +24,7 @@ struct PlaySong : tinyfsm::Event {
std::optional<database::SongTags> tags; std::optional<database::SongTags> tags;
}; };
struct InputFileFinished : tinyfsm::Event {};
struct AudioPipelineIdle : tinyfsm::Event {};
} // namespace audio } // namespace audio

@ -6,6 +6,7 @@
#pragma once #pragma once
#include <deque>
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -16,6 +17,7 @@
#include "gpio_expander.hpp" #include "gpio_expander.hpp"
#include "i2s_audio_output.hpp" #include "i2s_audio_output.hpp"
#include "i2s_dac.hpp" #include "i2s_dac.hpp"
#include "song.hpp"
#include "storage.hpp" #include "storage.hpp"
#include "tinyfsm.hpp" #include "tinyfsm.hpp"
@ -40,6 +42,9 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
virtual void react(const PlaySong&) {} virtual void react(const PlaySong&) {}
virtual void react(const PlayFile&) {} virtual void react(const PlayFile&) {}
virtual void react(const InputFileFinished&) {}
virtual void react(const AudioPipelineIdle&) {}
protected: protected:
static drivers::GpioExpander* sGpioExpander; static drivers::GpioExpander* sGpioExpander;
static std::shared_ptr<drivers::I2SDac> sDac; static std::shared_ptr<drivers::I2SDac> sDac;
@ -49,6 +54,9 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
static std::unique_ptr<FatfsAudioInput> sFileSource; static std::unique_ptr<FatfsAudioInput> sFileSource;
static std::unique_ptr<I2SAudioOutput> sI2SOutput; static std::unique_ptr<I2SAudioOutput> sI2SOutput;
static std::vector<std::unique_ptr<IAudioElement>> sPipeline; static std::vector<std::unique_ptr<IAudioElement>> sPipeline;
typedef std::variant<database::SongId, std::string> EnqueuedItem;
static std::deque<EnqueuedItem> sSongQueue;
}; };
namespace states { namespace states {
@ -68,6 +76,12 @@ class Standby : public AudioState {
class Playback : public AudioState { class Playback : public AudioState {
public: public:
void entry() override;
void exit() override;
void react(const InputFileFinished&) override;
void react(const AudioPipelineIdle&) override;
using AudioState::react; using AudioState::react;
}; };

@ -40,6 +40,8 @@ class IAudioSink {
free(metadata_); free(metadata_);
} }
virtual auto SetInUse(bool) -> void {}
virtual auto SetVolumeImbalance(int_fast8_t balance) -> void = 0; virtual auto SetVolumeImbalance(int_fast8_t balance) -> void = 0;
virtual auto SetVolume(uint_fast8_t percent) -> void = 0; virtual auto SetVolume(uint_fast8_t percent) -> void = 0;
virtual auto GetVolume() -> uint_fast8_t = 0; virtual auto GetVolume() -> uint_fast8_t = 0;

@ -29,6 +29,8 @@ class I2SAudioOutput : public IAudioSink {
std::weak_ptr<drivers::DigitalPot> pots); std::weak_ptr<drivers::DigitalPot> pots);
~I2SAudioOutput(); ~I2SAudioOutput();
auto SetInUse(bool) -> void override;
auto SetVolumeImbalance(int_fast8_t balance) -> void override; auto SetVolumeImbalance(int_fast8_t balance) -> void override;
auto SetVolume(uint_fast8_t percent) -> void override; auto SetVolume(uint_fast8_t percent) -> void override;
auto GetVolume() -> uint_fast8_t override; auto GetVolume() -> uint_fast8_t override;

@ -36,6 +36,10 @@ class Pipeline {
auto GetIterationOrder() -> std::vector<Pipeline*>; auto GetIterationOrder() -> std::vector<Pipeline*>;
// Not copyable or movable.
Pipeline(const Pipeline&) = delete;
Pipeline& operator=(const Pipeline&) = delete;
private: private:
IAudioElement* root_; IAudioElement* root_;
std::vector<std::unique_ptr<Pipeline>> subtrees_; std::vector<std::unique_ptr<Pipeline>> subtrees_;

@ -22,7 +22,8 @@ namespace audio {
void InputStream::consume(std::size_t bytes) const { void InputStream::consume(std::size_t bytes) const {
assert(raw_->info->bytes_in_stream >= bytes); assert(raw_->info->bytes_in_stream >= bytes);
auto new_data = raw_->data.subspan(bytes); auto new_data =
raw_->data.subspan(bytes, raw_->info->bytes_in_stream - bytes);
std::move(new_data.begin(), new_data.end(), raw_->data.begin()); std::move(new_data.begin(), new_data.end(), raw_->data.begin());
raw_->info->bytes_in_stream = new_data.size_bytes(); raw_->info->bytes_in_stream = new_data.size_bytes();
} }

@ -11,6 +11,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include <utility> #include <utility>
@ -32,7 +33,7 @@ class ICodec {
uint32_t sample_rate_hz; uint32_t sample_rate_hz;
}; };
virtual auto GetOutputFormat() -> OutputFormat = 0; virtual auto GetOutputFormat() -> std::optional<OutputFormat> = 0;
enum ProcessingError { MALFORMED_DATA }; enum ProcessingError { MALFORMED_DATA };

@ -8,6 +8,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <optional>
#include <string> #include <string>
#include <utility> #include <utility>
@ -24,7 +25,7 @@ class MadMp3Decoder : public ICodec {
~MadMp3Decoder(); ~MadMp3Decoder();
auto CanHandleType(StreamType type) -> bool override; auto CanHandleType(StreamType type) -> bool override;
auto GetOutputFormat() -> OutputFormat override; auto GetOutputFormat() -> std::optional<OutputFormat> override;
auto ResetForNewStream() -> void override; auto ResetForNewStream() -> void override;
auto SetInput(cpp::span<const std::byte> input) -> void override; auto SetInput(cpp::span<const std::byte> input) -> void override;
auto GetInputPosition() -> std::size_t override; auto GetInputPosition() -> std::size_t override;
@ -37,9 +38,6 @@ class MadMp3Decoder : public ICodec {
mad_frame frame_; mad_frame frame_;
mad_synth synth_; mad_synth synth_;
mad_header header_;
bool has_decoded_header_;
int current_sample_; int current_sample_;
}; };

@ -8,6 +8,7 @@
#include <stdint.h> #include <stdint.h>
#include <cstdint> #include <cstdint>
#include <optional>
#include "mad.h" #include "mad.h"
@ -34,31 +35,29 @@ MadMp3Decoder::MadMp3Decoder() {
mad_stream_init(&stream_); mad_stream_init(&stream_);
mad_frame_init(&frame_); mad_frame_init(&frame_);
mad_synth_init(&synth_); mad_synth_init(&synth_);
mad_header_init(&header_);
} }
MadMp3Decoder::~MadMp3Decoder() { MadMp3Decoder::~MadMp3Decoder() {
mad_stream_finish(&stream_); mad_stream_finish(&stream_);
mad_frame_finish(&frame_); mad_frame_finish(&frame_);
mad_synth_finish(&synth_); mad_synth_finish(&synth_);
mad_header_finish(&header_);
} }
auto MadMp3Decoder::CanHandleType(StreamType type) -> bool { auto MadMp3Decoder::CanHandleType(StreamType type) -> bool {
return type == STREAM_MP3; return type == STREAM_MP3;
} }
auto MadMp3Decoder::GetOutputFormat() -> OutputFormat { auto MadMp3Decoder::GetOutputFormat() -> std::optional<OutputFormat> {
return OutputFormat{ if (synth_.pcm.channels == 0 || synth_.pcm.samplerate == 0) {
return {};
}
return std::optional<OutputFormat>({
.num_channels = static_cast<uint8_t>(synth_.pcm.channels), .num_channels = static_cast<uint8_t>(synth_.pcm.channels),
.bits_per_sample = 16, .bits_per_sample = 24,
.sample_rate_hz = .sample_rate_hz = synth_.pcm.samplerate,
synth_.pcm.samplerate == 0 ? 44100 : synth_.pcm.samplerate, });
};
} }
auto MadMp3Decoder::ResetForNewStream() -> void { auto MadMp3Decoder::ResetForNewStream() -> void {}
has_decoded_header_ = false;
}
auto MadMp3Decoder::SetInput(cpp::span<const std::byte> input) -> void { auto MadMp3Decoder::SetInput(cpp::span<const std::byte> input) -> void {
mad_stream_buffer(&stream_, mad_stream_buffer(&stream_,
@ -71,16 +70,6 @@ auto MadMp3Decoder::GetInputPosition() -> std::size_t {
} }
auto MadMp3Decoder::ProcessNextFrame() -> cpp::result<bool, ProcessingError> { auto MadMp3Decoder::ProcessNextFrame() -> cpp::result<bool, ProcessingError> {
if (!has_decoded_header_) {
// The header of any given frame should be representative of the
// entire stream, so only need to read it once.
mad_header_decode(&header_, &stream_);
has_decoded_header_ = true;
// TODO: Use the info in the header for something. I think the
// duration will help with seeking?
}
// Whatever was last synthesized is now invalid, so ensure we don't try to // Whatever was last synthesized is now invalid, so ensure we don't try to
// send it. // send it.
current_sample_ = -1; current_sample_ = -1;
@ -128,7 +117,6 @@ auto MadMp3Decoder::WriteOutputSamples(cpp::span<std::byte> output)
for (int channel = 0; channel < synth_.pcm.channels; channel++) { for (int channel = 0; channel < synth_.pcm.channels; channel++) {
// TODO(jacqueline): output 24 bit samples when (if?) we have a downmix // TODO(jacqueline): output 24 bit samples when (if?) we have a downmix
// step in the pipeline. // step in the pipeline.
/*
uint32_t sample_24 = uint32_t sample_24 =
scaleToBits(synth_.pcm.samples[channel][current_sample_], 24); scaleToBits(synth_.pcm.samples[channel][current_sample_], 24);
output[output_byte++] = static_cast<std::byte>((sample_24 >> 16) & 0xFF); output[output_byte++] = static_cast<std::byte>((sample_24 >> 16) & 0xFF);
@ -136,11 +124,12 @@ auto MadMp3Decoder::WriteOutputSamples(cpp::span<std::byte> output)
output[output_byte++] = static_cast<std::byte>((sample_24)&0xFF); output[output_byte++] = static_cast<std::byte>((sample_24)&0xFF);
// 24 bit samples must still be aligned to 32 bits. The LSB is ignored. // 24 bit samples must still be aligned to 32 bits. The LSB is ignored.
output[output_byte++] = static_cast<std::byte>(0); output[output_byte++] = static_cast<std::byte>(0);
*/ /*
uint16_t sample_16 = uint16_t sample_16 =
scaleToBits(synth_.pcm.samples[channel][current_sample_], 16); scaleToBits(synth_.pcm.samples[channel][current_sample_], 16);
output[output_byte++] = static_cast<std::byte>((sample_16 >> 8) & 0xFF); output[output_byte++] = static_cast<std::byte>((sample_16 >> 8) & 0xFF);
output[output_byte++] = static_cast<std::byte>((sample_16)&0xFF); output[output_byte++] = static_cast<std::byte>((sample_16)&0xFF);
*/
} }
current_sample_++; current_sample_++;
} }

@ -126,7 +126,7 @@ auto Display::Create(GpioExpander* expander,
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0)); ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0));
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0)); ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0));
ledc_fade_func_install(0); // ledc_fade_func_install(0);
// Next, init the SPI device // Next, init the SPI device
spi_device_interface_config_t spi_cfg = { spi_device_interface_config_t spi_cfg = {
@ -194,8 +194,9 @@ auto Display::SetDisplayOn(bool enabled) -> void {
display_on_ = enabled; display_on_ = enabled;
int new_duty = display_on_ ? brightness_ : 0; int new_duty = display_on_ ? brightness_ : 0;
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, new_duty, 250); // ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, new_duty,
ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT); // 250); ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0,
// LEDC_FADE_NO_WAIT);
} }
void Display::SendInitialisationSequence(const uint8_t* data) { void Display::SendInitialisationSequence(const uint8_t* data) {

@ -116,24 +116,30 @@ auto I2SDac::Stop() -> void {
i2s_active_ = false; i2s_active_ = false;
} }
auto I2SDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void { auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate)
-> void {
if (i2s_active_) { if (i2s_active_) {
i2s_channel_disable(i2s_handle_); i2s_channel_disable(i2s_handle_);
} }
uint8_t bps_bits = 0; switch (ch) {
case CHANNELS_MONO:
slot_config_.slot_mode = I2S_SLOT_MODE_MONO;
break;
case CHANNELS_STEREO:
slot_config_.slot_mode = I2S_SLOT_MODE_STEREO;
break;
}
switch (bps) { switch (bps) {
case BPS_16: case BPS_16:
slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT; slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT;
bps_bits = 0;
break; break;
case BPS_24: case BPS_24:
slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_24BIT; slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_24BIT;
bps_bits = 0b10;
break; break;
case BPS_32: case BPS_32:
slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT; slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT;
bps_bits = 0b11;
break; break;
} }
ESP_ERROR_CHECK(i2s_channel_reconfig_std_slot(i2s_handle_, &slot_config_)); ESP_ERROR_CHECK(i2s_channel_reconfig_std_slot(i2s_handle_, &slot_config_));

@ -43,6 +43,10 @@ class Display {
const lv_area_t* area, const lv_area_t* area,
lv_color_t* color_map); lv_color_t* color_map);
// Not copyable or movable.
Display(const Display&) = delete;
Display& operator=(const Display&) = delete;
private: private:
GpioExpander* gpio_; GpioExpander* gpio_;
spi_device_handle_t handle_; spi_device_handle_t handle_;

@ -40,6 +40,10 @@ class I2SDac {
auto Start() -> void; auto Start() -> void;
auto Stop() -> void; auto Stop() -> void;
enum Channels {
CHANNELS_MONO,
CHANNELS_STEREO,
};
enum BitsPerSample { enum BitsPerSample {
BPS_16 = I2S_DATA_BIT_WIDTH_16BIT, BPS_16 = I2S_DATA_BIT_WIDTH_16BIT,
BPS_24 = I2S_DATA_BIT_WIDTH_24BIT, BPS_24 = I2S_DATA_BIT_WIDTH_24BIT,
@ -56,7 +60,7 @@ class I2SDac {
SAMPLE_RATE_192 = 192000, SAMPLE_RATE_192 = 192000,
}; };
auto Reconfigure(BitsPerSample bps, SampleRate rate) -> void; auto Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) -> void;
auto WriteData(const cpp::span<const std::byte>& data) -> void; auto WriteData(const cpp::span<const std::byte>& data) -> void;
auto SetSource(StreamBufferHandle_t buffer) -> void; auto SetSource(StreamBufferHandle_t buffer) -> void;

Loading…
Cancel
Save