Merge branch 'main' into file-browser

custom
ailurux 12 months ago
commit 3f177cdb88
  1. 4
      .reuse/dep5
  2. 7
      CMakeLists.txt
  3. 1
      lib/lvgl/env_support/cmake/esp.cmake
  4. 3
      lib/lvgl/lv_conf.h
  5. 5
      lib/span/CMakeLists.txt
  6. 23
      lib/span/LICENSE.txt
  7. 618
      lib/span/include/span.hpp
  8. 13
      lua/licenses.lua
  9. 16
      lua/main.lua
  10. 9
      src/app_console/CMakeLists.txt
  11. 14
      src/audio/CMakeLists.txt
  12. 148
      src/audio/audio_decoder.cpp
  13. 161
      src/audio/fatfs_audio_input.cpp
  14. 56
      src/audio/include/audio_decoder.hpp
  15. 66
      src/audio/include/fatfs_audio_input.hpp
  16. 10
      src/battery/CMakeLists.txt
  17. 2
      src/codecs/CMakeLists.txt
  18. 2
      src/codecs/codec.cpp
  19. 2
      src/codecs/dr_flac.cpp
  20. 9
      src/codecs/include/codec.hpp
  21. 4
      src/codecs/include/dr_flac.hpp
  22. 4
      src/codecs/include/mad.hpp
  23. 4
      src/codecs/include/opus.hpp
  24. 9
      src/codecs/include/source_buffer.hpp
  25. 4
      src/codecs/include/vorbis.hpp
  26. 2
      src/codecs/include/wav.hpp
  27. 10
      src/codecs/mad.cpp
  28. 2
      src/codecs/opus.cpp
  29. 10
      src/codecs/source_buffer.cpp
  30. 6
      src/codecs/test/test_mad.cpp
  31. 2
      src/codecs/vorbis.cpp
  32. 55
      src/codecs/wav.cpp
  33. 22
      src/database/CMakeLists.txt
  34. 9
      src/dev_console/CMakeLists.txt
  35. 4
      src/drivers/CMakeLists.txt
  36. 2
      src/drivers/adc.cpp
  37. 19
      src/drivers/bluetooth.cpp
  38. 8
      src/drivers/display.cpp
  39. 2
      src/drivers/display_init.cpp
  40. 4
      src/drivers/gpios.cpp
  41. 12
      src/drivers/haptics.cpp
  42. 2
      src/drivers/i2c.cpp
  43. 27
      src/drivers/i2s_dac.cpp
  44. 0
      src/drivers/include/drivers/a2dp_audio_output.hpp
  45. 0
      src/drivers/include/drivers/adc.hpp
  46. 5
      src/drivers/include/drivers/bluetooth.hpp
  47. 0
      src/drivers/include/drivers/bluetooth_types.hpp
  48. 4
      src/drivers/include/drivers/display.hpp
  49. 0
      src/drivers/include/drivers/display_init.hpp
  50. 0
      src/drivers/include/drivers/fatfs_audio_input.hpp
  51. 0
      src/drivers/include/drivers/gpios.hpp
  52. 0
      src/drivers/include/drivers/haptics.hpp
  53. 0
      src/drivers/include/drivers/i2c.hpp
  54. 8
      src/drivers/include/drivers/i2s_dac.hpp
  55. 3
      src/drivers/include/drivers/nvs.hpp
  56. 0
      src/drivers/include/drivers/samd.hpp
  57. 0
      src/drivers/include/drivers/spi.hpp
  58. 0
      src/drivers/include/drivers/spiffs.hpp
  59. 2
      src/drivers/include/drivers/storage.hpp
  60. 2
      src/drivers/include/drivers/touchwheel.hpp
  61. 0
      src/drivers/include/drivers/wm8523.hpp
  62. 25
      src/drivers/nvs.cpp
  63. 4
      src/drivers/samd.cpp
  64. 2
      src/drivers/spi.cpp
  65. 2
      src/drivers/spiffs.cpp
  66. 4
      src/drivers/storage.cpp
  67. 4
      src/drivers/test/test_dac.cpp
  68. 4
      src/drivers/test/test_gpio_expander.cpp
  69. 8
      src/drivers/test/test_storage.cpp
  70. 4
      src/drivers/touchwheel.cpp
  71. 4
      src/drivers/wm8523.cpp
  72. 9
      src/events/CMakeLists.txt
  73. 7
      src/graphics/CMakeLists.txt
  74. 0
      src/graphics/font_fusion_10.c
  75. 0
      src/graphics/font_fusion_12.c
  76. 0
      src/graphics/splash.c
  77. 13
      src/input/CMakeLists.txt
  78. 60
      src/input/include/lvgl_input_driver.hpp
  79. 127
      src/input/lvgl_input_driver.cpp
  80. 2
      src/locale/CMakeLists.txt
  81. 2
      src/locale/include/collation.hpp
  82. 13
      src/lua/CMakeLists.txt
  83. 5
      src/main/CMakeLists.txt
  84. 12
      src/main/main.cpp
  85. 6
      src/memory/include/himem.hpp
  86. 11
      src/system_fsm/CMakeLists.txt
  87. 22
      src/tangara/CMakeLists.txt
  88. 36
      src/tangara/app_console/app_console.cpp
  89. 12
      src/tangara/app_console/app_console.hpp
  90. 0
      src/tangara/audio/README.md
  91. 199
      src/tangara/audio/audio_decoder.cpp
  92. 60
      src/tangara/audio/audio_decoder.hpp
  93. 37
      src/tangara/audio/audio_events.hpp
  94. 342
      src/tangara/audio/audio_fsm.cpp
  95. 64
      src/tangara/audio/audio_fsm.hpp
  96. 1
      src/tangara/audio/audio_sink.hpp
  97. 10
      src/tangara/audio/audio_source.cpp
  98. 7
      src/tangara/audio/audio_source.hpp
  99. 14
      src/tangara/audio/bt_audio_output.cpp
  100. 10
      src/tangara/audio/bt_audio_output.hpp
  101. Some files were not shown because too many files have changed in this diff Show More

@ -66,10 +66,6 @@ Files: lib/result/include/*
Copyright: 2017-2021 Matthew Rodusek Copyright: 2017-2021 Matthew Rodusek
License: MIT License: MIT
Files: lib/span/include/*
Copyright: 2018 Tristan Brindle
License: BSL-1.0
Files: lib/speexdsp/* Files: lib/speexdsp/*
Copyright: 2002-2008 Xiph.org Foundation Copyright: 2002-2008 Xiph.org Foundation
2002-2008 Jean-Marc Valin 2002-2008 Jean-Marc Valin

@ -16,10 +16,15 @@ list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/src")
project(tangara) project(tangara)
# 'collate' partition on internal flash. Contains collation data for sorting
# strings across languages (defaults to a generic dataset based on ISO14651).
get_filename_component(collate_full_path "tools/collate/Generic.LC_COLLATE" ABSOLUTE)
esptool_py_flash_to_partition(flash "collate" ${collate_full_path})
# /lua partition on internal flash, for storing the lua application # /lua partition on internal flash, for storing the lua application
spiffs_create_partition_image(lua lua FLASH_IN_PROJECT) spiffs_create_partition_image(lua lua FLASH_IN_PROJECT)
# /repl partition on internal flash, for storing the developer repl and its deps. # /repl partition on internal flash, for storing the developer repl and its deps
file(COPY lib/lua-repl/repl DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/repl) file(COPY lib/lua-repl/repl DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/repl)
file(COPY lib/lua-term/term DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/repl) file(COPY lib/lua-term/term DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/repl)
spiffs_create_partition_image(repl ${CMAKE_CURRENT_BINARY_DIR}/repl FLASH_IN_PROJECT) spiffs_create_partition_image(repl ${CMAKE_CURRENT_BINARY_DIR}/repl FLASH_IN_PROJECT)

@ -27,7 +27,6 @@ else()
${LVGL_ROOT_DIR} ${LVGL_ROOT_DIR}
${LVGL_ROOT_DIR}/src ${LVGL_ROOT_DIR}/src
${LVGL_ROOT_DIR}/../ ${LVGL_ROOT_DIR}/../
$ENV{PROJ_PATH}/src/ui/include/
REQUIRES esp_timer) REQUIRES esp_timer)
target_compile_definitions(${COMPONENT_LIB} PUBLIC "-DLV_CONF_INCLUDE_SIMPLE") target_compile_definitions(${COMPONENT_LIB} PUBLIC "-DLV_CONF_INCLUDE_SIMPLE")

@ -87,7 +87,8 @@
*It removes the need to manually update the tick with `lv_tick_inc()`)*/ *It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 1 #define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM #if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE "ui_tick.hpp" /*Header for the system time function*/ #define LV_TICK_CUSTOM_INCLUDE "esp_timer.h"
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (esp_timer_get_time() / 1000)
#endif /*LV_TICK_CUSTOM*/ #endif /*LV_TICK_CUSTOM*/
/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings. /*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.

@ -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

@ -63,16 +63,6 @@ The above copyright notice and this permission notice shall be included in all c
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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.]]) 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.]])
end end
local function boost(copyright)
show_license(copyright .. [[
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.]])
end
return function(self) return function(self)
local container = self.root:Object { local container = self.root:Object {
flex = { flex = {
@ -159,9 +149,6 @@ return function(self)
library("result", "MIT", function() library("result", "MIT", function()
mit("Copyright (c) 2017-2021 Matthew Rodusek") mit("Copyright (c) 2017-2021 Matthew Rodusek")
end) end)
library("span", "Boost", function()
boost("Copyright Tristan Brindle 2018")
end)
library("speexdsp", "bsd", function() library("speexdsp", "bsd", function()
xiphbsd( xiphbsd(
"Copyright 2002-2008 Xiph.org Foundation, Copyright 2002-2008 Jean-Marc Valin, Copyright 2005-2007 Analog Devices Inc., Copyright 2005-2008 Commonwealth Scientific and Industrial Research, Organisation (CSIRO), Copyright 1993, 2002, 2006 David Rowe, Copyright 2003 EpicGames, Copyright 1992-1994 Jutta Degener, Carsten Bormann") "Copyright 2002-2008 Xiph.org Foundation, Copyright 2002-2008 Jean-Marc Valin, Copyright 2005-2007 Analog Devices Inc., Copyright 2005-2008 Commonwealth Scientific and Industrial Research, Organisation (CSIRO), Copyright 1993, 2002, 2006 David Rowe, Copyright 2003 EpicGames, Copyright 1992-1994 Jutta Degener, Carsten Bormann")

@ -3,12 +3,15 @@ local vol = require("volume")
local theme = require("theme") local theme = require("theme")
local controls = require("controls") local controls = require("controls")
local time = require("time") local time = require("time")
local sd_card = require("sd_card")
local lock_time = time.ticks() local backstack = require("backstack")
local main_menu = require("main_menu")
local theme_dark = require("theme_dark") local theme_dark = require("theme_dark")
theme.set(theme_dark) theme.set(theme_dark)
local lock_time = time.ticks()
-- Set up property bindings that are used across every screen. -- Set up property bindings that are used across every screen.
GLOBAL_BINDINGS = { GLOBAL_BINDINGS = {
-- Show an alert with the current volume whenever the volume changes -- Show an alert with the current volume whenever the volume changes
@ -52,9 +55,8 @@ GLOBAL_BINDINGS = {
end end
end end
end), end),
sd_card.mounted:bind(function(mounted)
print("reset ui stack")
backstack.reset(main_menu:new())
end),
} }
local backstack = require("backstack")
local main_menu = require("main_menu")
backstack.push(main_menu)

@ -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})

@ -6,7 +6,7 @@ idf_component_register(
SRCS "dr_flac.cpp" "codec.cpp" "mad.cpp" "opus.cpp" "vorbis.cpp" SRCS "dr_flac.cpp" "codec.cpp" "mad.cpp" "opus.cpp" "vorbis.cpp"
"source_buffer.cpp" "sample.cpp" "wav.cpp" "source_buffer.cpp" "sample.cpp" "wav.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "result" "span" "libmad" "drflac" "tremor" "opusfile" "memory" "util" REQUIRES "result" "libmad" "drflac" "tremor" "opusfile" "memory" "util"
"komihash") "komihash")
target_compile_options("${COMPONENT_LIB}" PRIVATE ${EXTRA_WARNINGS}) target_compile_options("${COMPONENT_LIB}" PRIVATE ${EXTRA_WARNINGS})

@ -9,8 +9,8 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include "mad.hpp"
#include "dr_flac.hpp" #include "dr_flac.hpp"
#include "mad.hpp"
#include "opus.hpp" #include "opus.hpp"
#include "types.hpp" #include "types.hpp"
#include "vorbis.hpp" #include "vorbis.hpp"

@ -100,7 +100,7 @@ auto DrFlacDecoder::OpenStream(std::shared_ptr<IStream> input, uint32_t offset)
return format; return format;
} }
auto DrFlacDecoder::DecodeTo(cpp::span<sample::Sample> output) auto DrFlacDecoder::DecodeTo(std::span<sample::Sample> output)
-> cpp::result<OutputInfo, Error> { -> cpp::result<OutputInfo, Error> {
size_t frames_to_read = output.size() / flac_->channels / 2; size_t frames_to_read = output.size() / flac_->channels / 2;

@ -6,19 +6,16 @@
#pragma once #pragma once
#include <stdint.h>
#include <sys/_stdint.h>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <utility> #include <utility>
#include "result.hpp" #include "result.hpp"
#include "sample.hpp" #include "sample.hpp"
#include "span.hpp"
#include "types.hpp" #include "types.hpp"
#include "memory_resource.hpp" #include "memory_resource.hpp"
@ -35,7 +32,7 @@ class IStream {
auto type() -> StreamType { return t_; } auto type() -> StreamType { return t_; }
virtual auto Read(cpp::span<std::byte> dest) -> ssize_t = 0; virtual auto Read(std::span<std::byte> dest) -> ssize_t = 0;
virtual auto CanSeek() -> bool = 0; virtual auto CanSeek() -> bool = 0;
@ -128,7 +125,7 @@ class ICodec {
/* /*
* Writes PCM samples to the given output buffer. * Writes PCM samples to the given output buffer.
*/ */
virtual auto DecodeTo(cpp::span<sample::Sample> destination) virtual auto DecodeTo(std::span<sample::Sample> destination)
-> cpp::result<OutputInfo, Error> = 0; -> cpp::result<OutputInfo, Error> = 0;
}; };

@ -10,13 +10,13 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <utility> #include <utility>
#include "dr_flac.h" #include "dr_flac.h"
#include "sample.hpp" #include "sample.hpp"
#include "source_buffer.hpp" #include "source_buffer.hpp"
#include "span.hpp"
#include "codec.hpp" #include "codec.hpp"
@ -30,7 +30,7 @@ class DrFlacDecoder : public ICodec {
auto OpenStream(std::shared_ptr<IStream> input, uint32_t offset) auto OpenStream(std::shared_ptr<IStream> input, uint32_t offset)
-> cpp::result<OutputFormat, Error> override; -> cpp::result<OutputFormat, Error> override;
auto DecodeTo(cpp::span<sample::Sample> destination) auto DecodeTo(std::span<sample::Sample> destination)
-> cpp::result<OutputInfo, Error> override; -> cpp::result<OutputInfo, Error> override;
DrFlacDecoder(const DrFlacDecoder&) = delete; DrFlacDecoder(const DrFlacDecoder&) = delete;

@ -11,11 +11,11 @@
#include <optional> #include <optional>
#include <string> #include <string>
#include <utility> #include <utility>
#include <span>
#include "mad.h" #include "mad.h"
#include "sample.hpp" #include "sample.hpp"
#include "source_buffer.hpp" #include "source_buffer.hpp"
#include "span.hpp"
#include "codec.hpp" #include "codec.hpp"
@ -29,7 +29,7 @@ class MadMp3Decoder : public ICodec {
auto OpenStream(std::shared_ptr<IStream> input,uint32_t offset) auto OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
-> cpp::result<OutputFormat, Error> override; -> cpp::result<OutputFormat, Error> override;
auto DecodeTo(cpp::span<sample::Sample> destination) auto DecodeTo(std::span<sample::Sample> destination)
-> cpp::result<OutputInfo, Error> override; -> cpp::result<OutputInfo, Error> override;
MadMp3Decoder(const MadMp3Decoder&) = delete; MadMp3Decoder(const MadMp3Decoder&) = delete;

@ -10,12 +10,12 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <utility> #include <utility>
#include "opusfile.h" #include "opusfile.h"
#include "sample.hpp" #include "sample.hpp"
#include "span.hpp"
#include "codec.hpp" #include "codec.hpp"
@ -29,7 +29,7 @@ class XiphOpusDecoder : public ICodec {
auto OpenStream(std::shared_ptr<IStream> input, uint32_t offset) auto OpenStream(std::shared_ptr<IStream> input, uint32_t offset)
-> cpp::result<OutputFormat, Error> override; -> cpp::result<OutputFormat, Error> override;
auto DecodeTo(cpp::span<sample::Sample> destination) auto DecodeTo(std::span<sample::Sample> destination)
-> cpp::result<OutputInfo, Error> override; -> cpp::result<OutputInfo, Error> override;
XiphOpusDecoder(const XiphOpusDecoder&) = delete; XiphOpusDecoder(const XiphOpusDecoder&) = delete;

@ -9,8 +9,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <span>
#include "span.hpp"
#include "codec.hpp" #include "codec.hpp"
@ -22,15 +21,15 @@ class SourceBuffer {
~SourceBuffer(); ~SourceBuffer();
auto Refill(IStream* src) -> bool; auto Refill(IStream* src) -> bool;
auto AddBytes(std::function<size_t(cpp::span<std::byte>)> writer) -> void; auto AddBytes(std::function<size_t(std::span<std::byte>)> writer) -> void;
auto ConsumeBytes(std::function<size_t(cpp::span<std::byte>)> reader) -> void; auto ConsumeBytes(std::function<size_t(std::span<std::byte>)> reader) -> void;
auto Empty() -> void; auto Empty() -> void;
SourceBuffer(const SourceBuffer&) = delete; SourceBuffer(const SourceBuffer&) = delete;
SourceBuffer& operator=(const SourceBuffer&) = delete; SourceBuffer& operator=(const SourceBuffer&) = delete;
private: private:
const cpp::span<std::byte> buffer_; const std::span<std::byte> buffer_;
size_t bytes_in_buffer_; size_t bytes_in_buffer_;
size_t offset_of_bytes_; size_t offset_of_bytes_;
}; };

@ -10,12 +10,12 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <utility> #include <utility>
#include "ivorbisfile.h" #include "ivorbisfile.h"
#include "sample.hpp" #include "sample.hpp"
#include "span.hpp"
#include "codec.hpp" #include "codec.hpp"
@ -29,7 +29,7 @@ class TremorVorbisDecoder : public ICodec {
auto OpenStream(std::shared_ptr<IStream> input, uint32_t offset) auto OpenStream(std::shared_ptr<IStream> input, uint32_t offset)
-> cpp::result<OutputFormat, Error> override; -> cpp::result<OutputFormat, Error> override;
auto DecodeTo(cpp::span<sample::Sample> destination) auto DecodeTo(std::span<sample::Sample> destination)
-> cpp::result<OutputInfo, Error> override; -> cpp::result<OutputInfo, Error> override;
TremorVorbisDecoder(const TremorVorbisDecoder&) = delete; TremorVorbisDecoder(const TremorVorbisDecoder&) = delete;

@ -34,7 +34,7 @@ class WavDecoder : public ICodec {
auto OpenStream(std::shared_ptr<IStream> input,uint32_t offset) auto OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
-> cpp::result<OutputFormat, Error> override; -> cpp::result<OutputFormat, Error> override;
auto DecodeTo(cpp::span<sample::Sample> destination) auto DecodeTo(std::span<sample::Sample> destination)
-> cpp::result<OutputInfo, Error> override; -> cpp::result<OutputInfo, Error> override;
WavDecoder(const WavDecoder&) = delete; WavDecoder(const WavDecoder&) = delete;

@ -74,7 +74,7 @@ auto MadMp3Decoder::OpenStream(std::shared_ptr<IStream> input, uint32_t offset)
while (!eof && !got_header) { while (!eof && !got_header) {
eof = buffer_.Refill(input_.get()); eof = buffer_.Refill(input_.get());
buffer_.ConsumeBytes([&](cpp::span<std::byte> buf) -> size_t { buffer_.ConsumeBytes([&](std::span<std::byte> buf) -> size_t {
mad_stream_buffer(stream_.get(), mad_stream_buffer(stream_.get(),
reinterpret_cast<const unsigned char*>(buf.data()), reinterpret_cast<const unsigned char*>(buf.data()),
buf.size_bytes()); buf.size_bytes());
@ -130,7 +130,7 @@ auto MadMp3Decoder::OpenStream(std::shared_ptr<IStream> input, uint32_t offset)
} }
need_refill = false; need_refill = false;
buffer_.ConsumeBytes([&](cpp::span<std::byte> buf) -> size_t { buffer_.ConsumeBytes([&](std::span<std::byte> buf) -> size_t {
mad_stream_buffer(stream_.get(), mad_stream_buffer(stream_.get(),
reinterpret_cast<const unsigned char*>(buf.data()), reinterpret_cast<const unsigned char*>(buf.data()),
buf.size()); buf.size());
@ -156,13 +156,13 @@ auto MadMp3Decoder::OpenStream(std::shared_ptr<IStream> input, uint32_t offset)
return output; return output;
} }
auto MadMp3Decoder::DecodeTo(cpp::span<sample::Sample> output) auto MadMp3Decoder::DecodeTo(std::span<sample::Sample> output)
-> cpp::result<OutputInfo, Error> { -> cpp::result<OutputInfo, Error> {
if (current_sample_ < 0 && !is_eos_) { if (current_sample_ < 0 && !is_eos_) {
if (!is_eof_) { if (!is_eof_) {
is_eof_ = buffer_.Refill(input_.get()); is_eof_ = buffer_.Refill(input_.get());
if (is_eof_) { if (is_eof_) {
buffer_.AddBytes([&](cpp::span<std::byte> buf) -> size_t { buffer_.AddBytes([&](std::span<std::byte> buf) -> size_t {
if (buf.size() < MAD_BUFFER_GUARD) { if (buf.size() < MAD_BUFFER_GUARD) {
is_eof_ = false; is_eof_ = false;
return 0; return 0;
@ -174,7 +174,7 @@ auto MadMp3Decoder::DecodeTo(cpp::span<sample::Sample> output)
} }
} }
buffer_.ConsumeBytes([&](cpp::span<std::byte> buf) -> size_t { buffer_.ConsumeBytes([&](std::span<std::byte> buf) -> size_t {
mad_stream_buffer(stream_.get(), mad_stream_buffer(stream_.get(),
reinterpret_cast<const unsigned char*>(buf.data()), reinterpret_cast<const unsigned char*>(buf.data()),
buf.size()); buf.size());

@ -140,7 +140,7 @@ auto XiphOpusDecoder::OpenStream(std::shared_ptr<IStream> input,
}; };
} }
auto XiphOpusDecoder::DecodeTo(cpp::span<sample::Sample> output) auto XiphOpusDecoder::DecodeTo(std::span<sample::Sample> output)
-> cpp::result<OutputInfo, Error> { -> cpp::result<OutputInfo, Error> {
int samples_written = op_read_stereo(opus_, output.data(), output.size()); int samples_written = op_read_stereo(opus_, output.data(), output.size());

@ -39,7 +39,7 @@ auto SourceBuffer::Refill(IStream* src) -> bool {
return false; return false;
} }
bool eof = false; bool eof = false;
AddBytes([&](cpp::span<std::byte> buf) -> size_t { AddBytes([&](std::span<std::byte> buf) -> size_t {
ssize_t bytes_read = src->Read(buf); ssize_t bytes_read = src->Read(buf);
// Treat read errors as EOF. // Treat read errors as EOF.
eof = bytes_read <= 0; eof = bytes_read <= 0;
@ -48,7 +48,7 @@ auto SourceBuffer::Refill(IStream* src) -> bool {
return eof; return eof;
} }
auto SourceBuffer::AddBytes(std::function<size_t(cpp::span<std::byte>)> writer) auto SourceBuffer::AddBytes(std::function<size_t(std::span<std::byte>)> writer)
-> void { -> void {
if (offset_of_bytes_ > 0) { if (offset_of_bytes_ > 0) {
std::memmove(buffer_.data(), buffer_.data() + offset_of_bytes_, std::memmove(buffer_.data(), buffer_.data() + offset_of_bytes_,
@ -61,9 +61,9 @@ auto SourceBuffer::AddBytes(std::function<size_t(cpp::span<std::byte>)> writer)
} }
auto SourceBuffer::ConsumeBytes( auto SourceBuffer::ConsumeBytes(
std::function<size_t(cpp::span<std::byte>)> reader) -> void { std::function<size_t(std::span<std::byte>)> reader) -> void {
size_t bytes_consumed = std::invoke( size_t bytes_consumed =
reader, buffer_.subspan(offset_of_bytes_, bytes_in_buffer_)); std::invoke(reader, buffer_.subspan(offset_of_bytes_, bytes_in_buffer_));
assert(bytes_consumed <= bytes_in_buffer_); assert(bytes_consumed <= bytes_in_buffer_);
bytes_in_buffer_ -= bytes_consumed; bytes_in_buffer_ -= bytes_consumed;

@ -8,14 +8,14 @@
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
#include <span>
#include "catch2/catch.hpp" #include "catch2/catch.hpp"
#include "span.hpp"
#include "test.mp3.hpp" #include "test.mp3.hpp"
void load_mp3(cpp::span<std::byte> dest) { void load_mp3(std::span<std::byte> dest) {
cpp::span<std::byte> src(reinterpret_cast<std::byte*>(test_mp3), std::span<std::byte> src(reinterpret_cast<std::byte*>(test_mp3),
test_mp3_len); test_mp3_len);
std::copy(src.begin(), src.begin() + dest.size(), dest.begin()); std::copy(src.begin(), src.begin() + dest.size(), dest.begin());
} }

@ -129,7 +129,7 @@ auto TremorVorbisDecoder::OpenStream(std::shared_ptr<IStream> input,
}; };
} }
auto TremorVorbisDecoder::DecodeTo(cpp::span<sample::Sample> output) auto TremorVorbisDecoder::DecodeTo(std::span<sample::Sample> output)
-> cpp::result<OutputInfo, Error> { -> cpp::result<OutputInfo, Error> {
int unused = 0; int unused = 0;
long bytes_written = long bytes_written =

@ -20,24 +20,24 @@ namespace codecs {
[[maybe_unused]] static const char kTag[] = "wav"; [[maybe_unused]] static const char kTag[] = "wav";
static inline auto bytes_to_u16(cpp::span<std::byte const, 2> bytes) static inline auto bytes_to_u16(std::span<std::byte const, 2> bytes)
-> uint16_t { -> uint16_t {
return (uint16_t)bytes[0] | (uint16_t)bytes[1] << 8; return (uint16_t)bytes[0] | (uint16_t)bytes[1] << 8;
} }
static inline auto bytes_to_u32(cpp::span<std::byte const, 4> bytes) static inline auto bytes_to_u32(std::span<std::byte const, 4> bytes)
-> uint32_t { -> uint32_t {
return (uint32_t)bytes[0] | (uint32_t)bytes[1] << 8 | return (uint32_t)bytes[0] | (uint32_t)bytes[1] << 8 |
(uint32_t)bytes[2] << 16 | (uint32_t)bytes[3] << 24; (uint32_t)bytes[2] << 16 | (uint32_t)bytes[3] << 24;
} }
static inline auto bytes_to_str(cpp::span<std::byte const> bytes) static inline auto bytes_to_str(std::span<std::byte const> bytes)
-> std::string { -> std::string {
return std::string(reinterpret_cast<const char*>(bytes.data()), return std::string(reinterpret_cast<const char*>(bytes.data()),
bytes.size_bytes()); bytes.size_bytes());
} }
static int16_t convert_f32_to_16_bit(cpp::span<const std::byte> bytes) { static int16_t convert_f32_to_16_bit(std::span<const std::byte> bytes) {
uint64_t val = 0; uint64_t val = 0;
val = (uint8_t)bytes[3]; val = (uint8_t)bytes[3];
val = (val << 8) | (uint8_t)bytes[2]; val = (val << 8) | (uint8_t)bytes[2];
@ -57,7 +57,7 @@ static int16_t convert_f32_to_16_bit(cpp::span<const std::byte> bytes) {
return sample::FromDouble(*fval); return sample::FromDouble(*fval);
} }
static int16_t convert_f64_to_16_bit(cpp::span<const std::byte> bytes) { static int16_t convert_f64_to_16_bit(std::span<const std::byte> bytes) {
uint64_t val = 0; uint64_t val = 0;
val = (uint8_t)bytes[7]; val = (uint8_t)bytes[7];
val = (val << 8) | (uint8_t)bytes[6]; val = (val << 8) | (uint8_t)bytes[6];
@ -71,7 +71,7 @@ static int16_t convert_f64_to_16_bit(cpp::span<const std::byte> bytes) {
return sample::FromDouble(*fval); return sample::FromDouble(*fval);
} }
static int16_t convert_to_16_bit(cpp::span<const std::byte> bytes) { static int16_t convert_to_16_bit(std::span<const std::byte> bytes) {
int depth = bytes.size(); int depth = bytes.size();
int32_t val = 0; int32_t val = 0;
// If 8-bit Assume Unsigned // If 8-bit Assume Unsigned
@ -82,10 +82,13 @@ static int16_t convert_to_16_bit(cpp::span<const std::byte> bytes) {
switch (depth) { switch (depth) {
case 4: case 4:
val = (uint8_t)bytes[3]; val = (uint8_t)bytes[3];
[[fallthrough]];
case 3: case 3:
val = (val << 8) | (uint8_t)bytes[2]; val = (val << 8) | (uint8_t)bytes[2];
[[fallthrough]];
case 2: case 2:
val = (val << 8) | (uint8_t)bytes[1]; val = (val << 8) | (uint8_t)bytes[1];
[[fallthrough]];
case 1: case 1:
val = (val << 8) | (uint8_t)bytes[0]; val = (val << 8) | (uint8_t)bytes[0];
} }
@ -123,7 +126,7 @@ auto WavDecoder::OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
// - end of this part, next header we care about is 'data' // - end of this part, next header we care about is 'data'
// - and then the next 4 bytes = 32 bit int = size of data // - and then the next 4 bytes = 32 bit int = size of data
auto buffer_span = cpp::span{buf}; auto buffer_span = std::span{buf};
std::string riff = bytes_to_str(buffer_span.subspan(0, 4)); std::string riff = bytes_to_str(buffer_span.subspan(0, 4));
if (riff != "RIFF") { if (riff != "RIFF") {
@ -131,7 +134,7 @@ auto WavDecoder::OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
return cpp::fail(Error::kMalformedData); return cpp::fail(Error::kMalformedData);
} }
uint32_t file_size = bytes_to_u32(buffer_span.subspan(4, 4)) + 8; // uint32_t file_size = bytes_to_u32(buffer_span.subspan(4, 4)) + 8;
std::string fmt_header = bytes_to_str(buffer_span.subspan(12, 4)); std::string fmt_header = bytes_to_str(buffer_span.subspan(12, 4));
ESP_LOGI(kTag, "fmt header found? %s", ESP_LOGI(kTag, "fmt header found? %s",
@ -142,9 +145,9 @@ auto WavDecoder::OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
} }
// Size of the fmt header, should be 16, 18 or 40 // Size of the fmt header, should be 16, 18 or 40
uint32_t fmt_header_size = bytes_to_u32(buffer_span.subspan(16, 4)); // uint32_t fmt_header_size = bytes_to_u32(buffer_span.subspan(16, 4));
wave_format_ = bytes_to_u16(buffer_span.subspan(20, 2)); wave_format_ = bytes_to_u16(buffer_span.subspan<20, 2>());
if (wave_format_ == kWaveFormatPCM) { if (wave_format_ == kWaveFormatPCM) {
ESP_LOGD(kTag, "wave format: PCM"); ESP_LOGD(kTag, "wave format: PCM");
} else if (wave_format_ == kWaveFormatExtensible) { } else if (wave_format_ == kWaveFormatExtensible) {
@ -156,17 +159,17 @@ auto WavDecoder::OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
return cpp::fail(Error::kUnsupportedFormat); return cpp::fail(Error::kUnsupportedFormat);
} }
num_channels_ = bytes_to_u16(buffer_span.subspan(22, 2)); num_channels_ = bytes_to_u16(buffer_span.subspan<22, 2>());
uint32_t samples_per_second = bytes_to_u32(buffer_span.subspan(24, 4)); uint32_t samples_per_second = bytes_to_u32(buffer_span.subspan<24, 4>());
uint32_t avg_bytes_per_second = bytes_to_u32(buffer_span.subspan(28, 4)); // uint32_t avg_bytes_per_second = bytes_to_u32(buffer_span.subspan(28, 4));
uint16_t block_align = bytes_to_u16(buffer_span.subspan(32, 2)); uint16_t block_align = bytes_to_u16(buffer_span.subspan<32, 2>());
bytes_per_sample_ = block_align / num_channels_; bytes_per_sample_ = block_align / num_channels_;
uint16_t bits_per_sample = bytes_to_u16(buffer_span.subspan(34, 2)); // uint16_t bits_per_sample = bytes_to_u16(buffer_span.subspan(34, 2));
// find the start of the data chunk // find the start of the data chunk
std::array<std::byte, 4> data_tag = {std::byte{0x64}, std::byte{0x61}, std::array<std::byte, 4> data_tag = {std::byte{0x64}, std::byte{0x61},
@ -180,7 +183,7 @@ auto WavDecoder::OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
int data_chunk_index = std::distance(buffer_span.begin(), data_loc.begin()); int data_chunk_index = std::distance(buffer_span.begin(), data_loc.begin());
uint32_t data_chunk_size = uint32_t data_chunk_size =
bytes_to_u32(buffer_span.subspan(data_chunk_index + 4, 4)); bytes_to_u32(buffer_span.subspan(data_chunk_index + 4, 4).first<4>());
// calculate number of samples // calculate number of samples
int number_of_samples = data_chunk_size / bytes_per_sample_; int number_of_samples = data_chunk_size / bytes_per_sample_;
@ -188,20 +191,20 @@ auto WavDecoder::OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
// extension to the fmt chunk size (0 or 22) // extension to the fmt chunk size (0 or 22)
uint16_t extension_size = 0; uint16_t extension_size = 0;
if (wave_format_ == kWaveFormatExtensible) { if (wave_format_ == kWaveFormatExtensible) {
extension_size = bytes_to_u16(buffer_span.subspan(36, 2)); extension_size = bytes_to_u16(buffer_span.subspan<36, 2>());
} }
// Parse extension if applicable // Parse extension if applicable
if (extension_size == 22) { if (extension_size == 22) {
// Valid bits per sample // Valid bits per sample
uint16_t valid_bits_per_sample = bytes_to_u16(buffer_span.subspan(38, 2)); // uint16_t valid_bits_per_sample = bytes_to_u16(buffer_span.subspan(38,
// 2));
uint32_t speaker_mask = bytes_to_u32(buffer_span.subspan(40, 4)); // uint32_t speaker_mask = bytes_to_u32(buffer_span.subspan(40, 4));
// Parse subformat // Parse subformat
subformat_ = bytes_to_u16(buffer_span.subspan(44, 2)); subformat_ = bytes_to_u16(buffer_span.subspan<44, 2>());
if (!(subformat_ == kWaveFormatPCM || if (!(subformat_ == kWaveFormatPCM || subformat_ == kWaveFormatIEEEFloat)) {
subformat_ == kWaveFormatIEEEFloat)) {
ESP_LOGW(kTag, "WAVE extensible subformat_ not supported"); ESP_LOGW(kTag, "WAVE extensible subformat_ not supported");
return cpp::fail(Error::kUnsupportedFormat); return cpp::fail(Error::kUnsupportedFormat);
} }
@ -210,7 +213,8 @@ auto WavDecoder::OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
int64_t data_offset = offset * samples_per_second * bytes_per_sample_; int64_t data_offset = offset * samples_per_second * bytes_per_sample_;
// Seek track to start of data // Seek track to start of data
input->SeekTo(data_chunk_index + 8 + data_offset, IStream::SeekFrom::kStartOfStream); input->SeekTo(data_chunk_index + 8 + data_offset,
IStream::SeekFrom::kStartOfStream);
output_format_ = {.num_channels = (uint8_t)num_channels_, output_format_ = {.num_channels = (uint8_t)num_channels_,
.sample_rate_hz = samples_per_second, .sample_rate_hz = samples_per_second,
@ -219,12 +223,12 @@ auto WavDecoder::OpenStream(std::shared_ptr<IStream> input,uint32_t offset)
return output_format_; return output_format_;
} }
auto WavDecoder::DecodeTo(cpp::span<sample::Sample> output) auto WavDecoder::DecodeTo(std::span<sample::Sample> output)
-> cpp::result<OutputInfo, Error> { -> cpp::result<OutputInfo, Error> {
bool is_eof = buffer_.Refill(input_.get()); bool is_eof = buffer_.Refill(input_.get());
size_t samples_written = 0; size_t samples_written = 0;
buffer_.ConsumeBytes([&](cpp::span<std::byte> buf) -> size_t { buffer_.ConsumeBytes([&](std::span<std::byte> buf) -> size_t {
size_t bytes_read = buf.size_bytes(); size_t bytes_read = buf.size_bytes();
size_t frames_read = size_t frames_read =
bytes_read / bytes_per_sample_ / output_format_.num_channels; bytes_read / bytes_per_sample_ / output_format_.num_channels;
@ -254,7 +258,6 @@ auto WavDecoder::DecodeTo(cpp::span<sample::Sample> output)
return samples_written * bytes_per_sample_; return samples_written * bytes_per_sample_;
}); });
return OutputInfo{.samples_written = samples_written, return OutputInfo{.samples_written = samples_written,
.is_stream_finished = samples_written == 0 && is_eof}; .is_stream_finished = samples_written == 0 && is_eof};
} }

@ -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})

@ -7,6 +7,6 @@ idf_component_register(
"i2c.cpp" "bluetooth.cpp" "spi.cpp" "display.cpp" "display_init.cpp" "i2c.cpp" "bluetooth.cpp" "spi.cpp" "display.cpp" "display_init.cpp"
"samd.cpp" "wm8523.cpp" "nvs.cpp" "haptics.cpp" "spiffs.cpp" "samd.cpp" "wm8523.cpp" "nvs.cpp" "haptics.cpp" "spiffs.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span" "tasks" "nvs_flash" "spiffs" REQUIRES "esp_adc" "fatfs" "result" "lvgl" "nvs_flash" "spiffs" "bt"
"bt" "tinyfsm" "util") "tasks" "tinyfsm" "util" "libcppbor")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "adc.hpp" #include "drivers/adc.hpp"
#include <cstdint> #include <cstdint>
#include "esp_adc/adc_cali.h" #include "esp_adc/adc_cali.h"

@ -1,4 +1,4 @@
#include "bluetooth.hpp" #include "drivers/bluetooth.hpp"
#include <stdint.h> #include <stdint.h>
@ -25,12 +25,11 @@
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
#include "freertos/projdefs.h" #include "freertos/projdefs.h"
#include "freertos/timers.h" #include "freertos/timers.h"
#include "sample.hpp"
#include "tinyfsm/include/tinyfsm.hpp" #include "tinyfsm/include/tinyfsm.hpp"
#include "bluetooth_types.hpp" #include "drivers/bluetooth_types.hpp"
#include "drivers/nvs.hpp"
#include "memory_resource.hpp" #include "memory_resource.hpp"
#include "nvs.hpp"
#include "tasks.hpp" #include "tasks.hpp"
namespace drivers { namespace drivers {
@ -39,6 +38,7 @@ namespace drivers {
DRAM_ATTR static StreamBufferHandle_t sStream = nullptr; DRAM_ATTR static StreamBufferHandle_t sStream = nullptr;
DRAM_ATTR static std::atomic<float> sVolumeFactor = 1.f; DRAM_ATTR static std::atomic<float> sVolumeFactor = 1.f;
DRAM_ATTR static std::atomic<uint32_t> sSamplesUsed = 0;
static tasks::WorkerPool* sBgWorker; static tasks::WorkerPool* sBgWorker;
@ -74,6 +74,13 @@ IRAM_ATTR auto a2dp_data_cb(uint8_t* buf, int32_t buf_size) -> int32_t {
} }
size_t bytes_received = xStreamBufferReceive(stream, buf, buf_size, 0); size_t bytes_received = xStreamBufferReceive(stream, buf, buf_size, 0);
size_t samples_received = bytes_received / 2;
if (UINT32_MAX - sSamplesUsed < samples_received) {
sSamplesUsed = samples_received - (UINT32_MAX - sSamplesUsed);
} else {
sSamplesUsed += samples_received;
}
// Apply software volume scaling. // Apply software volume scaling.
int16_t* samples = reinterpret_cast<int16_t*>(buf); int16_t* samples = reinterpret_cast<int16_t*>(buf);
float factor = sVolumeFactor.load(); float factor = sVolumeFactor.load();
@ -166,6 +173,10 @@ auto Bluetooth::SetVolumeFactor(float f) -> void {
sVolumeFactor = f; sVolumeFactor = f;
} }
auto Bluetooth::SamplesUsed() -> uint32_t {
return sSamplesUsed;
}
auto Bluetooth::SetEventHandler(std::function<void(bluetooth::Event)> cb) auto Bluetooth::SetEventHandler(std::function<void(bluetooth::Event)> cb)
-> void { -> void {
auto lock = bluetooth::BluetoothState::lock(); auto lock = bluetooth::BluetoothState::lock();

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "display.hpp" #include "drivers/display.hpp"
#include <stdint.h> #include <stdint.h>
#include <cmath> #include <cmath>
@ -31,11 +31,11 @@
#include "hal/spi_types.h" #include "hal/spi_types.h"
#include "lvgl/lvgl.h" #include "lvgl/lvgl.h"
#include "display_init.hpp" #include "drivers/display_init.hpp"
#include "gpios.hpp" #include "drivers/gpios.hpp"
#include "drivers/spi.hpp"
#include "misc/lv_color.h" #include "misc/lv_color.h"
#include "soc/soc.h" #include "soc/soc.h"
#include "spi.hpp"
#include "tasks.hpp" #include "tasks.hpp"
[[maybe_unused]] static const char* kTag = "DISPLAY"; [[maybe_unused]] static const char* kTag = "DISPLAY";

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "display_init.hpp" #include "drivers/display_init.hpp"
namespace drivers { namespace drivers {
namespace displays { namespace displays {

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "gpios.hpp" #include "drivers/gpios.hpp"
#include <cstdint> #include <cstdint>
@ -15,7 +15,7 @@
#include "esp_intr_alloc.h" #include "esp_intr_alloc.h"
#include "hal/gpio_types.h" #include "hal/gpio_types.h"
#include "i2c.hpp" #include "drivers/i2c.hpp"
namespace drivers { namespace drivers {

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "haptics.hpp" #include "drivers/haptics.hpp"
#include <stdint.h> #include <stdint.h>
#include <cstdint> #include <cstdint>
@ -21,7 +21,7 @@
#include "hal/gpio_types.h" #include "hal/gpio_types.h"
#include "hal/i2c_types.h" #include "hal/i2c_types.h"
#include "i2c.hpp" #include "drivers/i2c.hpp"
namespace drivers { namespace drivers {
@ -54,7 +54,8 @@ Haptics::Haptics(const std::variant<ErmMotor, LraMotor>& motor) {
// Set library // Set library
// TODO(robin): try the other libraries and test response. C is marginal, D // TODO(robin): try the other libraries and test response. C is marginal, D
// too much? // too much?
WriteRegister(Register::kWaveformLibrary, static_cast<uint8_t>(kDefaultErmLibrary)); WriteRegister(Register::kWaveformLibrary,
static_cast<uint8_t>(kDefaultErmLibrary));
} else if (std::holds_alternative<LraMotor>(motor)) { } else if (std::holds_alternative<LraMotor>(motor)) {
ESP_LOGI(kTag, "Setting up LRA motor..."); ESP_LOGI(kTag, "Setting up LRA motor...");
@ -75,7 +76,8 @@ Haptics::Haptics(const std::variant<ErmMotor, LraMotor>& motor) {
ControlMask::kLraOpenLoop); ControlMask::kLraOpenLoop);
// Set library; only option is the LRA one for, well, LRA motors. // Set library; only option is the LRA one for, well, LRA motors.
WriteRegister(Register::kWaveformLibrary, static_cast<uint8_t>(Library::LRA)); WriteRegister(Register::kWaveformLibrary,
static_cast<uint8_t>(Library::LRA));
} }
// Set mode (internal trigger, on writing 1 to Go register) // Set mode (internal trigger, on writing 1 to Go register)
@ -123,7 +125,6 @@ auto Haptics::SetWaveformEffect(Effect effect) -> void {
current_effect_ = effect; current_effect_ = effect;
} }
auto Haptics::TourEffects() -> void { auto Haptics::TourEffects() -> void {
TourEffects(Effect::kFirst, Effect::kLast, kDefaultErmLibrary); TourEffects(Effect::kFirst, Effect::kLast, kDefaultErmLibrary);
} }
@ -174,7 +175,6 @@ auto Haptics::TourLibraries(Effect from, Effect to) -> void {
} }
} }
auto Haptics::PowerDown() -> void { auto Haptics::PowerDown() -> void {
WriteRegister(Register::kMode, static_cast<uint8_t>(Mode::kInternalTrigger) | WriteRegister(Register::kMode, static_cast<uint8_t>(Mode::kInternalTrigger) |
ModeMask::kStandby); ModeMask::kStandby);

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "i2c.hpp" #include "drivers/i2c.hpp"
#include <cstdint> #include <cstdint>

@ -4,7 +4,8 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "i2s_dac.hpp" #include "drivers/i2s_dac.hpp"
#include <stdint.h>
#include <cmath> #include <cmath>
#include <cstdint> #include <cstdint>
@ -25,11 +26,11 @@
#include "hal/gpio_types.h" #include "hal/gpio_types.h"
#include "hal/i2c_types.h" #include "hal/i2c_types.h"
#include "gpios.hpp" #include "drivers/gpios.hpp"
#include "drivers/i2c.hpp"
#include "drivers/wm8523.hpp"
#include "hal/i2s_types.h" #include "hal/i2s_types.h"
#include "i2c.hpp"
#include "soc/clk_tree_defs.h" #include "soc/clk_tree_defs.h"
#include "wm8523.hpp"
namespace drivers { namespace drivers {
@ -140,7 +141,7 @@ auto I2SDac::SetPaused(bool paused) -> void {
} }
} }
static volatile bool sSwapWords = false; DRAM_ATTR static volatile bool sSwapWords = false;
auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate)
-> void { -> void {
@ -207,7 +208,7 @@ auto I2SDac::Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate)
} }
} }
auto I2SDac::WriteData(const cpp::span<const std::byte>& data) -> void { auto I2SDac::WriteData(const std::span<const std::byte>& data) -> void {
std::size_t bytes_written = 0; std::size_t bytes_written = 0;
esp_err_t err = i2s_channel_write(i2s_handle_, data.data(), data.size_bytes(), esp_err_t err = i2s_channel_write(i2s_handle_, data.data(), data.size_bytes(),
&bytes_written, portMAX_DELAY); &bytes_written, portMAX_DELAY);
@ -216,6 +217,8 @@ auto I2SDac::WriteData(const cpp::span<const std::byte>& data) -> void {
} }
} }
DRAM_ATTR static volatile uint32_t sSamplesRead = 0;
extern "C" IRAM_ATTR auto callback(i2s_chan_handle_t handle, extern "C" IRAM_ATTR auto callback(i2s_chan_handle_t handle,
i2s_event_data_t* event, i2s_event_data_t* event,
void* user_ctx) -> bool { void* user_ctx) -> bool {
@ -234,6 +237,14 @@ extern "C" IRAM_ATTR auto callback(i2s_chan_handle_t handle,
size_t bytes_written = size_t bytes_written =
xStreamBufferReceiveFromISR(src, buf, event->size, &ret); xStreamBufferReceiveFromISR(src, buf, event->size, &ret);
// Assume 16 bit samples.
size_t samples = bytes_written / 2;
if (UINT32_MAX - sSamplesRead < samples) {
sSamplesRead = samples - (UINT32_MAX - sSamplesRead);
} else {
sSamplesRead = sSamplesRead + samples;
}
// The ESP32's I2S peripheral has a different endianness to its processors. // The ESP32's I2S peripheral has a different endianness to its processors.
// ESP-IDF handles this difference for stereo channels, but not for mono // ESP-IDF handles this difference for stereo channels, but not for mono
// channels. We therefore sometimes need to swap each pair of words as they're // channels. We therefore sometimes need to swap each pair of words as they're
@ -275,6 +286,10 @@ auto I2SDac::SetSource(StreamBufferHandle_t buffer) -> void {
} }
} }
auto I2SDac::SamplesUsed() -> uint32_t {
return sSamplesRead;
}
auto I2SDac::set_channel(bool enabled) -> void { auto I2SDac::set_channel(bool enabled) -> void {
if (i2s_active_ == enabled) { if (i2s_active_ == enabled) {
return; return;

@ -12,11 +12,11 @@
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/stream_buffer.h> #include <freertos/stream_buffer.h>
#include <stdint.h> #include <stdint.h>
#include "bluetooth_types.hpp" #include "drivers/bluetooth_types.hpp"
#include "drivers/nvs.hpp"
#include "esp_a2dp_api.h" #include "esp_a2dp_api.h"
#include "esp_avrc_api.h" #include "esp_avrc_api.h"
#include "esp_gap_bt_api.h" #include "esp_gap_bt_api.h"
#include "nvs.hpp"
#include "tasks.hpp" #include "tasks.hpp"
#include "tinyfsm.hpp" #include "tinyfsm.hpp"
#include "tinyfsm/include/tinyfsm.hpp" #include "tinyfsm/include/tinyfsm.hpp"
@ -44,6 +44,7 @@ class Bluetooth {
auto SetSource(StreamBufferHandle_t) -> void; auto SetSource(StreamBufferHandle_t) -> void;
auto SetVolumeFactor(float) -> void; auto SetVolumeFactor(float) -> void;
auto SamplesUsed() -> uint32_t;
auto SetEventHandler(std::function<void(bluetooth::Event)> cb) -> void; auto SetEventHandler(std::function<void(bluetooth::Event)> cb) -> void;
}; };

@ -15,8 +15,8 @@
#include "result.hpp" #include "result.hpp"
#include "tasks.hpp" #include "tasks.hpp"
#include "display_init.hpp" #include "drivers/display_init.hpp"
#include "gpios.hpp" #include "drivers/gpios.hpp"
namespace drivers { namespace drivers {

@ -11,6 +11,7 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <utility> #include <utility>
#include "driver/i2s_std.h" #include "driver/i2s_std.h"
@ -20,9 +21,8 @@
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
#include "freertos/stream_buffer.h" #include "freertos/stream_buffer.h"
#include "result.hpp" #include "result.hpp"
#include "span.hpp"
#include "gpios.hpp" #include "drivers/gpios.hpp"
#include "sys/_stdint.h" #include "sys/_stdint.h"
namespace drivers { namespace drivers {
@ -68,9 +68,11 @@ class I2SDac {
auto Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) -> void; auto Reconfigure(Channels ch, BitsPerSample bps, SampleRate rate) -> void;
auto WriteData(const cpp::span<const std::byte>& data) -> void; auto WriteData(const std::span<const std::byte>& data) -> void;
auto SetSource(StreamBufferHandle_t buffer) -> void; auto SetSource(StreamBufferHandle_t buffer) -> void;
auto SamplesUsed() -> uint32_t;
// Not copyable or movable. // Not copyable or movable.
I2SDac(const I2SDac&) = delete; I2SDac(const I2SDac&) = delete;
I2SDac& operator=(const I2SDac&) = delete; I2SDac& operator=(const I2SDac&) = delete;

@ -13,9 +13,8 @@
#include "esp_err.h" #include "esp_err.h"
#include "nvs.h" #include "nvs.h"
#include "bluetooth_types.hpp" #include "drivers/bluetooth_types.hpp"
#include "lru_cache.hpp" #include "lru_cache.hpp"
#include "tasks.hpp"
namespace drivers { namespace drivers {

@ -15,7 +15,7 @@
#include "ff.h" #include "ff.h"
#include "result.hpp" #include "result.hpp"
#include "gpios.hpp" #include "drivers/gpios.hpp"
namespace drivers { namespace drivers {

@ -12,7 +12,7 @@
#include "esp_err.h" #include "esp_err.h"
#include "result.hpp" #include "result.hpp"
#include "gpios.hpp" #include "drivers/gpios.hpp"
namespace drivers { namespace drivers {

@ -4,22 +4,19 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "nvs.hpp" #include "drivers/nvs.hpp"
#include <stdint.h>
#include <sys/_stdint.h>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include "bluetooth.hpp"
#include "bluetooth_types.hpp"
#include "cppbor.h" #include "cppbor.h"
#include "cppbor_parse.h" #include "cppbor_parse.h"
#include "drivers/bluetooth.hpp"
#include "drivers/bluetooth_types.hpp"
#include "drivers/wm8523.hpp"
#include "esp_log.h" #include "esp_log.h"
#include "nvs.h" #include "nvs.h"
#include "nvs_flash.h" #include "nvs_flash.h"
#include "tasks.hpp"
#include "wm8523.hpp"
namespace drivers { namespace drivers {
@ -42,8 +39,8 @@ static constexpr char kKeyDisplayRows[] = "disprows";
static constexpr char kKeyHapticMotorType[] = "hapticmtype"; static constexpr char kKeyHapticMotorType[] = "hapticmtype";
static constexpr char kKeyDbAutoIndex[] = "dbautoindex"; static constexpr char kKeyDbAutoIndex[] = "dbautoindex";
static auto nvs_get_string(nvs_handle_t nvs, const char* key) static auto nvs_get_string(nvs_handle_t nvs,
-> std::optional<std::string> { const char* key) -> std::optional<std::string> {
size_t len = 0; size_t len = 0;
if (nvs_get_blob(nvs, key, NULL, &len) != ESP_OK) { if (nvs_get_blob(nvs, key, NULL, &len) != ESP_OK) {
return {}; return {};
@ -190,8 +187,7 @@ auto NvsStorage::Read() -> void {
lock_polarity_.read(handle_); lock_polarity_.read(handle_);
display_cols_.read(handle_); display_cols_.read(handle_);
display_rows_.read(handle_); display_rows_.read(handle_);
haptic_motor_type_.read(handle_), haptic_motor_type_.read(handle_), brightness_.read(handle_);
brightness_.read(handle_);
sensitivity_.read(handle_); sensitivity_.read(handle_);
amp_max_vol_.read(handle_); amp_max_vol_.read(handle_);
amp_cur_vol_.read(handle_); amp_cur_vol_.read(handle_);
@ -208,8 +204,7 @@ auto NvsStorage::Write() -> bool {
lock_polarity_.write(handle_); lock_polarity_.write(handle_);
display_cols_.write(handle_); display_cols_.write(handle_);
display_rows_.write(handle_); display_rows_.write(handle_);
haptic_motor_type_.write(handle_), haptic_motor_type_.write(handle_), brightness_.write(handle_);
brightness_.write(handle_);
sensitivity_.write(handle_); sensitivity_.write(handle_);
amp_max_vol_.write(handle_); amp_max_vol_.write(handle_);
amp_cur_vol_.write(handle_); amp_cur_vol_.write(handle_);
@ -290,8 +285,8 @@ auto NvsStorage::BluetoothVolume(const bluetooth::mac_addr_t& mac) -> uint8_t {
return bt_volumes_.Get(mac).value_or(50); return bt_volumes_.Get(mac).value_or(50);
} }
auto NvsStorage::BluetoothVolume(const bluetooth::mac_addr_t& mac, uint8_t vol) auto NvsStorage::BluetoothVolume(const bluetooth::mac_addr_t& mac,
-> void { uint8_t vol) -> void {
std::lock_guard<std::mutex> lock{mutex_}; std::lock_guard<std::mutex> lock{mutex_};
bt_volumes_dirty_ = true; bt_volumes_dirty_ = true;
bt_volumes_.Put(mac, vol); bt_volumes_.Put(mac, vol);

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "samd.hpp" #include "drivers/samd.hpp"
#include <cstdint> #include <cstdint>
#include <optional> #include <optional>
@ -15,7 +15,7 @@
#include "hal/gpio_types.h" #include "hal/gpio_types.h"
#include "hal/i2c_types.h" #include "hal/i2c_types.h"
#include "i2c.hpp" #include "drivers/i2c.hpp"
enum Registers : uint8_t { enum Registers : uint8_t {
kSamdFirmwareVersion = 0, kSamdFirmwareVersion = 0,

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "spi.hpp" #include "drivers/spi.hpp"
#include "driver/sdspi_host.h" #include "driver/sdspi_host.h"
#include "driver/spi_common.h" #include "driver/spi_common.h"

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "spiffs.hpp" #include "drivers/spiffs.hpp"
#include "esp_err.h" #include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "storage.hpp" #include "drivers/storage.hpp"
#include <atomic> #include <atomic>
#include <memory> #include <memory>
@ -23,7 +23,7 @@
#include "hal/spi_types.h" #include "hal/spi_types.h"
#include "sdmmc_cmd.h" #include "sdmmc_cmd.h"
#include "gpios.hpp" #include "drivers/gpios.hpp"
#include "memory_resource.hpp" #include "memory_resource.hpp"
[[maybe_unused]] static const char* kTag = "SDSTORAGE"; [[maybe_unused]] static const char* kTag = "SDSTORAGE";

@ -10,8 +10,8 @@
#include "catch2/catch.hpp" #include "catch2/catch.hpp"
#include "gpios.hpp" #include "drivers/gpios.hpp"
#include "i2c.hpp" #include "drivers/i2c.hpp"
#include "i2c_fixture.hpp" #include "i2c_fixture.hpp"
namespace drivers { namespace drivers {

@ -4,11 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "gpios.hpp" #include "drivers/gpios.hpp"
#include "catch2/catch.hpp" #include "catch2/catch.hpp"
#include "i2c.hpp" #include "drivers/i2c.hpp"
#include "i2c_fixture.hpp" #include "i2c_fixture.hpp"
namespace drivers { namespace drivers {

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "storage.hpp" #include "drivers/storage.hpp"
#include <dirent.h> #include <dirent.h>
@ -14,10 +14,10 @@
#include "catch2/catch.hpp" #include "catch2/catch.hpp"
#include "gpios.hpp" #include "drivers/gpios.hpp"
#include "i2c.hpp" #include "drivers/i2c.hpp"
#include "drivers/spi.hpp"
#include "i2c_fixture.hpp" #include "i2c_fixture.hpp"
#include "spi.hpp"
#include "spi_fixture.hpp" #include "spi_fixture.hpp"
namespace drivers { namespace drivers {

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "touchwheel.hpp" #include "drivers/touchwheel.hpp"
#include <stdint.h> #include <stdint.h>
#include <cstdint> #include <cstdint>
@ -18,7 +18,7 @@
#include "hal/gpio_types.h" #include "hal/gpio_types.h"
#include "hal/i2c_types.h" #include "hal/i2c_types.h"
#include "i2c.hpp" #include "drivers/i2c.hpp"
namespace drivers { namespace drivers {

@ -3,14 +3,14 @@
* *
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "wm8523.hpp" #include "drivers/wm8523.hpp"
#include <cstdint> #include <cstdint>
#include "esp_err.h" #include "esp_err.h"
#include "drivers/i2c.hpp"
#include "hal/i2c_types.h" #include "hal/i2c_types.h"
#include "i2c.hpp"
namespace drivers { namespace drivers {
namespace wm8523 { namespace wm8523 {

@ -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

@ -6,6 +6,6 @@ idf_component_register(
SRCS "collation.cpp" "strxfrm_l.c" SRCS "collation.cpp" "strxfrm_l.c"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "priv_include" PRIV_INCLUDE_DIRS "priv_include"
REQUIRES "span" "esp_partition" "spi_flash") REQUIRES "esp_partition" "spi_flash")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})

@ -9,10 +9,10 @@
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include "esp_partition.h" #include "esp_partition.h"
#include "span.hpp"
#include "strxfrm.h" #include "strxfrm.h"

@ -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})

@ -2,8 +2,5 @@
# #
# SPDX-License-Identifier: GPL-3.0-only # SPDX-License-Identifier: GPL-3.0-only
idf_component_register( idf_component_register(SRCS "main.cpp" INCLUDE_DIRS "." REQUIRES "tangara")
SRCS "main.cpp"
INCLUDE_DIRS "."
REQUIRES "audio" "ui" "system_fsm" "events")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})

@ -7,14 +7,14 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
#include "i2c.hpp"
#include "system_events.hpp"
#include "tinyfsm.hpp" #include "tinyfsm.hpp"
#include "audio_fsm.hpp" #include "audio/audio_fsm.hpp"
#include "event_queue.hpp" #include "drivers/i2c.hpp"
#include "system_fsm.hpp" #include "events/event_queue.hpp"
#include "ui_fsm.hpp" #include "system_fsm/system_events.hpp"
#include "system_fsm/system_fsm.hpp"
#include "ui/ui_fsm.hpp"
extern "C" void app_main(void) { extern "C" void app_main(void) {
ESP_ERROR_CHECK(drivers::init_i2c()); ESP_ERROR_CHECK(drivers::init_i2c());

@ -8,9 +8,9 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <span>
#include "esp32/himem.h" #include "esp32/himem.h"
#include "span.hpp"
/* /*
* Wrapper around an ESP-IDF himem allocation, which uses RAII to clean up after * Wrapper around an ESP-IDF himem allocation, which uses RAII to clean up after
@ -62,14 +62,14 @@ class MappableRegion {
} }
} }
auto Get() -> cpp::span<std::byte> { auto Get() -> std::span<std::byte> {
if (bytes_ == nullptr) { if (bytes_ == nullptr) {
return {}; return {};
} }
return {bytes_, size}; return {bytes_, size};
} }
auto Map(const HimemAlloc<size>& alloc) -> cpp::span<std::byte> { auto Map(const HimemAlloc<size>& alloc) -> std::span<std::byte> {
assert(bytes_ == nullptr); assert(bytes_ == nullptr);
ESP_ERROR_CHECK(esp_himem_map(alloc.handle, range_handle, 0, 0, size, 0, ESP_ERROR_CHECK(esp_himem_map(alloc.handle, range_handle, 0, 0, size, 0,
reinterpret_cast<void**>(&bytes_))); reinterpret_cast<void**>(&bytes_)));

@ -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)

@ -4,11 +4,9 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "app_console.hpp" #include "app_console/app_console.hpp"
#include <dirent.h> #include <dirent.h>
#include <stdint.h>
#include <sys/_stdint.h>
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
@ -21,11 +19,7 @@
#include <string> #include <string>
#include "FreeRTOSConfig.h" #include "FreeRTOSConfig.h"
#include "audio_events.hpp"
#include "audio_fsm.hpp"
#include "bluetooth.hpp"
#include "bluetooth_types.hpp"
#include "database.hpp"
#include "esp_app_desc.h" #include "esp_app_desc.h"
#include "esp_console.h" #include "esp_console.h"
#include "esp_err.h" #include "esp_err.h"
@ -34,19 +28,25 @@
#include "esp_intr_alloc.h" #include "esp_intr_alloc.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_system.h" #include "esp_system.h"
#include "event_queue.hpp"
#include "ff.h" #include "ff.h"
#include "freertos/FreeRTOSConfig_arch.h"
#include "freertos/projdefs.h" #include "freertos/projdefs.h"
#include "haptics.hpp"
#include "index.hpp" #include "drivers/bluetooth.hpp"
#include "lua_registry.hpp" #include "drivers/bluetooth_types.hpp"
#include "drivers/haptics.hpp"
#include "drivers/samd.hpp"
#include "memory_resource.hpp" #include "memory_resource.hpp"
#include "samd.hpp"
#include "service_locator.hpp" #include "audio/audio_events.hpp"
#include "system_events.hpp" #include "audio/audio_fsm.hpp"
#include "track.hpp" #include "database/database.hpp"
#include "ui_events.hpp" #include "database/index.hpp"
#include "database/track.hpp"
#include "events/event_queue.hpp"
#include "lua/lua_registry.hpp"
#include "system_fsm/service_locator.hpp"
#include "system_fsm/system_events.hpp"
#include "ui/ui_events.hpp"
namespace console { namespace console {

@ -8,12 +8,12 @@
#include <memory> #include <memory>
#include "bluetooth.hpp" #include "audio/track_queue.hpp"
#include "console.hpp" #include "drivers/bluetooth.hpp"
#include "database.hpp" #include "dev_console/console.hpp"
#include "samd.hpp" #include "database/database.hpp"
#include "service_locator.hpp" #include "drivers/samd.hpp"
#include "track_queue.hpp" #include "system_fsm/service_locator.hpp"
namespace console { namespace console {

@ -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

@ -12,10 +12,10 @@
#include <optional> #include <optional>
#include <string> #include <string>
#include "audio_sink.hpp" #include "audio/audio_sink.hpp"
#include "tinyfsm.hpp" #include "tinyfsm.hpp"
#include "track.hpp" #include "database/track.hpp"
#include "types.hpp" #include "types.hpp"
namespace audio { namespace audio {
@ -84,13 +84,6 @@ struct PlaybackUpdate : tinyfsm::Event {
struct SetTrack : tinyfsm::Event { struct SetTrack : tinyfsm::Event {
std::variant<std::string, database::TrackId, std::monostate> new_track; std::variant<std::string, database::TrackId, std::monostate> new_track;
std::optional<uint32_t> seek_to_second; std::optional<uint32_t> seek_to_second;
enum Transition {
kHardCut,
kGapless,
// TODO: kCrossFade
};
Transition transition;
}; };
struct TogglePlayPause : tinyfsm::Event { struct TogglePlayPause : tinyfsm::Event {
@ -138,17 +131,33 @@ struct OutputModeChanged : tinyfsm::Event {};
namespace internal { namespace internal {
struct DecodingStarted : tinyfsm::Event {
std::shared_ptr<TrackInfo> track;
};
struct DecodingFailedToStart : tinyfsm::Event {
std::shared_ptr<TrackInfo> track;
};
struct DecodingCancelled : tinyfsm::Event {
std::shared_ptr<TrackInfo> track;
};
struct DecodingFinished : tinyfsm::Event {
std::shared_ptr<TrackInfo> track;
};
struct StreamStarted : tinyfsm::Event { struct StreamStarted : tinyfsm::Event {
std::shared_ptr<TrackInfo> track; std::shared_ptr<TrackInfo> track;
IAudioOutput::Format src_format; IAudioOutput::Format sink_format;
IAudioOutput::Format dst_format; uint32_t cue_at_sample;
}; };
struct StreamUpdate : tinyfsm::Event { struct StreamEnded : tinyfsm::Event {
uint32_t samples_sunk; uint32_t cue_at_sample;
}; };
struct StreamEnded : tinyfsm::Event {}; struct StreamHeartbeat : tinyfsm::Event {};
} // namespace internal } // namespace internal

@ -4,15 +4,13 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "audio_fsm.hpp" #include "audio/audio_fsm.hpp"
#include <stdint.h>
#include <cstdint>
#include <future> #include <future>
#include <memory> #include <memory>
#include <variant> #include <variant>
#include "audio_sink.hpp"
#include "bluetooth_types.hpp"
#include "cppbor.h" #include "cppbor.h"
#include "cppbor_parse.h" #include "cppbor_parse.h"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
@ -20,25 +18,28 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
#include "freertos/projdefs.h" #include "freertos/projdefs.h"
#include "tinyfsm.hpp"
#include "audio_converter.hpp" #include "audio/audio_decoder.hpp"
#include "audio_decoder.hpp" #include "audio/audio_events.hpp"
#include "audio_events.hpp" #include "audio/audio_sink.hpp"
#include "bluetooth.hpp" #include "audio/bt_audio_output.hpp"
#include "bt_audio_output.hpp" #include "audio/fatfs_stream_factory.hpp"
#include "event_queue.hpp" #include "audio/i2s_audio_output.hpp"
#include "fatfs_audio_input.hpp" #include "audio/stream_cues.hpp"
#include "future_fetcher.hpp" #include "audio/track_queue.hpp"
#include "i2s_audio_output.hpp" #include "database/future_fetcher.hpp"
#include "i2s_dac.hpp" #include "database/track.hpp"
#include "nvs.hpp" #include "drivers/bluetooth.hpp"
#include "drivers/bluetooth_types.hpp"
#include "drivers/i2s_dac.hpp"
#include "drivers/nvs.hpp"
#include "drivers/storage.hpp"
#include "drivers/wm8523.hpp"
#include "events/event_queue.hpp"
#include "sample.hpp" #include "sample.hpp"
#include "service_locator.hpp" #include "system_fsm/service_locator.hpp"
#include "system_events.hpp" #include "system_fsm/system_events.hpp"
#include "tinyfsm.hpp"
#include "track.hpp"
#include "track_queue.hpp"
#include "wm8523.hpp"
namespace audio { namespace audio {
@ -46,12 +47,14 @@ namespace audio {
std::shared_ptr<system_fsm::ServiceLocator> AudioState::sServices; std::shared_ptr<system_fsm::ServiceLocator> AudioState::sServices;
std::shared_ptr<FatfsAudioInput> AudioState::sFileSource; std::shared_ptr<FatfsStreamFactory> AudioState::sStreamFactory;
std::unique_ptr<Decoder> AudioState::sDecoder; std::unique_ptr<Decoder> AudioState::sDecoder;
std::shared_ptr<SampleConverter> AudioState::sSampleConverter; std::shared_ptr<SampleProcessor> AudioState::sSampleProcessor;
std::shared_ptr<IAudioOutput> AudioState::sOutput;
std::shared_ptr<I2SAudioOutput> AudioState::sI2SOutput; std::shared_ptr<I2SAudioOutput> AudioState::sI2SOutput;
std::shared_ptr<BluetoothAudioOutput> AudioState::sBtOutput; std::shared_ptr<BluetoothAudioOutput> AudioState::sBtOutput;
std::shared_ptr<IAudioOutput> AudioState::sOutput;
// Two seconds of samples for two channels, at a representative sample rate. // Two seconds of samples for two channels, at a representative sample rate.
constexpr size_t kDrainLatencySamples = 48000 * 2 * 2; constexpr size_t kDrainLatencySamples = 48000 * 2 * 2;
@ -61,30 +64,33 @@ constexpr size_t kDrainBufferSize =
StreamBufferHandle_t AudioState::sDrainBuffer; StreamBufferHandle_t AudioState::sDrainBuffer;
std::optional<IAudioOutput::Format> AudioState::sDrainFormat; std::optional<IAudioOutput::Format> AudioState::sDrainFormat;
std::shared_ptr<TrackInfo> AudioState::sCurrentTrack; StreamCues AudioState::sStreamCues;
uint64_t AudioState::sCurrentSamples;
bool AudioState::sCurrentTrackIsFromQueue;
std::shared_ptr<TrackInfo> AudioState::sNextTrack;
uint64_t AudioState::sNextTrackCueSamples;
bool AudioState::sNextTrackIsFromQueue;
bool AudioState::sIsResampling;
bool AudioState::sIsPaused = true; bool AudioState::sIsPaused = true;
auto AudioState::currentPositionSeconds() -> std::optional<uint32_t> { auto AudioState::emitPlaybackUpdate(bool paused) -> void {
if (!sCurrentTrack || !sDrainFormat) { std::optional<uint32_t> position;
return {}; auto current = sStreamCues.current();
if (current.first && sDrainFormat) {
position = (current.second /
(sDrainFormat->num_channels * sDrainFormat->sample_rate)) +
current.first->start_offset.value_or(0);
} }
return sCurrentSamples /
(sDrainFormat->num_channels * sDrainFormat->sample_rate); PlaybackUpdate event{
.current_track = current.first,
.track_position = position,
.paused = paused,
};
events::System().Dispatch(event);
events::Ui().Dispatch(event);
} }
void AudioState::react(const QueueUpdate& ev) { void AudioState::react(const QueueUpdate& ev) {
SetTrack cmd{ SetTrack cmd{
.new_track = std::monostate{}, .new_track = std::monostate{},
.seek_to_second = {}, .seek_to_second = {},
.transition = SetTrack::Transition::kHardCut,
}; };
auto current = sServices->track_queue().current(); auto current = sServices->track_queue().current();
@ -97,20 +103,13 @@ void AudioState::react(const QueueUpdate& ev) {
if (!ev.current_changed) { if (!ev.current_changed) {
return; return;
} }
sNextTrackIsFromQueue = true;
cmd.transition = SetTrack::Transition::kHardCut;
break; break;
case QueueUpdate::kRepeatingLastTrack: case QueueUpdate::kRepeatingLastTrack:
sNextTrackIsFromQueue = true;
cmd.transition = SetTrack::Transition::kGapless;
break; break;
case QueueUpdate::kTrackFinished: case QueueUpdate::kTrackFinished:
if (!ev.current_changed) { if (!ev.current_changed) {
cmd.new_track = std::monostate{}; cmd.new_track = std::monostate{};
} else {
sNextTrackIsFromQueue = true;
} }
cmd.transition = SetTrack::Transition::kGapless;
break; break;
case QueueUpdate::kDeserialised: case QueueUpdate::kDeserialised:
default: default:
@ -123,32 +122,9 @@ void AudioState::react(const QueueUpdate& ev) {
} }
void AudioState::react(const SetTrack& ev) { void AudioState::react(const SetTrack& ev) {
// Remember the current track if there is one, since we need to preserve some
// of the state if it turns out this SetTrack event corresponds to seeking
// within the current track.
std::string prev_uri;
bool prev_from_queue = false;
if (sCurrentTrack) {
prev_uri = sCurrentTrack->uri;
prev_from_queue = sCurrentTrackIsFromQueue;
}
if (ev.transition == SetTrack::Transition::kHardCut) {
sCurrentTrack.reset();
sCurrentSamples = 0;
sCurrentTrackIsFromQueue = false;
clearDrainBuffer();
}
if (std::holds_alternative<std::monostate>(ev.new_track)) { if (std::holds_alternative<std::monostate>(ev.new_track)) {
ESP_LOGI(kTag, "playback finished, awaiting drain"); ESP_LOGI(kTag, "playback finished, awaiting drain");
sFileSource->SetPath(); sDecoder->open({});
awaitEmptyDrainBuffer();
sCurrentTrack.reset();
sDrainFormat.reset();
sCurrentSamples = 0;
sCurrentTrackIsFromQueue = false;
transit<states::Standby>();
return; return;
} }
@ -157,94 +133,74 @@ void AudioState::react(const SetTrack& ev) {
auto new_track = ev.new_track; auto new_track = ev.new_track;
uint32_t seek_to = ev.seek_to_second.value_or(0); uint32_t seek_to = ev.seek_to_second.value_or(0);
sServices->bg_worker().Dispatch<void>([=]() { sServices->bg_worker().Dispatch<void>([=]() {
std::optional<std::string> path; std::shared_ptr<TaggedStream> stream;
if (std::holds_alternative<database::TrackId>(new_track)) { if (std::holds_alternative<database::TrackId>(new_track)) {
auto db = sServices->database().lock(); stream = sStreamFactory->create(std::get<database::TrackId>(new_track),
if (db) { seek_to);
path = db->getTrackPath(std::get<database::TrackId>(new_track));
}
} else if (std::holds_alternative<std::string>(new_track)) { } else if (std::holds_alternative<std::string>(new_track)) {
path = std::get<std::string>(new_track); stream =
sStreamFactory->create(std::get<std::string>(new_track), seek_to);
} }
if (path) { sDecoder->open(stream);
if (*path == prev_uri) {
// This was a seek or replay within the same track; don't forget where
// the track originally came from.
sNextTrackIsFromQueue = prev_from_queue;
}
sFileSource->SetPath(*path, seek_to);
} else {
sFileSource->SetPath();
}
}); });
} }
void AudioState::react(const TogglePlayPause& ev) { void AudioState::react(const TogglePlayPause& ev) {
sIsPaused = !ev.set_to.value_or(sIsPaused); sIsPaused = !ev.set_to.value_or(sIsPaused);
if (!sIsPaused && is_in_state<states::Standby>() && sCurrentTrack) { if (!sIsPaused && is_in_state<states::Standby>() &&
sStreamCues.current().first) {
transit<states::Playback>(); transit<states::Playback>();
} else if (sIsPaused && is_in_state<states::Playback>()) { } else if (sIsPaused && is_in_state<states::Playback>()) {
transit<states::Standby>(); transit<states::Standby>();
} }
} }
void AudioState::react(const internal::StreamStarted& ev) { void AudioState::react(const internal::DecodingFinished& ev) {
sDrainFormat = ev.dst_format; // If we just finished playing whatever's at the front of the queue, then we
sIsResampling = ev.src_format != ev.dst_format; // need to advanve and start playing the next one ASAP in order to continue
// gaplessly.
sNextTrack = ev.track; sServices->bg_worker().Dispatch<void>([=]() {
sNextTrackCueSamples = sCurrentSamples + (kDrainLatencySamples / 2); auto& queue = sServices->track_queue();
auto current = queue.current();
ESP_LOGI(kTag, "new stream %s %u ch @ %lu hz (resample=%i)", if (!current) {
ev.track->uri.c_str(), sDrainFormat->num_channels, return;
sDrainFormat->sample_rate, sIsResampling);
} }
auto db = sServices->database().lock();
void AudioState::react(const internal::StreamEnded&) { if (!db) {
ESP_LOGI(kTag, "stream ended"); return;
if (sCurrentTrackIsFromQueue) {
sServices->track_queue().finish();
} else {
tinyfsm::FsmList<AudioState>::dispatch(SetTrack{
.new_track = std::monostate{},
.seek_to_second = {},
.transition = SetTrack::Transition::kGapless,
});
} }
auto path = db->getTrackPath(*current);
if (!path) {
return;
} }
if (*path == ev.track->uri) {
void AudioState::react(const internal::StreamUpdate& ev) { queue.finish();
sCurrentSamples += ev.samples_sunk; }
});
if (sNextTrack && sCurrentSamples >= sNextTrackCueSamples) {
ESP_LOGI(kTag, "next track is now sinking");
sCurrentTrack = sNextTrack;
sCurrentSamples -= sNextTrackCueSamples;
sCurrentSamples += sNextTrack->start_offset.value_or(0) *
(sDrainFormat->num_channels * sDrainFormat->sample_rate);
sCurrentTrackIsFromQueue = sNextTrackIsFromQueue;
sNextTrack.reset();
sNextTrackCueSamples = 0;
sNextTrackIsFromQueue = false;
} }
if (sCurrentTrack) { void AudioState::react(const internal::StreamStarted& ev) {
PlaybackUpdate event{ if (sDrainFormat != ev.sink_format) {
.current_track = sCurrentTrack, sDrainFormat = ev.sink_format;
.track_position = currentPositionSeconds(), ESP_LOGI(kTag, "sink_format=%u ch @ %lu hz", sDrainFormat->num_channels,
.paused = !is_in_state<states::Playback>(), sDrainFormat->sample_rate);
};
events::System().Dispatch(event);
events::Ui().Dispatch(event);
} }
if (sCurrentTrack && !sIsPaused && !is_in_state<states::Playback>()) { sStreamCues.addCue(ev.track, ev.cue_at_sample);
ESP_LOGI(kTag, "ready to play!");
if (!sIsPaused && !is_in_state<states::Playback>()) {
transit<states::Playback>(); transit<states::Playback>();
} else {
// Make sure everyone knows we've got a track ready to go, even if we're
// not playing it yet. This mostly matters when restoring the queue from
// disk after booting.
emitPlaybackUpdate(true);
}
} }
void AudioState::react(const internal::StreamEnded& ev) {
sStreamCues.addCue({}, ev.cue_at_sample);
} }
void AudioState::react(const system_fsm::BluetoothEvent& ev) { void AudioState::react(const system_fsm::BluetoothEvent& ev) {
@ -282,14 +238,6 @@ void AudioState::react(const StepDownVolume& ev) {
} }
} }
void AudioState::react(const system_fsm::HasPhonesChanged& ev) {
if (ev.has_headphones) {
ESP_LOGI(kTag, "headphones in!");
} else {
ESP_LOGI(kTag, "headphones out!");
}
}
void AudioState::react(const SetVolume& ev) { void AudioState::react(const SetVolume& ev) {
if (ev.db.has_value()) { if (ev.db.has_value()) {
if (sOutput->SetVolumeDb(ev.db.value())) { if (sOutput->SetVolumeDb(ev.db.value())) {
@ -349,7 +297,7 @@ void AudioState::react(const OutputModeChanged& ev) {
break; break;
} }
sOutput->mode(IAudioOutput::Modes::kOnPaused); sOutput->mode(IAudioOutput::Modes::kOnPaused);
sSampleConverter->SetOutput(sOutput); sSampleProcessor->SetOutput(sOutput);
// Bluetooth volume isn't 'changed' until we've connected to a device. // Bluetooth volume isn't 'changed' until we've connected to a device.
if (new_mode == drivers::NvsStorage::Output::kHeadphones) { if (new_mode == drivers::NvsStorage::Output::kHeadphones) {
@ -360,43 +308,6 @@ void AudioState::react(const OutputModeChanged& ev) {
} }
} }
auto AudioState::clearDrainBuffer() -> void {
// Tell the decoder to stop adding new samples. This might not take effect
// immediately, since the decoder might currently be stuck waiting for space
// to become available in the drain buffer.
sFileSource->SetPath();
auto mode = sOutput->mode();
if (mode == IAudioOutput::Modes::kOnPlaying) {
// If we're currently playing, then the drain buffer will be actively
// draining on its own. Just keep trying to reset until it works.
while (xStreamBufferReset(sDrainBuffer) != pdPASS) {
}
} else {
// If we're not currently playing, then we need to actively pull samples
// out of the drain buffer to unblock the decoder.
while (!xStreamBufferIsEmpty(sDrainBuffer)) {
// Read a little to unblock the decoder.
uint8_t drain[2048];
xStreamBufferReceive(sDrainBuffer, drain, sizeof(drain), 0);
// Try to quickly discard the rest.
xStreamBufferReset(sDrainBuffer);
}
}
}
auto AudioState::awaitEmptyDrainBuffer() -> void {
if (is_in_state<states::Playback>()) {
for (int i = 0; i < 10 && !xStreamBufferIsEmpty(sDrainBuffer); i++) {
vTaskDelay(pdMS_TO_TICKS(250));
}
}
if (!xStreamBufferIsEmpty(sDrainBuffer)) {
clearDrainBuffer();
}
}
auto AudioState::commitVolume() -> void { auto AudioState::commitVolume() -> void {
auto mode = sServices->nvs().OutputMode(); auto mode = sServices->nvs().OutputMode();
auto vol = sOutput->GetVolume(); auto vol = sOutput->GetVolume();
@ -427,8 +338,7 @@ void Uninitialised::react(const system_fsm::BootComplete& ev) {
sDrainBuffer = xStreamBufferCreateStatic( sDrainBuffer = xStreamBufferCreateStatic(
kDrainBufferSize, sizeof(sample::Sample), storage, meta); kDrainBufferSize, sizeof(sample::Sample), storage, meta);
sFileSource.reset( sStreamFactory.reset(new FatfsStreamFactory(*sServices));
new FatfsAudioInput(sServices->tag_parser(), sServices->bg_worker()));
sI2SOutput.reset(new I2SAudioOutput(sDrainBuffer, sServices->gpios())); sI2SOutput.reset(new I2SAudioOutput(sDrainBuffer, sServices->gpios()));
sBtOutput.reset(new BluetoothAudioOutput(sDrainBuffer, sServices->bluetooth(), sBtOutput.reset(new BluetoothAudioOutput(sDrainBuffer, sServices->bluetooth(),
sServices->bg_worker())); sServices->bg_worker()));
@ -462,10 +372,10 @@ void Uninitialised::react(const system_fsm::BootComplete& ev) {
.left_bias = nvs.AmpLeftBias(), .left_bias = nvs.AmpLeftBias(),
}); });
sSampleConverter.reset(new SampleConverter()); sSampleProcessor.reset(new SampleProcessor(sDrainBuffer));
sSampleConverter->SetOutput(sOutput); sSampleProcessor->SetOutput(sOutput);
Decoder::Start(sFileSource, sSampleConverter); sDecoder.reset(Decoder::Start(sSampleProcessor));
transit<Standby>(); transit<Standby>();
} }
@ -477,7 +387,8 @@ void Standby::react(const system_fsm::KeyLockChanged& ev) {
if (!ev.locking) { if (!ev.locking) {
return; return;
} }
sServices->bg_worker().Dispatch<void>([this]() { auto current = sStreamCues.current();
sServices->bg_worker().Dispatch<void>([=]() {
auto db = sServices->database().lock(); auto db = sServices->database().lock();
if (!db) { if (!db) {
return; return;
@ -490,17 +401,24 @@ void Standby::react(const system_fsm::KeyLockChanged& ev) {
} }
db->put(kQueueKey, queue.serialise()); db->put(kQueueKey, queue.serialise());
if (sCurrentTrack) { if (current.first && sDrainFormat) {
uint32_t seconds = (current.second / (sDrainFormat->num_channels *
sDrainFormat->sample_rate)) +
current.first->start_offset.value_or(0);
cppbor::Array current_track{ cppbor::Array current_track{
cppbor::Tstr{sCurrentTrack->uri}, cppbor::Tstr{current.first->uri},
cppbor::Uint{currentPositionSeconds().value_or(0)}, cppbor::Uint{seconds},
}; };
db->put(kCurrentFileKey, current_track.toString()); db->put(kCurrentFileKey, current_track.toString());
} }
}); });
} }
void Standby::react(const system_fsm::StorageMounted& ev) { void Standby::react(const system_fsm::SdStateChanged& ev) {
auto state = sServices->sd();
if (state != drivers::SdState::kMounted) {
return;
}
sServices->bg_worker().Dispatch<void>([]() { sServices->bg_worker().Dispatch<void>([]() {
auto db = sServices->database().lock(); auto db = sServices->database().lock();
if (!db) { if (!db) {
@ -524,7 +442,6 @@ void Standby::react(const system_fsm::StorageMounted& ev) {
events::Audio().Dispatch(SetTrack{ events::Audio().Dispatch(SetTrack{
.new_track = filename, .new_track = filename,
.seek_to_second = pos, .seek_to_second = pos,
.transition = SetTrack::Transition::kHardCut,
}); });
} }
} }
@ -540,32 +457,47 @@ void Standby::react(const system_fsm::StorageMounted& ev) {
}); });
} }
static TimerHandle_t sHeartbeatTimer;
static void heartbeat(TimerHandle_t) {
events::Audio().Dispatch(internal::StreamHeartbeat{});
}
void Playback::entry() { void Playback::entry() {
ESP_LOGI(kTag, "audio output resumed"); ESP_LOGI(kTag, "audio output resumed");
sOutput->mode(IAudioOutput::Modes::kOnPlaying); sOutput->mode(IAudioOutput::Modes::kOnPlaying);
emitPlaybackUpdate(false);
PlaybackUpdate event{ if (!sHeartbeatTimer) {
.current_track = sCurrentTrack, sHeartbeatTimer =
.track_position = currentPositionSeconds(), xTimerCreate("stream", pdMS_TO_TICKS(250), true, NULL, heartbeat);
.paused = false, }
}; xTimerStart(sHeartbeatTimer, portMAX_DELAY);
events::System().Dispatch(event);
events::Ui().Dispatch(event);
} }
void Playback::exit() { void Playback::exit() {
ESP_LOGI(kTag, "audio output paused"); ESP_LOGI(kTag, "audio output paused");
xTimerStop(sHeartbeatTimer, portMAX_DELAY);
sOutput->mode(IAudioOutput::Modes::kOnPaused); sOutput->mode(IAudioOutput::Modes::kOnPaused);
emitPlaybackUpdate(true);
}
PlaybackUpdate event{ void Playback::react(const system_fsm::SdStateChanged& ev) {
.current_track = sCurrentTrack, if (sServices->sd() != drivers::SdState::kMounted) {
.track_position = currentPositionSeconds(), transit<Standby>();
.paused = true, }
}; }
events::System().Dispatch(event); void Playback::react(const internal::StreamHeartbeat& ev) {
events::Ui().Dispatch(event); sStreamCues.update(sOutput->samplesUsed());
if (sStreamCues.hasStream()) {
emitPlaybackUpdate(false);
} else {
// Finished the current stream, and there's nothing upcoming. We must be
// finished.
transit<Standby>();
}
} }
} // namespace states } // namespace states

@ -11,24 +11,25 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "audio_sink.hpp" #include "audio/stream_cues.hpp"
#include "service_locator.hpp"
#include "tinyfsm.hpp" #include "tinyfsm.hpp"
#include "audio_decoder.hpp" #include "audio/audio_decoder.hpp"
#include "audio_events.hpp" #include "audio/audio_events.hpp"
#include "bt_audio_output.hpp" #include "audio/audio_sink.hpp"
#include "database.hpp" #include "audio/bt_audio_output.hpp"
#include "display.hpp" #include "audio/fatfs_stream_factory.hpp"
#include "fatfs_audio_input.hpp" #include "audio/i2s_audio_output.hpp"
#include "gpios.hpp" #include "audio/track_queue.hpp"
#include "i2s_audio_output.hpp" #include "database/database.hpp"
#include "i2s_dac.hpp" #include "database/tag_parser.hpp"
#include "storage.hpp" #include "database/track.hpp"
#include "system_events.hpp" #include "drivers/display.hpp"
#include "tag_parser.hpp" #include "drivers/gpios.hpp"
#include "track.hpp" #include "drivers/i2s_dac.hpp"
#include "track_queue.hpp" #include "drivers/storage.hpp"
#include "system_fsm/service_locator.hpp"
#include "system_fsm/system_events.hpp"
namespace audio { namespace audio {
@ -46,13 +47,13 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
void react(const SetTrack&); void react(const SetTrack&);
void react(const TogglePlayPause&); void react(const TogglePlayPause&);
void react(const internal::DecodingFinished&);
void react(const internal::StreamStarted&); void react(const internal::StreamStarted&);
void react(const internal::StreamUpdate&);
void react(const internal::StreamEnded&); void react(const internal::StreamEnded&);
virtual void react(const internal::StreamHeartbeat&) {}
void react(const StepUpVolume&); void react(const StepUpVolume&);
void react(const StepDownVolume&); void react(const StepDownVolume&);
virtual void react(const system_fsm::HasPhonesChanged&);
void react(const SetVolume&); void react(const SetVolume&);
void react(const SetVolumeLimit&); void react(const SetVolumeLimit&);
@ -62,40 +63,28 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
virtual void react(const system_fsm::BootComplete&) {} virtual void react(const system_fsm::BootComplete&) {}
virtual void react(const system_fsm::KeyLockChanged&){}; virtual void react(const system_fsm::KeyLockChanged&){};
virtual void react(const system_fsm::StorageMounted&) {} virtual void react(const system_fsm::SdStateChanged&) {}
virtual void react(const system_fsm::BluetoothEvent&); virtual void react(const system_fsm::BluetoothEvent&);
protected: protected:
auto clearDrainBuffer() -> void; auto emitPlaybackUpdate(bool paused) -> void;
auto awaitEmptyDrainBuffer() -> void;
auto playTrack(database::TrackId id) -> void;
auto commitVolume() -> void; auto commitVolume() -> void;
static std::shared_ptr<system_fsm::ServiceLocator> sServices; static std::shared_ptr<system_fsm::ServiceLocator> sServices;
static std::shared_ptr<FatfsAudioInput> sFileSource; static std::shared_ptr<FatfsStreamFactory> sStreamFactory;
static std::unique_ptr<Decoder> sDecoder; static std::unique_ptr<Decoder> sDecoder;
static std::shared_ptr<SampleConverter> sSampleConverter; static std::shared_ptr<SampleProcessor> sSampleProcessor;
static std::shared_ptr<I2SAudioOutput> sI2SOutput; static std::shared_ptr<I2SAudioOutput> sI2SOutput;
static std::shared_ptr<BluetoothAudioOutput> sBtOutput; static std::shared_ptr<BluetoothAudioOutput> sBtOutput;
static std::shared_ptr<IAudioOutput> sOutput; static std::shared_ptr<IAudioOutput> sOutput;
static StreamBufferHandle_t sDrainBuffer; static StreamBufferHandle_t sDrainBuffer;
static std::shared_ptr<TrackInfo> sCurrentTrack; static StreamCues sStreamCues;
static uint64_t sCurrentSamples;
static std::optional<IAudioOutput::Format> sDrainFormat; static std::optional<IAudioOutput::Format> sDrainFormat;
static bool sCurrentTrackIsFromQueue;
static std::shared_ptr<TrackInfo> sNextTrack;
static uint64_t sNextTrackCueSamples;
static bool sNextTrackIsFromQueue;
static bool sIsResampling;
static bool sIsPaused; static bool sIsPaused;
auto currentPositionSeconds() -> std::optional<uint32_t>;
}; };
namespace states { namespace states {
@ -111,7 +100,7 @@ class Uninitialised : public AudioState {
class Standby : public AudioState { class Standby : public AudioState {
public: public:
void react(const system_fsm::KeyLockChanged&) override; void react(const system_fsm::KeyLockChanged&) override;
void react(const system_fsm::StorageMounted&) override; void react(const system_fsm::SdStateChanged&) override;
using AudioState::react; using AudioState::react;
}; };
@ -121,6 +110,9 @@ class Playback : public AudioState {
void entry() override; void entry() override;
void exit() override; void exit() override;
void react(const system_fsm::SdStateChanged&) override;
void react(const internal::StreamHeartbeat&) override;
using AudioState::react; using AudioState::react;
}; };

@ -75,6 +75,7 @@ class IAudioOutput {
virtual auto PrepareFormat(const Format&) -> Format = 0; virtual auto PrepareFormat(const Format&) -> Format = 0;
virtual auto Configure(const Format& format) -> void = 0; virtual auto Configure(const Format& format) -> void = 0;
virtual auto samplesUsed() -> uint32_t = 0;
auto stream() -> StreamBufferHandle_t { return stream_; } auto stream() -> StreamBufferHandle_t { return stream_; }

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "audio_source.hpp" #include "audio/audio_source.hpp"
#include "codec.hpp" #include "codec.hpp"
#include "types.hpp" #include "types.hpp"
@ -14,13 +14,17 @@ TaggedStream::TaggedStream(std::shared_ptr<database::TrackTags> t,
std::unique_ptr<codecs::IStream> w, std::unique_ptr<codecs::IStream> w,
std::string filepath, std::string filepath,
uint32_t offset) uint32_t offset)
: codecs::IStream(w->type()), tags_(t), wrapped_(std::move(w)), filepath_(filepath), offset_(offset) {} : codecs::IStream(w->type()),
tags_(t),
wrapped_(std::move(w)),
filepath_(filepath),
offset_(offset) {}
auto TaggedStream::tags() -> std::shared_ptr<database::TrackTags> { auto TaggedStream::tags() -> std::shared_ptr<database::TrackTags> {
return tags_; return tags_;
} }
auto TaggedStream::Read(cpp::span<std::byte> dest) -> ssize_t { auto TaggedStream::Read(std::span<std::byte> dest) -> ssize_t {
return wrapped_->Read(dest); return wrapped_->Read(dest);
} }

@ -8,7 +8,7 @@
#include <memory> #include <memory>
#include "codec.hpp" #include "codec.hpp"
#include "track.hpp" #include "database/track.hpp"
#include "types.hpp" #include "types.hpp"
namespace audio { namespace audio {
@ -18,12 +18,11 @@ class TaggedStream : public codecs::IStream {
TaggedStream(std::shared_ptr<database::TrackTags>, TaggedStream(std::shared_ptr<database::TrackTags>,
std::unique_ptr<codecs::IStream> wrapped, std::unique_ptr<codecs::IStream> wrapped,
std::string path, std::string path,
uint32_t offset = 0 uint32_t offset = 0);
);
auto tags() -> std::shared_ptr<database::TrackTags>; auto tags() -> std::shared_ptr<database::TrackTags>;
auto Read(cpp::span<std::byte> dest) -> ssize_t override; auto Read(std::span<std::byte> dest) -> ssize_t override;
auto CanSeek() -> bool override; auto CanSeek() -> bool override;

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*/ */
#include "bt_audio_output.hpp" #include "audio/bt_audio_output.hpp"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
@ -18,12 +18,12 @@
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
#include "freertos/projdefs.h" #include "freertos/projdefs.h"
#include "gpios.hpp" #include "drivers/gpios.hpp"
#include "i2c.hpp" #include "drivers/i2c.hpp"
#include "i2s_dac.hpp" #include "drivers/i2s_dac.hpp"
#include "drivers/wm8523.hpp"
#include "result.hpp" #include "result.hpp"
#include "tasks.hpp" #include "tasks.hpp"
#include "wm8523.hpp"
[[maybe_unused]] static const char* kTag = "BTOUT"; [[maybe_unused]] static const char* kTag = "BTOUT";
@ -121,4 +121,8 @@ auto BluetoothAudioOutput::Configure(const Format& fmt) -> void {
// No configuration necessary; the output format is fixed. // No configuration necessary; the output format is fixed.
} }
auto BluetoothAudioOutput::samplesUsed() -> uint32_t {
return bluetooth_.SamplesUsed();
}
} // namespace audio } // namespace audio

@ -13,10 +13,10 @@
#include "result.hpp" #include "result.hpp"
#include "audio_sink.hpp" #include "audio/audio_sink.hpp"
#include "bluetooth.hpp" #include "drivers/bluetooth.hpp"
#include "gpios.hpp" #include "drivers/gpios.hpp"
#include "i2s_dac.hpp" #include "drivers/i2s_dac.hpp"
#include "tasks.hpp" #include "tasks.hpp"
namespace audio { namespace audio {
@ -45,6 +45,8 @@ class BluetoothAudioOutput : public IAudioOutput {
auto PrepareFormat(const Format&) -> Format override; auto PrepareFormat(const Format&) -> Format override;
auto Configure(const Format& format) -> void override; auto Configure(const Format& format) -> void override;
auto samplesUsed() -> uint32_t override;
BluetoothAudioOutput(const BluetoothAudioOutput&) = delete; BluetoothAudioOutput(const BluetoothAudioOutput&) = delete;
BluetoothAudioOutput& operator=(const BluetoothAudioOutput&) = delete; BluetoothAudioOutput& operator=(const BluetoothAudioOutput&) = delete;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save