Merge branch 'main' into seek-support

custom
jacqueline 1 year ago
commit 7d3ddac0ea
  1. 4
      ldoc-stubs/alerts.lua
  2. 6
      lib/libtags/examples/readtags.c
  3. 3
      lib/libtags/id3v1.c
  4. 4
      lib/libtags/id3v2.c
  5. 6
      lib/libtags/m4a.c
  6. 2
      lib/libtags/opus.c
  7. 4
      lib/libtags/tags.c
  8. 2
      lib/libtags/tags.h
  9. 51
      lib/libtags/vorbis.c
  10. 22
      lib/libtags/wav.c
  11. 1
      sdkconfig.common
  12. 8
      src/app_console/app_console.cpp
  13. 28
      src/audio/audio_fsm.cpp
  14. 57
      src/audio/track_queue.cpp
  15. 7
      src/database/database.cpp
  16. 10
      src/database/file_gatherer.cpp
  17. 4
      src/database/include/file_gatherer.hpp
  18. 6
      src/database/include/tag_parser.hpp
  19. 8
      src/database/tag_parser.cpp
  20. 2
      src/lua/CMakeLists.txt
  21. 74
      src/lua/bridge.cpp
  22. 27
      src/lua/include/bridge.hpp
  23. 51
      src/lua/include/lua_registry.hpp
  24. 12
      src/lua/include/lua_thread.hpp
  25. 4
      src/lua/include/property.hpp
  26. 3
      src/lua/lua_queue.cpp
  27. 51
      src/lua/lua_thread.cpp
  28. 78
      src/lua/property.cpp
  29. 73
      src/lua/registry.cpp
  30. 2
      src/ui/include/ui_fsm.hpp
  31. 144
      src/ui/ui_fsm.cpp
  32. 2
      tools/cmake/common.cmake

@ -1,9 +1,9 @@
--- Module for interacting with playback volume. The Bluetooth and wired outputs store their current volume separately; this API only allows interacting with the volume of the currently used output device. --- Module for showing transient popups over the current screen.
-- @module alerts -- @module alerts
local alerts = {} local alerts = {}
--- Returns the current volume as a percentage of the current volume limit. --- Shows a new alert, replacing any already visible alerts.
-- @tparam function constructor Called to create the UI for the alert. A new default root object and group will be set before calling this function.i Alerts are non-interactable; the group created for the constructor will not be granted focus. -- @tparam function constructor Called to create the UI for the alert. A new default root object and group will be set before calling this function.i Alerts are non-interactable; the group created for the constructor will not be granted focus.
function alerts.show(constructor) end function alerts.show(constructor) end

@ -35,6 +35,8 @@ static const char *t2s[] =
[Ttrackpeak] = "trackpeak", [Ttrackpeak] = "trackpeak",
[Tgenre] = "genre", [Tgenre] = "genre",
[Timage] = "image", [Timage] = "image",
[Tcomposer] = "composer",
[Tcomment] = "comment",
}; };
static void static void
@ -43,7 +45,9 @@ tag(Tagctx *ctx, int t, const char *k, const char *v, int offset, int size, Tagr
USED(ctx); USED(k); USED(f); USED(ctx); USED(k); USED(f);
if(t == Timage) if(t == Timage)
print("%-12s %s %d %d\n", t2s[t], v, offset, size); print("%-12s %s %d %d\n", t2s[t], v, offset, size);
else if(t != Tunknown) else if(t == Tunknown)
print("%-12s %s\n", k, v);
else
print("%-12s %s\n", t2s[t], v); print("%-12s %s\n", t2s[t], v);
} }

@ -36,6 +36,9 @@ tagid3v1(Tagctx *ctx)
if((ctx->found & 1<<Tdate) == 0 && in[93] != 0) if((ctx->found & 1<<Tdate) == 0 && in[93] != 0)
txtcb(ctx, Tdate, "", &in[93]); txtcb(ctx, Tdate, "", &in[93]);
if((ctx->found & 1<<Tcomment) == 0 && in[97] != 0)
txtcb(ctx, Tcomment, "", &in[97]);
if((ctx->found & 1<<Ttrack) == 0 && in[125] == 0 && in[126] > 0){ if((ctx->found & 1<<Ttrack) == 0 && in[125] == 0 && in[126] > 0){
snprint((char*)out, Outsz, "%d", in[126]); snprint((char*)out, Outsz, "%d", in[126]);
txtcb(ctx, Ttrack, "", out); txtcb(ctx, Ttrack, "", out);

@ -31,6 +31,8 @@ v2cb(Tagctx *ctx, char *k, char *v)
txtcb(ctx, Ttrack, k-1, v); txtcb(ctx, Ttrack, k-1, v);
else if(strcmp(k, "LEN") == 0) else if(strcmp(k, "LEN") == 0)
ctx->duration = atoi(v); ctx->duration = atoi(v);
else if(strcmp(k, "CM") == 0 || strcmp(k, "COM") == 0)
txtcb(ctx, Tcomposer, k-1, v);
else if(strcmp(k, "CO") == 0 || strcmp(k, "CON") == 0){ else if(strcmp(k, "CO") == 0 || strcmp(k, "CON") == 0){
for(; v[0]; v++){ for(; v[0]; v++){
if(v[0] == '(' && v[1] <= '9' && v[1] >= '0'){ if(v[0] == '(' && v[1] <= '9' && v[1] >= '0'){
@ -64,6 +66,8 @@ v2cb(Tagctx *ctx, char *k, char *v)
txtcb(ctx, type, k-1, v+5); txtcb(ctx, type, k-1, v+5);
else else
return 0; return 0;
}else if(strcmp(k-1, "COM") == 0 || strcmp(k-1, "COMM") == 0){
txtcb(ctx, Tcomment, k-1, v);
}else{ }else{
txtcb(ctx, Tunknown, k-1, v); txtcb(ctx, Tunknown, k-1, v);
} }

@ -93,6 +93,10 @@ tagm4a(Tagctx *ctx)
type = Timage; type = Timage;
else if(memcmp(d, "trkn", 4) == 0) else if(memcmp(d, "trkn", 4) == 0)
type = Ttrack; type = Ttrack;
else if(memcmp(d, "\251wrt", 4) == 0)
type = Tcomposer;
else if(memcmp(d, "\251cmt", 4) == 0)
type = Tcomment;
else if(memcmp(d, "mdhd", 4) == 0){ else if(memcmp(d, "mdhd", 4) == 0){
if(ctx->read(ctx, d, 4) != 4) if(ctx->read(ctx, d, 4) != 4)
return -1; return -1;
@ -133,7 +137,7 @@ tagm4a(Tagctx *ctx)
sz -= 4; sz -= 4;
snprint((char*)d, ctx->bufsz, "%d", beuint(d)); snprint((char*)d, ctx->bufsz, "%d", beuint(d));
txtcb(ctx, type, "", d); txtcb(ctx, type, "", d);
}else if(type == Tgenre){ }else if(type == Tgenre && dtype != 1){
if(ctx->read(ctx, d, 2) != 2) if(ctx->read(ctx, d, 2) != 2)
return -1; return -1;
sz -= 2; sz -= 2;

@ -63,7 +63,7 @@ tagopus(Tagctx *ctx)
ctx->buf[sz] = 0; ctx->buf[sz] = 0;
if((v = strchr(ctx->buf, '=')) == nil) if((v = strchr(ctx->buf, '=')) == nil)
continue; return -1;
*v++ = 0; *v++ = 0;
cbvorbiscomment(ctx, ctx->buf, v); cbvorbiscomment(ctx, ctx->buf, v);
} }

@ -46,8 +46,8 @@ tagscallcb(Tagctx *ctx, int type, const char *k, char *s, int offset, int size,
e = s + strlen(s); e = s + strlen(s);
while(e != s && (uchar)e[-1] <= ' ') while(e != s && (uchar)e[-1] <= ' ')
e--; e--;
if (*e != 0) if(*e != 0)
*e = 0; *e = 0;
} }
if(*s){ if(*s){
ctx->tag(ctx, type, k, s, offset, size, f); ctx->tag(ctx, type, k, s, offset, size, f);

@ -21,6 +21,8 @@ enum
Ttrackpeak, Ttrackpeak,
Tgenre, Tgenre,
Timage, Timage,
Tcomposer,
Tcomment,
}; };
/* Format of the audio file. */ /* Format of the audio file. */

@ -4,33 +4,38 @@
*/ */
#include "tagspriv.h" #include "tagspriv.h"
static const struct {
char *s;
int type;
}t[] = {
{"album", Talbum},
{"title", Ttitle},
{"artist", Tartist},
{"albumartist", Talbumartist},
{"tracknumber", Ttrack},
{"date", Tdate},
{"replaygain_track_peak", Ttrackpeak},
{"replaygain_track_gain", Ttrackgain},
{"replaygain_album_peak", Talbumpeak},
{"replaygain_album_gain", Talbumgain},
{"genre", Tgenre},
{"composer", Tcomposer},
{"comment", Tcomment},
};
void void
cbvorbiscomment(Tagctx *ctx, char *k, char *v){ cbvorbiscomment(Tagctx *ctx, char *k, char *v){
int i;
if(*v == 0) if(*v == 0)
return; return;
if(cistrcmp(k, "album") == 0) for(i = 0; i < nelem(t); i++){
txtcb(ctx, Talbum, k, v); if(cistrcmp(k, t[i].s) == 0){
else if(cistrcmp(k, "title") == 0) txtcb(ctx, t[i].type, k, v);
txtcb(ctx, Ttitle, k, v); break;
else if(cistrcmp(k, "artist") == 0) }
txtcb(ctx, Tartist, k, v); }
else if(cistrcmp(k, "albumartist") == 0) if(i == nelem(t))
txtcb(ctx, Talbumartist, k, v);
else if(cistrcmp(k, "tracknumber") == 0)
txtcb(ctx, Ttrack, k, v);
else if(cistrcmp(k, "date") == 0)
txtcb(ctx, Tdate, k, v);
else if(cistrcmp(k, "replaygain_track_peak") == 0)
txtcb(ctx, Ttrackpeak, k, v);
else if(cistrcmp(k, "replaygain_track_gain") == 0)
txtcb(ctx, Ttrackgain, k, v);
else if(cistrcmp(k, "replaygain_album_peak") == 0)
txtcb(ctx, Talbumpeak, k, v);
else if(cistrcmp(k, "replaygain_album_gain") == 0)
txtcb(ctx, Talbumgain, k, v);
else if(cistrcmp(k, "genre") == 0)
txtcb(ctx, Tgenre, k, v);
else
txtcb(ctx, Tunknown, k, v); txtcb(ctx, Tunknown, k, v);
} }

@ -2,7 +2,7 @@
#define le16u(d) (u16int)((d)[0] | (d)[1]<<8) #define le16u(d) (u16int)((d)[0] | (d)[1]<<8)
static struct { static const struct {
char *s; char *s;
int type; int type;
}t[] = { }t[] = {
@ -12,6 +12,8 @@ static struct {
{"INAM", Ttitle}, {"INAM", Ttitle},
{"IPRD", Talbum}, {"IPRD", Talbum},
{"ITRK", Ttrack}, {"ITRK", Ttrack},
{"ICMT", Tcomment},
{"????", Tunknown},
}; };
int int
@ -26,7 +28,7 @@ tagwav(Tagctx *ctx)
sz = 1; sz = 1;
info = 0; info = 0;
for(i = 0; i < 8 && sz > 0; i++){ for(i = 0; sz > 0; i++){
if(ctx->read(ctx, d, 4+4+(i?0:4)) != 4+4+(i?0:4)) if(ctx->read(ctx, d, 4+4+(i?0:4)) != 4+4+(i?0:4))
return -1; return -1;
if(i == 0){ if(i == 0){
@ -66,20 +68,20 @@ tagwav(Tagctx *ctx)
}else if(memcmp(d, "LIST", 4) == 0){ }else if(memcmp(d, "LIST", 4) == 0){
sz = csz - 4; sz = csz - 4;
continue; continue;
}else if(memcmp(d, "data", 4) == 0){ }else if(info && csz < (u32int)ctx->bufsz){
break;
}else if(info){
csz++;
for(n = 0; n < nelem(t); n++){ for(n = 0; n < nelem(t); n++){
if(memcmp(d, t[n].s, 4) == 0){ if(memcmp(d, t[n].s, 4) == 0 || t[n].type == Tunknown){
if(ctx->read(ctx, d, csz) != (int)csz) if(ctx->read(ctx, d+5, csz) != (int)csz)
return -1; return -1;
d[csz-1] = 0; d[4] = 0;
txtcb(ctx, t[n].type, "", d); d[5+csz] = 0;
txtcb(ctx, t[n].type, t[n].type == Tunknown ? (char*)d : "", d+5);
csz = 0; csz = 0;
break; break;
} }
} }
if(n < nelem(t))
continue;
} }
if(ctx->seek(ctx, csz, 1) < 0) if(ctx->seek(ctx, csz, 1) < 0)

@ -22,7 +22,6 @@ CONFIG_SPI_MASTER_IN_IRAM=y
# CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT is not set # CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT is not set
# CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM is not set # CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM is not set
CONFIG_GPIO_CTRL_FUNC_IN_IRAM=y CONFIG_GPIO_CTRL_FUNC_IN_IRAM=y
CONFIG_I2S_ISR_IRAM_SAFE=y
# CONFIG_ETH_USE_ESP32_EMAC is not set # CONFIG_ETH_USE_ESP32_EMAC is not set
# CONFIG_ETH_USE_SPI_ETHERNET is not set # CONFIG_ETH_USE_SPI_ETHERNET is not set
# CONFIG_ESP_EVENT_POST_FROM_ISR is not set # CONFIG_ESP_EVENT_POST_FROM_ISR is not set

@ -40,7 +40,7 @@
#include "freertos/projdefs.h" #include "freertos/projdefs.h"
#include "haptics.hpp" #include "haptics.hpp"
#include "index.hpp" #include "index.hpp"
#include "lua_thread.hpp" #include "lua_registry.hpp"
#include "memory_resource.hpp" #include "memory_resource.hpp"
#include "samd.hpp" #include "samd.hpp"
#include "service_locator.hpp" #include "service_locator.hpp"
@ -628,8 +628,7 @@ static const char kReplMain[] =
"repl:run()\n"; "repl:run()\n";
int CmdLua(int argc, char** argv) { int CmdLua(int argc, char** argv) {
std::unique_ptr<lua::LuaThread> context{ auto context = lua::Registry::instance(*AppConsole::sServices).newThread();
lua::LuaThread::Start(*AppConsole::sServices)};
if (!context) { if (!context) {
return 1; return 1;
} }
@ -652,8 +651,7 @@ int CmdLua(int argc, char** argv) {
} }
int CmdLuaRun(int argc, char** argv) { int CmdLuaRun(int argc, char** argv) {
std::unique_ptr<lua::LuaThread> context{ auto context = lua::Registry::instance(*AppConsole::sServices).newThread();
lua::LuaThread::Start(*AppConsole::sServices)};
if (!context) { if (!context) {
return 1; return 1;
} }

@ -13,7 +13,9 @@
#include "audio_sink.hpp" #include "audio_sink.hpp"
#include "bluetooth_types.hpp" #include "bluetooth_types.hpp"
#include "esp_heap_caps.h"
#include "esp_log.h" #include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h" #include "freertos/portmacro.h"
#include "freertos/projdefs.h" #include "freertos/projdefs.h"
@ -192,20 +194,28 @@ void AudioState::react(const TogglePlayPause& ev) {
namespace states { namespace states {
// Two seconds of samples for two channels, at a representative sample rate.
constexpr size_t kDrainBufferSize = sizeof(sample::Sample) * 48000 * 4;
static StreamBufferHandle_t sDrainBuffer;
void Uninitialised::react(const system_fsm::BootComplete& ev) { void Uninitialised::react(const system_fsm::BootComplete& ev) {
sServices = ev.services; sServices = ev.services;
constexpr size_t kDrainBufferSize =
drivers::kI2SBufferLengthFrames * sizeof(sample::Sample) * 2 * 8;
ESP_LOGI(kTag, "allocating drain buffer, size %u KiB", ESP_LOGI(kTag, "allocating drain buffer, size %u KiB",
kDrainBufferSize / 1024); kDrainBufferSize / 1024);
StreamBufferHandle_t stream = xStreamBufferCreateWithCaps(
kDrainBufferSize, sizeof(sample::Sample), MALLOC_CAP_DMA); auto meta = reinterpret_cast<StaticStreamBuffer_t*>(
heap_caps_malloc(sizeof(StaticStreamBuffer_t), MALLOC_CAP_DMA));
auto storage = reinterpret_cast<uint8_t*>(
heap_caps_malloc(kDrainBufferSize, MALLOC_CAP_SPIRAM));
sDrainBuffer = xStreamBufferCreateStatic(
kDrainBufferSize, sizeof(sample::Sample), storage, meta);
sFileSource.reset( sFileSource.reset(
new FatfsAudioInput(sServices->tag_parser(), sServices->bg_worker())); new FatfsAudioInput(sServices->tag_parser(), sServices->bg_worker()));
sI2SOutput.reset(new I2SAudioOutput(stream, sServices->gpios())); sI2SOutput.reset(new I2SAudioOutput(sDrainBuffer, sServices->gpios()));
sBtOutput.reset(new BluetoothAudioOutput(stream, sServices->bluetooth(), sBtOutput.reset(new BluetoothAudioOutput(sDrainBuffer, sServices->bluetooth(),
sServices->bg_worker())); sServices->bg_worker()));
auto& nvs = sServices->nvs(); auto& nvs = sServices->nvs();
@ -366,6 +376,12 @@ void Playback::react(const internal::InputFileFinished& ev) {
ESP_LOGI(kTag, "finished playing file"); ESP_LOGI(kTag, "finished playing file");
sServices->track_queue().finish(); sServices->track_queue().finish();
if (!sServices->track_queue().current()) { if (!sServices->track_queue().current()) {
for (int i = 0; i < 20; i++) {
if (xStreamBufferIsEmpty(sDrainBuffer)) {
break;
}
vTaskDelay(pdMS_TO_TICKS(200));
}
transit<Standby>(); transit<Standby>();
} }
} }

@ -200,39 +200,52 @@ auto TrackQueue::append(Item i) -> void {
} }
auto TrackQueue::next() -> void { auto TrackQueue::next() -> void {
const std::unique_lock<std::shared_mutex> lock(mutex_); bool changed = true;
if (shuffle_) {
shuffle_->next(); {
pos_ = shuffle_->current(); const std::unique_lock<std::shared_mutex> lock(mutex_);
} else { if (shuffle_) {
if (pos_ + 1 >= tracks_.size()) { shuffle_->next();
if (replay_) { pos_ = shuffle_->current();
pos_ = 0;
}
} else { } else {
pos_++; if (pos_ + 1 >= tracks_.size()) {
if (replay_) {
pos_ = 0;
} else {
pos_ = tracks_.size();
changed = false;
}
} else {
pos_++;
}
} }
} }
notifyChanged(true); notifyChanged(changed);
} }
auto TrackQueue::previous() -> void { auto TrackQueue::previous() -> void {
const std::unique_lock<std::shared_mutex> lock(mutex_); bool changed = true;
if (shuffle_) {
shuffle_->prev(); {
pos_ = shuffle_->current(); const std::unique_lock<std::shared_mutex> lock(mutex_);
} else { if (shuffle_) {
if (pos_ == 0) { shuffle_->prev();
if (repeat_) { pos_ = shuffle_->current();
pos_ = tracks_.size() - 1;
}
} else { } else {
pos_--; if (pos_ == 0) {
if (repeat_) {
pos_ = tracks_.size() - 1;
} else {
changed = false;
}
} else {
pos_--;
}
} }
} }
notifyChanged(true); notifyChanged(changed);
} }
auto TrackQueue::finish() -> void { auto TrackQueue::finish() -> void {

@ -383,8 +383,7 @@ auto Database::updateIndexes() -> void {
ESP_LOGI(kTag, "scanning for new tracks"); ESP_LOGI(kTag, "scanning for new tracks");
uint64_t num_processed = 0; uint64_t num_processed = 0;
std::pair<uint16_t, uint16_t> newest_track = last_update; std::pair<uint16_t, uint16_t> newest_track = last_update;
file_gatherer_.FindFiles("", [&](const std::string& path, file_gatherer_.FindFiles("", [&](std::string_view path, const FILINFO& info) {
const FILINFO& info) {
num_processed++; num_processed++;
events::Ui().Dispatch(event::UpdateProgress{ events::Ui().Dispatch(event::UpdateProgress{
.stage = event::UpdateProgress::Stage::kScanningForNewTracks, .stage = event::UpdateProgress::Stage::kScanningForNewTracks,
@ -456,9 +455,7 @@ auto Database::updateIndexes() -> void {
dbCreateIndexesForTrack(*t); dbCreateIndexesForTrack(*t);
} else if (existing_data->filepath != } else if (existing_data->filepath !=
std::pmr::string{path.data(), path.size()}) { std::pmr::string{path.data(), path.size()}) {
ESP_LOGW(kTag, "tag hash collision for %s and %s", ESP_LOGW(kTag, "hash collision: %s, %s, %s",
existing_data->filepath.c_str(), path.c_str());
ESP_LOGI(kTag, "hash components: %s, %s, %s",
tags->title().value_or("no title").c_str(), tags->title().value_or("no title").c_str(),
tags->artist().value_or("no artist").c_str(), tags->artist().value_or("no artist").c_str(),
tags->album().value_or("no album").c_str()); tags->album().value_or("no album").c_str());

@ -22,12 +22,12 @@ static_assert(sizeof(TCHAR) == sizeof(char), "TCHAR must be CHAR");
auto FileGathererImpl::FindFiles( auto FileGathererImpl::FindFiles(
const std::string& root, const std::string& root,
std::function<void(const std::string&, const FILINFO&)> cb) -> void { std::function<void(std::string_view, const FILINFO&)> cb) -> void {
std::deque<std::string> to_explore; std::pmr::deque<std::pmr::string> to_explore{&memory::kSpiRamResource};
to_explore.push_back(root); to_explore.push_back({root.data(), root.size()});
while (!to_explore.empty()) { while (!to_explore.empty()) {
std::string next_path_str = to_explore.front(); auto next_path_str = to_explore.front();
to_explore.pop_front(); to_explore.pop_front();
const TCHAR* next_path = static_cast<const TCHAR*>(next_path_str.c_str()); const TCHAR* next_path = static_cast<const TCHAR*>(next_path_str.c_str());
@ -56,7 +56,7 @@ auto FileGathererImpl::FindFiles(
// System or hidden file. Ignore it and move on. // System or hidden file. Ignore it and move on.
continue; continue;
} else { } else {
std::string full_path; std::pmr::string full_path{&memory::kSpiRamResource};
full_path += next_path_str; full_path += next_path_str;
full_path += "/"; full_path += "/";
full_path += info.fname; full_path += info.fname;

@ -21,7 +21,7 @@ class IFileGatherer {
virtual auto FindFiles( virtual auto FindFiles(
const std::string& root, const std::string& root,
std::function<void(const std::string&, const FILINFO&)> cb) std::function<void(std::string_view, const FILINFO&)> cb)
-> void = 0; -> void = 0;
}; };
@ -29,7 +29,7 @@ class FileGathererImpl : public IFileGatherer {
public: public:
virtual auto FindFiles( virtual auto FindFiles(
const std::string& root, const std::string& root,
std::function<void(const std::string&, const FILINFO&)> cb) std::function<void(std::string_view, const FILINFO&)> cb)
-> void override; -> void override;
}; };

@ -16,18 +16,18 @@ namespace database {
class ITagParser { class ITagParser {
public: public:
virtual ~ITagParser() {} virtual ~ITagParser() {}
virtual auto ReadAndParseTags(const std::string& path) virtual auto ReadAndParseTags(std::string_view path)
-> std::shared_ptr<TrackTags> = 0; -> std::shared_ptr<TrackTags> = 0;
}; };
class TagParserImpl : public ITagParser { class TagParserImpl : public ITagParser {
public: public:
TagParserImpl(); TagParserImpl();
auto ReadAndParseTags(const std::string& path) auto ReadAndParseTags(std::string_view path)
-> std::shared_ptr<TrackTags> override; -> std::shared_ptr<TrackTags> override;
private: private:
auto parseNew(const std::string& path) -> std::shared_ptr<TrackTags>; auto parseNew(std::string_view path) -> std::shared_ptr<TrackTags>;
/* /*
* Cache of tags that have already been extracted from files. Ideally this * Cache of tags that have already been extracted from files. Ideally this

@ -108,7 +108,7 @@ static const std::size_t kBufSize = 1024;
TagParserImpl::TagParserImpl() {} TagParserImpl::TagParserImpl() {}
auto TagParserImpl::ReadAndParseTags(const std::string& path) auto TagParserImpl::ReadAndParseTags(std::string_view path)
-> std::shared_ptr<TrackTags> { -> std::shared_ptr<TrackTags> {
{ {
std::lock_guard<std::mutex> lock{cache_mutex_}; std::lock_guard<std::mutex> lock{cache_mutex_};
@ -130,7 +130,7 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path)
if (!tags->track()) { if (!tags->track()) {
auto slash_pos = path.find_last_of("/"); auto slash_pos = path.find_last_of("/");
if (slash_pos != std::string::npos && path.size() - slash_pos > 1) { if (slash_pos != std::string::npos && path.size() - slash_pos > 1) {
std::string trunc = path.substr(slash_pos + 1); auto trunc = path.substr(slash_pos + 1);
tags->track({trunc.data(), trunc.size()}); tags->track({trunc.data(), trunc.size()});
} }
} }
@ -143,8 +143,8 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path)
return tags; return tags;
} }
auto TagParserImpl::parseNew(const std::string& path) auto TagParserImpl::parseNew(std::string_view p) -> std::shared_ptr<TrackTags> {
-> std::shared_ptr<TrackTags> { std::string path{p};
libtags::Aux aux; libtags::Aux aux;
auto out = TrackTags::create(); auto out = TrackTags::create();
aux.tags = out.get(); aux.tags = out.get();

@ -4,7 +4,7 @@
idf_component_register( idf_component_register(
SRCS "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp" SRCS "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp"
"lua_queue.cpp" "lua_version.cpp" "lua_controls.cpp" "lua_queue.cpp" "lua_version.cpp" "lua_controls.cpp" "registry.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database"
"esp_timer" "battery" "esp-idf-lua" "luavgl" "lua-linenoise" "lua-term" "esp_timer" "battery" "esp-idf-lua" "luavgl" "lua-linenoise" "lua-term"

@ -22,6 +22,9 @@
#include "lua_version.hpp" #include "lua_version.hpp"
#include "lvgl.h" #include "lvgl.h"
#include "font/lv_font_loader.h"
#include "luavgl.h"
#include "event_queue.hpp" #include "event_queue.hpp"
#include "property.hpp" #include "property.hpp"
#include "service_locator.hpp" #include "service_locator.hpp"
@ -32,34 +35,62 @@ int luaopen_linenoise(lua_State* L);
int luaopen_term_core(lua_State* L); int luaopen_term_core(lua_State* L);
} }
LV_FONT_DECLARE(font_fusion_12);
LV_FONT_DECLARE(font_fusion_10);
namespace lua { namespace lua {
[[maybe_unused]] static constexpr char kTag[] = "lua_bridge"; [[maybe_unused]] static constexpr char kTag[] = "lua_bridge";
static constexpr char kBridgeKey[] = "bridge"; static constexpr char kBridgeKey[] = "bridge";
static auto make_font_cb(const char* name, int size, int weight)
-> const lv_font_t* {
if (std::string{"fusion"} == name) {
if (size == 12) {
return &font_fusion_12;
}
if (size == 10) {
return &font_fusion_10;
}
}
return NULL;
}
static auto delete_font_cb(lv_font_t* font) -> void {}
auto Bridge::Get(lua_State* state) -> Bridge* { auto Bridge::Get(lua_State* state) -> Bridge* {
lua_pushstring(state, kBridgeKey); lua_pushstring(state, kBridgeKey);
lua_gettable(state, LUA_REGISTRYINDEX); lua_gettable(state, LUA_REGISTRYINDEX);
return reinterpret_cast<Bridge*>(lua_touserdata(state, -1)); return reinterpret_cast<Bridge*>(lua_touserdata(state, -1));
} }
Bridge::Bridge(system_fsm::ServiceLocator& services, lua_State& s) Bridge::Bridge(system_fsm::ServiceLocator& services) : services_(services) {}
: services_(services), state_(s), bindings_(s) {
lua_pushstring(&s, kBridgeKey); auto Bridge::installBaseModules(lua_State* L) -> void {
lua_pushlightuserdata(&s, this); lua_pushstring(L, kBridgeKey);
lua_settable(&s, LUA_REGISTRYINDEX); lua_pushlightuserdata(L, this);
lua_settable(L, LUA_REGISTRYINDEX);
bindings_.install(L);
luaL_requiref(&s, "linenoise", luaopen_linenoise, true); luaL_requiref(L, "linenoise", luaopen_linenoise, true);
lua_pop(&s, 1); lua_pop(L, 1);
luaL_requiref(&s, "term.core", luaopen_term_core, true); luaL_requiref(L, "term.core", luaopen_term_core, true);
lua_pop(&s, 1); lua_pop(L, 1);
RegisterControlsModule(L);
RegisterDatabaseModule(L);
RegisterQueueModule(L);
RegisterVersionModule(L);
}
RegisterControlsModule(&s); auto Bridge::installLvgl(lua_State* L) -> void {
RegisterDatabaseModule(&s); luavgl_set_pcall(L, CallProtected);
RegisterQueueModule(&s); luavgl_set_font_extension(L, make_font_cb, delete_font_cb);
RegisterVersionModule(&s); luaL_requiref(L, "lvgl", luaopen_lvgl, true);
lua_pop(L, 1);
} }
static auto new_property_module(lua_State* state) -> int { static auto new_property_module(lua_State* state) -> int {
@ -76,32 +107,33 @@ static auto new_property_module(lua_State* state) -> int {
template <class... Ts> template <class... Ts>
inline constexpr bool always_false_v = false; inline constexpr bool always_false_v = false;
auto Bridge::AddPropertyModule( auto Bridge::installPropertyModule(
lua_State* L,
const std::string& name, const std::string& name,
std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>> std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>>&
props) -> void { props) -> void {
// Create the module (or retrieve it if one with this name already exists) // Create the module (or retrieve it if one with this name already exists)
luaL_requiref(&state_, name.c_str(), new_property_module, true); luaL_requiref(L, name.c_str(), new_property_module, true);
for (const auto& prop : props) { for (const auto& prop : props) {
lua_pushstring(&state_, prop.first.c_str()); lua_pushstring(L, prop.first.c_str());
std::visit( std::visit(
[&](auto&& arg) { [&](auto&& arg) {
using T = std::decay_t<decltype(arg)>; using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, LuaFunction>) { if constexpr (std::is_same_v<T, LuaFunction>) {
bindings_.Register(&state_, arg); bindings_.Register(L, arg);
} else if constexpr (std::is_same_v<T, Property*>) { } else if constexpr (std::is_same_v<T, Property*>) {
bindings_.Register(&state_, arg); bindings_.Register(L, arg);
} else { } else {
static_assert(always_false_v<T>, "missing case"); static_assert(always_false_v<T>, "missing case");
} }
}, },
prop.second); prop.second);
lua_settable(&state_, -3); // metatable.propname = property lua_settable(L, -3); // metatable.propname = property
} }
lua_pop(&state_, 1); // pop the module off the stack lua_pop(L, 1); // pop the module off the stack
} }
} // namespace lua } // namespace lua

@ -16,25 +16,38 @@
namespace lua { namespace lua {
/*
* Responsible for adding C/C++ module bindings to Lua threads. This class
* keeps no thread-specific internal state, and instead uses the LUA_REGISTRY
* table of its host threads to store data.
*/
class Bridge { class Bridge {
public: public:
/*
* Utility for retrieving the Bridge from a Lua thread in which the Bridge's
* bindings have been installed. Use by Lua's C callbacks to access the rest
* of the system.
*/
static auto Get(lua_State* state) -> Bridge*; static auto Get(lua_State* state) -> Bridge*;
Bridge(system_fsm::ServiceLocator&, lua_State& s); Bridge(system_fsm::ServiceLocator& s);
auto AddPropertyModule( system_fsm::ServiceLocator& services() { return services_; }
auto installBaseModules(lua_State* L) -> void;
auto installLvgl(lua_State* L) -> void;
auto installPropertyModule(
lua_State* L,
const std::string&, const std::string&,
std::vector< std::vector<
std::pair<std::string, std::pair<std::string, std::variant<LuaFunction, Property*>>>&)
std::variant<LuaFunction, Property*>>>)
-> void; -> void;
system_fsm::ServiceLocator& services() { return services_; } Bridge(const Bridge&) = delete;
PropertyBindings& bindings() { return bindings_; } Bridge& operator=(const Bridge&) = delete;
private: private:
system_fsm::ServiceLocator& services_; system_fsm::ServiceLocator& services_;
lua_State& state_;
PropertyBindings bindings_; PropertyBindings bindings_;
}; };

@ -0,0 +1,51 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#pragma once
#include <memory>
#include <string>
#include "lua.hpp"
#include "bridge.hpp"
#include "lua_thread.hpp"
#include "service_locator.hpp"
namespace lua {
class Registry {
public:
static auto instance(system_fsm::ServiceLocator&) -> Registry&;
auto uiThread() -> std::shared_ptr<LuaThread>;
auto newThread() -> std::shared_ptr<LuaThread>;
auto AddPropertyModule(
const std::string&,
std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>>)
-> void;
Registry(const Registry&) = delete;
Registry& operator=(const Registry&) = delete;
private:
Registry(system_fsm::ServiceLocator&);
system_fsm::ServiceLocator& services_;
std::unique_ptr<Bridge> bridge_;
std::shared_ptr<LuaThread> ui_thread_;
std::list<std::weak_ptr<LuaThread>> threads_;
std::vector<
std::pair<std::string,
std::vector<std::pair<std::string,
std::variant<LuaFunction, Property*>>>>>
modules_;
};
} // namespace lua

@ -10,9 +10,7 @@
#include <string> #include <string>
#include "lua.hpp" #include "lua.hpp"
#include "lvgl.h"
#include "bridge.hpp"
#include "service_locator.hpp" #include "service_locator.hpp"
namespace lua { namespace lua {
@ -23,8 +21,7 @@ auto CallProtected(lua_State*, int nargs, int nresults) -> int;
class LuaThread { class LuaThread {
public: public:
static auto Start(system_fsm::ServiceLocator&, lv_obj_t* lvgl_root = nullptr) static auto Start(system_fsm::ServiceLocator&) -> LuaThread*;
-> LuaThread*;
~LuaThread(); ~LuaThread();
auto RunScript(const std::string& path) -> bool; auto RunScript(const std::string& path) -> bool;
@ -32,14 +29,15 @@ class LuaThread {
auto DumpStack() -> void; auto DumpStack() -> void;
auto bridge() -> Bridge& { return *bridge_; }
auto state() -> lua_State* { return state_; } auto state() -> lua_State* { return state_; }
LuaThread(const LuaThread&) = delete;
LuaThread& operator=(const LuaThread&) = delete;
private: private:
LuaThread(std::unique_ptr<Allocator>&, std::unique_ptr<Bridge>&, lua_State*); LuaThread(std::unique_ptr<Allocator>&, lua_State*);
std::unique_ptr<Allocator> alloc_; std::unique_ptr<Allocator> alloc_;
std::unique_ptr<Bridge> bridge_;
lua_State* state_; lua_State* state_;
}; };

@ -53,7 +53,9 @@ class Property {
class PropertyBindings { class PropertyBindings {
public: public:
PropertyBindings(lua_State&); PropertyBindings();
auto install(lua_State*) -> void;
auto Register(lua_State*, Property*) -> void; auto Register(lua_State*, Property*) -> void;
auto Register(lua_State*, LuaFunction) -> void; auto Register(lua_State*, LuaFunction) -> void;

@ -16,6 +16,7 @@
#include "lua.h" #include "lua.h"
#include "lvgl.h" #include "lvgl.h"
#include "bridge.hpp"
#include "database.hpp" #include "database.hpp"
#include "event_queue.hpp" #include "event_queue.hpp"
#include "index.hpp" #include "index.hpp"
@ -70,4 +71,4 @@ auto RegisterQueueModule(lua_State* s) -> void {
lua_pop(s, 1); lua_pop(s, 1);
} }
} // namespace lua } // namespace lua

@ -9,22 +9,16 @@
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include "lauxlib.h"
#include "lua.h"
#include "lua.hpp"
#include "font/lv_font_loader.h"
#include "luavgl.h"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "esp_log.h" #include "esp_log.h"
#include "lua.hpp"
#include "bridge.hpp"
#include "event_queue.hpp" #include "event_queue.hpp"
#include "memory_resource.hpp"
#include "service_locator.hpp" #include "service_locator.hpp"
#include "ui_events.hpp" #include "ui_events.hpp"
LV_FONT_DECLARE(font_fusion_12);
LV_FONT_DECLARE(font_fusion_10);
namespace lua { namespace lua {
[[maybe_unused]] static constexpr char kTag[] = "lua"; [[maybe_unused]] static constexpr char kTag[] = "lua";
@ -59,23 +53,7 @@ static int lua_panic(lua_State* L) {
return 0; return 0;
} }
static auto make_font_cb(const char* name, int size, int weight) auto LuaThread::Start(system_fsm::ServiceLocator& services) -> LuaThread* {
-> const lv_font_t* {
if (std::string{"fusion"} == name) {
if (size == 12) {
return &font_fusion_12;
}
if (size == 10) {
return &font_fusion_10;
}
}
return NULL;
}
static auto delete_font_cb(lv_font_t* font) -> void {}
auto LuaThread::Start(system_fsm::ServiceLocator& services, lv_obj_t* lvgl_root)
-> LuaThread* {
auto alloc = std::make_unique<Allocator>(); auto alloc = std::make_unique<Allocator>();
lua_State* state = lua_newstate(lua_alloc, alloc.get()); lua_State* state = lua_newstate(lua_alloc, alloc.get());
if (!state) { if (!state) {
@ -85,24 +63,11 @@ auto LuaThread::Start(system_fsm::ServiceLocator& services, lv_obj_t* lvgl_root)
luaL_openlibs(state); luaL_openlibs(state);
lua_atpanic(state, lua_panic); lua_atpanic(state, lua_panic);
auto bridge = std::make_unique<Bridge>(services, *state); return new LuaThread(alloc, state);
// FIXME: luavgl init should probably be a part of the bridge.
if (lvgl_root) {
luavgl_set_pcall(state, CallProtected);
luavgl_set_font_extension(state, make_font_cb, delete_font_cb);
luavgl_set_root(state, lvgl_root);
luaL_requiref(state, "lvgl", luaopen_lvgl, true);
lua_pop(state, 1);
}
return new LuaThread(alloc, bridge, state);
} }
LuaThread::LuaThread(std::unique_ptr<Allocator>& alloc, LuaThread::LuaThread(std::unique_ptr<Allocator>& alloc, lua_State* state)
std::unique_ptr<Bridge>& bridge, : alloc_(std::move(alloc)), state_(state) {}
lua_State* state)
: alloc_(std::move(alloc)), bridge_(std::move(bridge)), state_(state) {}
LuaThread::~LuaThread() { LuaThread::~LuaThread() {
lua_close(state_); lua_close(state_);

@ -10,10 +10,12 @@
#include <cmath> #include <cmath>
#include <memory> #include <memory>
#include <memory_resource> #include <memory_resource>
#include <sstream>
#include <string> #include <string>
#include <variant> #include <variant>
#include "bluetooth_types.hpp" #include "bluetooth_types.hpp"
#include "lauxlib.h"
#include "lua.h" #include "lua.h"
#include "lua.hpp" #include "lua.hpp"
#include "lua_thread.hpp" #include "lua_thread.hpp"
@ -76,10 +78,30 @@ static auto property_bind(lua_State* state) -> int {
return 1; return 1;
} }
static const struct luaL_Reg kPropertyBindingFuncs[] = {{"get", property_get}, static auto property_tostring(lua_State* state) -> int {
{"set", property_set}, Property* p = check_property(state);
{"bind", property_bind}, p->PushValue(*state);
{NULL, NULL}};
std::stringstream str{};
str << "property { " << luaL_tolstring(state, -1, NULL);
if (!p->IsTwoWay()) {
str << ", read-only";
}
str << " }";
lua_settop(state, 0);
std::string res = str.str();
lua_pushlstring(state, res.data(), res.size());
return 1;
}
static const struct luaL_Reg kPropertyBindingFuncs[] = {
{"get", property_get},
{"set", property_set},
{"bind", property_bind},
{"__tostring", property_tostring},
{NULL, NULL}};
static auto generic_function_cb(lua_State* state) -> int { static auto generic_function_cb(lua_State* state) -> int {
lua_pushstring(state, kBinderKey); lua_pushstring(state, kBinderKey);
@ -98,45 +120,47 @@ static auto generic_function_cb(lua_State* state) -> int {
return std::invoke(fn, state); return std::invoke(fn, state);
} }
PropertyBindings::PropertyBindings(lua_State& s) { PropertyBindings::PropertyBindings() : functions_(&memory::kSpiRamResource) {}
lua_pushstring(&s, kBinderKey);
lua_pushlightuserdata(&s, this); auto PropertyBindings::install(lua_State* L) -> void {
lua_settable(&s, LUA_REGISTRYINDEX); lua_pushstring(L, kBinderKey);
lua_pushlightuserdata(L, this);
lua_settable(L, LUA_REGISTRYINDEX);
// Create the metatable responsible for the Property API. // Create the metatable responsible for the Property API.
luaL_newmetatable(&s, kPropertyMetatable); luaL_newmetatable(L, kPropertyMetatable);
lua_pushliteral(&s, "__index"); lua_pushliteral(L, "__index");
lua_pushvalue(&s, -2); lua_pushvalue(L, -2);
lua_settable(&s, -3); // metatable.__index = metatable lua_settable(L, -3); // metatable.__index = metatable
// Add our binding funcs (get, set, bind) to the metatable. // Add our binding funcs (get, set, bind) to the metatable.
luaL_setfuncs(&s, kPropertyBindingFuncs, 0); luaL_setfuncs(L, kPropertyBindingFuncs, 0);
// We've finished setting up the metatable, so pop it. // We've finished setting up the metatable, so pop it.
lua_pop(&s, 1); lua_pop(L, 1);
// Create a weak table in the registry to hold live bindings. // Create a weak table in the registry to hold live bindings.
lua_pushstring(&s, kBindingsTable); lua_pushstring(L, kBindingsTable);
lua_newtable(&s); // bindings = {} lua_newtable(L); // bindings = {}
// Metatable for the weak table. Values are weak. // Metatable for the weak table. Values are weak.
lua_newtable(&s); // meta = {} lua_newtable(L); // meta = {}
lua_pushliteral(&s, "__mode"); lua_pushliteral(L, "__mode");
lua_pushliteral(&s, "v"); lua_pushliteral(L, "v");
lua_settable(&s, -3); // meta.__mode='v' lua_settable(L, -3); // meta.__mode='v'
lua_setmetatable(&s, -2); // setmetatable(bindings, meta) lua_setmetatable(L, -2); // setmetatable(bindings, meta)
lua_settable(&s, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable] = bindings lua_settable(L, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable] = bindings
// Create the metatable for C++ functions. // Create the metatable for C++ functions.
luaL_newmetatable(&s, kFunctionMetatable); luaL_newmetatable(L, kFunctionMetatable);
lua_pushliteral(&s, "__call"); lua_pushliteral(L, "__call");
lua_pushcfunction(&s, generic_function_cb); lua_pushcfunction(L, generic_function_cb);
lua_settable(&s, -3); // metatable.__call = metatable lua_settable(L, -3); // metatable.__call = metatable
lua_pop(&s, 1); // Clean up the function metatable lua_pop(L, 1); // Clean up the function metatable
} }
auto PropertyBindings::Register(lua_State* s, Property* prop) -> void { auto PropertyBindings::Register(lua_State* s, Property* prop) -> void {

@ -0,0 +1,73 @@
/*
* Copyright 2023 jacqueline <me@jacqueline.id.au>
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#include "lua_registry.hpp"
#include <iostream>
#include <memory>
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "lua.hpp"
#include "bridge.hpp"
#include "event_queue.hpp"
#include "memory_resource.hpp"
#include "service_locator.hpp"
#include "ui_events.hpp"
namespace lua {
[[maybe_unused]] static constexpr char kTag[] = "lua";
auto Registry::instance(system_fsm::ServiceLocator& s) -> Registry& {
static Registry sRegistry{s};
return sRegistry;
}
Registry::Registry(system_fsm::ServiceLocator& services)
: services_(services), bridge_(new Bridge(services)) {}
auto Registry::uiThread() -> std::shared_ptr<LuaThread> {
if (!ui_thread_) {
ui_thread_ = newThread();
bridge_->installLvgl(ui_thread_->state());
}
return ui_thread_;
}
auto Registry::newThread() -> std::shared_ptr<LuaThread> {
std::shared_ptr<LuaThread> thread{LuaThread::Start(services_)};
bridge_->installBaseModules(thread->state());
for (auto& module : modules_) {
bridge_->installPropertyModule(thread->state(), module.first,
module.second);
}
threads_.push_back(thread);
return thread;
}
auto Registry::AddPropertyModule(
const std::string& name,
std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>>
properties) -> void {
modules_.push_back(std::make_pair(name, properties));
// Any live threads will need to be updated to include the new module.
auto it = threads_.begin();
while (it != threads_.end()) {
auto thread = it->lock();
if (!thread) {
// Thread has been destroyed; stop tracking it.
it = threads_.erase(it);
} else {
bridge_->installPropertyModule(thread->state(), name, properties);
it++;
}
}
}
} // namespace lua

@ -163,6 +163,8 @@ class Lua : public UiState {
auto ShowAlert(lua_State*) -> int; auto ShowAlert(lua_State*) -> int;
auto HideAlert(lua_State*) -> int; auto HideAlert(lua_State*) -> int;
auto Ticks(lua_State*) -> int;
auto SetPlaying(const lua::LuaValue&) -> bool; auto SetPlaying(const lua::LuaValue&) -> bool;
auto SetRandom(const lua::LuaValue&) -> bool; auto SetRandom(const lua::LuaValue&) -> bool;
auto SetRepeat(const lua::LuaValue&) -> bool; auto SetRepeat(const lua::LuaValue&) -> bool;

@ -24,6 +24,7 @@
#include "core/lv_obj_tree.h" #include "core/lv_obj_tree.h"
#include "database.hpp" #include "database.hpp"
#include "esp_heap_caps.h" #include "esp_heap_caps.h"
#include "esp_timer.h"
#include "haptics.hpp" #include "haptics.hpp"
#include "lauxlib.h" #include "lauxlib.h"
#include "lua_thread.hpp" #include "lua_thread.hpp"
@ -36,6 +37,7 @@
#include "encoder_input.hpp" #include "encoder_input.hpp"
#include "event_queue.hpp" #include "event_queue.hpp"
#include "gpios.hpp" #include "gpios.hpp"
#include "lua_registry.hpp"
#include "lvgl_task.hpp" #include "lvgl_task.hpp"
#include "nvs.hpp" #include "nvs.hpp"
#include "property.hpp" #include "property.hpp"
@ -142,29 +144,29 @@ lua::Property UiState::sPlaybackPosition{0, [](const lua::LuaValue& val) {
lua::Property UiState::sQueuePosition{0}; lua::Property UiState::sQueuePosition{0};
lua::Property UiState::sQueueSize{0}; lua::Property UiState::sQueueSize{0};
lua::Property UiState::sQueueRepeat{false, [](const lua::LuaValue& val) { lua::Property UiState::sQueueRepeat{false, [](const lua::LuaValue& val) {
if (!std::holds_alternative<bool>(val)) { if (!std::holds_alternative<bool>(val)) {
return false; return false;
} }
bool new_val = std::get<bool>(val); bool new_val = std::get<bool>(val);
sServices->track_queue().repeat(new_val); sServices->track_queue().repeat(new_val);
return true; return true;
}}; }};
lua::Property UiState::sQueueReplay{false, [](const lua::LuaValue& val) { lua::Property UiState::sQueueReplay{false, [](const lua::LuaValue& val) {
if (!std::holds_alternative<bool>(val)) { if (!std::holds_alternative<bool>(val)) {
return false; return false;
} }
bool new_val = std::get<bool>(val); bool new_val = std::get<bool>(val);
sServices->track_queue().replay(new_val); sServices->track_queue().replay(new_val);
return true; return true;
}}; }};
lua::Property UiState::sQueueRandom{false, [](const lua::LuaValue& val) { lua::Property UiState::sQueueRandom{false, [](const lua::LuaValue& val) {
if (!std::holds_alternative<bool>(val)) { if (!std::holds_alternative<bool>(val)) {
return false; return false;
} }
bool new_val = std::get<bool>(val); bool new_val = std::get<bool>(val);
sServices->track_queue().random(new_val); sServices->track_queue().random(new_val);
return true; return true;
}}; }};
lua::Property UiState::sVolumeCurrentPct{ lua::Property UiState::sVolumeCurrentPct{
0, [](const lua::LuaValue& val) { 0, [](const lua::LuaValue& val) {
@ -456,27 +458,26 @@ void Lua::entry() {
alert_timer_callback); alert_timer_callback);
sAlertContainer = lv_obj_create(sCurrentScreen->alert()); sAlertContainer = lv_obj_create(sCurrentScreen->alert());
sLua.reset(lua::LuaThread::Start(*sServices, sCurrentScreen->content())); auto& registry = lua::Registry::instance(*sServices);
sLua->bridge().AddPropertyModule("power", sLua = registry.uiThread();
{ registry.AddPropertyModule("power", {
{"battery_pct", &sBatteryPct}, {"battery_pct", &sBatteryPct},
{"battery_millivolts", &sBatteryMv}, {"battery_millivolts", &sBatteryMv},
{"plugged_in", &sBatteryCharging}, {"plugged_in", &sBatteryCharging},
}); });
sLua->bridge().AddPropertyModule( registry.AddPropertyModule("bluetooth",
"bluetooth", { {
{"enabled", &sBluetoothEnabled}, {"enabled", &sBluetoothEnabled},
{"connected", &sBluetoothConnected}, {"connected", &sBluetoothConnected},
{"paired_device", &sBluetoothPairedDevice}, {"paired_device", &sBluetoothPairedDevice},
{"devices", &sBluetoothDevices}, {"devices", &sBluetoothDevices},
}); });
sLua->bridge().AddPropertyModule("playback", registry.AddPropertyModule("playback", {
{ {"playing", &sPlaybackPlaying},
{"playing", &sPlaybackPlaying}, {"track", &sPlaybackTrack},
{"track", &sPlaybackTrack}, {"position", &sPlaybackPosition},
{"position", &sPlaybackPosition}, });
}); registry.AddPropertyModule(
sLua->bridge().AddPropertyModule(
"queue", "queue",
{ {
{"next", [&](lua_State* s) { return QueueNext(s); }}, {"next", [&](lua_State* s) { return QueueNext(s); }},
@ -487,40 +488,44 @@ void Lua::entry() {
{"repeat_track", &sQueueRepeat}, {"repeat_track", &sQueueRepeat},
{"random", &sQueueRandom}, {"random", &sQueueRandom},
}); });
sLua->bridge().AddPropertyModule("volume", registry.AddPropertyModule("volume",
{ {
{"current_pct", &sVolumeCurrentPct}, {"current_pct", &sVolumeCurrentPct},
{"current_db", &sVolumeCurrentDb}, {"current_db", &sVolumeCurrentDb},
{"left_bias", &sVolumeLeftBias}, {"left_bias", &sVolumeLeftBias},
{"limit_db", &sVolumeLimit}, {"limit_db", &sVolumeLimit},
}); });
sLua->bridge().AddPropertyModule("display", registry.AddPropertyModule("display",
{ {
{"brightness", &sDisplayBrightness}, {"brightness", &sDisplayBrightness},
}); });
sLua->bridge().AddPropertyModule("controls", registry.AddPropertyModule("controls",
{ {
{"scheme", &sControlsScheme}, {"scheme", &sControlsScheme},
{"scroll_sensitivity", &sScrollSensitivity}, {"scroll_sensitivity", &sScrollSensitivity},
}); });
sLua->bridge().AddPropertyModule( registry.AddPropertyModule(
"backstack", "backstack",
{ {
{"push", [&](lua_State* s) { return PushLuaScreen(s); }}, {"push", [&](lua_State* s) { return PushLuaScreen(s); }},
{"pop", [&](lua_State* s) { return PopLuaScreen(s); }}, {"pop", [&](lua_State* s) { return PopLuaScreen(s); }},
}); });
sLua->bridge().AddPropertyModule( registry.AddPropertyModule(
"alerts", { "alerts", {
{"show", [&](lua_State* s) { return ShowAlert(s); }}, {"show", [&](lua_State* s) { return ShowAlert(s); }},
{"hide", [&](lua_State* s) { return HideAlert(s); }}, {"hide", [&](lua_State* s) { return HideAlert(s); }},
}); });
sLua->bridge().AddPropertyModule("database",
{ registry.AddPropertyModule(
{"updating", &sDatabaseUpdating}, "time", {
}); {"ticks", [&](lua_State* s) { return Ticks(s); }},
});
registry.AddPropertyModule("database", {
{"updating", &sDatabaseUpdating},
});
auto bt = sServices->bluetooth(); auto bt = sServices->bluetooth();
sBluetoothEnabled.Update(bt.IsEnabled()); sBluetoothEnabled.Update(bt.IsEnabled());
@ -579,6 +584,11 @@ auto Lua::PopLuaScreen(lua_State* s) -> int {
return 0; return 0;
} }
auto Lua::Ticks(lua_State* s) -> int {
lua_pushinteger(s, esp_timer_get_time()/1000);
return 1;
}
auto Lua::ShowAlert(lua_State* s) -> int { auto Lua::ShowAlert(lua_State* s) -> int {
if (!sCurrentScreen) { if (!sCurrentScreen) {
return 0; return 0;

@ -5,7 +5,7 @@
# For more information about build system see # For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
set(PROJECT_VER "0.5.2") set(PROJECT_VER "0.6.0")
# esp-idf sets the C++ standard weird. Set cmake vars to match. # esp-idf sets the C++ standard weird. Set cmake vars to match.
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)

Loading…
Cancel
Save