summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-06-07 13:19:45 +1000
committerjacqueline <me@jacqueline.id.au>2023-06-07 13:19:45 +1000
commit1f903accd95361735c841c87fdc6494ad3331b40 (patch)
tree5b69bd0548c06a93852a576fa90a170fa303a15e
parent2a568846bd8f1c9e23e86e7570557eed6f18cf9f (diff)
downloadtangara-fw-1f903accd95361735c841c87fdc6494ad3331b40.tar.gz
Flesh out audio state machine for playback
Also fix mono playback
-rw-r--r--src/audio/audio_decoder.cpp32
-rw-r--r--src/audio/audio_fsm.cpp32
-rw-r--r--src/audio/audio_task.cpp10
-rw-r--r--src/audio/fatfs_audio_input.cpp13
-rw-r--r--src/audio/i2s_audio_output.cpp27
-rw-r--r--src/audio/include/audio_events.hpp3
-rw-r--r--src/audio/include/audio_fsm.hpp14
-rw-r--r--src/audio/include/audio_sink.hpp2
-rw-r--r--src/audio/include/i2s_audio_output.hpp2
-rw-r--r--src/audio/include/pipeline.hpp4
-rw-r--r--src/audio/stream_info.cpp3
-rw-r--r--src/codecs/include/codec.hpp3
-rw-r--r--src/codecs/include/mad.hpp6
-rw-r--r--src/codecs/mad.cpp35
-rw-r--r--src/drivers/display.cpp7
-rw-r--r--src/drivers/i2s_dac.cpp16
-rw-r--r--src/drivers/include/display.hpp4
-rw-r--r--src/drivers/include/i2s_dac.hpp6
18 files changed, 161 insertions, 58 deletions
diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp
index 5a2c75c7..af026262 100644
--- a/src/audio/audio_decoder.cpp
+++ b/src/audio/audio_decoder.cpp
@@ -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;
+ }
}
}
diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp
index 3ab8b6e8..6c974905 100644
--- a/src/audio/audio_fsm.cpp
+++ b/src/audio/audio_fsm.cpp
@@ -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
diff --git a/src/audio/audio_task.cpp b/src/audio/audio_task.cpp
index 46d527b5..9dd7d994 100644
--- a/src/audio/audio_task.cpp
+++ b/src/audio/audio_task.cpp
@@ -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;
}
}
diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp
index 8abc7d32..9affcf1a 100644
--- a/src/audio/fatfs_audio_input.cpp
+++ b/src/audio/fatfs_audio_input.cpp
@@ -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(&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_)) {
f_close(&current_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>({});
}
}
diff --git a/src/audio/i2s_audio_output.cpp b/src/audio/i2s_audio_output.cpp
index aab011d1..982499a0 100644
--- a/src/audio/i2s_audio_output.cpp
+++ b/src/audio/i2s_audio_output.cpp
@@ -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;
diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp
index 765e0899..920b134e 100644
--- a/src/audio/include/audio_events.hpp
+++ b/src/audio/include/audio_events.hpp
@@ -24,4 +24,7 @@ struct PlaySong : tinyfsm::Event {
std::optional<database::SongTags> tags;
};
+struct InputFileFinished : tinyfsm::Event {};
+struct AudioPipelineIdle : tinyfsm::Event {};
+
} // namespace audio
diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp
index 6274041c..5dad87c0 100644
--- a/src/audio/include/audio_fsm.hpp
+++ b/src/audio/include/audio_fsm.hpp
@@ -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;
};
diff --git a/src/audio/include/audio_sink.hpp b/src/audio/include/audio_sink.hpp
index 2cc706ff..e6538bda 100644
--- a/src/audio/include/audio_sink.hpp
+++ b/src/audio/include/audio_sink.hpp
@@ -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;
diff --git a/src/audio/include/i2s_audio_output.hpp b/src/audio/include/i2s_audio_output.hpp
index 20980573..7c125476 100644
--- a/src/audio/include/i2s_audio_output.hpp
+++ b/src/audio/include/i2s_audio_output.hpp
@@ -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;
diff --git a/src/audio/include/pipeline.hpp b/src/audio/include/pipeline.hpp
index c1f6bf59..bb773006 100644
--- a/src/audio/include/pipeline.hpp
+++ b/src/audio/include/pipeline.hpp
@@ -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_;
diff --git a/src/audio/stream_info.cpp b/src/audio/stream_info.cpp
index ee85f35d..8fc31530 100644
--- a/src/audio/stream_info.cpp
+++ b/src/audio/stream_info.cpp
@@ -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();
}
diff --git a/src/codecs/include/codec.hpp b/src/codecs/include/codec.hpp
index 9dd717c9..c8a68ff3 100644
--- a/src/codecs/include/codec.hpp
+++ b/src/codecs/include/codec.hpp
@@ -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 };
diff --git a/src/codecs/include/mad.hpp b/src/codecs/include/mad.hpp
index 3b1f5757..ea16cdc8 100644
--- a/src/codecs/include/mad.hpp
+++ b/src/codecs/include/mad.hpp
@@ -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_;
};
diff --git a/src/codecs/mad.cpp b/src/codecs/mad.cpp
index 2e55d4c6..5044c22f 100644
--- a/src/codecs/mad.cpp
+++ b/src/codecs/mad.cpp
@@ -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_++;
}
diff --git a/src/drivers/display.cpp b/src/drivers/display.cpp
index dd4cecb8..3c0df978 100644
--- a/src/drivers/display.cpp
+++ b/src/drivers/display.cpp
@@ -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) {
diff --git a/src/drivers/i2s_dac.cpp b/src/drivers/i2s_dac.cpp
index 68e92970..dbbb58f1 100644
--- a/src/drivers/i2s_dac.cpp
+++ b/src/drivers/i2s_dac.cpp
@@ -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_));
diff --git a/src/drivers/include/display.hpp b/src/drivers/include/display.hpp
index b394dd9e..4b63e1c4 100644
--- a/src/drivers/include/display.hpp
+++ b/src/drivers/include/display.hpp
@@ -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_;
diff --git a/src/drivers/include/i2s_dac.hpp b/src/drivers/include/i2s_dac.hpp
index 42c094b1..388d09fa 100644
--- a/src/drivers/include/i2s_dac.hpp
+++ b/src/drivers/include/i2s_dac.hpp
@@ -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;