diff options
Diffstat (limited to 'src/audio/track_queue.cpp')
| -rw-r--r-- | src/audio/track_queue.cpp | 492 |
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 |
