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