summaryrefslogtreecommitdiff
path: root/src/audio/track_queue.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio/track_queue.cpp')
-rw-r--r--src/audio/track_queue.cpp492
1 files changed, 0 insertions, 492 deletions
diff --git a/src/audio/track_queue.cpp b/src/audio/track_queue.cpp
deleted file mode 100644
index dbe283c4..00000000
--- a/src/audio/track_queue.cpp
+++ /dev/null
@@ -1,492 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "track_queue.hpp"
-#include <stdint.h>
-
-#include <algorithm>
-#include <cstdint>
-#include <memory>
-#include <mutex>
-#include <optional>
-#include <shared_mutex>
-#include <variant>
-
-#include "MillerShuffle.h"
-#include "esp_random.h"
-
-#include "audio_events.hpp"
-#include "audio_fsm.hpp"
-#include "cppbor.h"
-#include "cppbor_parse.h"
-#include "database.hpp"
-#include "event_queue.hpp"
-#include "memory_resource.hpp"
-#include "tasks.hpp"
-#include "track.hpp"
-#include "ui_fsm.hpp"
-
-namespace audio {
-
-[[maybe_unused]] static constexpr char kTag[] = "tracks";
-
-using Reason = QueueUpdate::Reason;
-
-RandomIterator::RandomIterator()
- : seed_(0), pos_(0), size_(0), replay_(false) {}
-
-RandomIterator::RandomIterator(size_t size)
- : seed_(), pos_(0), size_(size), replay_(false) {
- esp_fill_random(&seed_, sizeof(seed_));
-}
-
-auto RandomIterator::current() const -> size_t {
- if (pos_ < size_ || replay_) {
- return MillerShuffle(pos_, seed_, size_);
- }
- return size_;
-}
-
-auto RandomIterator::next() -> void {
- // MillerShuffle behaves well with pos > size, returning different
- // permutations each 'cycle'. We therefore don't need to worry about wrapping
- // this value.
- pos_++;
-}
-
-auto RandomIterator::prev() -> void {
- if (pos_ > 0) {
- pos_--;
- }
-}
-
-auto RandomIterator::resize(size_t s) -> void {
- size_ = s;
- // Changing size will yield a different current position anyway, so reset pos
- // to ensure we yield a full sweep of both new and old indexes.
- pos_ = 0;
-}
-
-auto RandomIterator::replay(bool r) -> void {
- replay_ = r;
-}
-
-auto notifyChanged(bool current_changed, Reason reason) -> void {
- QueueUpdate ev{
- .current_changed = current_changed,
- .reason = reason,
- };
- events::Ui().Dispatch(ev);
- events::Audio().Dispatch(ev);
-}
-
-TrackQueue::TrackQueue(tasks::WorkerPool& bg_worker)
- : mutex_(),
- bg_worker_(bg_worker),
- pos_(0),
- tracks_(&memory::kSpiRamResource),
- shuffle_(),
- repeat_(false),
- replay_(false) {}
-
-auto TrackQueue::current() const -> std::optional<database::TrackId> {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- if (pos_ >= tracks_.size()) {
- return {};
- }
- return tracks_[pos_];
-}
-
-auto TrackQueue::peekNext(std::size_t limit) const
- -> std::vector<database::TrackId> {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- std::vector<database::TrackId> out;
- for (size_t i = pos_ + 1; i < pos_ + limit + 1 && i < tracks_.size(); i++) {
- out.push_back(i);
- }
- return out;
-}
-
-auto TrackQueue::peekPrevious(std::size_t limit) const
- -> std::vector<database::TrackId> {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- std::vector<database::TrackId> out;
- for (size_t i = pos_ - 1; i < pos_ - limit - 1 && i >= tracks_.size(); i--) {
- out.push_back(i);
- }
- return out;
-}
-
-auto TrackQueue::currentPosition() const -> size_t {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- return pos_;
-}
-
-auto TrackQueue::totalSize() const -> size_t {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- return tracks_.size();
-}
-
-auto TrackQueue::insert(Item i, size_t index) -> void {
- bool was_queue_empty;
- bool current_changed;
- {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- was_queue_empty = pos_ == tracks_.size();
- current_changed = was_queue_empty || index == pos_;
- }
-
- auto update_shuffler = [=, this]() {
- if (shuffle_) {
- shuffle_->resize(tracks_.size());
- // If there wasn't anything already playing, then we should make sure we
- // begin playback at a random point, instead of always starting with
- // whatever was inserted first and *then* shuffling.
- // We don't base this purely off of current_changed because we would like
- // 'play this track now' (by inserting at the current pos) to work even
- // when shuffling is enabled.
- if (was_queue_empty) {
- pos_ = shuffle_->current();
- }
- }
- };
-
- if (std::holds_alternative<database::TrackId>(i)) {
- {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- if (index <= tracks_.size()) {
- tracks_.insert(tracks_.begin() + index, std::get<database::TrackId>(i));
- update_shuffler();
- }
- }
- notifyChanged(current_changed, Reason::kExplicitUpdate);
- } else if (std::holds_alternative<database::TrackIterator>(i)) {
- // Iterators can be very large, and retrieving items from them often
- // requires disk i/o. Handle them asynchronously so that inserting them
- // doesn't block.
- bg_worker_.Dispatch<void>([=, this]() {
- database::TrackIterator it = std::get<database::TrackIterator>(i);
- size_t working_pos = index;
- while (true) {
- auto next = *it;
- if (!next) {
- break;
- }
- // Keep this critical section small so that we're not blocking methods
- // like current().
- {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- if (working_pos <= tracks_.size()) {
- tracks_.insert(tracks_.begin() + working_pos, *next);
- }
- }
- working_pos++;
- it++;
- }
- {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- update_shuffler();
- }
- notifyChanged(current_changed, Reason::kExplicitUpdate);
- });
- }
-}
-
-auto TrackQueue::append(Item i) -> void {
- size_t end;
- {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- end = tracks_.size();
- }
- insert(i, end);
-}
-
-auto TrackQueue::next() -> void {
- next(Reason::kExplicitUpdate);
-}
-
-auto TrackQueue::next(Reason r) -> void {
- bool changed = true;
-
- {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- if (shuffle_) {
- shuffle_->next();
- pos_ = shuffle_->current();
- } else {
- if (pos_ + 1 >= tracks_.size()) {
- if (replay_) {
- pos_ = 0;
- } else {
- pos_ = tracks_.size();
- changed = false;
- }
- } else {
- pos_++;
- }
- }
- }
-
- notifyChanged(changed, r);
-}
-
-auto TrackQueue::previous() -> void {
- bool changed = true;
-
- {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- if (shuffle_) {
- shuffle_->prev();
- pos_ = shuffle_->current();
- } else {
- if (pos_ == 0) {
- if (repeat_) {
- pos_ = tracks_.size() - 1;
- } else {
- changed = false;
- }
- } else {
- pos_--;
- }
- }
- }
-
- notifyChanged(changed, Reason::kExplicitUpdate);
-}
-
-auto TrackQueue::finish() -> void {
- if (repeat_) {
- notifyChanged(true, Reason::kRepeatingLastTrack);
- } else {
- next(Reason::kTrackFinished);
- }
-}
-
-auto TrackQueue::skipTo(database::TrackId id) -> void {
- // Defer this work to the background not because it's particularly
- // long-running (although it could be), but because we want to ensure we
- // only search for the given id after any previously pending iterator
- // insertions have finished.
- bg_worker_.Dispatch<void>([=, this]() {
- bool found = false;
- {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- for (size_t i = 0; i < tracks_.size(); i++) {
- if (tracks_[i] == id) {
- pos_ = i;
- found = true;
- break;
- }
- }
- }
- if (found) {
- notifyChanged(true, Reason::kExplicitUpdate);
- }
- });
-}
-
-auto TrackQueue::clear() -> void {
- {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- if (tracks_.empty()) {
- return;
- }
-
- pos_ = 0;
- tracks_.clear();
-
- if (shuffle_) {
- shuffle_->resize(0);
- }
- }
-
- notifyChanged(true, Reason::kExplicitUpdate);
-}
-
-auto TrackQueue::random(bool en) -> void {
- {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- // Don't check for en == true already; this has the side effect that
- // repeated calls with en == true will re-shuffle.
- if (en) {
- shuffle_.emplace(tracks_.size());
- shuffle_->replay(replay_);
- } else {
- shuffle_.reset();
- }
- }
-
- // Current track doesn't get randomised until next().
- notifyChanged(false, Reason::kExplicitUpdate);
-}
-
-auto TrackQueue::random() const -> bool {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- return shuffle_.has_value();
-}
-
-auto TrackQueue::repeat(bool en) -> void {
- {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- repeat_ = en;
- }
-
- notifyChanged(false, Reason::kExplicitUpdate);
-}
-
-auto TrackQueue::repeat() const -> bool {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- return repeat_;
-}
-
-auto TrackQueue::replay(bool en) -> void {
- {
- const std::unique_lock<std::shared_mutex> lock(mutex_);
- replay_ = en;
- if (shuffle_) {
- shuffle_->replay(en);
- }
- }
- notifyChanged(false, Reason::kExplicitUpdate);
-}
-
-auto TrackQueue::replay() const -> bool {
- const std::shared_lock<std::shared_mutex> lock(mutex_);
- return replay_;
-}
-
-auto TrackQueue::serialise() -> std::string {
- cppbor::Array tracks{};
- for (database::TrackId track : tracks_) {
- tracks.add(cppbor::Uint(track));
- }
- cppbor::Map encoded;
- encoded.add(cppbor::Uint{0}, cppbor::Array{
- cppbor::Uint{pos_},
- cppbor::Bool{repeat_},
- cppbor::Bool{replay_},
- });
- if (shuffle_) {
- encoded.add(cppbor::Uint{1}, cppbor::Array{
- cppbor::Uint{shuffle_->size()},
- cppbor::Uint{shuffle_->seed()},
- cppbor::Uint{shuffle_->pos()},
- });
- }
- encoded.add(cppbor::Uint{2}, std::move(tracks));
- return encoded.toString();
-}
-
-TrackQueue::QueueParseClient::QueueParseClient(TrackQueue& queue)
- : queue_(queue), state_(State::kInit), i_(0) {}
-
-cppbor::ParseClient* TrackQueue::QueueParseClient::item(
- std::unique_ptr<cppbor::Item>& item,
- const uint8_t* hdrBegin,
- const uint8_t* valueBegin,
- const uint8_t* end) {
- if (state_ == State::kInit) {
- if (item->type() == cppbor::MAP) {
- state_ = State::kRoot;
- }
- } else if (state_ == State::kRoot) {
- if (item->type() == cppbor::UINT) {
- switch (item->asUint()->unsignedValue()) {
- case 0:
- state_ = State::kMetadata;
- break;
- case 1:
- state_ = State::kShuffle;
- break;
- case 2:
- state_ = State::kTracks;
- break;
- default:
- state_ = State::kFinished;
- }
- }
- } else if (state_ == State::kMetadata) {
- if (item->type() == cppbor::ARRAY) {
- i_ = 0;
- } else if (item->type() == cppbor::UINT) {
- queue_.pos_ = item->asUint()->unsignedValue();
- } else if (item->type() == cppbor::SIMPLE) {
- bool val = item->asBool()->value();
- if (i_ == 0) {
- queue_.repeat_ = val;
- } else if (i_ == 1) {
- queue_.replay_ = val;
- }
- i_++;
- }
- } else if (state_ == State::kShuffle) {
- if (item->type() == cppbor::ARRAY) {
- i_ = 0;
- queue_.shuffle_.emplace();
- queue_.shuffle_->replay(queue_.replay_);
- } else if (item->type() == cppbor::UINT) {
- auto val = item->asUint()->unsignedValue();
- switch (i_) {
- case 0:
- queue_.shuffle_->size() = val;
- break;
- case 1:
- queue_.shuffle_->seed() = val;
- break;
- case 2:
- queue_.shuffle_->pos() = val;
- break;
- default:
- break;
- }
- i_++;
- }
- } else if (state_ == State::kTracks) {
- if (item->type() == cppbor::UINT) {
- queue_.tracks_.push_back(item->asUint()->unsignedValue());
- }
- } else if (state_ == State::kFinished) {
- }
- return this;
-}
-
-cppbor::ParseClient* TrackQueue::QueueParseClient::itemEnd(
- std::unique_ptr<cppbor::Item>& item,
- const uint8_t* hdrBegin,
- const uint8_t* valueBegin,
- const uint8_t* end) {
- if (state_ == State::kInit) {
- state_ = State::kFinished;
- } else if (state_ == State::kRoot) {
- state_ = State::kFinished;
- } else if (state_ == State::kMetadata) {
- if (item->type() == cppbor::ARRAY) {
- state_ = State::kRoot;
- }
- } else if (state_ == State::kShuffle) {
- if (item->type() == cppbor::ARRAY) {
- state_ = State::kRoot;
- }
- } else if (state_ == State::kTracks) {
- if (item->type() == cppbor::ARRAY) {
- state_ = State::kRoot;
- }
- } else if (state_ == State::kFinished) {
- }
- return this;
-}
-
-auto TrackQueue::deserialise(const std::string& s) -> void {
- if (s.empty()) {
- return;
- }
- QueueParseClient client{*this};
- const uint8_t* data = reinterpret_cast<const uint8_t*>(s.data());
- cppbor::parse(data, data + s.size(), &client);
- notifyChanged(true, Reason::kDeserialised);
-}
-
-} // namespace audio