Flesh out audio state machine for playback
Also fix mono playback
This commit is contained in:
+16
-16
@@ -98,27 +98,27 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
|
||||
|
||||
while (true) {
|
||||
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{
|
||||
.channels = format.num_channels,
|
||||
.bits_per_sample = format.bits_per_sample,
|
||||
.sample_rate = format.sample_rate_hz,
|
||||
.channels = format->num_channels,
|
||||
.bits_per_sample = format->bits_per_sample,
|
||||
.sample_rate = format->sample_rate_hz,
|
||||
};
|
||||
}
|
||||
|
||||
if (!output->prepare(*current_output_format_)) {
|
||||
break;
|
||||
}
|
||||
if (!output->prepare(*current_output_format_)) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto write_res = current_codec_->WriteOutputSamples(output->data());
|
||||
output->add(write_res.first);
|
||||
has_samples_to_send_ = !write_res.second;
|
||||
auto write_res = current_codec_->WriteOutputSamples(output->data());
|
||||
output->add(write_res.first);
|
||||
has_samples_to_send_ = !write_res.second;
|
||||
|
||||
if (has_samples_to_send_) {
|
||||
// We weren't able to fit all the generated samples into the output
|
||||
// buffer. Stop trying; we'll finish up during the next pass.
|
||||
break;
|
||||
if (has_samples_to_send_) {
|
||||
// We weren't able to fit all the generated samples into the output
|
||||
// buffer. Stop trying; we'll finish up during the next pass.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "audio_fsm.hpp"
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
#include "audio_decoder.hpp"
|
||||
#include "audio_events.hpp"
|
||||
#include "audio_task.hpp"
|
||||
@@ -16,6 +17,8 @@
|
||||
|
||||
namespace audio {
|
||||
|
||||
static const char kTag[] = "audio_fsm";
|
||||
|
||||
drivers::GpioExpander* AudioState::sGpioExpander;
|
||||
std::shared_ptr<drivers::I2SDac> AudioState::sDac;
|
||||
std::shared_ptr<drivers::DigitalPot> AudioState::sPots;
|
||||
@@ -25,6 +28,8 @@ std::unique_ptr<FatfsAudioInput> AudioState::sFileSource;
|
||||
std::unique_ptr<I2SAudioOutput> AudioState::sI2SOutput;
|
||||
std::vector<std::unique_ptr<IAudioElement>> AudioState::sPipeline;
|
||||
|
||||
std::deque<AudioState::EnqueuedItem> AudioState::sSongQueue;
|
||||
|
||||
auto AudioState::Init(drivers::GpioExpander* gpio_expander,
|
||||
std::weak_ptr<database::Database> database) -> bool {
|
||||
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 audio
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
#include "audio_events.hpp"
|
||||
#include "audio_fsm.hpp"
|
||||
#include "audio_sink.hpp"
|
||||
#include "cbor.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();
|
||||
|
||||
bool previously_had_work = false;
|
||||
events::EventQueue& event_queue = events::EventQueue::GetInstance();
|
||||
while (1) {
|
||||
// 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.
|
||||
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) {
|
||||
// No new bytes to sink, so skip sinking completely.
|
||||
ESP_LOGI(kTag, "no bytes to sink");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -130,6 +139,7 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) {
|
||||
output_format = sink_stream.info().format;
|
||||
sink->Configure(*output_format);
|
||||
} else {
|
||||
ESP_LOGI(kTag, "waiting to reconfigure");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@
|
||||
#include <variant>
|
||||
|
||||
#include "arena.hpp"
|
||||
#include "audio_events.hpp"
|
||||
#include "audio_fsm.hpp"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_log.h"
|
||||
#include "event_queue.hpp"
|
||||
#include "ff.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();
|
||||
if (max_size < output->data().size_bytes() / 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t size = 0;
|
||||
FRESULT result =
|
||||
f_read(¤t_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(¤t_file_)) {
|
||||
f_close(¤t_file_);
|
||||
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()) {
|
||||
SetVolume(25); // For testing
|
||||
dac_->SetSource(buffer());
|
||||
dac_->Start();
|
||||
}
|
||||
|
||||
I2SAudioOutput::~I2SAudioOutput() {
|
||||
@@ -46,6 +45,15 @@ I2SAudioOutput::~I2SAudioOutput() {
|
||||
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 {
|
||||
int_fast8_t new_difference = balance - left_difference_;
|
||||
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,
|
||||
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;
|
||||
switch (pcm.bits_per_sample) {
|
||||
case 16:
|
||||
@@ -153,9 +174,7 @@ auto I2SAudioOutput::Configure(const StreamInfo::Format& format) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(jacqueline): probs do something with the channel hey
|
||||
|
||||
dac_->Reconfigure(bps, sample_rate);
|
||||
dac_->Reconfigure(ch, bps, sample_rate);
|
||||
current_config_ = pcm;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -24,4 +24,7 @@ struct PlaySong : tinyfsm::Event {
|
||||
std::optional<database::SongTags> tags;
|
||||
};
|
||||
|
||||
struct InputFileFinished : tinyfsm::Event {};
|
||||
struct AudioPipelineIdle : tinyfsm::Event {};
|
||||
|
||||
} // namespace audio
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -16,6 +17,7 @@
|
||||
#include "gpio_expander.hpp"
|
||||
#include "i2s_audio_output.hpp"
|
||||
#include "i2s_dac.hpp"
|
||||
#include "song.hpp"
|
||||
#include "storage.hpp"
|
||||
#include "tinyfsm.hpp"
|
||||
|
||||
@@ -40,6 +42,9 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
|
||||
virtual void react(const PlaySong&) {}
|
||||
virtual void react(const PlayFile&) {}
|
||||
|
||||
virtual void react(const InputFileFinished&) {}
|
||||
virtual void react(const AudioPipelineIdle&) {}
|
||||
|
||||
protected:
|
||||
static drivers::GpioExpander* sGpioExpander;
|
||||
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<I2SAudioOutput> sI2SOutput;
|
||||
static std::vector<std::unique_ptr<IAudioElement>> sPipeline;
|
||||
|
||||
typedef std::variant<database::SongId, std::string> EnqueuedItem;
|
||||
static std::deque<EnqueuedItem> sSongQueue;
|
||||
};
|
||||
|
||||
namespace states {
|
||||
@@ -68,6 +76,12 @@ class Standby : public AudioState {
|
||||
|
||||
class Playback : public AudioState {
|
||||
public:
|
||||
void entry() override;
|
||||
void exit() override;
|
||||
|
||||
void react(const InputFileFinished&) override;
|
||||
void react(const AudioPipelineIdle&) override;
|
||||
|
||||
using AudioState::react;
|
||||
};
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ class IAudioSink {
|
||||
free(metadata_);
|
||||
}
|
||||
|
||||
virtual auto SetInUse(bool) -> void {}
|
||||
|
||||
virtual auto SetVolumeImbalance(int_fast8_t balance) -> void = 0;
|
||||
virtual auto SetVolume(uint_fast8_t percent) -> void = 0;
|
||||
virtual auto GetVolume() -> uint_fast8_t = 0;
|
||||
|
||||
@@ -29,6 +29,8 @@ class I2SAudioOutput : public IAudioSink {
|
||||
std::weak_ptr<drivers::DigitalPot> pots);
|
||||
~I2SAudioOutput();
|
||||
|
||||
auto SetInUse(bool) -> void override;
|
||||
|
||||
auto SetVolumeImbalance(int_fast8_t balance) -> void override;
|
||||
auto SetVolume(uint_fast8_t percent) -> void override;
|
||||
auto GetVolume() -> uint_fast8_t override;
|
||||
|
||||
@@ -36,6 +36,10 @@ class Pipeline {
|
||||
|
||||
auto GetIterationOrder() -> std::vector<Pipeline*>;
|
||||
|
||||
// Not copyable or movable.
|
||||
Pipeline(const Pipeline&) = delete;
|
||||
Pipeline& operator=(const Pipeline&) = delete;
|
||||
|
||||
private:
|
||||
IAudioElement* root_;
|
||||
std::vector<std::unique_ptr<Pipeline>> subtrees_;
|
||||
|
||||
@@ -22,7 +22,8 @@ namespace audio {
|
||||
|
||||
void InputStream::consume(std::size_t bytes) const {
|
||||
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());
|
||||
raw_->info->bytes_in_stream = new_data.size_bytes();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
@@ -32,7 +33,7 @@ class ICodec {
|
||||
uint32_t sample_rate_hz;
|
||||
};
|
||||
|
||||
virtual auto GetOutputFormat() -> OutputFormat = 0;
|
||||
virtual auto GetOutputFormat() -> std::optional<OutputFormat> = 0;
|
||||
|
||||
enum ProcessingError { MALFORMED_DATA };
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
@@ -24,7 +25,7 @@ class MadMp3Decoder : public ICodec {
|
||||
~MadMp3Decoder();
|
||||
|
||||
auto CanHandleType(StreamType type) -> bool override;
|
||||
auto GetOutputFormat() -> OutputFormat override;
|
||||
auto GetOutputFormat() -> std::optional<OutputFormat> override;
|
||||
auto ResetForNewStream() -> void override;
|
||||
auto SetInput(cpp::span<const std::byte> input) -> void override;
|
||||
auto GetInputPosition() -> std::size_t override;
|
||||
@@ -37,9 +38,6 @@ class MadMp3Decoder : public ICodec {
|
||||
mad_frame frame_;
|
||||
mad_synth synth_;
|
||||
|
||||
mad_header header_;
|
||||
bool has_decoded_header_;
|
||||
|
||||
int current_sample_;
|
||||
};
|
||||
|
||||
|
||||
+12
-23
@@ -8,6 +8,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "mad.h"
|
||||
|
||||
@@ -34,31 +35,29 @@ MadMp3Decoder::MadMp3Decoder() {
|
||||
mad_stream_init(&stream_);
|
||||
mad_frame_init(&frame_);
|
||||
mad_synth_init(&synth_);
|
||||
mad_header_init(&header_);
|
||||
}
|
||||
MadMp3Decoder::~MadMp3Decoder() {
|
||||
mad_stream_finish(&stream_);
|
||||
mad_frame_finish(&frame_);
|
||||
mad_synth_finish(&synth_);
|
||||
mad_header_finish(&header_);
|
||||
}
|
||||
|
||||
auto MadMp3Decoder::CanHandleType(StreamType type) -> bool {
|
||||
return type == STREAM_MP3;
|
||||
}
|
||||
|
||||
auto MadMp3Decoder::GetOutputFormat() -> OutputFormat {
|
||||
return OutputFormat{
|
||||
auto MadMp3Decoder::GetOutputFormat() -> std::optional<OutputFormat> {
|
||||
if (synth_.pcm.channels == 0 || synth_.pcm.samplerate == 0) {
|
||||
return {};
|
||||
}
|
||||
return std::optional<OutputFormat>({
|
||||
.num_channels = static_cast<uint8_t>(synth_.pcm.channels),
|
||||
.bits_per_sample = 16,
|
||||
.sample_rate_hz =
|
||||
synth_.pcm.samplerate == 0 ? 44100 : synth_.pcm.samplerate,
|
||||
};
|
||||
.bits_per_sample = 24,
|
||||
.sample_rate_hz = synth_.pcm.samplerate,
|
||||
});
|
||||
}
|
||||
|
||||
auto MadMp3Decoder::ResetForNewStream() -> void {
|
||||
has_decoded_header_ = false;
|
||||
}
|
||||
auto MadMp3Decoder::ResetForNewStream() -> void {}
|
||||
|
||||
auto MadMp3Decoder::SetInput(cpp::span<const std::byte> input) -> void {
|
||||
mad_stream_buffer(&stream_,
|
||||
@@ -71,16 +70,6 @@ auto MadMp3Decoder::GetInputPosition() -> std::size_t {
|
||||
}
|
||||
|
||||
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
|
||||
// send it.
|
||||
current_sample_ = -1;
|
||||
@@ -128,7 +117,6 @@ auto MadMp3Decoder::WriteOutputSamples(cpp::span<std::byte> output)
|
||||
for (int channel = 0; channel < synth_.pcm.channels; channel++) {
|
||||
// TODO(jacqueline): output 24 bit samples when (if?) we have a downmix
|
||||
// step in the pipeline.
|
||||
/*
|
||||
uint32_t sample_24 =
|
||||
scaleToBits(synth_.pcm.samples[channel][current_sample_], 24);
|
||||
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);
|
||||
// 24 bit samples must still be aligned to 32 bits. The LSB is ignored.
|
||||
output[output_byte++] = static_cast<std::byte>(0);
|
||||
*/
|
||||
/*
|
||||
uint16_t 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)&0xFF);
|
||||
*/
|
||||
}
|
||||
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_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0));
|
||||
|
||||
ledc_fade_func_install(0);
|
||||
// ledc_fade_func_install(0);
|
||||
|
||||
// Next, init the SPI device
|
||||
spi_device_interface_config_t spi_cfg = {
|
||||
@@ -194,8 +194,9 @@ auto Display::SetDisplayOn(bool enabled) -> void {
|
||||
display_on_ = enabled;
|
||||
|
||||
int new_duty = display_on_ ? brightness_ : 0;
|
||||
ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, new_duty, 250);
|
||||
ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, LEDC_FADE_NO_WAIT);
|
||||
// ledc_set_fade_with_time(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, new_duty,
|
||||
// 250); ledc_fade_start(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0,
|
||||
// LEDC_FADE_NO_WAIT);
|
||||
}
|
||||
|
||||
void Display::SendInitialisationSequence(const uint8_t* data) {
|
||||
|
||||
+11
-5
@@ -116,24 +116,30 @@ auto I2SDac::Stop() -> void {
|
||||
i2s_active_ = false;
|
||||
}
|
||||
|
||||
auto I2SDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
|
||||
auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate)
|
||||
-> void {
|
||||
if (i2s_active_) {
|
||||
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) {
|
||||
case BPS_16:
|
||||
slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT;
|
||||
bps_bits = 0;
|
||||
break;
|
||||
case BPS_24:
|
||||
slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_24BIT;
|
||||
bps_bits = 0b10;
|
||||
break;
|
||||
case BPS_32:
|
||||
slot_config_.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT;
|
||||
bps_bits = 0b11;
|
||||
break;
|
||||
}
|
||||
ESP_ERROR_CHECK(i2s_channel_reconfig_std_slot(i2s_handle_, &slot_config_));
|
||||
|
||||
@@ -43,6 +43,10 @@ class Display {
|
||||
const lv_area_t* area,
|
||||
lv_color_t* color_map);
|
||||
|
||||
// Not copyable or movable.
|
||||
Display(const Display&) = delete;
|
||||
Display& operator=(const Display&) = delete;
|
||||
|
||||
private:
|
||||
GpioExpander* gpio_;
|
||||
spi_device_handle_t handle_;
|
||||
|
||||
@@ -40,6 +40,10 @@ class I2SDac {
|
||||
auto Start() -> void;
|
||||
auto Stop() -> void;
|
||||
|
||||
enum Channels {
|
||||
CHANNELS_MONO,
|
||||
CHANNELS_STEREO,
|
||||
};
|
||||
enum BitsPerSample {
|
||||
BPS_16 = I2S_DATA_BIT_WIDTH_16BIT,
|
||||
BPS_24 = I2S_DATA_BIT_WIDTH_24BIT,
|
||||
@@ -56,7 +60,7 @@ class I2SDac {
|
||||
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 SetSource(StreamBufferHandle_t buffer) -> void;
|
||||
|
||||
Reference in New Issue
Block a user