summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJonathan Bradley <jcb@pikum.xyz>2025-10-17 18:01:11 -0400
committerJonathan Bradley <jcb@pikum.xyz>2025-10-17 18:01:11 -0400
commit6ca4d7a6b6c896cfcba0b3dfe02bf135b5c8ead7 (patch)
treee35928104292b91693489bd3fb1952b70b4916ce /src
parent464e3ff178545c21de8e2d64f8c8cd7dec4d5384 (diff)
pke-at: first-pass parse planning center json
Diffstat (limited to 'src')
-rw-r--r--src/pke-at-common.cpp127
-rw-r--r--src/pke-at-common.hpp5
-rw-r--r--src/pke-at-data-interface-types.hpp68
-rw-r--r--src/pke-at-data-interface.hpp38
-rw-r--r--src/pke-at-data-parser.cpp218
-rw-r--r--src/pke-at-data-parser.hpp11
-rw-r--r--src/pke-at-data-stub.cpp61
-rw-r--r--src/pke-at-data-stub.hpp15
-rw-r--r--src/pke-at-setlist-types.hpp27
9 files changed, 570 insertions, 0 deletions
diff --git a/src/pke-at-common.cpp b/src/pke-at-common.cpp
index 8372bcc..1df5521 100644
--- a/src/pke-at-common.cpp
+++ b/src/pke-at-common.cpp
@@ -53,3 +53,130 @@ AssetHandle pke_at_audio_get_or_generate_sawtooth(double pitch_freq, double dura
handle = AM_Register(key, PKE_ASSET_TYPE_AUDIO, bytes, len, 64, &details);
return handle;
}
+
+int case_insensitive_equal(const char *lhs, const char *rhs) {
+ while (*lhs && *rhs) {
+ if (tolower((unsigned char)*lhs) != tolower((unsigned char)*rhs)) {
+ return 0;
+ }
+ lhs++;
+ rhs++;
+ }
+ return *lhs == *rhs;
+}
+
+PKE_AT_KEY_INDEX parse_key_from_string(const char *key) {
+ switch (key[0]) {
+ case 'c':
+ case 'C':
+ if (strlen(key) == 1 || key[1] != '#') {
+ return PKE_AT_KEY_INDEX_C;
+ }
+ if (key[1] == '#') {
+ return PKE_AT_KEY_INDEX_C_SHARP;
+ }
+ break;
+ case 'd':
+ case 'D':
+ if (strlen(key) == 1 || (key[1] != 'b' && key[1] != '#')) {
+ return PKE_AT_KEY_INDEX_D;
+ }
+ if (key[1] == 'b') {
+ return PKE_AT_KEY_INDEX_D_FLAT;
+ }
+ if (key[1] == '#') {
+ return PKE_AT_KEY_INDEX_D_SHARP;
+ }
+ break;
+ case 'e':
+ case 'E':
+ if (strlen(key) == 1 || key[1] != 'b') {
+ return PKE_AT_KEY_INDEX_E;
+ }
+ if (key[1] == 'b') {
+ return PKE_AT_KEY_INDEX_E_FLAT;
+ }
+ break;
+ case 'f':
+ case 'F':
+ if (strlen(key) == 1 || (key[1] != 'b' && key[1] != '#')) {
+ return PKE_AT_KEY_INDEX_F;
+ }
+ if (key[1] == '#') {
+ return PKE_AT_KEY_INDEX_F_SHARP;
+ }
+ break;
+ case 'g':
+ case 'G':
+ if (strlen(key) == 1 || (key[1] != 'b' && key[1] != '#')) {
+ return PKE_AT_KEY_INDEX_G;
+ }
+ if (key[1] == 'b') {
+ return PKE_AT_KEY_INDEX_G_FLAT;
+ }
+ if (key[1] == '#') {
+ return PKE_AT_KEY_INDEX_G_SHARP;
+ }
+ break;
+ case 'a':
+ case 'A':
+ if (strlen(key) == 1 || (key[1] != 'b' && key[1] != '#')) {
+ return PKE_AT_KEY_INDEX_A;
+ }
+ if (key[1] == 'b') {
+ return PKE_AT_KEY_INDEX_A_FLAT;
+ }
+ if (key[1] == '#') {
+ return PKE_AT_KEY_INDEX_A_SHARP;
+ }
+ break;
+ case 'b':
+ case 'B':
+ if (strlen(key) == 1 || key[1] != 'b') {
+ return PKE_AT_KEY_INDEX_B;
+ }
+ if (key[1] == 'b') {
+ return PKE_AT_KEY_INDEX_B_FLAT;
+ }
+ default:
+ break;
+ }
+ return PKE_AT_KEY_INDEX_NONE;
+}
+
+PKE_AT_SECTION_TYPE_INDEX parse_section_type_from_string(const char *section) {
+ if (case_insensitive_equal(section, "verse")) {
+ return PKE_AT_SECTION_TYPE_INDEX_VERSE;
+ }
+ if (case_insensitive_equal(section, "chorus")) {
+ return PKE_AT_SECTION_TYPE_INDEX_CHORUS;
+ }
+ if (case_insensitive_equal(section, "bridge")) {
+ return PKE_AT_SECTION_TYPE_INDEX_BRIDGE;
+ }
+ if (case_insensitive_equal(section, "intro")) {
+ return PKE_AT_SECTION_TYPE_INDEX_INTRO;
+ }
+ if (case_insensitive_equal(section, "outro")) {
+ return PKE_AT_SECTION_TYPE_INDEX_OUTRO;
+ }
+ if (case_insensitive_equal(section, "end")) {
+ return PKE_AT_SECTION_TYPE_INDEX_END;
+ }
+ if (case_insensitive_equal(section, "ending")) {
+ return PKE_AT_SECTION_TYPE_INDEX_ENDING;
+ }
+ if (case_insensitive_equal(section, "instrumental")) {
+ return PKE_AT_SECTION_TYPE_INDEX_INSTRUMENTAL;
+ }
+ if (case_insensitive_equal(section, "interlude")) {
+ return PKE_AT_SECTION_TYPE_INDEX_INTERLUDE;
+ }
+ if (case_insensitive_equal(section, "refrain")) {
+ return PKE_AT_SECTION_TYPE_INDEX_REFRAIN;
+ }
+ if (case_insensitive_equal(section, "tag")) {
+ return PKE_AT_SECTION_TYPE_INDEX_TAG;
+ }
+ return PKE_AT_SECTION_TYPE_INDEX_NONE;
+}
diff --git a/src/pke-at-common.hpp b/src/pke-at-common.hpp
index a7b9a69..524cde3 100644
--- a/src/pke-at-common.hpp
+++ b/src/pke-at-common.hpp
@@ -1,12 +1,17 @@
#ifndef PKE_AT_PKE_AT_COMMON_HPP
#define PKE_AT_PKE_AT_COMMON_HPP
+#include "pke-at-setlist-types.hpp"
#include "pke/asset-manager.hpp"
#include <cstdint>
void pke_at_bpm_reset(uint8_t bpm);
+int case_insensitive_equal(const char *lhs, const char *rhs);
+PKE_AT_KEY_INDEX parse_key_from_string(const char *key_str);
+PKE_AT_SECTION_TYPE_INDEX parse_section_type_from_string(const char *section);
+
AssetHandle pke_at_audio_get_or_generate_sawtooth(double pitch_freq, double duration);
#endif /* PKE_AT_PKE_AT_COMMON_HPP */
diff --git a/src/pke-at-data-interface-types.hpp b/src/pke-at-data-interface-types.hpp
new file mode 100644
index 0000000..e83af26
--- /dev/null
+++ b/src/pke-at-data-interface-types.hpp
@@ -0,0 +1,68 @@
+#ifndef PKE_AT_PKE_AT_DATA_INTERFACE_TYPES_HPP
+#define PKE_AT_PKE_AT_DATA_INTERFACE_TYPES_HPP
+
+#include "pke-at-setlist-types.hpp"
+#include <pke/pk.h>
+
+union di_data_id {
+ long id_long;
+};
+
+struct di_service_type_details {
+ di_data_id id;
+ pk_cstr name;
+};
+struct di_service_type {
+ di_service_type_details details{};
+};
+
+struct di_plan_details {
+ di_data_id id;
+ time_t date;
+ pk_cstr title;
+ pk_cstr series_title;
+};
+struct di_plan {
+ di_plan_details details{};
+};
+
+struct di_song_details {
+ di_data_id id;
+ pk_cstr title;
+ long ccli;
+};
+struct di_song {
+ di_song_details details{};
+};
+struct di_arrangement_details {
+ di_data_id id;
+ pk_cstr title;
+ pk_arr_t<PKE_AT_SECTION_TYPE_INDEX> sequence;
+ uint8_t beats_per_minute;
+ uint8_t beats_per_bar;
+ PKE_AT_KEY_INDEX chord_chart_key;
+};
+struct di_arrangement {
+ di_arrangement_details details{};
+};
+struct di_arrangement_key_details {
+ di_data_id id;
+ PKE_AT_KEY_INDEX key;
+};
+struct di_arrangement_key {
+ di_arrangement_key_details details{};
+};
+
+struct di_plan_item_details {
+ di_data_id id;
+ PKE_AT_KEY_INDEX key;
+ uint8_t sequence;
+};
+struct di_plan_item {
+ di_plan_item_details details{};
+ di_song song{};
+ di_arrangement arrangement{};
+ di_arrangement_key key{};
+};
+
+#endif /* PKE_AT_PKE_AT_DATA_INTERFACE_TYPES_HPP */
diff --git a/src/pke-at-data-interface.hpp b/src/pke-at-data-interface.hpp
new file mode 100644
index 0000000..ec10747
--- /dev/null
+++ b/src/pke-at-data-interface.hpp
@@ -0,0 +1,38 @@
+#ifndef PKE_AT_PKE_AT_DATA_INTERFACE_HPP
+#define PKE_AT_PKE_AT_DATA_INTERFACE_HPP
+
+#include "pke-at-data-interface-types.hpp"
+
+enum pke_at_data_interface_result_code {
+ pke_at_data_interface_result_code_none = 0,
+ pke_at_data_interface_result_code_success = 1,
+ pke_at_data_interface_result_code_error = 2,
+};
+
+struct pke_at_data_interface_response {
+ pke_at_data_interface_result_code result_code;
+};
+
+template <typename T>
+struct pke_at_data_interface_response_t {
+ pke_at_data_interface_result_code result_code;
+ T *value;
+};
+
+#define FPADIR std::future<pke_at_data_interface_response>
+#define FPADIRT(T) std::future<pke_at_data_interface_response_t<T>>
+#define PPADIR std::promise<pke_at_data_interface_response>
+#define PPADIRT(T) std::promise<pke_at_data_interface_response_t<T>>
+
+struct pke_at_data_interface {
+ pke_at_data_interface() = default;
+ virtual ~pke_at_data_interface() = default;
+ virtual void init() const = 0;
+ virtual void teardown() const = 0;
+ virtual FPADIRT(pk_arr_t<di_service_type>) get_service_types() const = 0;
+ virtual FPADIRT(pk_arr_t<di_plan>) get_plans_upcoming_from_service_type() const = 0;
+ virtual FPADIRT(pk_arr_t<di_plan_item>) get_plan_items() const = 0;
+ // virtual FPADIRT(pk_arr_t<pke_at_plan_details>) get_song_arrangements() const = 0;
+};
+
+#endif /* PKE_AT_PKE_AT_DATA_INTERFACE_HPP */
diff --git a/src/pke-at-data-parser.cpp b/src/pke-at-data-parser.cpp
new file mode 100644
index 0000000..da70e4e
--- /dev/null
+++ b/src/pke-at-data-parser.cpp
@@ -0,0 +1,218 @@
+
+#include "pke-at-data-parser.hpp"
+#include "pke-at-common.hpp"
+
+#include <ctime>
+#include <nlohmann/json.hpp>
+#include <sstream>
+
+using json = nlohmann::json;
+
+pk_arr_t<di_service_type> pke_at_data_parser_service_types(char *data) {
+ PK_STN_RES stn_res;
+ pk_arr_t<di_service_type> ret{};
+ json j = json::parse(data);
+ char *val;
+ std::string s;
+ if (!j.contains("data") || j.at("data").is_null()) {
+ fprintf(stderr, "[pke_at_data_parser_service_types] no 'data'\n");
+ return {};
+ }
+ json dat = j.at("data");
+ for (json::iterator it = dat.begin(); it != dat.end(); ++it) {
+ di_service_type obj{};
+ it->at("id").get_to(s);
+ stn_res = pk_stn(&obj.details.id.id_long, s.c_str(), nullptr);
+ if (stn_res != PK_STN_RES_SUCCESS) {
+ fprintf(stderr, "[pke_at_data_parser_service_types] failed to parse id: %i\n", stn_res);
+ return {};
+ }
+ json attr = (*it).at("attributes");
+ attr.at("name").get_to(s);
+ obj.details.name.length = s.length();
+ obj.details.name.reserved = s.length() + 1;
+ val = pk_new_arr<char>(obj.details.name.reserved);
+ std::strncpy(val, s.c_str(), obj.details.name.reserved);
+ obj.details.name.val = val;
+ pk_arr_append_t<di_service_type>(&ret, obj);
+ }
+ return ret;
+}
+
+pk_arr_t<di_plan> pke_at_data_parser_plans(char *data) {
+ PK_STN_RES stn_res;
+ pk_arr_t<di_plan> ret{};
+ json j = json::parse(data);
+ char *val;
+ std::string s;
+ if (!j.contains("data") || j.at("data").is_null()) {
+ fprintf(stderr, "[pke_at_data_parser_plans] no 'data'\n");
+ return {};
+ }
+ json dat = j.at("data");
+ for (json::iterator it = dat.begin(); it != dat.end(); ++it) {
+ di_plan obj{};
+ it->at("id").get_to(s);
+ stn_res = pk_stn(&obj.details.id.id_long, s.c_str(), nullptr);
+ if (stn_res != PK_STN_RES_SUCCESS) {
+ fprintf(stderr, "[pke_at_data_parser_plans] failed to parse id: %i\n", stn_res);
+ return {};
+ }
+ json attr = (*it).at("attributes");
+ attr.at("sort_date").get_to(s);
+ std::istringstream ss(s);
+ std::tm t{};
+ ss >> std::get_time(&t, "%Y-%m-%dT%H:%M:%SZ");
+ obj.details.date = std::mktime(&t);
+ if (attr.contains("title") && !attr.at("title").is_null()) {
+ attr.at("title").get_to(s);
+ obj.details.title.length = s.length();
+ obj.details.title.reserved = s.length() + 1;
+ val = pk_new_arr<char>(obj.details.title.reserved);
+ std::strncpy(val, s.c_str(), obj.details.title.reserved);
+ obj.details.title.val = val;
+ }
+ if (attr.contains("series_title") && !attr.at("series_title").is_null()) {
+ attr.at("series_title").get_to(s);
+ obj.details.series_title.length = s.length();
+ obj.details.series_title.reserved = s.length() + 1;
+ val = pk_new_arr<char>(obj.details.series_title.reserved);
+ std::strncpy(val, s.c_str(), obj.details.series_title.reserved);
+ obj.details.series_title.val = val;
+ }
+ pk_arr_append_t<di_plan>(&ret, obj);
+ }
+ return ret;
+}
+
+pk_arr_t<di_plan_item> pke_at_data_parser_plan_items(char *data) {
+ PK_STN_RES stn_res;
+ pk_arr_t<di_plan_item> ret{};
+ json j = json::parse(data);
+ char *val;
+ std::string s;
+ long id, id_song, id_arrangement, id_key;
+ if (!j.contains("data") || j.at("data").is_null()) {
+ fprintf(stderr, "[pke_at_data_parser_plan_items] no 'data'\n");
+ return {};
+ }
+ json dat = j.at("data");
+ if (!j.contains("included") || j.at("included").is_null()) {
+ fprintf(stderr, "[pke_at_data_parser_plan_items] no 'included'\n");
+ return {};
+ }
+ json incl = j.at("included");
+
+ for (json::iterator it = dat.begin(); it != dat.end(); ++it) {
+ di_plan_item obj{};
+
+ id_song = -1;
+ id_arrangement = -1;
+ id_key = -1;
+
+ json attr = it->at("attributes");
+ if (!attr.contains("item_type") || attr.at("item_type").is_null()) {
+ continue;
+ }
+ attr.at("item_type").get_to(s);
+ if (!case_insensitive_equal(s.c_str(), "song")) {
+ continue;
+ }
+
+ it->at("id").get_to(s);
+ stn_res = pk_stn(&obj.details.id.id_long, s.c_str(), nullptr);
+ if (stn_res != PK_STN_RES_SUCCESS) {
+ fprintf(stderr, "[pke_at_data_parser_plan_items] failed to parse id: %i\n", stn_res);
+ return {};
+ }
+
+ if (attr.contains("key_name") && !attr.at("key_name").is_null()) {
+ attr.at("key_name").get_to(s);
+ obj.details.key = parse_key_from_string(s.c_str());
+ }
+
+ attr.at("sequence").get_to(obj.details.sequence);
+
+ json rel_song = it->at("relationships").at("song").at("data");
+ json rel_arrangement = it->at("relationships").at("arrangement").at("data");
+ json rel_key = it->at("relationships").at("key").at("data");
+
+ rel_song.at("id").get_to(s);
+ stn_res = pk_stn(&id_song, s.c_str(), nullptr);
+ rel_arrangement.at("id").get_to(s);
+ stn_res = pk_stn(&id_arrangement, s.c_str(), nullptr);
+ rel_key.at("id").get_to(s);
+ stn_res = pk_stn(&id_key, s.c_str(), nullptr);
+
+ for (json::iterator incl_it = incl.begin(); incl_it != incl.end(); ++incl_it) {
+ incl_it->at("type").get_to(s);
+ if (case_insensitive_equal(s.c_str(), "song")) {
+ attr = incl_it->at("attributes");
+ incl_it->at("id").get_to(s);
+ pk_stn(&id, s.c_str(), nullptr);
+ if (id != id_song) continue;
+ obj.song.details.id.id_long = id;
+ attr.at("ccli_number").get_to(obj.song.details.ccli);
+ if (attr.contains("title") && !attr.at("title").is_null()) {
+ attr.at("title").get_to(s);
+ obj.song.details.title.length = s.length();
+ obj.song.details.title.reserved = s.length() + 1;
+ val = pk_new_arr<char>(obj.song.details.title.reserved);
+ std::strncpy(val, s.c_str(), obj.song.details.title.reserved);
+ obj.song.details.title.val = val;
+ }
+ continue;
+ }
+ if (case_insensitive_equal(s.c_str(), "arrangement")) {
+ attr = incl_it->at("attributes");
+ incl_it->at("id").get_to(s);
+ pk_stn(&id, s.c_str(), nullptr);
+ if (id != id_arrangement) continue;
+ obj.arrangement.details.id.id_long = id;
+ if (attr.contains("bpm") && !attr.at("bpm").is_null()) {
+ attr.at("bpm").get_to(obj.arrangement.details.beats_per_minute);
+ }
+ if (attr.contains("meter") && !attr.at("meter").is_null()) {
+ attr.at("meter").get_to(s);
+ char cc[2] = { s.at(0), '\0'};
+ pk_stn(&obj.arrangement.details.beats_per_bar, cc, nullptr);
+ }
+ if (attr.contains("chord_chart_key") && !attr.at("chord_chart_key").is_null()) {
+ attr.at("chord_chart_key").get_to(s);
+ obj.arrangement.details.chord_chart_key = parse_key_from_string(s.c_str());
+ }
+ if (attr.contains("name") && !attr.at("name").is_null()) {
+ attr.at("name").get_to(s);
+ obj.arrangement.details.title.length = s.length();
+ obj.arrangement.details.title.reserved = s.length() + 1;
+ val = pk_new_arr<char>(obj.arrangement.details.title.reserved);
+ std::strncpy(val, s.c_str(), obj.arrangement.details.title.reserved);
+ obj.arrangement.details.title.val = val;
+ }
+ if (attr.contains("sequence_full") && !attr.at("sequence_full").is_null()) {
+ for (json::iterator sec_it = attr.at("sequence_full").begin(); sec_it != attr.at("sequence_full").end(); ++sec_it) {
+ sec_it->at("label").get_to(s);
+ pk_arr_append_t<PKE_AT_SECTION_TYPE_INDEX>(&obj.arrangement.details.sequence, parse_section_type_from_string(s.c_str()));
+ }
+ }
+ continue;
+ }
+ if (case_insensitive_equal(s.c_str(), "key")) {
+ attr = incl_it->at("attributes");
+ incl_it->at("id").get_to(s);
+ pk_stn(&id, s.c_str(), nullptr);
+ if (id != id_key) continue;
+ obj.key.details.id.id_long = id;
+ if (attr.contains("starting_key") && !attr.at("starting_key").is_null()) {
+ attr.at("starting_key").get_to(s);
+ obj.key.details.key = parse_key_from_string(s.c_str());
+ }
+ continue;
+ }
+ fprintf(stderr, "[pke_at_data_parser_plan_items] unhandled include \"type\": '%s'\n", s.c_str());
+ }
+
+ pk_arr_append_t<di_plan_item>(&ret, obj);
+ }
+ return ret;
+}
diff --git a/src/pke-at-data-parser.hpp b/src/pke-at-data-parser.hpp
new file mode 100644
index 0000000..fd491ef
--- /dev/null
+++ b/src/pke-at-data-parser.hpp
@@ -0,0 +1,11 @@
+#ifndef PKE_AT_PKE_AT_DATA_PARSER_HPP
+#define PKE_AT_PKE_AT_DATA_PARSER_HPP
+
+#include "pke-at-data-interface-types.hpp"
+#include <pke/pk.h>
+
+pk_arr_t<di_service_type> pke_at_data_parser_service_types(char *data);
+pk_arr_t<di_plan> pke_at_data_parser_plans(char *data);
+pk_arr_t<di_plan_item> pke_at_data_parser_plan_items(char *data);
+
+#endif /* PKE_AT_PKE_AT_DATA_PARSER_HPP */
diff --git a/src/pke-at-data-stub.cpp b/src/pke-at-data-stub.cpp
new file mode 100644
index 0000000..c36583f
--- /dev/null
+++ b/src/pke-at-data-stub.cpp
@@ -0,0 +1,61 @@
+
+#include "pke-at-data-stub.hpp"
+
+#include "../data/service_types_json.h"
+#include "../data/upcoming_plans_json.h"
+#include "../data/plan_items_json.h"
+
+#include "pke-at-data-parser.hpp"
+
+void pke_at_data_stub::init() const {
+}
+
+void pke_at_data_stub::teardown() const {
+};
+
+FPADIRT(pk_arr_t<di_service_type>)
+pke_at_data_stub::get_service_types() const {
+ PPADIRT(pk_arr_t<di_service_type>) ret{};
+ std::thread([&ret]() {
+ pke_at_data_interface_response_t<pk_arr_t<di_service_type>> val{};
+ val.result_code = pke_at_data_interface_result_code_success;
+ val.value = pk_new<pk_arr_t<di_service_type>>();
+ *val.value = pke_at_data_parser_service_types((char*)data_service_types_json);
+ ret.set_value(val);
+ }).detach();
+ return ret.get_future();
+};
+
+FPADIRT(pk_arr_t<di_plan>)
+pke_at_data_stub::get_plans_upcoming_from_service_type() const {
+ PPADIRT(pk_arr_t<di_plan>) ret{};
+ std::thread([&ret]() {
+ pke_at_data_interface_response_t<pk_arr_t<di_plan>> val{};
+ val.result_code = pke_at_data_interface_result_code_success;
+ val.value = pk_new<pk_arr_t<di_plan>>();
+ *val.value = pke_at_data_parser_plans((char*)data_upcoming_plans_json);
+ ret.set_value(val);
+ }).detach();
+ return ret.get_future();
+}
+
+FPADIRT(pk_arr_t<di_plan_item>)
+pke_at_data_stub::get_plan_items() const {
+ PPADIRT(pk_arr_t<di_plan_item>) ret{};
+ std::thread([&ret]() {
+ pke_at_data_interface_response_t<pk_arr_t<di_plan_item>> val{};
+ val.result_code = pke_at_data_interface_result_code_success;
+ val.value = pk_new<pk_arr_t<di_plan_item>>();
+ *val.value = pke_at_data_parser_plan_items((char*)data_plan_items_json);
+ ret.set_value(val);
+ }).detach();
+ return ret.get_future();
+}
+
+/*
+PPADIRT(pk_arr_t<pke_at_plan_details>)
+pke_at_data_stub::get_song_arrangements() const {
+ PPADIRT(pk_arr_t<pke_at_plan_details>) ret{};
+ return ret;
+};
+*/
diff --git a/src/pke-at-data-stub.hpp b/src/pke-at-data-stub.hpp
new file mode 100644
index 0000000..4f8d929
--- /dev/null
+++ b/src/pke-at-data-stub.hpp
@@ -0,0 +1,15 @@
+#ifndef PKE_AT_PKE_AT_DATA_STUB_HPP
+#define PKE_AT_PKE_AT_DATA_STUB_HPP
+
+#include "pke-at-data-interface.hpp"
+
+class pke_at_data_stub : public pke_at_data_interface {
+ void init() const override;
+ void teardown() const override;
+ FPADIRT(pk_arr_t<di_service_type>) get_service_types() const override;
+ FPADIRT(pk_arr_t<di_plan>) get_plans_upcoming_from_service_type() const override;
+ FPADIRT(pk_arr_t<di_plan_item>) get_plan_items() const override;
+ // FPADIRT(pk_arr_t<pke_at_plan_details>) get_song_arrangements() const override;
+};
+
+#endif /* PKE_AT_PKE_AT_DATA_STUB_HPP */
diff --git a/src/pke-at-setlist-types.hpp b/src/pke-at-setlist-types.hpp
index 0ab6815..90c86d0 100644
--- a/src/pke-at-setlist-types.hpp
+++ b/src/pke-at-setlist-types.hpp
@@ -10,8 +10,12 @@ enum PKE_AT_SECTION_TYPE_INDEX {
PKE_AT_SECTION_TYPE_INDEX_BRIDGE,
PKE_AT_SECTION_TYPE_INDEX_INTRO,
PKE_AT_SECTION_TYPE_INDEX_OUTRO,
+ PKE_AT_SECTION_TYPE_INDEX_END,
+ PKE_AT_SECTION_TYPE_INDEX_ENDING = PKE_AT_SECTION_TYPE_INDEX_END,
PKE_AT_SECTION_TYPE_INDEX_INSTRUMENTAL,
PKE_AT_SECTION_TYPE_INDEX_INTERLUDE,
+ PKE_AT_SECTION_TYPE_INDEX_REFRAIN,
+ PKE_AT_SECTION_TYPE_INDEX_TAG,
PKE_AT_SECTION_TYPE_INDEX_COUNT,
};
@@ -32,8 +36,31 @@ struct pke_at_section {
pke_at_section_details details{};
};
+enum PKE_AT_KEY_INDEX {
+ PKE_AT_KEY_INDEX_NONE = 0,
+ PKE_AT_KEY_INDEX_C,
+ PKE_AT_KEY_INDEX_C_SHARP,
+ PKE_AT_KEY_INDEX_D_FLAT,
+ PKE_AT_KEY_INDEX_D,
+ PKE_AT_KEY_INDEX_D_SHARP,
+ PKE_AT_KEY_INDEX_E_FLAT,
+ PKE_AT_KEY_INDEX_E,
+ PKE_AT_KEY_INDEX_F,
+ PKE_AT_KEY_INDEX_F_SHARP,
+ PKE_AT_KEY_INDEX_G_FLAT,
+ PKE_AT_KEY_INDEX_G,
+ PKE_AT_KEY_INDEX_G_SHARP,
+ PKE_AT_KEY_INDEX_A_FLAT,
+ PKE_AT_KEY_INDEX_A,
+ PKE_AT_KEY_INDEX_A_SHARP,
+ PKE_AT_KEY_INDEX_B_FLAT,
+ PKE_AT_KEY_INDEX_B,
+ PKE_AT_KEY_INDEX_COUNT,
+};
+
struct pke_at_song_details {
pk_uuid uuid;
+ enum PKE_AT_KEY_INDEX key;
long ccli;
pk_cstr title;
pk_cstr arrangement;