musix 0.3.5

Music player library for esoteric audio formats (music from C64,Amiga etc)
Documentation

#include "panel.hpp"
#include "player.hpp"

#include "colors.hpp"

#include <coreutils/log.h>

#include <fmt/format.h>

#include <atomic>
#include <chrono>
#include <csignal>
#include <functional>
#include <memory>
#include <sstream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>

#include <sol/sol.hpp>

using namespace std::string_literals;
using namespace std::chrono_literals;


fs::path findConfig(std::string const& file = "")
{
    namespace fs = std::filesystem;
    auto home = utils::get_home_dir();
    auto searchPath =
        std::vector{home / ".config" / "musix", fs::path("/etc/musix")};
    fs::path dataPath;
    for (auto&& p : searchPath) {
        if (file.empty() ? fs::exists(p) : fs::exists(p / file)) {
            dataPath = p;
            break;
        }
    }
    return file.empty() ? dataPath : dataPath / file;
}

int main(int argc, const char** argv)
{
    logging::setLevel(logging::Level::Info);

    sol::state lua;

    lua.open_libraries(sol::lib::base, sol::lib::string, sol::lib::table,
                       sol::lib::io);

    std::string songFile;
    int startSong = -1;
    bool output = true;
    bool bg = false;
    bool clear = true;
    bool quitPlayer = false;
    bool useColors = false;
    int forcedLength = 0;
    std::string command;

    auto playerType = MusicPlayer::Type::Piped;

    std::vector<fs::path> songFiles;
    for (int i = 1; i < argc; i++) {
        if (argv[i][0] == '-') {
            auto opt = std::string_view(&argv[i][1]);
            if (opt == "song" || opt == "s") {
                startSong = std::stoi(argv[++i]);
            } else if (opt == "length" || opt == "l") {
                forcedLength = std::stoi(argv[++i]);
            } else if (opt == "color" || opt == "c") {
                useColors = true;
            } else if (opt == "simple") {
                playerType = MusicPlayer::Type::Basic;
            } else if (opt == "d") {
                bg = true;
            } else if (opt == "a") {
                clear = false;
            } else if (opt == "o") {
                playerType = MusicPlayer::Type::Writer;
            } else if (opt == "q") {
                quitPlayer = true;
            } else if (opt == "n") {
                command = "next";
                bg = true;
            } else if (opt == "p") {
                command = "prev";
                bg = true;
            }

        } else {
            songFile = argv[i];
            if (forcedLength > 0) {
                songFile = songFile + ";" + std::to_string(forcedLength);
                forcedLength = 0;
            }
            songFiles.emplace_back(songFile);
        }
    }

    if (isatty(fileno(stdin)) == 0) {
        bg = true;
        std::string line;
        while (std::getline(std::cin, line)) {
            songFiles.emplace_back(line);
        }
        fmt::print("{} files\n", songFiles.size());
    }

    auto music_player = MusicPlayer::create(playerType);
    if (music_player == nullptr) { return 0; }

    if (quitPlayer) {
        music_player->clear();
        music_player = nullptr;
        return 0;
    }

    if (startSong > 0) { music_player->set_song(startSong - 1); }
    if (!songFiles.empty()) {
        if (clear) { music_player->clear(); }
        for (auto&& sf : songFiles) {
            music_player->add(sf);
        }
    }

    if (playerType == MusicPlayer::Type::Writer) { return 0; }

    if (command == "next") { music_player->next(); }
    if (command == "prev") { music_player->prev(); }

    if (bg) {
        music_player->detach();
        return 0;
    }

    Panel panel;
    panel.useColors = useColors;
    std::unordered_map<std::string, Meta> meta;

    bool theme_set = false;
    sol::function update_fn;
    std::unordered_map<int, std::function<void()>> mapping;

    for (auto&& [name, color] : html_colors) {
        lua[utils::toUpper(name)] = (color << 8) | 0xff;
    }
    lua["DEFAULT"] = 0x12345600;

    for (int i = KEY_F1; i <= KEY_F8; i++) {
        lua[fmt::format("KEY_F{}", i - KEY_F1 + 1)] = i;
    }

    lua["get_meta"] = [&] {
        sol::table t = lua.create_table();
        for (auto [name, val] : meta) {
            std::visit([&, n = name](auto&& v) { t[n] = v; }, val);
        }
        return t;
    };

    lua["set_song"] = [&](int song) { music_player->set_song(song); };

    lua["play_next"] = [&] { music_player->next(); };
    lua["play_prev"] = [&] { music_player->prev(); };
    lua["clear_all"] = [&] { music_player->clear(); };
    lua["add_file"] = [&](std::string const& file_name) {
        music_player->add(file_name);
    };

    lua.set_function("var_color",
                     sol::overload(
                         [&](std::string const& var, uint32_t color) {
                             if (auto* target = panel.get_var(var)) {
                                 target->fg = color;
                             }
                         },
                         [&](std::string const& var, uint32_t fg, uint32_t bg) {
                             if (auto* target = panel.get_var(var)) {
                                 target->fg = fg;
                                 target->bg = bg;
                             }
                         }));
    lua.set_function("map",
                     sol::overload(
                         [&](std::string key, std::function<void()> const& fn) {
                             mapping[static_cast<int>(key[0])] = fn;
                         },
                         [&](int key, std::function<void()> const& fn) {
                             mapping[key] = fn;
                         }));

    lua.set_function(
        "colorize",
        sol::overload(
            [&](std::string const& pattern, uint32_t color) {
                auto [x, y] = panel.find(pattern);
                if (x >= 0) {
                    panel.set_color(color);
                    panel.colorize(x, y, static_cast<int>(pattern.size()), 1);
                }
            },
            [&](std::string const& pattern, uint32_t fg, uint32_t bg) {
                auto [x, y] = panel.find(pattern);
                if (x >= 0) {
                    panel.set_color(fg, bg);
                    panel.colorize(x, y, static_cast<int>(pattern.size()), 1);
                }
            },
            [&](int x, int y, int len, uint32_t color) {
                panel.set_color(color);
                panel.colorize(x, y, len, 1);
            },
            [&](int x, int y, int len, uint32_t fg, uint32_t bg) {
                panel.set_color(fg, bg);
                panel.colorize(x, y, len, 1);
            }));

    lua["set_theme"] = [&](sol::table args) {
        theme_set = true;
        std::string panelText = args["panel"];
        auto panel_bg =
            args.get_or<uint32_t>("panel_bg", bbs::Console::DefaultColor);
        auto panel_fg =
            args.get_or<uint32_t>("panel_fg", bbs::Console::DefaultColor);
        auto var_bg =
            args.get_or<uint32_t>("var_bg", bbs::Console::DefaultColor);
        auto var_fg =
            args.get_or<uint32_t>("var_fg", bbs::Console::DefaultColor);
        panel.set_color(panel_fg, panel_bg);
        panel.set_var_color(var_fg, var_bg);
        panel.set_panel(panelText);
        sol::function v = args["init_fn"];
        if (v.valid()) { v(); }
        panel.set_color(panel_fg, panel_bg);
        update_fn = args["update_fn"];
    };

    auto dataPath = findConfig("init.lua");

    if (!dataPath.empty() && fs::exists(dataPath)) {
        auto res = lua.script_file(dataPath.string());
        if (!res.valid()) { fmt::print("ERROR\n"); }
    }
    if (!theme_set) { panel.set_panel(); }
    panel.flush();

    uint32_t song = 0;

    auto clear_meta = [&] {
        meta.clear();
        meta["title"] = ""s;
        meta["sub_title"] = ""s;
        meta["game"] = ""s;
        meta["composer"] = ""s;
        meta["filename"] = ""s;
        meta["song"] = static_cast<uint32_t>(0);
        meta["songs"] = static_cast<uint32_t>(0);
        meta["length"] = static_cast<uint32_t>(0);
        meta["seconds"] = static_cast<uint32_t>(0);
        song = 0;
    };
    clear_meta();

    static std::atomic<bool> quit{false};

    std::signal(SIGINT, [](int) { quit = true; });

    while (!quit) {
        auto&& info = music_player->get_info();
        for (auto&& [name, val] : info) {
            if (name == "init") {
                clear_meta();
                continue;
            }
            if (name == "song") { song = std::get<uint32_t>(val); }
            meta[name] = val;
        }
        if (output) {
            if (!info.empty()) {

                panel.update(meta);
                if (update_fn.valid()) {
                    sol::table t = lua.create_table();
                    for (auto [name, val] : meta) {
                        std::visit([&, n = name](auto&& v) { t[n] = v; }, val);
                    }
                    update_fn(t);
                    for (auto&& [key, val] : t) {
                        if (val.get_type() == sol::type::number) {
                            meta[key.as<std::string>()] = val.as<uint32_t>();
                        } else {
                            meta[key.as<std::string>()] = val.as<std::string>();
                        }
                    }
                }

                panel.render(meta);
                panel.flush();
            }
        }

        auto key = panel.read_key();
        if (key == KEY_NONE) { std::this_thread::sleep_for(100ms); }
        auto it = mapping.find(key);
        if (it != mapping.end()) { it->second(); }
        if (key == KEY_ESCAPE) {
            music_player->detach();
            quit = true;
        }
        if (key == 'q') { quit = true; }
        if (key == KEY_ENTER || key == 'n') { music_player->next(); }
        if (key == KEY_BACKSPACE || key == 'p') { music_player->prev(); }
        if (key == KEY_RIGHT || key == ']') {
            music_player->set_song(song + 1);
        }
        if (key == KEY_LEFT || key == '[') { music_player->set_song(song - 1); }
    }
    music_player = nullptr;
    return 0;
}