diff options
| author | Jonathan Bradley <jcb@pikum.xyz> | 2025-10-17 18:01:11 -0400 |
|---|---|---|
| committer | Jonathan Bradley <jcb@pikum.xyz> | 2025-10-17 18:01:11 -0400 |
| commit | 6ca4d7a6b6c896cfcba0b3dfe02bf135b5c8ead7 (patch) | |
| tree | e35928104292b91693489bd3fb1952b70b4916ce /src | |
| parent | 464e3ff178545c21de8e2d64f8c8cd7dec4d5384 (diff) | |
pke-at: first-pass parse planning center json
Diffstat (limited to 'src')
| -rw-r--r-- | src/pke-at-common.cpp | 127 | ||||
| -rw-r--r-- | src/pke-at-common.hpp | 5 | ||||
| -rw-r--r-- | src/pke-at-data-interface-types.hpp | 68 | ||||
| -rw-r--r-- | src/pke-at-data-interface.hpp | 38 | ||||
| -rw-r--r-- | src/pke-at-data-parser.cpp | 218 | ||||
| -rw-r--r-- | src/pke-at-data-parser.hpp | 11 | ||||
| -rw-r--r-- | src/pke-at-data-stub.cpp | 61 | ||||
| -rw-r--r-- | src/pke-at-data-stub.hpp | 15 | ||||
| -rw-r--r-- | src/pke-at-setlist-types.hpp | 27 |
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; |
