commit
3f177cdb88
@ -1,5 +0,0 @@ |
|||||||
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
# |
|
||||||
# SPDX-License-Identifier: GPL-3.0-only |
|
||||||
|
|
||||||
idf_component_register(INCLUDE_DIRS "include") |
|
@ -1,23 +0,0 @@ |
|||||||
Boost Software License - Version 1.0 - August 17th, 2003 |
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person or organization |
|
||||||
obtaining a copy of the software and accompanying documentation covered by |
|
||||||
this license (the "Software") to use, reproduce, display, distribute, |
|
||||||
execute, and transmit the Software, and to prepare derivative works of the |
|
||||||
Software, and to permit third-parties to whom the Software is furnished to |
|
||||||
do so, all subject to the following: |
|
||||||
|
|
||||||
The copyright notices in the Software and this entire statement, including |
|
||||||
the above license grant, this restriction and the following disclaimer, |
|
||||||
must be included in all copies of the Software, in whole or in part, and |
|
||||||
all derivative works of the Software, unless such copies or derivative |
|
||||||
works are solely in the form of machine-executable object code generated by |
|
||||||
a source language processor. |
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT |
|
||||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE |
|
||||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, |
|
||||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
||||||
DEALINGS IN THE SOFTWARE. |
|
@ -1,618 +0,0 @@ |
|||||||
|
|
||||||
/*
|
|
||||||
This is an implementation of C++20's std::span |
|
||||||
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/n4820.pdf
|
|
||||||
*/ |
|
||||||
|
|
||||||
// Copyright Tristan Brindle 2018.
|
|
||||||
// Distributed under the Boost Software License, Version 1.0.
|
|
||||||
// (See accompanying file ../../LICENSE_1_0.txt or copy at
|
|
||||||
// https://www.boost.org/LICENSE_1_0.txt)
|
|
||||||
|
|
||||||
#ifndef TCB_SPAN_HPP_INCLUDED |
|
||||||
#define TCB_SPAN_HPP_INCLUDED |
|
||||||
|
|
||||||
#include <array> |
|
||||||
#include <cstddef> |
|
||||||
#include <cstdint> |
|
||||||
#include <type_traits> |
|
||||||
|
|
||||||
#ifndef TCB_SPAN_NO_EXCEPTIONS |
|
||||||
// Attempt to discover whether we're being compiled with exception support
|
|
||||||
#if !(defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) |
|
||||||
#define TCB_SPAN_NO_EXCEPTIONS |
|
||||||
#endif |
|
||||||
#endif |
|
||||||
|
|
||||||
#ifndef TCB_SPAN_NO_EXCEPTIONS |
|
||||||
#include <cstdio> |
|
||||||
#include <stdexcept> |
|
||||||
#endif |
|
||||||
|
|
||||||
// Various feature test macros
|
|
||||||
|
|
||||||
#ifndef TCB_SPAN_NAMESPACE_NAME |
|
||||||
#define TCB_SPAN_NAMESPACE_NAME tcb |
|
||||||
#endif |
|
||||||
|
|
||||||
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) |
|
||||||
#define TCB_SPAN_HAVE_CPP17 |
|
||||||
#endif |
|
||||||
|
|
||||||
#if __cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) |
|
||||||
#define TCB_SPAN_HAVE_CPP14 |
|
||||||
#endif |
|
||||||
|
|
||||||
namespace TCB_SPAN_NAMESPACE_NAME { |
|
||||||
|
|
||||||
// Establish default contract checking behavior
|
|
||||||
#if !defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION) && \ |
|
||||||
!defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) && \
|
|
||||||
!defined(TCB_SPAN_NO_CONTRACT_CHECKING) |
|
||||||
#if defined(NDEBUG) || !defined(TCB_SPAN_HAVE_CPP14) |
|
||||||
#define TCB_SPAN_NO_CONTRACT_CHECKING |
|
||||||
#else |
|
||||||
#define TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION |
|
||||||
#endif |
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined(TCB_SPAN_THROW_ON_CONTRACT_VIOLATION) |
|
||||||
struct contract_violation_error : std::logic_error { |
|
||||||
explicit contract_violation_error(const char* msg) : std::logic_error(msg) |
|
||||||
{} |
|
||||||
}; |
|
||||||
|
|
||||||
inline void contract_violation(const char* msg) |
|
||||||
{ |
|
||||||
throw contract_violation_error(msg); |
|
||||||
} |
|
||||||
|
|
||||||
#elif defined(TCB_SPAN_TERMINATE_ON_CONTRACT_VIOLATION) |
|
||||||
[[noreturn]] inline void contract_violation(const char* /*unused*/) |
|
||||||
{ |
|
||||||
std::terminate(); |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
#if !defined(TCB_SPAN_NO_CONTRACT_CHECKING) |
|
||||||
#define TCB_SPAN_STRINGIFY(cond) #cond |
|
||||||
#define TCB_SPAN_EXPECT(cond) \ |
|
||||||
cond ? (void) 0 : contract_violation("Expected " TCB_SPAN_STRINGIFY(cond)) |
|
||||||
#else |
|
||||||
#define TCB_SPAN_EXPECT(cond) |
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_inline_variables) |
|
||||||
#define TCB_SPAN_INLINE_VAR inline |
|
||||||
#else |
|
||||||
#define TCB_SPAN_INLINE_VAR |
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined(TCB_SPAN_HAVE_CPP14) || \ |
|
||||||
(defined(__cpp_constexpr) && __cpp_constexpr >= 201304) |
|
||||||
#define TCB_SPAN_HAVE_CPP14_CONSTEXPR |
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR) |
|
||||||
#define TCB_SPAN_CONSTEXPR14 constexpr |
|
||||||
#else |
|
||||||
#define TCB_SPAN_CONSTEXPR14 |
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined(TCB_SPAN_HAVE_CPP14_CONSTEXPR) && \ |
|
||||||
(!defined(_MSC_VER) || _MSC_VER > 1900) |
|
||||||
#define TCB_SPAN_CONSTEXPR_ASSIGN constexpr |
|
||||||
#else |
|
||||||
#define TCB_SPAN_CONSTEXPR_ASSIGN |
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined(TCB_SPAN_NO_CONTRACT_CHECKING) |
|
||||||
#define TCB_SPAN_CONSTEXPR11 constexpr |
|
||||||
#else |
|
||||||
#define TCB_SPAN_CONSTEXPR11 TCB_SPAN_CONSTEXPR14 |
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_deduction_guides) |
|
||||||
#define TCB_SPAN_HAVE_DEDUCTION_GUIDES |
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_byte) |
|
||||||
#define TCB_SPAN_HAVE_STD_BYTE |
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_array_constexpr) |
|
||||||
#define TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC |
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined(TCB_SPAN_HAVE_CONSTEXPR_STD_ARRAY_ETC) |
|
||||||
#define TCB_SPAN_ARRAY_CONSTEXPR constexpr |
|
||||||
#else |
|
||||||
#define TCB_SPAN_ARRAY_CONSTEXPR |
|
||||||
#endif |
|
||||||
|
|
||||||
#ifdef TCB_SPAN_HAVE_STD_BYTE |
|
||||||
using byte = std::byte; |
|
||||||
#else |
|
||||||
using byte = unsigned char; |
|
||||||
#endif |
|
||||||
|
|
||||||
#if defined(TCB_SPAN_HAVE_CPP17) |
|
||||||
#define TCB_SPAN_NODISCARD [[nodiscard]] |
|
||||||
#else |
|
||||||
#define TCB_SPAN_NODISCARD |
|
||||||
#endif |
|
||||||
|
|
||||||
TCB_SPAN_INLINE_VAR constexpr std::size_t dynamic_extent = SIZE_MAX; |
|
||||||
|
|
||||||
template <typename ElementType, std::size_t Extent = dynamic_extent> |
|
||||||
class span; |
|
||||||
|
|
||||||
namespace detail { |
|
||||||
|
|
||||||
template <typename E, std::size_t S> |
|
||||||
struct span_storage { |
|
||||||
constexpr span_storage() noexcept = default; |
|
||||||
|
|
||||||
constexpr span_storage(E* p_ptr, std::size_t /*unused*/) noexcept |
|
||||||
: ptr(p_ptr) |
|
||||||
{} |
|
||||||
|
|
||||||
E* ptr = nullptr; |
|
||||||
static constexpr std::size_t size = S; |
|
||||||
}; |
|
||||||
|
|
||||||
template <typename E> |
|
||||||
struct span_storage<E, dynamic_extent> { |
|
||||||
constexpr span_storage() noexcept = default; |
|
||||||
|
|
||||||
constexpr span_storage(E* p_ptr, std::size_t p_size) noexcept |
|
||||||
: ptr(p_ptr), size(p_size) |
|
||||||
{} |
|
||||||
|
|
||||||
E* ptr = nullptr; |
|
||||||
std::size_t size = 0; |
|
||||||
}; |
|
||||||
|
|
||||||
// Reimplementation of C++17 std::size() and std::data()
|
|
||||||
#if defined(TCB_SPAN_HAVE_CPP17) || \ |
|
||||||
defined(__cpp_lib_nonmember_container_access) |
|
||||||
using std::data; |
|
||||||
using std::size; |
|
||||||
#else |
|
||||||
template <class C> |
|
||||||
constexpr auto size(const C& c) -> decltype(c.size()) |
|
||||||
{ |
|
||||||
return c.size(); |
|
||||||
} |
|
||||||
|
|
||||||
template <class T, std::size_t N> |
|
||||||
constexpr std::size_t size(const T (&)[N]) noexcept |
|
||||||
{ |
|
||||||
return N; |
|
||||||
} |
|
||||||
|
|
||||||
template <class C> |
|
||||||
constexpr auto data(C& c) -> decltype(c.data()) |
|
||||||
{ |
|
||||||
return c.data(); |
|
||||||
} |
|
||||||
|
|
||||||
template <class C> |
|
||||||
constexpr auto data(const C& c) -> decltype(c.data()) |
|
||||||
{ |
|
||||||
return c.data(); |
|
||||||
} |
|
||||||
|
|
||||||
template <class T, std::size_t N> |
|
||||||
constexpr T* data(T (&array)[N]) noexcept |
|
||||||
{ |
|
||||||
return array; |
|
||||||
} |
|
||||||
|
|
||||||
template <class E> |
|
||||||
constexpr const E* data(std::initializer_list<E> il) noexcept |
|
||||||
{ |
|
||||||
return il.begin(); |
|
||||||
} |
|
||||||
#endif // TCB_SPAN_HAVE_CPP17
|
|
||||||
|
|
||||||
#if defined(TCB_SPAN_HAVE_CPP17) || defined(__cpp_lib_void_t) |
|
||||||
using std::void_t; |
|
||||||
#else |
|
||||||
template <typename...> |
|
||||||
using void_t = void; |
|
||||||
#endif |
|
||||||
|
|
||||||
template <typename T> |
|
||||||
using uncvref_t = |
|
||||||
typename std::remove_cv<typename std::remove_reference<T>::type>::type; |
|
||||||
|
|
||||||
template <typename> |
|
||||||
struct is_span : std::false_type {}; |
|
||||||
|
|
||||||
template <typename T, std::size_t S> |
|
||||||
struct is_span<span<T, S>> : std::true_type {}; |
|
||||||
|
|
||||||
template <typename> |
|
||||||
struct is_std_array : std::false_type {}; |
|
||||||
|
|
||||||
template <typename T, std::size_t N> |
|
||||||
struct is_std_array<std::array<T, N>> : std::true_type {}; |
|
||||||
|
|
||||||
template <typename, typename = void> |
|
||||||
struct has_size_and_data : std::false_type {}; |
|
||||||
|
|
||||||
template <typename T> |
|
||||||
struct has_size_and_data<T, void_t<decltype(detail::size(std::declval<T>())), |
|
||||||
decltype(detail::data(std::declval<T>()))>> |
|
||||||
: std::true_type {}; |
|
||||||
|
|
||||||
template <typename C, typename U = uncvref_t<C>> |
|
||||||
struct is_container { |
|
||||||
static constexpr bool value = |
|
||||||
!is_span<U>::value && !is_std_array<U>::value && |
|
||||||
!std::is_array<U>::value && has_size_and_data<C>::value; |
|
||||||
}; |
|
||||||
|
|
||||||
template <typename T> |
|
||||||
using remove_pointer_t = typename std::remove_pointer<T>::type; |
|
||||||
|
|
||||||
template <typename, typename, typename = void> |
|
||||||
struct is_container_element_type_compatible : std::false_type {}; |
|
||||||
|
|
||||||
template <typename T, typename E> |
|
||||||
struct is_container_element_type_compatible< |
|
||||||
T, E, |
|
||||||
typename std::enable_if< |
|
||||||
!std::is_same< |
|
||||||
typename std::remove_cv<decltype(detail::data(std::declval<T>()))>::type, |
|
||||||
void>::value && |
|
||||||
std::is_convertible< |
|
||||||
remove_pointer_t<decltype(detail::data(std::declval<T>()))> (*)[], |
|
||||||
E (*)[]>::value |
|
||||||
>::type> |
|
||||||
: std::true_type {}; |
|
||||||
|
|
||||||
template <typename, typename = size_t> |
|
||||||
struct is_complete : std::false_type {}; |
|
||||||
|
|
||||||
template <typename T> |
|
||||||
struct is_complete<T, decltype(sizeof(T))> : std::true_type {}; |
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
template <typename ElementType, std::size_t Extent> |
|
||||||
class span { |
|
||||||
static_assert(std::is_object<ElementType>::value, |
|
||||||
"A span's ElementType must be an object type (not a " |
|
||||||
"reference type or void)"); |
|
||||||
static_assert(detail::is_complete<ElementType>::value, |
|
||||||
"A span's ElementType must be a complete type (not a forward " |
|
||||||
"declaration)"); |
|
||||||
static_assert(!std::is_abstract<ElementType>::value, |
|
||||||
"A span's ElementType cannot be an abstract class type"); |
|
||||||
|
|
||||||
using storage_type = detail::span_storage<ElementType, Extent>; |
|
||||||
|
|
||||||
public: |
|
||||||
// constants and types
|
|
||||||
using element_type = ElementType; |
|
||||||
using value_type = typename std::remove_cv<ElementType>::type; |
|
||||||
using size_type = std::size_t; |
|
||||||
using difference_type = std::ptrdiff_t; |
|
||||||
using pointer = element_type*; |
|
||||||
using const_pointer = const element_type*; |
|
||||||
using reference = element_type&; |
|
||||||
using const_reference = const element_type&; |
|
||||||
using iterator = pointer; |
|
||||||
using reverse_iterator = std::reverse_iterator<iterator>; |
|
||||||
|
|
||||||
static constexpr size_type extent = Extent; |
|
||||||
|
|
||||||
// [span.cons], span constructors, copy, assignment, and destructor
|
|
||||||
template < |
|
||||||
std::size_t E = Extent, |
|
||||||
typename std::enable_if<(E == dynamic_extent || E <= 0), int>::type = 0> |
|
||||||
constexpr span() noexcept |
|
||||||
{} |
|
||||||
|
|
||||||
TCB_SPAN_CONSTEXPR11 span(pointer ptr, size_type count) |
|
||||||
: storage_(ptr, count) |
|
||||||
{ |
|
||||||
TCB_SPAN_EXPECT(extent == dynamic_extent || count == extent); |
|
||||||
} |
|
||||||
|
|
||||||
TCB_SPAN_CONSTEXPR11 span(pointer first_elem, pointer last_elem) |
|
||||||
: storage_(first_elem, last_elem - first_elem) |
|
||||||
{ |
|
||||||
TCB_SPAN_EXPECT(extent == dynamic_extent || |
|
||||||
last_elem - first_elem == |
|
||||||
static_cast<std::ptrdiff_t>(extent)); |
|
||||||
} |
|
||||||
|
|
||||||
template <std::size_t N, std::size_t E = Extent, |
|
||||||
typename std::enable_if< |
|
||||||
(E == dynamic_extent || N == E) && |
|
||||||
detail::is_container_element_type_compatible< |
|
||||||
element_type (&)[N], ElementType>::value, |
|
||||||
int>::type = 0> |
|
||||||
constexpr span(element_type (&arr)[N]) noexcept : storage_(arr, N) |
|
||||||
{} |
|
||||||
|
|
||||||
template <typename T, std::size_t N, std::size_t E = Extent, |
|
||||||
typename std::enable_if< |
|
||||||
(E == dynamic_extent || N == E) && |
|
||||||
detail::is_container_element_type_compatible< |
|
||||||
std::array<T, N>&, ElementType>::value, |
|
||||||
int>::type = 0> |
|
||||||
TCB_SPAN_ARRAY_CONSTEXPR span(std::array<T, N>& arr) noexcept |
|
||||||
: storage_(arr.data(), N) |
|
||||||
{} |
|
||||||
|
|
||||||
template <typename T, std::size_t N, std::size_t E = Extent, |
|
||||||
typename std::enable_if< |
|
||||||
(E == dynamic_extent || N == E) && |
|
||||||
detail::is_container_element_type_compatible< |
|
||||||
const std::array<T, N>&, ElementType>::value, |
|
||||||
int>::type = 0> |
|
||||||
TCB_SPAN_ARRAY_CONSTEXPR span(const std::array<T, N>& arr) noexcept |
|
||||||
: storage_(arr.data(), N) |
|
||||||
{} |
|
||||||
|
|
||||||
template < |
|
||||||
typename Container, std::size_t E = Extent, |
|
||||||
typename std::enable_if< |
|
||||||
E == dynamic_extent && detail::is_container<Container>::value && |
|
||||||
detail::is_container_element_type_compatible< |
|
||||||
Container&, ElementType>::value, |
|
||||||
int>::type = 0> |
|
||||||
constexpr span(Container& cont) |
|
||||||
: storage_(detail::data(cont), detail::size(cont)) |
|
||||||
{} |
|
||||||
|
|
||||||
template < |
|
||||||
typename Container, std::size_t E = Extent, |
|
||||||
typename std::enable_if< |
|
||||||
E == dynamic_extent && detail::is_container<Container>::value && |
|
||||||
detail::is_container_element_type_compatible< |
|
||||||
const Container&, ElementType>::value, |
|
||||||
int>::type = 0> |
|
||||||
constexpr span(const Container& cont) |
|
||||||
: storage_(detail::data(cont), detail::size(cont)) |
|
||||||
{} |
|
||||||
|
|
||||||
constexpr span(const span& other) noexcept = default; |
|
||||||
|
|
||||||
template <typename OtherElementType, std::size_t OtherExtent, |
|
||||||
typename std::enable_if< |
|
||||||
(Extent == dynamic_extent || OtherExtent == dynamic_extent || |
|
||||||
Extent == OtherExtent) && |
|
||||||
std::is_convertible<OtherElementType (*)[], |
|
||||||
ElementType (*)[]>::value, |
|
||||||
int>::type = 0> |
|
||||||
constexpr span(const span<OtherElementType, OtherExtent>& other) noexcept |
|
||||||
: storage_(other.data(), other.size()) |
|
||||||
{} |
|
||||||
|
|
||||||
~span() noexcept = default; |
|
||||||
|
|
||||||
TCB_SPAN_CONSTEXPR_ASSIGN span& |
|
||||||
operator=(const span& other) noexcept = default; |
|
||||||
|
|
||||||
// [span.sub], span subviews
|
|
||||||
template <std::size_t Count> |
|
||||||
TCB_SPAN_CONSTEXPR11 span<element_type, Count> first() const |
|
||||||
{ |
|
||||||
TCB_SPAN_EXPECT(Count <= size()); |
|
||||||
return {data(), Count}; |
|
||||||
} |
|
||||||
|
|
||||||
template <std::size_t Count> |
|
||||||
TCB_SPAN_CONSTEXPR11 span<element_type, Count> last() const |
|
||||||
{ |
|
||||||
TCB_SPAN_EXPECT(Count <= size()); |
|
||||||
return {data() + (size() - Count), Count}; |
|
||||||
} |
|
||||||
|
|
||||||
template <std::size_t Offset, std::size_t Count = dynamic_extent> |
|
||||||
using subspan_return_t = |
|
||||||
span<ElementType, Count != dynamic_extent |
|
||||||
? Count |
|
||||||
: (Extent != dynamic_extent ? Extent - Offset |
|
||||||
: dynamic_extent)>; |
|
||||||
|
|
||||||
template <std::size_t Offset, std::size_t Count = dynamic_extent> |
|
||||||
TCB_SPAN_CONSTEXPR11 subspan_return_t<Offset, Count> subspan() const |
|
||||||
{ |
|
||||||
TCB_SPAN_EXPECT(Offset <= size() && |
|
||||||
(Count == dynamic_extent || Offset + Count <= size())); |
|
||||||
return {data() + Offset, |
|
||||||
Count != dynamic_extent ? Count : size() - Offset}; |
|
||||||
} |
|
||||||
|
|
||||||
TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent> |
|
||||||
first(size_type count) const |
|
||||||
{ |
|
||||||
TCB_SPAN_EXPECT(count <= size()); |
|
||||||
return {data(), count}; |
|
||||||
} |
|
||||||
|
|
||||||
TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent> |
|
||||||
last(size_type count) const |
|
||||||
{ |
|
||||||
TCB_SPAN_EXPECT(count <= size()); |
|
||||||
return {data() + (size() - count), count}; |
|
||||||
} |
|
||||||
|
|
||||||
TCB_SPAN_CONSTEXPR11 span<element_type, dynamic_extent> |
|
||||||
subspan(size_type offset, size_type count = dynamic_extent) const |
|
||||||
{ |
|
||||||
TCB_SPAN_EXPECT(offset <= size() && |
|
||||||
(count == dynamic_extent || offset + count <= size())); |
|
||||||
return {data() + offset, |
|
||||||
count == dynamic_extent ? size() - offset : count}; |
|
||||||
} |
|
||||||
|
|
||||||
// [span.obs], span observers
|
|
||||||
constexpr size_type size() const noexcept { return storage_.size; } |
|
||||||
|
|
||||||
constexpr size_type size_bytes() const noexcept |
|
||||||
{ |
|
||||||
return size() * sizeof(element_type); |
|
||||||
} |
|
||||||
|
|
||||||
TCB_SPAN_NODISCARD constexpr bool empty() const noexcept |
|
||||||
{ |
|
||||||
return size() == 0; |
|
||||||
} |
|
||||||
|
|
||||||
// [span.elem], span element access
|
|
||||||
TCB_SPAN_CONSTEXPR11 reference operator[](size_type idx) const |
|
||||||
{ |
|
||||||
TCB_SPAN_EXPECT(idx < size()); |
|
||||||
return *(data() + idx); |
|
||||||
} |
|
||||||
|
|
||||||
TCB_SPAN_CONSTEXPR11 reference front() const |
|
||||||
{ |
|
||||||
TCB_SPAN_EXPECT(!empty()); |
|
||||||
return *data(); |
|
||||||
} |
|
||||||
|
|
||||||
TCB_SPAN_CONSTEXPR11 reference back() const |
|
||||||
{ |
|
||||||
TCB_SPAN_EXPECT(!empty()); |
|
||||||
return *(data() + (size() - 1)); |
|
||||||
} |
|
||||||
|
|
||||||
constexpr pointer data() const noexcept { return storage_.ptr; } |
|
||||||
|
|
||||||
// [span.iterators], span iterator support
|
|
||||||
constexpr iterator begin() const noexcept { return data(); } |
|
||||||
|
|
||||||
constexpr iterator end() const noexcept { return data() + size(); } |
|
||||||
|
|
||||||
TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rbegin() const noexcept |
|
||||||
{ |
|
||||||
return reverse_iterator(end()); |
|
||||||
} |
|
||||||
|
|
||||||
TCB_SPAN_ARRAY_CONSTEXPR reverse_iterator rend() const noexcept |
|
||||||
{ |
|
||||||
return reverse_iterator(begin()); |
|
||||||
} |
|
||||||
|
|
||||||
private: |
|
||||||
storage_type storage_{}; |
|
||||||
}; |
|
||||||
|
|
||||||
#ifdef TCB_SPAN_HAVE_DEDUCTION_GUIDES |
|
||||||
|
|
||||||
/* Deduction Guides */ |
|
||||||
template <class T, size_t N> |
|
||||||
span(T (&)[N])->span<T, N>; |
|
||||||
|
|
||||||
template <class T, size_t N> |
|
||||||
span(std::array<T, N>&)->span<T, N>; |
|
||||||
|
|
||||||
template <class T, size_t N> |
|
||||||
span(const std::array<T, N>&)->span<const T, N>; |
|
||||||
|
|
||||||
template <class Container> |
|
||||||
span(Container&)->span<typename std::remove_reference< |
|
||||||
decltype(*detail::data(std::declval<Container&>()))>::type>; |
|
||||||
|
|
||||||
template <class Container> |
|
||||||
span(const Container&)->span<const typename Container::value_type>; |
|
||||||
|
|
||||||
#endif // TCB_HAVE_DEDUCTION_GUIDES
|
|
||||||
|
|
||||||
template <typename ElementType, std::size_t Extent> |
|
||||||
constexpr span<ElementType, Extent> |
|
||||||
make_span(span<ElementType, Extent> s) noexcept |
|
||||||
{ |
|
||||||
return s; |
|
||||||
} |
|
||||||
|
|
||||||
template <typename T, std::size_t N> |
|
||||||
constexpr span<T, N> make_span(T (&arr)[N]) noexcept |
|
||||||
{ |
|
||||||
return {arr}; |
|
||||||
} |
|
||||||
|
|
||||||
template <typename T, std::size_t N> |
|
||||||
TCB_SPAN_ARRAY_CONSTEXPR span<T, N> make_span(std::array<T, N>& arr) noexcept |
|
||||||
{ |
|
||||||
return {arr}; |
|
||||||
} |
|
||||||
|
|
||||||
template <typename T, std::size_t N> |
|
||||||
TCB_SPAN_ARRAY_CONSTEXPR span<const T, N> |
|
||||||
make_span(const std::array<T, N>& arr) noexcept |
|
||||||
{ |
|
||||||
return {arr}; |
|
||||||
} |
|
||||||
|
|
||||||
template <typename Container> |
|
||||||
constexpr span<typename std::remove_reference< |
|
||||||
decltype(*detail::data(std::declval<Container&>()))>::type> |
|
||||||
make_span(Container& cont) |
|
||||||
{ |
|
||||||
return {cont}; |
|
||||||
} |
|
||||||
|
|
||||||
template <typename Container> |
|
||||||
constexpr span<const typename Container::value_type> |
|
||||||
make_span(const Container& cont) |
|
||||||
{ |
|
||||||
return {cont}; |
|
||||||
} |
|
||||||
|
|
||||||
template <typename ElementType, std::size_t Extent> |
|
||||||
span<const byte, ((Extent == dynamic_extent) ? dynamic_extent |
|
||||||
: sizeof(ElementType) * Extent)> |
|
||||||
as_bytes(span<ElementType, Extent> s) noexcept |
|
||||||
{ |
|
||||||
return {reinterpret_cast<const byte*>(s.data()), s.size_bytes()}; |
|
||||||
} |
|
||||||
|
|
||||||
template < |
|
||||||
class ElementType, size_t Extent, |
|
||||||
typename std::enable_if<!std::is_const<ElementType>::value, int>::type = 0> |
|
||||||
span<byte, ((Extent == dynamic_extent) ? dynamic_extent |
|
||||||
: sizeof(ElementType) * Extent)> |
|
||||||
as_writable_bytes(span<ElementType, Extent> s) noexcept |
|
||||||
{ |
|
||||||
return {reinterpret_cast<byte*>(s.data()), s.size_bytes()}; |
|
||||||
} |
|
||||||
|
|
||||||
template <std::size_t N, typename E, std::size_t S> |
|
||||||
constexpr auto get(span<E, S> s) -> decltype(s[N]) |
|
||||||
{ |
|
||||||
return s[N]; |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace TCB_SPAN_NAMESPACE_NAME
|
|
||||||
|
|
||||||
namespace std { |
|
||||||
|
|
||||||
template <typename ElementType, size_t Extent> |
|
||||||
class tuple_size<TCB_SPAN_NAMESPACE_NAME::span<ElementType, Extent>> |
|
||||||
: public integral_constant<size_t, Extent> {}; |
|
||||||
|
|
||||||
template <typename ElementType> |
|
||||||
class tuple_size<TCB_SPAN_NAMESPACE_NAME::span< |
|
||||||
ElementType, TCB_SPAN_NAMESPACE_NAME::dynamic_extent>>; // not defined
|
|
||||||
|
|
||||||
template <size_t I, typename ElementType, size_t Extent> |
|
||||||
class tuple_element<I, TCB_SPAN_NAMESPACE_NAME::span<ElementType, Extent>> { |
|
||||||
public: |
|
||||||
static_assert(Extent != TCB_SPAN_NAMESPACE_NAME::dynamic_extent && |
|
||||||
I < Extent, |
|
||||||
""); |
|
||||||
using type = ElementType; |
|
||||||
}; |
|
||||||
|
|
||||||
} // end namespace std
|
|
||||||
|
|
||||||
#endif // TCB_SPAN_HPP_INCLUDED
|
|
@ -1,9 +0,0 @@ |
|||||||
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
# |
|
||||||
# SPDX-License-Identifier: GPL-3.0-only |
|
||||||
|
|
||||||
idf_component_register( |
|
||||||
SRCS "app_console.cpp" |
|
||||||
INCLUDE_DIRS "include" |
|
||||||
REQUIRES "dev_console" "events" "database") |
|
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
|
@ -1,14 +0,0 @@ |
|||||||
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
# |
|
||||||
# SPDX-License-Identifier: GPL-3.0-only |
|
||||||
|
|
||||||
idf_component_register( |
|
||||||
SRCS "audio_decoder.cpp" "fatfs_audio_input.cpp" "i2s_audio_output.cpp" |
|
||||||
"track_queue.cpp" "audio_fsm.cpp" "audio_converter.cpp" "resample.cpp" |
|
||||||
"fatfs_source.cpp" "bt_audio_output.cpp" "readahead_source.cpp" |
|
||||||
"audio_source.cpp" |
|
||||||
INCLUDE_DIRS "include" |
|
||||||
REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span" "memory" "tinyfsm" |
|
||||||
"database" "system_fsm" "speexdsp" "millershuffle" "libcppbor") |
|
||||||
|
|
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
|
@ -1,148 +0,0 @@ |
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
* |
|
||||||
* SPDX-License-Identifier: GPL-3.0-only |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "audio_decoder.hpp" |
|
||||||
#include <stdint.h> |
|
||||||
|
|
||||||
#include <cstdint> |
|
||||||
#include <cstdlib> |
|
||||||
|
|
||||||
#include <algorithm> |
|
||||||
#include <cmath> |
|
||||||
#include <cstddef> |
|
||||||
#include <cstdint> |
|
||||||
#include <cstring> |
|
||||||
#include <deque> |
|
||||||
#include <memory> |
|
||||||
#include <variant> |
|
||||||
|
|
||||||
#include "cbor.h" |
|
||||||
#include "esp_err.h" |
|
||||||
#include "esp_heap_caps.h" |
|
||||||
#include "esp_log.h" |
|
||||||
#include "freertos/portmacro.h" |
|
||||||
#include "freertos/projdefs.h" |
|
||||||
#include "freertos/queue.h" |
|
||||||
#include "freertos/ringbuf.h" |
|
||||||
#include "i2s_dac.hpp" |
|
||||||
#include "span.hpp" |
|
||||||
|
|
||||||
#include "audio_converter.hpp" |
|
||||||
#include "audio_events.hpp" |
|
||||||
#include "audio_fsm.hpp" |
|
||||||
#include "audio_sink.hpp" |
|
||||||
#include "audio_source.hpp" |
|
||||||
#include "codec.hpp" |
|
||||||
#include "event_queue.hpp" |
|
||||||
#include "fatfs_audio_input.hpp" |
|
||||||
#include "sample.hpp" |
|
||||||
#include "tasks.hpp" |
|
||||||
#include "track.hpp" |
|
||||||
#include "types.hpp" |
|
||||||
#include "ui_fsm.hpp" |
|
||||||
|
|
||||||
namespace audio { |
|
||||||
|
|
||||||
[[maybe_unused]] static const char* kTag = "audio_dec"; |
|
||||||
|
|
||||||
static constexpr std::size_t kCodecBufferLength = |
|
||||||
drivers::kI2SBufferLengthFrames * sizeof(sample::Sample); |
|
||||||
|
|
||||||
auto Decoder::Start(std::shared_ptr<IAudioSource> source, |
|
||||||
std::shared_ptr<SampleConverter> sink) -> Decoder* { |
|
||||||
Decoder* task = new Decoder(source, sink); |
|
||||||
tasks::StartPersistent<tasks::Type::kAudioDecoder>([=]() { task->Main(); }); |
|
||||||
return task; |
|
||||||
} |
|
||||||
|
|
||||||
Decoder::Decoder(std::shared_ptr<IAudioSource> source, |
|
||||||
std::shared_ptr<SampleConverter> mixer) |
|
||||||
: source_(source), converter_(mixer), codec_(), current_format_() { |
|
||||||
ESP_LOGI(kTag, "allocating codec buffer, %u KiB", kCodecBufferLength / 1024); |
|
||||||
codec_buffer_ = { |
|
||||||
reinterpret_cast<sample::Sample*>(heap_caps_calloc( |
|
||||||
kCodecBufferLength, sizeof(sample::Sample), MALLOC_CAP_DMA)), |
|
||||||
kCodecBufferLength}; |
|
||||||
} |
|
||||||
|
|
||||||
void Decoder::Main() { |
|
||||||
for (;;) { |
|
||||||
if (source_->HasNewStream() || !stream_) { |
|
||||||
std::shared_ptr<TaggedStream> new_stream = source_->NextStream(); |
|
||||||
if (new_stream && BeginDecoding(new_stream)) { |
|
||||||
stream_ = new_stream; |
|
||||||
} else { |
|
||||||
continue; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (ContinueDecoding()) { |
|
||||||
stream_.reset(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
auto Decoder::BeginDecoding(std::shared_ptr<TaggedStream> stream) -> bool { |
|
||||||
// Ensure any previous codec is freed before creating a new one.
|
|
||||||
codec_.reset(); |
|
||||||
codec_.reset(codecs::CreateCodecForType(stream->type()).value_or(nullptr)); |
|
||||||
if (!codec_) { |
|
||||||
ESP_LOGE(kTag, "no codec found for stream"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
auto open_res = codec_->OpenStream(stream, stream->Offset()); |
|
||||||
if (open_res.has_error()) { |
|
||||||
ESP_LOGE(kTag, "codec failed to start: %s", |
|
||||||
codecs::ICodec::ErrorString(open_res.error()).c_str()); |
|
||||||
return false; |
|
||||||
} |
|
||||||
stream->SetPreambleFinished(); |
|
||||||
current_sink_format_ = IAudioOutput::Format{ |
|
||||||
.sample_rate = open_res->sample_rate_hz, |
|
||||||
.num_channels = open_res->num_channels, |
|
||||||
.bits_per_sample = 16, |
|
||||||
}; |
|
||||||
|
|
||||||
std::optional<uint32_t> duration; |
|
||||||
if (open_res->total_samples) { |
|
||||||
duration = open_res->total_samples.value() / open_res->num_channels / |
|
||||||
open_res->sample_rate_hz; |
|
||||||
} |
|
||||||
|
|
||||||
converter_->beginStream(std::make_shared<TrackInfo>(TrackInfo{ |
|
||||||
.tags = stream->tags(), |
|
||||||
.uri = stream->Filepath(), |
|
||||||
.duration = duration, |
|
||||||
.start_offset = stream->Offset(), |
|
||||||
.bitrate_kbps = open_res->sample_rate_hz, |
|
||||||
.encoding = stream->type(), |
|
||||||
.format = *current_sink_format_, |
|
||||||
})); |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
auto Decoder::ContinueDecoding() -> bool { |
|
||||||
auto res = codec_->DecodeTo(codec_buffer_); |
|
||||||
if (res.has_error()) { |
|
||||||
converter_->endStream(); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
if (res->samples_written > 0) { |
|
||||||
converter_->continueStream(codec_buffer_.first(res->samples_written)); |
|
||||||
} |
|
||||||
|
|
||||||
if (res->is_stream_finished) { |
|
||||||
converter_->endStream(); |
|
||||||
codec_.reset(); |
|
||||||
} |
|
||||||
|
|
||||||
return res->is_stream_finished; |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace audio
|
|
@ -1,161 +0,0 @@ |
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
* |
|
||||||
* SPDX-License-Identifier: GPL-3.0-only |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "fatfs_audio_input.hpp" |
|
||||||
|
|
||||||
#include <algorithm> |
|
||||||
#include <climits> |
|
||||||
#include <cstddef> |
|
||||||
#include <cstdint> |
|
||||||
#include <functional> |
|
||||||
#include <future> |
|
||||||
#include <memory> |
|
||||||
#include <mutex> |
|
||||||
#include <string> |
|
||||||
#include <variant> |
|
||||||
|
|
||||||
#include "esp_heap_caps.h" |
|
||||||
#include "esp_log.h" |
|
||||||
#include "ff.h" |
|
||||||
#include "freertos/portmacro.h" |
|
||||||
#include "freertos/projdefs.h" |
|
||||||
#include "readahead_source.hpp" |
|
||||||
#include "span.hpp" |
|
||||||
|
|
||||||
#include "audio_events.hpp" |
|
||||||
#include "audio_fsm.hpp" |
|
||||||
#include "audio_source.hpp" |
|
||||||
#include "codec.hpp" |
|
||||||
#include "event_queue.hpp" |
|
||||||
#include "fatfs_source.hpp" |
|
||||||
#include "future_fetcher.hpp" |
|
||||||
#include "spi.hpp" |
|
||||||
#include "tag_parser.hpp" |
|
||||||
#include "tasks.hpp" |
|
||||||
#include "track.hpp" |
|
||||||
#include "types.hpp" |
|
||||||
|
|
||||||
[[maybe_unused]] static const char* kTag = "SRC"; |
|
||||||
|
|
||||||
namespace audio { |
|
||||||
|
|
||||||
FatfsAudioInput::FatfsAudioInput(database::ITagParser& tag_parser, |
|
||||||
tasks::WorkerPool& bg_worker) |
|
||||||
: IAudioSource(), |
|
||||||
tag_parser_(tag_parser), |
|
||||||
bg_worker_(bg_worker), |
|
||||||
new_stream_mutex_(), |
|
||||||
new_stream_(), |
|
||||||
has_new_stream_(false) {} |
|
||||||
|
|
||||||
FatfsAudioInput::~FatfsAudioInput() {} |
|
||||||
|
|
||||||
auto FatfsAudioInput::SetPath(std::optional<std::string> path) -> void { |
|
||||||
if (path) { |
|
||||||
SetPath(*path); |
|
||||||
} else { |
|
||||||
SetPath(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
auto FatfsAudioInput::SetPath(const std::string& path,uint32_t offset) -> void { |
|
||||||
std::lock_guard<std::mutex> guard{new_stream_mutex_}; |
|
||||||
if (OpenFile(path, offset)) { |
|
||||||
has_new_stream_ = true; |
|
||||||
has_new_stream_.notify_one(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
auto FatfsAudioInput::SetPath() -> void { |
|
||||||
std::lock_guard<std::mutex> guard{new_stream_mutex_}; |
|
||||||
new_stream_.reset(); |
|
||||||
has_new_stream_ = true; |
|
||||||
has_new_stream_.notify_one(); |
|
||||||
} |
|
||||||
|
|
||||||
auto FatfsAudioInput::HasNewStream() -> bool { |
|
||||||
return has_new_stream_; |
|
||||||
} |
|
||||||
|
|
||||||
auto FatfsAudioInput::NextStream() -> std::shared_ptr<TaggedStream> { |
|
||||||
while (true) { |
|
||||||
has_new_stream_.wait(false); |
|
||||||
|
|
||||||
{ |
|
||||||
std::lock_guard<std::mutex> guard{new_stream_mutex_}; |
|
||||||
if (!has_new_stream_.exchange(false)) { |
|
||||||
// If the new stream went away, then we need to go back to waiting.
|
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
if (new_stream_ == nullptr) { |
|
||||||
continue; |
|
||||||
} |
|
||||||
|
|
||||||
auto stream = new_stream_; |
|
||||||
new_stream_ = nullptr; |
|
||||||
return stream; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
auto FatfsAudioInput::OpenFile(const std::string& path,uint32_t offset) -> bool { |
|
||||||
ESP_LOGI(kTag, "opening file %s", path.c_str()); |
|
||||||
|
|
||||||
auto tags = tag_parser_.ReadAndParseTags(path); |
|
||||||
if (!tags) { |
|
||||||
ESP_LOGE(kTag, "failed to read tags"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
if (!tags->title()) { |
|
||||||
tags->title(path); |
|
||||||
} |
|
||||||
|
|
||||||
auto stream_type = ContainerToStreamType(tags->encoding()); |
|
||||||
if (!stream_type.has_value()) { |
|
||||||
ESP_LOGE(kTag, "couldn't match container to stream"); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
std::unique_ptr<FIL> file = std::make_unique<FIL>(); |
|
||||||
FRESULT res; |
|
||||||
|
|
||||||
{ |
|
||||||
auto lock = drivers::acquire_spi(); |
|
||||||
res = f_open(file.get(), path.c_str(), FA_READ); |
|
||||||
} |
|
||||||
|
|
||||||
if (res != FR_OK) { |
|
||||||
ESP_LOGE(kTag, "failed to open file! res: %i", res); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
auto source = |
|
||||||
std::make_unique<FatfsSource>(stream_type.value(), std::move(file)); |
|
||||||
new_stream_.reset(new TaggedStream(tags, std::move(source), path, offset)); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
auto FatfsAudioInput::ContainerToStreamType(database::Container enc) |
|
||||||
-> std::optional<codecs::StreamType> { |
|
||||||
switch (enc) { |
|
||||||
case database::Container::kMp3: |
|
||||||
return codecs::StreamType::kMp3; |
|
||||||
case database::Container::kWav: |
|
||||||
return codecs::StreamType::kWav; |
|
||||||
case database::Container::kOgg: |
|
||||||
return codecs::StreamType::kVorbis; |
|
||||||
case database::Container::kFlac: |
|
||||||
return codecs::StreamType::kFlac; |
|
||||||
case database::Container::kOpus: |
|
||||||
return codecs::StreamType::kOpus; |
|
||||||
case database::Container::kUnsupported: |
|
||||||
default: |
|
||||||
return {}; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace audio
|
|
@ -1,56 +0,0 @@ |
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
* |
|
||||||
* SPDX-License-Identifier: GPL-3.0-only |
|
||||||
*/ |
|
||||||
|
|
||||||
#pragma once |
|
||||||
|
|
||||||
#include <cstdint> |
|
||||||
#include <memory> |
|
||||||
|
|
||||||
#include "audio_converter.hpp" |
|
||||||
#include "audio_events.hpp" |
|
||||||
#include "audio_sink.hpp" |
|
||||||
#include "audio_source.hpp" |
|
||||||
#include "codec.hpp" |
|
||||||
#include "track.hpp" |
|
||||||
#include "types.hpp" |
|
||||||
|
|
||||||
namespace audio { |
|
||||||
|
|
||||||
/*
|
|
||||||
* Handle to a persistent task that takes bytes from the given source, decodes |
|
||||||
* them into sample::Sample (normalised to 16 bit signed PCM), and then |
|
||||||
* forwards the resulting stream to the given converter. |
|
||||||
*/ |
|
||||||
class Decoder { |
|
||||||
public: |
|
||||||
static auto Start(std::shared_ptr<IAudioSource> source, |
|
||||||
std::shared_ptr<SampleConverter> converter) -> Decoder*; |
|
||||||
|
|
||||||
auto Main() -> void; |
|
||||||
|
|
||||||
Decoder(const Decoder&) = delete; |
|
||||||
Decoder& operator=(const Decoder&) = delete; |
|
||||||
|
|
||||||
private: |
|
||||||
Decoder(std::shared_ptr<IAudioSource> source, |
|
||||||
std::shared_ptr<SampleConverter> converter); |
|
||||||
|
|
||||||
auto BeginDecoding(std::shared_ptr<TaggedStream>) -> bool; |
|
||||||
auto ContinueDecoding() -> bool; |
|
||||||
|
|
||||||
std::shared_ptr<IAudioSource> source_; |
|
||||||
std::shared_ptr<SampleConverter> converter_; |
|
||||||
|
|
||||||
std::shared_ptr<codecs::IStream> stream_; |
|
||||||
std::unique_ptr<codecs::ICodec> codec_; |
|
||||||
|
|
||||||
std::optional<codecs::ICodec::OutputFormat> current_format_; |
|
||||||
std::optional<IAudioOutput::Format> current_sink_format_; |
|
||||||
|
|
||||||
cpp::span<sample::Sample> codec_buffer_; |
|
||||||
}; |
|
||||||
|
|
||||||
} // namespace audio
|
|
@ -1,66 +0,0 @@ |
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
* |
|
||||||
* SPDX-License-Identifier: GPL-3.0-only |
|
||||||
*/ |
|
||||||
|
|
||||||
#pragma once |
|
||||||
|
|
||||||
#include <cstddef> |
|
||||||
#include <cstdint> |
|
||||||
#include <future> |
|
||||||
#include <memory> |
|
||||||
#include <string> |
|
||||||
|
|
||||||
#include "ff.h" |
|
||||||
#include "freertos/portmacro.h" |
|
||||||
|
|
||||||
#include "audio_source.hpp" |
|
||||||
#include "codec.hpp" |
|
||||||
#include "future_fetcher.hpp" |
|
||||||
#include "tag_parser.hpp" |
|
||||||
#include "tasks.hpp" |
|
||||||
#include "types.hpp" |
|
||||||
|
|
||||||
namespace audio { |
|
||||||
|
|
||||||
/*
|
|
||||||
* Audio source that fetches data from a FatFs (or exfat i guess) filesystem. |
|
||||||
* |
|
||||||
* All public methods are safe to call from any task. |
|
||||||
*/ |
|
||||||
class FatfsAudioInput : public IAudioSource { |
|
||||||
public: |
|
||||||
explicit FatfsAudioInput(database::ITagParser&, tasks::WorkerPool&); |
|
||||||
~FatfsAudioInput(); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Immediately cease reading any current source, and begin reading from the |
|
||||||
* given file path. |
|
||||||
*/ |
|
||||||
auto SetPath(std::optional<std::string>) -> void; |
|
||||||
auto SetPath(const std::string&,uint32_t offset = 0) -> void; |
|
||||||
auto SetPath() -> void; |
|
||||||
|
|
||||||
auto HasNewStream() -> bool override; |
|
||||||
auto NextStream() -> std::shared_ptr<TaggedStream> override; |
|
||||||
|
|
||||||
FatfsAudioInput(const FatfsAudioInput&) = delete; |
|
||||||
FatfsAudioInput& operator=(const FatfsAudioInput&) = delete; |
|
||||||
|
|
||||||
private: |
|
||||||
auto OpenFile(const std::string& path,uint32_t offset) -> bool; |
|
||||||
|
|
||||||
auto ContainerToStreamType(database::Container) |
|
||||||
-> std::optional<codecs::StreamType>; |
|
||||||
|
|
||||||
database::ITagParser& tag_parser_; |
|
||||||
tasks::WorkerPool& bg_worker_; |
|
||||||
|
|
||||||
std::mutex new_stream_mutex_; |
|
||||||
std::shared_ptr<TaggedStream> new_stream_; |
|
||||||
|
|
||||||
std::atomic<bool> has_new_stream_; |
|
||||||
}; |
|
||||||
|
|
||||||
} // namespace audio
|
|
@ -1,10 +0,0 @@ |
|||||||
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
# |
|
||||||
# SPDX-License-Identifier: GPL-3.0-only |
|
||||||
|
|
||||||
idf_component_register( |
|
||||||
SRCS "battery.cpp" |
|
||||||
INCLUDE_DIRS "include" |
|
||||||
REQUIRES "drivers" "events") |
|
||||||
|
|
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
|
@ -1,22 +0,0 @@ |
|||||||
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
# |
|
||||||
# SPDX-License-Identifier: GPL-3.0-only |
|
||||||
|
|
||||||
idf_component_register( |
|
||||||
SRCS "env_esp.cpp" "database.cpp" "track.cpp" "records.cpp" |
|
||||||
"file_gatherer.cpp" "tag_parser.cpp" "index.cpp" |
|
||||||
INCLUDE_DIRS "include" |
|
||||||
REQUIRES "result" "span" "esp_psram" "fatfs" "libtags" "komihash" "cbor" |
|
||||||
"tasks" "memory" "util" "tinyfsm" "events" "opusfile" "libcppbor") |
|
||||||
|
|
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
|
||||||
|
|
||||||
set(LEVELDB_BUILD_TESTS OFF) |
|
||||||
set(LEVELDB_BUILD_BENCHMARKS OFF) |
|
||||||
set(LEVELDB_INSTALL OFF) |
|
||||||
|
|
||||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) |
|
||||||
|
|
||||||
add_subdirectory($ENV{PROJ_PATH}/lib/leveldb ${CMAKE_CURRENT_BINARY_DIR}/leveldb) |
|
||||||
|
|
||||||
target_link_libraries(${COMPONENT_LIB} PUBLIC leveldb) |
|
@ -1,9 +0,0 @@ |
|||||||
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
# |
|
||||||
# SPDX-License-Identifier: GPL-3.0-only |
|
||||||
|
|
||||||
idf_component_register( |
|
||||||
SRCS "console.cpp" |
|
||||||
INCLUDE_DIRS "include" |
|
||||||
REQUIRES "console" "memory") |
|
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
|
@ -1,9 +0,0 @@ |
|||||||
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
# |
|
||||||
# SPDX-License-Identifier: GPL-3.0-only |
|
||||||
|
|
||||||
idf_component_register( |
|
||||||
SRCS "event_queue.cpp" |
|
||||||
INCLUDE_DIRS "include" |
|
||||||
REQUIRES "tinyfsm" "ui") |
|
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
|
@ -0,0 +1,7 @@ |
|||||||
|
# Copyright 2024 jacqueline <me@jacqueline.id.au> |
||||||
|
# |
||||||
|
# SPDX-License-Identifier: GPL-3.0-only |
||||||
|
|
||||||
|
idf_component_register( |
||||||
|
SRCS "font_fusion_10.c" "font_fusion_12.c" "splash.c" |
||||||
|
REQUIRES "lvgl") |
@ -1,13 +0,0 @@ |
|||||||
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
# |
|
||||||
# SPDX-License-Identifier: GPL-3.0-only |
|
||||||
|
|
||||||
idf_component_register( |
|
||||||
SRCS "input_touch_wheel.cpp" "input_touch_dpad.cpp" "input_trigger.cpp" |
|
||||||
"input_volume_buttons.cpp" "lvgl_input_driver.cpp" "feedback_haptics.cpp" |
|
||||||
"device_factory.cpp" "input_nav_buttons.cpp" "input_hook.cpp" |
|
||||||
"input_hook_actions.cpp" |
|
||||||
INCLUDE_DIRS "include" |
|
||||||
REQUIRES "drivers" "lvgl" "events" "system_fsm") |
|
||||||
|
|
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
|
@ -1,60 +0,0 @@ |
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
* |
|
||||||
* SPDX-License-Identifier: GPL-3.0-only |
|
||||||
*/ |
|
||||||
|
|
||||||
#pragma once |
|
||||||
|
|
||||||
#include <cstdint> |
|
||||||
#include <deque> |
|
||||||
#include <memory> |
|
||||||
#include <set> |
|
||||||
|
|
||||||
#include "core/lv_group.h" |
|
||||||
#include "device_factory.hpp" |
|
||||||
#include "feedback_device.hpp" |
|
||||||
#include "gpios.hpp" |
|
||||||
#include "hal/lv_hal_indev.h" |
|
||||||
|
|
||||||
#include "input_device.hpp" |
|
||||||
#include "nvs.hpp" |
|
||||||
#include "property.hpp" |
|
||||||
#include "touchwheel.hpp" |
|
||||||
|
|
||||||
namespace input { |
|
||||||
|
|
||||||
/*
|
|
||||||
* Implementation of an LVGL input device. This class composes multiple |
|
||||||
* IInputDevice and IFeedbackDevice instances together into a single LVGL |
|
||||||
* device. |
|
||||||
*/ |
|
||||||
class LvglInputDriver { |
|
||||||
public: |
|
||||||
LvglInputDriver(drivers::NvsStorage& nvs, DeviceFactory&); |
|
||||||
|
|
||||||
auto mode() -> lua::Property& { return mode_; } |
|
||||||
|
|
||||||
auto read(lv_indev_data_t* data) -> void; |
|
||||||
auto feedback(uint8_t) -> void; |
|
||||||
|
|
||||||
auto registration() -> lv_indev_t* { return registration_; } |
|
||||||
auto lock(bool l) -> void { is_locked_ = l; } |
|
||||||
|
|
||||||
auto pushHooks(lua_State* L) -> int; |
|
||||||
|
|
||||||
private: |
|
||||||
drivers::NvsStorage& nvs_; |
|
||||||
DeviceFactory& factory_; |
|
||||||
|
|
||||||
lua::Property mode_; |
|
||||||
lv_indev_drv_t driver_; |
|
||||||
lv_indev_t* registration_; |
|
||||||
|
|
||||||
std::vector<std::shared_ptr<IInputDevice>> inputs_; |
|
||||||
std::vector<std::shared_ptr<IFeedbackDevice>> feedbacks_; |
|
||||||
|
|
||||||
bool is_locked_; |
|
||||||
}; |
|
||||||
|
|
||||||
} // namespace input
|
|
@ -1,127 +0,0 @@ |
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
* |
|
||||||
* SPDX-License-Identifier: GPL-3.0-only |
|
||||||
*/ |
|
||||||
|
|
||||||
#include "lvgl_input_driver.hpp" |
|
||||||
#include <stdint.h> |
|
||||||
|
|
||||||
#include <cstdint> |
|
||||||
#include <memory> |
|
||||||
#include <variant> |
|
||||||
|
|
||||||
#include "device_factory.hpp" |
|
||||||
#include "feedback_haptics.hpp" |
|
||||||
#include "input_touch_wheel.hpp" |
|
||||||
#include "input_trigger.hpp" |
|
||||||
#include "input_volume_buttons.hpp" |
|
||||||
#include "lauxlib.h" |
|
||||||
#include "lua.h" |
|
||||||
#include "lvgl.h" |
|
||||||
#include "nvs.hpp" |
|
||||||
#include "property.hpp" |
|
||||||
|
|
||||||
[[maybe_unused]] static constexpr char kTag[] = "input"; |
|
||||||
|
|
||||||
namespace input { |
|
||||||
|
|
||||||
static void read_cb(lv_indev_drv_t* drv, lv_indev_data_t* data) { |
|
||||||
LvglInputDriver* instance = |
|
||||||
reinterpret_cast<LvglInputDriver*>(drv->user_data); |
|
||||||
instance->read(data); |
|
||||||
} |
|
||||||
|
|
||||||
static void feedback_cb(lv_indev_drv_t* drv, uint8_t event) { |
|
||||||
LvglInputDriver* instance = |
|
||||||
reinterpret_cast<LvglInputDriver*>(drv->user_data); |
|
||||||
instance->feedback(event); |
|
||||||
} |
|
||||||
|
|
||||||
auto intToMode(int raw) -> std::optional<drivers::NvsStorage::InputModes> { |
|
||||||
switch (raw) { |
|
||||||
case 0: |
|
||||||
return drivers::NvsStorage::InputModes::kButtonsOnly; |
|
||||||
case 1: |
|
||||||
return drivers::NvsStorage::InputModes::kButtonsWithWheel; |
|
||||||
case 2: |
|
||||||
return drivers::NvsStorage::InputModes::kDirectionalWheel; |
|
||||||
case 3: |
|
||||||
return drivers::NvsStorage::InputModes::kRotatingWheel; |
|
||||||
default: |
|
||||||
return {}; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs, |
|
||||||
DeviceFactory& factory) |
|
||||||
: nvs_(nvs), |
|
||||||
factory_(factory), |
|
||||||
mode_(static_cast<int>(nvs.PrimaryInput()), |
|
||||||
[&](const lua::LuaValue& val) { |
|
||||||
if (!std::holds_alternative<int>(val)) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
auto mode = intToMode(std::get<int>(val)); |
|
||||||
if (!mode) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
nvs.PrimaryInput(*mode); |
|
||||||
inputs_ = factory.createInputs(*mode); |
|
||||||
return true; |
|
||||||
}), |
|
||||||
driver_(), |
|
||||||
registration_(nullptr), |
|
||||||
inputs_(factory.createInputs(nvs.PrimaryInput())), |
|
||||||
feedbacks_(factory.createFeedbacks()), |
|
||||||
is_locked_(false) { |
|
||||||
lv_indev_drv_init(&driver_); |
|
||||||
driver_.type = LV_INDEV_TYPE_ENCODER; |
|
||||||
driver_.read_cb = read_cb; |
|
||||||
driver_.feedback_cb = feedback_cb; |
|
||||||
driver_.user_data = this; |
|
||||||
driver_.long_press_time = kLongPressDelayMs; |
|
||||||
driver_.long_press_repeat_time = kRepeatDelayMs; |
|
||||||
|
|
||||||
registration_ = lv_indev_drv_register(&driver_); |
|
||||||
} |
|
||||||
|
|
||||||
auto LvglInputDriver::read(lv_indev_data_t* data) -> void { |
|
||||||
// TODO: we should pass lock state on to the individual devices, since they
|
|
||||||
// may wish to either ignore the lock state, or power down until unlock.
|
|
||||||
if (is_locked_) { |
|
||||||
return; |
|
||||||
} |
|
||||||
for (auto&& device : inputs_) { |
|
||||||
device->read(data); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
auto LvglInputDriver::feedback(uint8_t event) -> void { |
|
||||||
if (is_locked_) { |
|
||||||
return; |
|
||||||
} |
|
||||||
for (auto&& device : feedbacks_) { |
|
||||||
device->feedback(event); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
auto LvglInputDriver::pushHooks(lua_State* L) -> int { |
|
||||||
lua_newtable(L); |
|
||||||
|
|
||||||
for (auto& dev : inputs_) { |
|
||||||
lua_pushlstring(L, dev->name().data(), dev->name().size()); |
|
||||||
lua_newtable(L); |
|
||||||
|
|
||||||
for (auto& hook : dev->hooks()) { |
|
||||||
lua_pushlstring(L, hook.name().data(), hook.name().size()); |
|
||||||
hook.pushHooks(L); |
|
||||||
lua_rawset(L, -3); |
|
||||||
} |
|
||||||
|
|
||||||
lua_rawset(L, -3); |
|
||||||
} |
|
||||||
return 1; |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace input
|
|
@ -1,13 +0,0 @@ |
|||||||
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
# |
|
||||||
# SPDX-License-Identifier: GPL-3.0-only |
|
||||||
|
|
||||||
idf_component_register( |
|
||||||
SRCS "lua_theme.cpp" "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp" |
|
||||||
"lua_queue.cpp" "lua_version.cpp" "lua_theme.cpp" "lua_controls.cpp" "registry.cpp" |
|
||||||
"lua_screen.cpp" "lua_filesystem.cpp" "file_iterator.cpp" |
|
||||||
INCLUDE_DIRS "include" |
|
||||||
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "fatfs" |
|
||||||
"esp_timer" "battery" "esp-idf-lua" "luavgl" "lua-linenoise" "lua-term" |
|
||||||
"esp_app_format") |
|
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
|
@ -1,11 +0,0 @@ |
|||||||
# Copyright 2023 jacqueline <me@jacqueline.id.au> |
|
||||||
# |
|
||||||
# SPDX-License-Identifier: GPL-3.0-only |
|
||||||
|
|
||||||
idf_component_register( |
|
||||||
SRCS "system_fsm.cpp" "running.cpp" "booting.cpp" "idle.cpp" |
|
||||||
"service_locator.cpp" |
|
||||||
INCLUDE_DIRS "include" |
|
||||||
REQUIRES "tinyfsm" "drivers" "database" "ui" "result" "events" "audio" |
|
||||||
"app_console" "battery" "locale") |
|
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
|
@ -0,0 +1,22 @@ |
|||||||
|
# Copyright 2024 jacqueline <me@jacqueline.id.au> |
||||||
|
# |
||||||
|
# SPDX-License-Identifier: GPL-3.0-only |
||||||
|
|
||||||
|
idf_component_register( |
||||||
|
SRC_DIRS "app_console" "audio" "battery" "database" "dev_console" "events" |
||||||
|
"input" "lua" "system_fsm" "ui" |
||||||
|
INCLUDE_DIRS "." |
||||||
|
REQUIRES "codecs" "drivers" "locale" "memory" "tasks" "util" "graphics" |
||||||
|
"tinyfsm" "lvgl" "esp_timer" "luavgl" "esp_app_format" "libcppbor" "libtags" |
||||||
|
"komihash" "result" "esp_psram" "fatfs" "millershuffle" "speexdsp" "console" |
||||||
|
"esp-idf-lua" "lua-linenoise" "lua-term") |
||||||
|
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
||||||
|
|
||||||
|
set(LEVELDB_BUILD_TESTS OFF) |
||||||
|
set(LEVELDB_BUILD_BENCHMARKS OFF) |
||||||
|
set(LEVELDB_INSTALL OFF) |
||||||
|
|
||||||
|
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) |
||||||
|
|
||||||
|
add_subdirectory($ENV{PROJ_PATH}/lib/leveldb ${CMAKE_CURRENT_BINARY_DIR}/leveldb) |
||||||
|
target_link_libraries(${COMPONENT_LIB} PUBLIC leveldb) |
@ -0,0 +1,199 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "audio/audio_decoder.hpp" |
||||||
|
|
||||||
|
#include <cassert> |
||||||
|
#include <cmath> |
||||||
|
#include <cstddef> |
||||||
|
#include <cstdint> |
||||||
|
#include <cstdlib> |
||||||
|
#include <cstring> |
||||||
|
#include <deque> |
||||||
|
#include <memory> |
||||||
|
#include <span> |
||||||
|
#include <variant> |
||||||
|
|
||||||
|
#include "esp_err.h" |
||||||
|
#include "esp_heap_caps.h" |
||||||
|
#include "esp_log.h" |
||||||
|
#include "freertos/portmacro.h" |
||||||
|
#include "freertos/projdefs.h" |
||||||
|
#include "freertos/queue.h" |
||||||
|
|
||||||
|
#include "audio/audio_events.hpp" |
||||||
|
#include "audio/audio_fsm.hpp" |
||||||
|
#include "audio/audio_sink.hpp" |
||||||
|
#include "audio/audio_source.hpp" |
||||||
|
#include "audio/processor.hpp" |
||||||
|
#include "codec.hpp" |
||||||
|
#include "database/track.hpp" |
||||||
|
#include "drivers/i2s_dac.hpp" |
||||||
|
#include "events/event_queue.hpp" |
||||||
|
#include "sample.hpp" |
||||||
|
#include "tasks.hpp" |
||||||
|
#include "types.hpp" |
||||||
|
#include "ui/ui_fsm.hpp" |
||||||
|
|
||||||
|
namespace audio { |
||||||
|
|
||||||
|
static const char* kTag = "decoder"; |
||||||
|
|
||||||
|
/*
|
||||||
|
* The size of the buffer used for holding decoded samples. This buffer is |
||||||
|
* allocated in internal memory for greater speed, so be careful when |
||||||
|
* increasing its size. |
||||||
|
*/ |
||||||
|
static constexpr std::size_t kCodecBufferLength = |
||||||
|
drivers::kI2SBufferLengthFrames * sizeof(sample::Sample); |
||||||
|
|
||||||
|
auto Decoder::Start(std::shared_ptr<SampleProcessor> sink) -> Decoder* { |
||||||
|
Decoder* task = new Decoder(sink); |
||||||
|
tasks::StartPersistent<tasks::Type::kAudioDecoder>([=]() { task->Main(); }); |
||||||
|
return task; |
||||||
|
} |
||||||
|
|
||||||
|
auto Decoder::open(std::shared_ptr<TaggedStream> stream) -> void { |
||||||
|
NextStream* next = new NextStream(); |
||||||
|
next->stream = stream; |
||||||
|
// The decoder services its queue very quickly, so blocking on this write
|
||||||
|
// should be fine. If we discover contention here, then adding more space for
|
||||||
|
// items to next_stream_ should be fine too.
|
||||||
|
xQueueSend(next_stream_, &next, portMAX_DELAY); |
||||||
|
} |
||||||
|
|
||||||
|
Decoder::Decoder(std::shared_ptr<SampleProcessor> processor) |
||||||
|
: processor_(processor), next_stream_(xQueueCreate(1, sizeof(void*))) { |
||||||
|
ESP_LOGI(kTag, "allocating codec buffer, %u KiB", kCodecBufferLength / 1024); |
||||||
|
codec_buffer_ = { |
||||||
|
reinterpret_cast<sample::Sample*>(heap_caps_calloc( |
||||||
|
kCodecBufferLength, sizeof(sample::Sample), MALLOC_CAP_DMA)), |
||||||
|
kCodecBufferLength}; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Main decoding loop. Handles watching for new streams, or continuing to nudge |
||||||
|
* along the current stream if we have one. |
||||||
|
*/ |
||||||
|
void Decoder::Main() { |
||||||
|
for (;;) { |
||||||
|
// Check whether there's a new stream to begin. If we're idle, then we
|
||||||
|
// simply park and wait forever for a stream to arrive.
|
||||||
|
TickType_t wait_time = stream_ ? 0 : portMAX_DELAY; |
||||||
|
NextStream* next; |
||||||
|
if (xQueueReceive(next_stream_, &next, wait_time)) { |
||||||
|
// Copy the data out of the queue, then clean up the item.
|
||||||
|
std::shared_ptr<TaggedStream> new_stream = next->stream; |
||||||
|
delete next; |
||||||
|
|
||||||
|
// If we were already decoding, then make sure we finish up the current
|
||||||
|
// file gracefully.
|
||||||
|
if (stream_) { |
||||||
|
finishDecode(true); |
||||||
|
} |
||||||
|
|
||||||
|
// Ensure there's actually stream data; we might have been given nullptr
|
||||||
|
// as a signal to stop.
|
||||||
|
if (!new_stream) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// Start decoding the new stream.
|
||||||
|
prepareDecode(new_stream); |
||||||
|
} |
||||||
|
|
||||||
|
if (!continueDecode()) { |
||||||
|
finishDecode(false); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
auto Decoder::prepareDecode(std::shared_ptr<TaggedStream> stream) -> void { |
||||||
|
auto stub_track = std::make_shared<TrackInfo>(TrackInfo{ |
||||||
|
.tags = stream->tags(), |
||||||
|
.uri = stream->Filepath(), |
||||||
|
.duration = {}, |
||||||
|
.start_offset = {}, |
||||||
|
.bitrate_kbps = {}, |
||||||
|
.encoding = stream->type(), |
||||||
|
.format = {}, |
||||||
|
}); |
||||||
|
|
||||||
|
codec_.reset(codecs::CreateCodecForType(stream->type()).value_or(nullptr)); |
||||||
|
if (!codec_) { |
||||||
|
ESP_LOGE(kTag, "no codec found for stream"); |
||||||
|
events::Audio().Dispatch( |
||||||
|
internal::DecodingFailedToStart{.track = stub_track}); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
auto open_res = codec_->OpenStream(stream, stream->Offset()); |
||||||
|
if (open_res.has_error()) { |
||||||
|
ESP_LOGE(kTag, "codec failed to start: %s", |
||||||
|
codecs::ICodec::ErrorString(open_res.error()).c_str()); |
||||||
|
events::Audio().Dispatch( |
||||||
|
internal::DecodingFailedToStart{.track = stub_track}); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Decoding started okay! Fill out the rest of the track info for this
|
||||||
|
// stream.
|
||||||
|
stream_ = stream; |
||||||
|
track_ = std::make_shared<TrackInfo>(TrackInfo{ |
||||||
|
.tags = stream->tags(), |
||||||
|
.uri = stream->Filepath(), |
||||||
|
.duration = {}, |
||||||
|
.start_offset = stream->Offset(), |
||||||
|
.bitrate_kbps = {}, |
||||||
|
.encoding = stream->type(), |
||||||
|
.format = |
||||||
|
{ |
||||||
|
.sample_rate = open_res->sample_rate_hz, |
||||||
|
.num_channels = open_res->num_channels, |
||||||
|
.bits_per_sample = 16, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
if (open_res->total_samples) { |
||||||
|
track_->duration = open_res->total_samples.value() / |
||||||
|
open_res->num_channels / open_res->sample_rate_hz; |
||||||
|
} |
||||||
|
|
||||||
|
events::Audio().Dispatch(internal::DecodingStarted{.track = track_}); |
||||||
|
processor_->beginStream(track_); |
||||||
|
} |
||||||
|
|
||||||
|
auto Decoder::continueDecode() -> bool { |
||||||
|
auto res = codec_->DecodeTo(codec_buffer_); |
||||||
|
if (res.has_error()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (res->samples_written > 0) { |
||||||
|
processor_->continueStream(codec_buffer_.first(res->samples_written)); |
||||||
|
} |
||||||
|
|
||||||
|
return !res->is_stream_finished; |
||||||
|
} |
||||||
|
|
||||||
|
auto Decoder::finishDecode(bool cancel) -> void { |
||||||
|
assert(track_); |
||||||
|
|
||||||
|
// Tell everyone we're finished.
|
||||||
|
if (cancel) { |
||||||
|
events::Audio().Dispatch(internal::DecodingCancelled{.track = track_}); |
||||||
|
} else { |
||||||
|
events::Audio().Dispatch(internal::DecodingFinished{.track = track_}); |
||||||
|
} |
||||||
|
processor_->endStream(cancel); |
||||||
|
|
||||||
|
// Clean up after ourselves.
|
||||||
|
stream_.reset(); |
||||||
|
codec_.reset(); |
||||||
|
track_.reset(); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace audio
|
@ -0,0 +1,60 @@ |
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au> |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: GPL-3.0-only |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
#include <memory> |
||||||
|
|
||||||
|
#include "audio/audio_events.hpp" |
||||||
|
#include "audio/audio_sink.hpp" |
||||||
|
#include "audio/audio_source.hpp" |
||||||
|
#include "audio/processor.hpp" |
||||||
|
#include "codec.hpp" |
||||||
|
#include "database/track.hpp" |
||||||
|
#include "types.hpp" |
||||||
|
|
||||||
|
namespace audio { |
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle to a persistent task that takes encoded bytes from arbitrary sources, |
||||||
|
* decodes them into sample::Sample (normalised to 16 bit signed PCM), and then |
||||||
|
* streams them onward to the sample processor. |
||||||
|
*/ |
||||||
|
class Decoder { |
||||||
|
public: |
||||||
|
static auto Start(std::shared_ptr<SampleProcessor>) -> Decoder*; |
||||||
|
|
||||||
|
auto open(std::shared_ptr<TaggedStream>) -> void; |
||||||
|
|
||||||
|
Decoder(const Decoder&) = delete; |
||||||
|
Decoder& operator=(const Decoder&) = delete; |
||||||
|
|
||||||
|
private: |
||||||
|
Decoder(std::shared_ptr<SampleProcessor>); |
||||||
|
|
||||||
|
auto Main() -> void; |
||||||
|
|
||||||
|
auto prepareDecode(std::shared_ptr<TaggedStream>) -> void; |
||||||
|
auto continueDecode() -> bool; |
||||||
|
auto finishDecode(bool cancel) -> void; |
||||||
|
|
||||||
|
std::shared_ptr<SampleProcessor> processor_; |
||||||
|
|
||||||
|
// Struct used with the next_stream_ queue.
|
||||||
|
struct NextStream { |
||||||
|
std::shared_ptr<TaggedStream> stream; |
||||||
|
}; |
||||||
|
QueueHandle_t next_stream_; |
||||||
|
|
||||||
|
std::shared_ptr<codecs::IStream> stream_; |
||||||
|
std::unique_ptr<codecs::ICodec> codec_; |
||||||
|
std::shared_ptr<TrackInfo> track_; |
||||||
|
|
||||||
|
std::span<sample::Sample> codec_buffer_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace audio
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue