summaryrefslogtreecommitdiff
path: root/src/audio/audio_fsm.cpp
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-02-29 15:47:21 +1100
committerjacqueline <me@jacqueline.id.au>2024-02-29 15:47:21 +1100
commit173b09b0151ae765b1a8e69dfb60d14d502801f6 (patch)
tree1b7c28cf7a772a98f8b615d60f373a65b2739eb1 /src/audio/audio_fsm.cpp
parent9fca08f8434a05e1fe93a1c4f8133f0e7fc118bf (diff)
downloadtangara-fw-173b09b0151ae765b1a8e69dfb60d14d502801f6.tar.gz
Clear the drain buffer when skipping between tracks
Diffstat (limited to 'src/audio/audio_fsm.cpp')
-rw-r--r--src/audio/audio_fsm.cpp53
1 files changed, 42 insertions, 11 deletions
diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp
index ea0315eb..08a0941a 100644
--- a/src/audio/audio_fsm.cpp
+++ b/src/audio/audio_fsm.cpp
@@ -51,6 +51,10 @@ std::shared_ptr<I2SAudioOutput> AudioState::sI2SOutput;
std::shared_ptr<BluetoothAudioOutput> AudioState::sBtOutput;
std::shared_ptr<IAudioOutput> AudioState::sOutput;
+// Two seconds of samples for two channels, at a representative sample rate.
+constexpr size_t kDrainBufferSize = sizeof(sample::Sample) * 48000 * 4;
+StreamBufferHandle_t AudioState::sDrainBuffer;
+
std::optional<database::TrackId> AudioState::sCurrentTrack;
bool AudioState::sIsPlaybackAllowed;
@@ -129,7 +133,7 @@ void AudioState::react(const SetVolumeBalance& ev) {
void AudioState::react(const OutputModeChanged& ev) {
ESP_LOGI(kTag, "output mode changed");
auto new_mode = sServices->nvs().OutputMode();
- sOutput->SetMode(IAudioOutput::Modes::kOff);
+ sOutput->mode(IAudioOutput::Modes::kOff);
switch (new_mode) {
case drivers::NvsStorage::Output::kBluetooth:
sOutput = sBtOutput;
@@ -138,7 +142,7 @@ void AudioState::react(const OutputModeChanged& ev) {
sOutput = sI2SOutput;
break;
}
- sOutput->SetMode(IAudioOutput::Modes::kOnPaused);
+ sOutput->mode(IAudioOutput::Modes::kOnPaused);
sSampleConverter->SetOutput(sOutput);
// Bluetooth volume isn't 'changed' until we've connected to a device.
@@ -150,6 +154,32 @@ void AudioState::react(const OutputModeChanged& ev) {
}
}
+auto AudioState::clearDrainBuffer() -> void {
+ // Tell the decoder to stop adding new samples. This might not take effect
+ // immediately, since the decoder might currently be stuck waiting for space
+ // to become available in the drain buffer.
+ sFileSource->SetPath();
+
+ auto mode = sOutput->mode();
+ if (mode == IAudioOutput::Modes::kOnPlaying) {
+ // If we're currently playing, then the drain buffer will be actively
+ // draining on its own. Just keep trying to reset until it works.
+ while (xStreamBufferReset(sDrainBuffer) != pdPASS) {
+ }
+ } else {
+ // If we're not currently playing, then we need to actively pull samples
+ // out of the drain buffer to unblock the decoder.
+ while (!xStreamBufferIsEmpty(sDrainBuffer)) {
+ // Read a little to unblock the decoder.
+ uint8_t drain[2048];
+ xStreamBufferReceive(sDrainBuffer, drain, sizeof(drain), 0);
+
+ // Try to quickly discard the rest.
+ xStreamBufferReset(sDrainBuffer);
+ }
+ }
+}
+
auto AudioState::playTrack(database::TrackId id) -> void {
sCurrentTrack = id;
sServices->bg_worker().Dispatch<void>([=]() {
@@ -194,10 +224,6 @@ void AudioState::react(const TogglePlayPause& ev) {
namespace states {
-// Two seconds of samples for two channels, at a representative sample rate.
-constexpr size_t kDrainBufferSize = sizeof(sample::Sample) * 48000 * 4;
-static StreamBufferHandle_t sDrainBuffer;
-
void Uninitialised::react(const system_fsm::BootComplete& ev) {
sServices = ev.services;
@@ -229,7 +255,7 @@ void Uninitialised::react(const system_fsm::BootComplete& ev) {
} else {
sOutput = sBtOutput;
}
- sOutput->SetMode(IAudioOutput::Modes::kOnPaused);
+ sOutput->mode(IAudioOutput::Modes::kOnPaused);
events::Ui().Dispatch(VolumeLimitChanged{
.new_limit_db =
@@ -272,6 +298,7 @@ void Standby::react(const QueueUpdate& ev) {
if (!current_track || (sCurrentTrack && (*sCurrentTrack == *current_track))) {
return;
}
+ clearDrainBuffer();
playTrack(*current_track);
}
@@ -315,7 +342,7 @@ void Standby::react(const system_fsm::StorageMounted& ev) {
void Playback::entry() {
ESP_LOGI(kTag, "beginning playback");
- sOutput->SetMode(IAudioOutput::Modes::kOnPlaying);
+ sOutput->mode(IAudioOutput::Modes::kOnPlaying);
events::System().Dispatch(PlaybackStarted{});
events::Ui().Dispatch(PlaybackStarted{});
@@ -323,10 +350,10 @@ void Playback::entry() {
void Playback::exit() {
ESP_LOGI(kTag, "finishing playback");
- sOutput->SetMode(IAudioOutput::Modes::kOnPaused);
+ sOutput->mode(IAudioOutput::Modes::kOnPaused);
- // Stash the current volume now, in case it changed during playback, since we
- // might be powering off soon.
+ // Stash the current volume now, in case it changed during playback, since
+ // we might be powering off soon.
commitVolume();
events::System().Dispatch(PlaybackStopped{});
@@ -343,6 +370,10 @@ void Playback::react(const QueueUpdate& ev) {
if (!ev.current_changed) {
return;
}
+ // Cut the current track immediately.
+ if (ev.reason == QueueUpdate::Reason::kExplicitUpdate) {
+ clearDrainBuffer();
+ }
auto current_track = sServices->track_queue().current();
if (!current_track) {
sFileSource->SetPath();