diff --git a/.gitignore b/.gitignore index 6b2c09025..30566b101 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,7 @@ _autosave* fp-info-cache .~* *.plan +utils/SpotifyActPopularitySorting/creds.json +utils/SpotifyActPopularitySorting/*festival_schedule_arr.txt +utils/SpotifyActPopularitySorting/.cache +utils/SpotifyActPopularitySorting/venv \ No newline at end of file diff --git a/movement/make/Makefile b/movement/make/Makefile index da5486b0c..6f3c66aea 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -129,6 +129,7 @@ SRCS += \ ../watch_faces/clock/minute_repeater_decimal_face.c \ ../watch_faces/complication/tuning_tones_face.c \ ../watch_faces/complication/kitchen_conversions_face.c \ + ../watch_faces/complication/festival_schedule_face.c \ # New watch faces go above this line. # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 35571109a..2c9ca66cb 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -104,6 +104,7 @@ #include "minute_repeater_decimal_face.h" #include "tuning_tones_face.h" #include "kitchen_conversions_face.h" +#include "festival_schedule_face.h" // New includes go above this line. #endif // MOVEMENT_FACES_H_ diff --git a/movement/watch_faces/complication/festival_schedule_arr.h b/movement/watch_faces/complication/festival_schedule_arr.h new file mode 100644 index 000000000..585c4dca5 --- /dev/null +++ b/movement/watch_faces/complication/festival_schedule_arr.h @@ -0,0 +1,1185 @@ +// Genre - https://old.reddit.com/r/ElectricForest/comments/1bqbwlv/electric_forest_2024_lineup_broken_down_by_genre/ +// Line-up - https://clashfinder.com/m/elecfor24/ +#include "festival_schedule_face.h" + +#define NUM_ACTS 146 + +const schedule_t festival_acts[NUM_ACTS + 1]= +{ + { + .artist = "ACRAZE", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 21, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 22, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 19 + }, + { + .artist = "ACRAZE", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 24, .unit.hour = 0, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 24, .unit.hour = 1, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 19 + }, + { + .artist = "AK SPO", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 22, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 0, .unit.minute = 0}, + .genre = DnB, + .popularity = 106 + }, + { + .artist = "ALLEYC", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 16, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 17, .unit.minute = 15}, + .genre = DUBSTEP, + .popularity = 71 + }, + { + .artist = "ATLIEN", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 22, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 23, .unit.minute = 45}, + .genre = DUBSTEP, + .popularity = 55 + }, + { + .artist = "AYYBO ", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 17, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 18, .unit.minute = 30}, + .genre = HOUSE, + .popularity = 39 + }, + { + .artist = "BAGGI ", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 18, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 19, .unit.minute = 15}, + .genre = HOUSE, + .popularity = 105 + }, + { + .artist = "BARCLA", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 20, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 21, .unit.minute = 30}, + .genre = BASS, + .popularity = 84 + }, + { + .artist = "BEN B:", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 23, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 0, .unit.minute = 30}, + .genre = HOUSE, + .popularity = 11 + }, + { + .artist = "BLACK ", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 1, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 2, .unit.minute = 15}, + .genre = DUBSTEP, + .popularity = 51 + }, + { + .artist = "BLASTO", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 23, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 0, .unit.minute = 45}, + .genre = TECHNO, + .popularity = 62 + }, + { + .artist = "BOOGIE", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 20, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 21, .unit.minute = 30}, + .genre = DUBSTEP, + .popularity = 64 + }, + { + .artist = "BOOGIE", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 18, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 19, .unit.minute = 15}, + .genre = JAM, + .popularity = 96 + }, + { + .artist = "BRANDI", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 14, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 15, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 0 + }, + { + .artist = "BRANDI", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 0, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 1, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 0 + }, + { + .artist = "CALUSS", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 19, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 20, .unit.minute = 45}, + .genre = HOUSE, + .popularity = 66 + }, + { + .artist = "CANABL", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 15, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 16, .unit.minute = 0}, + .genre = DUBSTEP, + .popularity = 87 + }, + { + .artist = "CANNON", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 19, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 20, .unit.minute = 30}, + .genre = INDIE, + .popularity = 17 + }, + { + .artist = "CARDIO", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 16, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 17, .unit.minute = 0}, + .genre = NO_GENRE, + .popularity = 0 + }, + { + .artist = "CASPA ", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 19, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 20, .unit.minute = 30}, + .genre = DUBSTEP, + .popularity = 88 + }, + { + .artist = "CASSIA", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 20, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 21, .unit.minute = 45}, + .genre = HOUSE, + .popularity = 46 + }, + { + .artist = "CHAOS ", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 15, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 16, .unit.minute = 45}, + .genre = HOUSE, + .popularity = 54 + }, + { + .artist = "CHARLO", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 23, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 24, .unit.hour = 0, .unit.minute = 15}, + .genre = TECHNO, + .popularity = 4 + }, + { + .artist = "CHASE ", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 21, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 22, .unit.minute = 15}, + .genre = DnB, + .popularity = 3 + }, + { + .artist = "CLOSIN", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 23, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 24, .unit.hour = 1, .unit.minute = 0}, + .genre = NO_GENRE, + .popularity = 0 + }, + { + .artist = "COCO &", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 18, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 19, .unit.minute = 45}, + .genre = DANCE, + .popularity = 65 + }, + { + .artist = "CUCO ", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 18, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 19, .unit.minute = 45}, + .genre = INDIE, + .popularity = 91 + }, + { + .artist = "DAVE Y", + .stage = NO_STAGE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 15, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 16, .unit.minute = 0}, + .genre = JAM, + .popularity = 114 + }, + { + .artist = "DIMENS", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 21, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 22, .unit.minute = 45}, + .genre = DnB, + .popularity = 26 + }, + { + .artist = "DIRTWI", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 16, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 17, .unit.minute = 0}, + .genre = JAM, + .popularity = 67 + }, + { + .artist = "DISCO ", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 20, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 22, .unit.minute = 30}, + .genre = JAM, + .popularity = 85 + }, + { + .artist = "DIXON'", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 15, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 16, .unit.minute = 30}, + .genre = SOUL, + .popularity = 110 + }, + { + .artist = "DIXON'", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 18, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 19, .unit.minute = 0}, + .genre = SOUL, + .popularity = 110 + }, + { + .artist = "DIXON'", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 24, .unit.hour = 0, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 24, .unit.hour = 1, .unit.minute = 0}, + .genre = SOUL, + .popularity = 110 + }, + { + .artist = "DJ BRO", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 0, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 1, .unit.minute = 55}, + .genre = HOUSE, + .popularity = 113 + }, + { + .artist = "DJ SUS", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 23, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 0, .unit.minute = 15}, + .genre = DANCE, + .popularity = 89 + }, + { + .artist = "DJ SUS", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 19, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 20, .unit.minute = 30}, + .genre = DANCE, + .popularity = 89 + }, + { + .artist = "DRAMA ", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 17, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 18, .unit.minute = 0}, + .genre = DANCE, + .popularity = 32 + }, + { + .artist = "DUMPST", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 17, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 18, .unit.minute = 15}, + .genre = JAM, + .popularity = 93 + }, + { + .artist = "EGGY ", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 17, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 19, .unit.minute = 0}, + .genre = JAM, + .popularity = 99 + }, + { + .artist = "EMO NI", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 23, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 1, .unit.minute = 0}, + .genre = INDIE, + .popularity = 0 + }, + { + .artist = "EQUANI", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 17, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 18, .unit.minute = 10}, + .genre = INDIE, + .popularity = 57 + }, + { + .artist = "EVERYT", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 22, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 0, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 5 + }, + { + .artist = "EXCISI", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 22, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 24, .unit.hour = 0, .unit.minute = 0}, + .genre = DUBSTEP, + .popularity = 7 + }, + { + .artist = "FEMME ", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 14, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 15, .unit.minute = 30}, + .genre = NO_GENRE, + .popularity = 0 + }, + { + .artist = "G JONE", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 22, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 23, .unit.minute = 15}, + .genre = DnB, + .popularity = 81 + }, + { + .artist = "GIGANT", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 20, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 21, .unit.minute = 45}, + .genre = DUBSTEP, + .popularity = 20 + }, + { + .artist = "GOODBO", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 21, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 22, .unit.minute = 30}, + .genre = INDIE, + .popularity = 18 + }, + { + .artist = "GREEN ", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 18, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 20, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 34 + }, + { + .artist = "H&RRY ", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 14, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 15, .unit.minute = 30}, + .genre = HOUSE, + .popularity = 0 + }, + { + .artist = "HAMDI ", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 22, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 23, .unit.minute = 30}, + .genre = DUBSTEP, + .popularity = 44 + }, + { + .artist = "HIATUS", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 23, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 0, .unit.minute = 45}, + .genre = SOUL, + .popularity = 13 + }, + { + .artist = "HUMANI", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 13, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 14, .unit.minute = 30}, + .genre = NO_GENRE, + .popularity = 0 + }, + { + .artist = "HYPERB", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 19, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 21, .unit.minute = 0}, + .genre = DUBSTEP, + .popularity = 43 + }, + { + .artist = "INZO ", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 19, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 20, .unit.minute = 30}, + .genre = CHILL, + .popularity = 47 + }, + { + .artist = "IVY LA", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 18, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 19, .unit.minute = 30}, + .genre = DnB, + .popularity = 76 + }, + { + .artist = "JASON ", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 18, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 19, .unit.minute = 15}, + .genre = DANCE, + .popularity = 101 + }, + { + .artist = "JENNA ", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 0, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 1, .unit.minute = 0}, + .genre = TECHNO, + .popularity = 115 + }, + { + .artist = "JJUUJJ", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 18, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 19, .unit.minute = 0}, + .genre = JAM, + .popularity = 104 + }, + { + .artist = "JOHN S", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 0, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 2, .unit.minute = 15}, + .genre = HOUSE, + .popularity = 8 + }, + { + .artist = "JUELZ ", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 18, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 19, .unit.minute = 15}, + .genre = CHILL, + .popularity = 70 + }, + { + .artist = "KALLAG", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 21, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 22, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 109 + }, + { + .artist = "KENNY ", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 16, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 17, .unit.minute = 45}, + .genre = RAP, + .popularity = 21 + }, + { + .artist = "KILTRO", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 17, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 18, .unit.minute = 30}, + .genre = INDIE, + .popularity = 72 + }, + { + .artist = "KNOCK2", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 23, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 1, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 33 + }, + { + .artist = "LAYTON", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 1, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 2, .unit.minute = 15}, + .genre = TECHNO, + .popularity = 36 + }, + { + .artist = "LE YOU", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 21, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 23, .unit.minute = 0}, + .genre = CHILL, + .popularity = 45 + }, + { + .artist = "LEAGUE", + .stage = NO_STAGE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 15, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 16, .unit.minute = 0}, + .genre = JAM, + .popularity = 0 + }, + { + .artist = "LETTUC", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 18, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 20, .unit.minute = 0}, + .genre = JAM, + .popularity = 59 + }, + { + .artist = "LEVEL ", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 17, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 18, .unit.minute = 15}, + .genre = DUBSTEP, + .popularity = 69 + }, + { + .artist = "LEVITY", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 18, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 19, .unit.minute = 30}, + .genre = DUBSTEP, + .popularity = 63 + }, + { + .artist = "LEVITY", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 18, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 19, .unit.minute = 30}, + .genre = DUBSTEP, + .popularity = 63 + }, + { + .artist = "LIBIAN", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 17, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 18, .unit.minute = 0}, + .genre = RAP, + .popularity = 10 + }, + { + .artist = "LIGHTC", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 14, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 15, .unit.minute = 45}, + .genre = NO_GENRE, + .popularity = 0 + }, + { + .artist = "LITTLE", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 16, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 17, .unit.minute = 30}, + .genre = RAP, + .popularity = 52 + }, + { + .artist = "LP GIO", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 22, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 23, .unit.minute = 30}, + .genre = DANCE, + .popularity = 27 + }, + { + .artist = "LSZEE ", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 0, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 2, .unit.minute = 15}, + .genre = BASS, + .popularity = 38 + }, + { + .artist = "LUCII ", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 18, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 19, .unit.minute = 30}, + .genre = DUBSTEP, + .popularity = 73 + }, + { + .artist = "LUDACR", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 18, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 19, .unit.minute = 15}, + .genre = RAP, + .popularity = 2 + }, + { + .artist = "LYNY ", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 19, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 20, .unit.minute = 45}, + .genre = DUBSTEP, + .popularity = 77 + }, + { + .artist = "MADDY ", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 16, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 17, .unit.minute = 0}, + .genre = CHILL, + .popularity = 86 + }, + { + .artist = "MADDY ", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 19, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 20, .unit.minute = 30}, + .genre = CHILL, + .popularity = 86 + }, + { + .artist = "MAJOR ", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 21, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 22, .unit.minute = 45}, + .genre = HOUSE, + .popularity = 12 + }, + { + .artist = "MARSH ", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 23, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 0, .unit.minute = 45}, + .genre = HOUSE, + .popularity = 49 + }, + { + .artist = "MASCOL", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 20, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 21, .unit.minute = 30}, + .genre = POP, + .popularity = 82 + }, + { + .artist = "MASONI", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 13, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 14, .unit.minute = 30}, + .genre = HOUSE, + .popularity = 0 + }, + { + .artist = "MATROD", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 22, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 23, .unit.minute = 15}, + .genre = HOUSE, + .popularity = 29 + }, + { + .artist = "MAU P ", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 23, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 0, .unit.minute = 45}, + .genre = HOUSE, + .popularity = 23 + }, + { + .artist = "MICHAE", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 20, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 21, .unit.minute = 45}, + .genre = DANCE, + .popularity = 53 + }, + { + .artist = "MOJAVE", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 22, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 23, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 102 + }, + { + .artist = "MOONTR", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 16, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 17, .unit.minute = 0}, + .genre = SOUL, + .popularity = 68 + }, + { + .artist = "MURPH", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 1, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 2, .unit.minute = 15}, + .genre = DANCE, + .popularity = 31 + }, + { + .artist = "MURPH", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 19, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 20, .unit.minute = 45}, + .genre = DANCE, + .popularity = 31 + }, + { + .artist = "NEIL F", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 22, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 23, .unit.minute = 0}, + .genre = INDIE, + .popularity = 15 + }, + { + .artist = "NELLY ", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 20, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 21, .unit.minute = 30}, + .genre = POP, + .popularity = 1 + }, + { + .artist = "NEOMA ", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 16, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 17, .unit.minute = 30}, + .genre = POP, + .popularity = 100 + }, + { + .artist = "ODEN &", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 21, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 22, .unit.minute = 15}, + .genre = HOUSE, + .popularity = 42 + }, + { + .artist = "ONLY F", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 17, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 18, .unit.minute = 15}, + .genre = HOUSE, + .popularity = 80 + }, + { + .artist = "PAPERW", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 21, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 22, .unit.minute = 30}, + .genre = RAP, + .popularity = 103 + }, + { + .artist = "PEACH ", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 18, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 19, .unit.minute = 45}, + .genre = INDIE, + .popularity = 30 + }, + { + .artist = "POLITI", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 22, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 0, .unit.minute = 0}, + .genre = CHILL, + .popularity = 112 + }, + { + .artist = "POLYRH", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 16, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 17, .unit.minute = 30}, + .genre = SOUL, + .popularity = 92 + }, + { + .artist = "PRETYL", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 23, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 1, .unit.minute = 0}, + .genre = BASS, + .popularity = 22 + }, + { + .artist = "PRETYP", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 20, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 21, .unit.minute = 45}, + .genre = HOUSE, + .popularity = 58 + }, + { + .artist = "PRIDE ", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 15, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 17, .unit.minute = 0}, + .genre = NO_GENRE, + .popularity = 0 + }, + { + .artist = "PROXIM", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 16, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 17, .unit.minute = 45}, + .genre = JAM, + .popularity = 50 + }, + { + .artist = "RANGER", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 17, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 18, .unit.minute = 15}, + .genre = HOUSE, + .popularity = 90 + }, + { + .artist = "RAWAYA", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 17, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 18, .unit.minute = 30}, + .genre = SOUL, + .popularity = 6 + }, + { + .artist = "RAYBEN", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 16, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 17, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 79 + }, + { + .artist = "REDRUM", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 17, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 18, .unit.minute = 0}, + .genre = CHILL, + .popularity = 111 + }, + { + .artist = "RUMBLE", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 15, .unit.minute = 5}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 17, .unit.minute = 0}, + .genre = NO_GENRE, + .popularity = 0 + }, + { + .artist = "RUMBLE", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 16, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 18, .unit.minute = 0}, + .genre = NO_GENRE, + .popularity = 0 + }, + { + .artist = "SAMMY ", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 22, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 23, .unit.minute = 15}, + .genre = DnB, + .popularity = 25 + }, + { + .artist = "SARA L", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 1, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 2, .unit.minute = 15}, + .genre = TECHNO, + .popularity = 37 + }, + { + .artist = "SEVEN ", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 1, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 2, .unit.minute = 0}, + .genre = BASS, + .popularity = 14 + }, + { + .artist = "SHAE D", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 15, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 16, .unit.minute = 45}, + .genre = HOUSE, + .popularity = 108 + }, + { + .artist = "SHAUN ", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 18, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 20, .unit.minute = 0}, + .genre = DANCE, + .popularity = 78 + }, + { + .artist = "SLAYYY", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 20, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 21, .unit.minute = 30}, + .genre = POP, + .popularity = 24 + }, + { + .artist = "STRING", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 20, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 22, .unit.minute = 0}, + .genre = JAM, + .popularity = 56 + }, + { + .artist = "STRING", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 18, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 22, .unit.minute = 30}, + .genre = JAM, + .popularity = 56 + }, + { + .artist = "SUBTRO", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 23, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 0, .unit.minute = 45}, + .genre = DUBSTEP, + .popularity = 16 + }, + { + .artist = "SULTAN", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 21, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 22, .unit.minute = 45}, + .genre = CHILL, + .popularity = 28 + }, + { + .artist = "SUPER ", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 19, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 20, .unit.minute = 0}, + .genre = DnB, + .popularity = 97 + }, + { + .artist = "SWAYLO", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 20, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 21, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 0 + }, + { + .artist = "SWAYLO", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 19, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 20, .unit.minute = 30}, + .genre = HOUSE, + .popularity = 0 + }, + { + .artist = " TBA ", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 22, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 23, .unit.minute = 5}, + .genre = NO_GENRE, + .popularity = 0 + }, + { + .artist = "THOUGH", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 17, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 18, .unit.minute = 15}, + .genre = CHILL, + .popularity = 98 + }, + { + .artist = "TRIPP ", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 20, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 21, .unit.minute = 0}, + .genre = CHILL, + .popularity = 95 + }, + { + .artist = "TSHA ", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 18, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 19, .unit.minute = 15}, + .genre = HOUSE, + .popularity = 48 + }, + { + .artist = "UMPHRE", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 17, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 19, .unit.minute = 30}, + .genre = JAM, + .popularity = 60 + }, + { + .artist = "UNUSUA", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 2, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 3, .unit.minute = 30}, + .genre = POP, + .popularity = 75 + }, + { + .artist = "UNUSUA", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 20, .unit.minute = 15}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 21, .unit.minute = 15}, + .genre = POP, + .popularity = 75 + }, + { + .artist = "VENBEE", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 21, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 21, .unit.minute = 55}, + .genre = DnB, + .popularity = 41 + }, + { + .artist = "VINI V", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 21, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 23, .unit.minute = 15}, + .genre = TECHNO, + .popularity = 9 + }, + { + .artist = "VNSSA ", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 16, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 18, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 83 + }, + { + .artist = "WESTEN", + .stage = RANCH_ARENA, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 15, .unit.minute = 30}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 16, .unit.minute = 30}, + .genre = HOUSE, + .popularity = 40 + }, + { + .artist = "WESTEN", + .stage = HONEYCOMB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 19, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 21, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 40 + }, + { + .artist = "WHYTE ", + .stage = SHERWOOD_COURT, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 21, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 22, .unit.minute = 45}, + .genre = BASS, + .popularity = 94 + }, + { + .artist = "WILL C", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 20, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 22, .unit.minute = 0}, + .genre = HOUSE, + .popularity = 74 + }, + { + .artist = "WOOLI ", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 23, .unit.minute = 45}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 0, .unit.minute = 50}, + .genre = DnB, + .popularity = 35 + }, + { + .artist = " YOGA1", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 11, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 21, .unit.hour = 12, .unit.minute = 15}, + .genre = NO_GENRE, + .popularity = 0 + }, + { + .artist = " YOGA2", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 11, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 22, .unit.hour = 12, .unit.minute = 10}, + .genre = NO_GENRE, + .popularity = 0 + }, + { + .artist = " YOGA3", + .stage = TRIPOLEE, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 11, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 12, .unit.minute = 15}, + .genre = NO_GENRE, + .popularity = 0 + }, + { + .artist = "ZEN SE", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 18, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 19, .unit.minute = 0}, + .genre = DnB, + .popularity = 107 + }, + { + .artist = "ZINGAR", + .stage = OBSERVATORY, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 21, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 20, .unit.hour = 22, .unit.minute = 0}, + .genre = BASS, + .popularity = 61 + }, + { + .artist = "6,nFor", + .stage = CAROUSEL_CLUB, + .start_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 12, .unit.minute = 0}, + .end_time = {.unit.year = 4, .unit.month = 6, .unit.day = 23, .unit.hour = 13, .unit.minute = 0}, + .genre = NO_GENRE, + .popularity = 0 + }, + [NUM_ACTS] = { //Fall back + .artist = "No Act", + .stage = STAGE_COUNT, + .start_time = {.unit.year = 0, .unit.month = 0, .unit.day = 0, .unit.hour = 0, .unit.minute = 0}, + .end_time = {.unit.year = 63, .unit.month = 15, .unit.day = 31, .unit.hour = 31, .unit.minute = 63}, + .genre = GENRE_COUNT, + .popularity = 0 + } +}; diff --git a/movement/watch_faces/complication/festival_schedule_face.c b/movement/watch_faces/complication/festival_schedule_face.c new file mode 100644 index 000000000..aae90a496 --- /dev/null +++ b/movement/watch_faces/complication/festival_schedule_face.c @@ -0,0 +1,521 @@ +/* + * MIT License + * + * Copyright (c) 2024 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "festival_schedule_face.h" +#include "festival_schedule_arr.h" +#include "watch_utility.h" + +const char festival_name[2] = "EF"; + +const char festival_stage[STAGE_COUNT + 1][2] = +{ + [NO_STAGE] = " ", + [RANCH_ARENA] = "Rn", + [SHERWOOD_COURT] = "SH", + [TRIPOLEE] = "TR", + [CAROUSEL_CLUB] = "CC", + [OBSERVATORY] = "OB", + [HONEYCOMB] = "HC", + [STAGE_COUNT] = " " +}; + +const char festival_genre[GENRE_COUNT + 1][6] = +{ + [NO_GENRE] = " NONE ", + [BASS] = " BASS ", + [DUBSTEP] = "DUBStP", + [DnB] = " dnB ", + [HOUSE] = " HOUSE", + [DANCE] = " DaNCE", + [TECHNO] = " tECNO", + [INDIE] = " INdIE", + [POP] = " POP ", + [JAM] = " Jan& ", + [CHILL] = " Chill", + [RAP] = " rAP ", + [SOUL] = " SOUL ", + [GENRE_COUNT] = " " +}; + +static watch_date_time _starting_time; +static watch_date_time _ending_time; +static bool _quick_ticks_running; +static uint8_t _ts_ticks; +static uint8_t _ts_ticks_purpose; +static const uint8_t _act_arr_size = sizeof(festival_acts) / sizeof(schedule_t); + + +static uint8_t _get_next_act_num(uint8_t act_num, bool get_prev){ + int increment = get_prev ? -1 : 1; + uint8_t next_act = act_num; + do{ + next_act = (next_act + increment + _act_arr_size) % _act_arr_size; + } + while (festival_acts[next_act].start_time.reg == 0); + return next_act; +} + + +// Returns 0 if they're the same; Positive if dt1 is newer than dt2; Negative o/w +static int _compare_dates_times(watch_date_time dt1, watch_date_time dt2) { + if (dt1.unit.year != dt2.unit.year) { + return dt1.unit.year - dt2.unit.year; + } + if (dt1.unit.month != dt2.unit.month) { + return dt1.unit.month - dt2.unit.month; + } + if (dt1.unit.day != dt2.unit.day) { + return dt1.unit.day - dt2.unit.day; + } + if (dt1.unit.hour != dt2.unit.hour) { + return dt1.unit.hour - dt2.unit.hour; + } + return dt1.unit.minute - dt2.unit.minute; +} + +// Returns -1 if already passed, o/w days until start. +static int16_t _get_days_until(watch_date_time start_time, watch_date_time curr_time){ + start_time.unit.hour = start_time.unit.minute = start_time.unit.second = 0; + curr_time.unit.hour = curr_time.unit.minute = curr_time.unit.second = 0; + uint32_t now_timestamp = watch_utility_date_time_to_unix_time(curr_time, 0); + uint32_t start_timestamp = watch_utility_date_time_to_unix_time(start_time, 0); + int16_t days_until; + if (now_timestamp > start_timestamp) // Date already passed + days_until = -1; + else + days_until = (start_timestamp - now_timestamp) / (60 * 60 * 24); + return days_until; +} + +static bool _act_is_playing(uint8_t act_num, watch_date_time curr_time){ + if (act_num == NUM_ACTS) return false; + return _compare_dates_times(festival_acts[act_num].start_time, curr_time) <= 0 && _compare_dates_times(curr_time, festival_acts[act_num].end_time) < 0; +} + +static uint8_t _act_performing_on_stage(uint8_t stage, watch_date_time curr_time) +{ + for (int i = 0; i < NUM_ACTS; i++) { + if (festival_acts[i].stage == stage && _act_is_playing(i, curr_time)) + return i; + } + return NUM_ACTS; +} + +static uint8_t _find_first_available_act(uint8_t first_stage_to_check, watch_date_time curr_time, bool reverse) +{ + int increment = reverse ? -1 : 1; + uint8_t last_stage = (first_stage_to_check - increment + STAGE_COUNT) % STAGE_COUNT; + for (int i = first_stage_to_check;; i = (i + increment + STAGE_COUNT) % STAGE_COUNT) { + uint8_t act_num = _act_performing_on_stage(i, curr_time); + if (act_num != NUM_ACTS) + return act_num; + if (i == last_stage) break; + } + return NUM_ACTS; +} + +static void _display_act(festival_schedule_state_t *state){ + char buf[11]; + uint8_t popularity = festival_acts[state->curr_act].popularity; + state->curr_screen = SCREEN_ACT; + if (popularity > 0 && popularity < 40) + sprintf(buf, "%.2s%2d%.6s", festival_stage[state->curr_stage], festival_acts[state->curr_act].popularity, festival_acts[state->curr_act].artist); + else + sprintf(buf, "%.2s %.6s", festival_stage[state->curr_stage], festival_acts[state->curr_act].artist); + watch_display_string(buf , 0); +} + +static void _display_act_genre(uint8_t act_num, bool show_weekday){ + char buf[11]; + if (show_weekday){ + watch_date_time start_time = festival_acts[act_num].start_time; + if (start_time.unit.hour < 5) + start_time.reg = start_time.reg - (1<<17); // Subtract a day if the act starts before 5am. + sprintf(buf, "%s G%.6s", watch_utility_get_weekday(start_time), festival_genre[festival_acts[act_num].genre]); + watch_display_string(buf , 0); + } + else{ + sprintf(buf, " G%.6s", festival_genre[festival_acts[act_num].genre]); + watch_display_string(buf , 2); + } +} + +static void _display_act_time(uint8_t act_num, bool clock_mode_24h, bool display_end){ + char buf[11]; + watch_date_time disp_time = display_end ? festival_acts[act_num].end_time : festival_acts[act_num].start_time; + watch_set_colon(); + if (clock_mode_24h){ + watch_set_indicator(WATCH_INDICATOR_24H); + + } + else{ + watch_clear_indicator(WATCH_INDICATOR_24H); + // if we are in 12 hour mode, do some cleanup. + if (disp_time.unit.hour < 12) { + watch_clear_indicator(WATCH_INDICATOR_PM); + } else { + watch_set_indicator(WATCH_INDICATOR_PM); + } + disp_time.unit.hour %= 12; + if (disp_time.unit.hour == 0) disp_time.unit.hour = 12; + } + sprintf(buf, "%s%2d%2d%.2d%s", watch_utility_get_weekday(disp_time), disp_time.unit.day, disp_time.unit.hour, disp_time.unit.minute, display_end ? "Ed" : "St"); + watch_display_string(buf, 0); +} + +static void _display_screen(festival_schedule_state_t *state, bool clock_mode_24h){ + _ts_ticks = 10; + _ts_ticks_purpose = TICK_SCREEN; + if (state->curr_screen != SCREEN_START_TIME && state->curr_screen != SCREEN_END_TIME) + { + watch_clear_colon(); + watch_clear_indicator(WATCH_INDICATOR_24H); + watch_clear_indicator(WATCH_INDICATOR_PM); + } + switch (state->curr_screen) + { + case SCREEN_ACT: + case SCREENS_COUNT: + _display_act(state); + break; + case SCREEN_GENRE: + _display_act_genre(state->curr_act, state->cyc_through_all_acts); + break; + case SCREEN_START_TIME: + _display_act_time(state->curr_act, clock_mode_24h, false); + break; + case SCREEN_END_TIME: + _display_act_time(state->curr_act, clock_mode_24h, true); + break; + } +} + +static watch_date_time _get_starting_time(void){ + watch_date_time date_oldest = festival_acts[0].start_time; + for (int i = 1; i < NUM_ACTS; i++) { + if (festival_acts[i].artist[0] == 0) continue; + watch_date_time date_check = festival_acts[i].start_time; + if (_compare_dates_times(date_check, date_oldest) < 0) + date_oldest= date_check; + } + return date_oldest; +} + +static watch_date_time _get_ending_time(void){ + watch_date_time date_newest = festival_acts[0].end_time; + for (int i = 1; i < NUM_ACTS; i++) { + watch_date_time date_check = festival_acts[i].end_time; + if (_compare_dates_times(date_check, date_newest) > 0) + date_newest= date_check; + } + return date_newest; +} + +static bool _festival_occurring(watch_date_time curr_time, bool update_display){ + char buf[15]; + if (_compare_dates_times(_starting_time, curr_time) > 0){ + if (update_display){ + int16_t days_until = _get_days_until(_starting_time, curr_time); + if (days_until == 0) return true; + if (days_until <= 999){ + if (days_until > 99) sprintf(buf, "%.2s%02d%3dday", festival_name, _starting_time.unit.year + 20, days_until); + else sprintf(buf, "%.2s%02d%2d day", festival_name, _starting_time.unit.year + 20, days_until); + } + else sprintf(buf, "%.2s%02dWAIT ", festival_name, _starting_time.unit.year + 20); + watch_display_string(buf , 0); + } + return false; + } + else if (_compare_dates_times(_ending_time, curr_time) <= 0){ + if (update_display){ + sprintf(buf, "%.2s%02dOVER ", festival_name, _starting_time.unit.year + 20); + watch_display_string(buf , 0); + } + return false; + } + return true; +} + +static void _display_curr_day(watch_date_time curr_time){ // Assumes festival_occurring function was run before it. + char buf[13]; + int16_t days_until = _get_days_until(curr_time, _starting_time) + 1; + if (days_until < 100 && days_until >= 0) + sprintf(buf, "%.2s%02d day%2d", festival_name, _starting_time.unit.year + 20, days_until); + else + sprintf(buf, "%.2s%02d LONg ", festival_name, _starting_time.unit.year + 20); + watch_display_string(buf , 0); + return; +} + +void festival_schedule_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(festival_schedule_state_t)); + memset(*context_ptr, 0, sizeof(festival_schedule_state_t)); + festival_schedule_state_t *state = (festival_schedule_state_t *)*context_ptr; + state->curr_act = NUM_ACTS; + state->prev_act = NUM_ACTS + 1; + state -> prev_day = 0; + state->cyc_through_all_acts = false; + state->curr_screen = SCREEN_ACT; + // Do any one-time tasks in here; the inside of this conditional happens only at boot. + } + // Do any pin or peripheral setup here; this will be called whenever the watch wakes from deep sleep. +} + +static void _cyc_all_acts(festival_schedule_state_t *state, bool clock_mode_24h, bool handling_light){ + state->cyc_through_all_acts = true; + watch_set_indicator(WATCH_INDICATOR_LAP); + state->curr_act = _get_next_act_num(state->curr_act, handling_light); + state->curr_stage = festival_acts[state->curr_act].stage; + state->curr_screen = SCREEN_ACT; + _display_screen(state, clock_mode_24h); + state->showing_title = false; + return; +} + +static void _handle_btn_up(festival_schedule_state_t *state, bool clock_mode_24h, bool handling_light){ + _ts_ticks = 0; + _ts_ticks_purpose = TICK_NONE; + if (state->cyc_through_all_acts){ + _cyc_all_acts(state, clock_mode_24h, handling_light); + return; + } + if (!state->festival_occurring) return; + watch_date_time curr_time = watch_rtc_get_date_time(); + if (!state->showing_title) state->curr_stage = handling_light ? (state->curr_stage - 1 + STAGE_COUNT) % STAGE_COUNT : (state->curr_stage + 1) % STAGE_COUNT; + else state->showing_title = false; + if (SHOW_EMPTY_STAGES) + state->curr_act = _act_performing_on_stage(state->curr_stage, curr_time); + else{ + state->curr_act = _find_first_available_act(state->curr_stage, curr_time, handling_light); + state->curr_stage = festival_acts[state->curr_act].stage; + } + state->curr_screen = SCREEN_ACT; + _display_screen(state, clock_mode_24h); +} + +static void _show_title(festival_schedule_state_t *state){ + state->showing_title = true; + state->curr_act = NUM_ACTS; + watch_clear_colon(); + watch_clear_all_indicators(); + state->cyc_through_all_acts = false; + watch_date_time curr_time = watch_rtc_get_date_time(); + state -> prev_day = (curr_time.reg >> 17); + state -> festival_occurring = _festival_occurring(curr_time, true); + if (state -> festival_occurring) _display_curr_day(curr_time); +} + +static void start_quick_cyc(void){ + _quick_ticks_running = true; + movement_request_tick_frequency(8); +} + +static void handle_ts_ticks(festival_schedule_state_t *state, bool clock_mode_24h){ + static bool _light_held; + if (_light_held){ + if (!watch_get_pin_level(BTN_LIGHT)) _light_held = false; + else return; + } + if (_ts_ticks != 0){ + --_ts_ticks; + switch (_ts_ticks_purpose){ + case TICK_NONE: + _ts_ticks = 0; + break; + case TICK_SCREEN: + if (state->showing_title || state->curr_screen == SCREEN_ACT){ + _ts_ticks = 0; + } + else if (_ts_ticks == 0){ + if(watch_get_pin_level(BTN_LIGHT)){ + _ts_ticks = 1; // Give one extra second of delay when the light is on + _light_held = true; + } + else{ + _ts_ticks_purpose = TICK_NONE; + state->curr_screen = SCREEN_ACT; + _display_screen(state, clock_mode_24h); + } + } + break; + case TICK_LEAVE: + if (!watch_get_pin_level(BTN_MODE))_ts_ticks = 0; + else if (_ts_ticks == 0){ + if(state -> showing_title) movement_move_to_face(0); + else{ + _ts_ticks_purpose = TICK_LEAVE; // This is unneeded, but explicit that we remain in TICK_LEAVE + _ts_ticks = 2; + _show_title(state); + } + } + break; + case TICK_CYCLE: + if (_ts_ticks == 0){ + _ts_ticks_purpose = TICK_NONE; + start_quick_cyc(); + } + break; + } + } +} + +void festival_schedule_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + _starting_time = _get_starting_time(); + _ending_time = _get_ending_time(); + _quick_ticks_running = false; + _ts_ticks = 0; + _ts_ticks_purpose = TICK_NONE; +} + +bool festival_schedule_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + festival_schedule_state_t *state = (festival_schedule_state_t *)context; + watch_date_time curr_time; + switch (event.event_type) { + case EVENT_ACTIVATE: + _show_title(state); + break; + case EVENT_TICK: + case EVENT_LOW_ENERGY_UPDATE: + if (_quick_ticks_running) { + if (watch_get_pin_level(BTN_LIGHT)) _handle_btn_up(state, settings->bit.clock_mode_24h, true); + else if (watch_get_pin_level(BTN_ALARM)) _handle_btn_up(state, settings->bit.clock_mode_24h, false); + else{ + _quick_ticks_running = false; + movement_request_tick_frequency(1); + } + } + handle_ts_ticks(state, settings->bit.clock_mode_24h); + + if (state->cyc_through_all_acts) break; + curr_time = watch_rtc_get_date_time(); + bool newDay = ((curr_time.reg >> 17) != (state -> prev_day)); + state -> prev_day = (curr_time.reg >> 17); + state -> festival_occurring = _festival_occurring(curr_time, (newDay && !state->cyc_through_all_acts)); + if (!state->festival_occurring) break; + if(state->showing_title){ + if (newDay) _display_curr_day(curr_time); + break; + } + if (!_act_is_playing(state->curr_act, curr_time)){ + if (SHOW_EMPTY_STAGES) + state->curr_act = NUM_ACTS; + else{ + state->curr_act = _find_first_available_act(state->curr_stage, curr_time, false); + state->curr_stage = festival_acts[state->curr_act].stage; + } + } + if ((state->curr_stage == state->prev_stage) && (state->curr_act == state->prev_act)) break; + state->prev_stage = state->curr_stage; + state->prev_act = state->curr_act; + _display_act(state); + break; + case EVENT_LIGHT_BUTTON_UP: + _handle_btn_up(state, settings->bit.clock_mode_24h, true); + break; + case EVENT_ALARM_BUTTON_UP: + _handle_btn_up(state, settings->bit.clock_mode_24h, false); + break; + case EVENT_ALARM_LONG_PRESS: + if (state->showing_title){ + _cyc_all_acts(state, settings->bit.clock_mode_24h, false); + _ts_ticks = 2; + _ts_ticks_purpose = TICK_CYCLE; + } + else if (state->festival_occurring && !state->cyc_through_all_acts) break; + else start_quick_cyc(); + break; + case EVENT_LIGHT_BUTTON_DOWN: + break; // To overwrite the default LED on + case EVENT_LIGHT_LONG_PRESS: + if (state->showing_title){ + _cyc_all_acts(state, settings->bit.clock_mode_24h, true); + _ts_ticks = 2; + _ts_ticks_purpose = TICK_CYCLE; + } + else if (state->curr_screen != SCREEN_ACT || (state->festival_occurring && !state->cyc_through_all_acts)) + movement_illuminate_led(); // Will allow led for see acts' genre and times + else start_quick_cyc(); + break; + case EVENT_MODE_LONG_PRESS: + if (state->curr_screen != SCREEN_ACT){ + state->curr_screen = SCREEN_ACT; + _display_screen(state, settings->bit.clock_mode_24h); + _ts_ticks = 2; + _ts_ticks_purpose = TICK_LEAVE; + } + else if (!state->showing_title){ + _ts_ticks = 2; + _ts_ticks_purpose = TICK_LEAVE; + _show_title(state); + } + else movement_move_to_face(0); + break; + case EVENT_MODE_BUTTON_UP: + if (state->showing_title) movement_move_to_next_face(); + else if (state->curr_act != NUM_ACTS){ + state->curr_screen = (state->curr_screen + 1) % SCREENS_COUNT; + _display_screen(state, settings->bit.clock_mode_24h); + } + break; + case EVENT_TIMEOUT: + if (state->cyc_through_all_acts){ + state->cyc_through_all_acts = false; + _show_title(state); + } + break; + default: + // Movement's default loop handler will step in for any cases you don't handle above: + // * EVENT_LIGHT_BUTTON_DOWN lights the LED + // * EVENT_MODE_BUTTON_UP moves to the next watch face in the list + // * EVENT_MODE_LONG_PRESS returns to the first watch face (or skips to the secondary watch face, if configured) + // You can override any of these behaviors by adding a case for these events to this switch statement. + return movement_default_loop_handler(event, settings); + } + + // return true if the watch can enter standby mode. Generally speaking, you should always return true. + // Exceptions: + // * If you are displaying a color using the low-level watch_set_led_color function, you should return false. + // * If you are sounding the buzzer using the low-level watch_set_buzzer_on function, you should return false. + // Note that if you are driving the LED or buzzer using Movement functions like movement_illuminate_led or + // movement_play_alarm, you can still return true. This guidance only applies to the low-level watch_ functions. + return true; +} + +void festival_schedule_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + festival_schedule_state_t *state = (festival_schedule_state_t *)context; + state->curr_act = NUM_ACTS; + state->cyc_through_all_acts = false; + state->prev_act = NUM_ACTS + 1; + + // handle any cleanup before your watch face goes off-screen. +} + diff --git a/movement/watch_faces/complication/festival_schedule_face.h b/movement/watch_faces/complication/festival_schedule_face.h new file mode 100644 index 000000000..966e22256 --- /dev/null +++ b/movement/watch_faces/complication/festival_schedule_face.h @@ -0,0 +1,113 @@ +/* + * MIT License + * + * Copyright (c) 2024 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef FESTIVAL_SCHEDULE_FACE_H_ +#define FESTIVAL_SCHEDULE_FACE_H_ + +#include "movement.h" + + +typedef enum FestivalStage { + NO_STAGE = 0, + RANCH_ARENA, + SHERWOOD_COURT, + TRIPOLEE, + CAROUSEL_CLUB, + OBSERVATORY, + HONEYCOMB, + STAGE_COUNT +} FestivalStage; + +typedef enum FestivalGenre { + NO_GENRE = 0, + BASS, + DUBSTEP, + DnB, + HOUSE, + DANCE, + TECHNO, + INDIE, + POP, + JAM, + CHILL, + RAP, + SOUL, + GENRE_COUNT +} FestivalGenre; + +typedef enum FestivalScreens { + SCREEN_ACT = 0, + SCREEN_GENRE, + SCREEN_START_TIME, + SCREEN_END_TIME, + SCREENS_COUNT +} FestivalScreens; + +typedef enum FestivalTickReason { + TICK_NONE = 0, + TICK_SCREEN, + TICK_LEAVE, + TICK_CYCLE +} FestivalTickReason; + +typedef struct { + char artist[6]; + FestivalStage stage; + watch_date_time start_time; + watch_date_time end_time; + FestivalGenre genre; + uint8_t popularity; +} schedule_t; + +#define SHOW_EMPTY_STAGES false + +typedef struct { + // Anything you need to keep track of, put it here! + FestivalStage curr_stage; + FestivalStage prev_stage; + uint8_t curr_act; + uint8_t prev_act; + uint16_t prev_day : 15; + FestivalScreens curr_screen; + bool cyc_through_all_acts; + bool showing_title; + bool festival_occurring; + +} festival_schedule_state_t; + +void festival_schedule_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void festival_schedule_face_activate(movement_settings_t *settings, void *context); +bool festival_schedule_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void festival_schedule_face_resign(movement_settings_t *settings, void *context); + +#define festival_schedule_face ((const watch_face_t){ \ + festival_schedule_face_setup, \ + festival_schedule_face_activate, \ + festival_schedule_face_loop, \ + festival_schedule_face_resign, \ + NULL, \ +}) + +#endif // FESTIVAL_SCHEDULE_FACE_H_ + diff --git a/utils/SpotifyActPopularitySorting/SpotifyActPopularitySorting.py b/utils/SpotifyActPopularitySorting/SpotifyActPopularitySorting.py new file mode 100644 index 000000000..e72dff21c --- /dev/null +++ b/utils/SpotifyActPopularitySorting/SpotifyActPopularitySorting.py @@ -0,0 +1,676 @@ +import spotipy +from spotipy.oauth2 import SpotifyClientCredentials +from unidecode import unidecode +import json +from bs4 import BeautifulSoup +from datetime import datetime +import urllib.request +import difflib +import pytz + +TZ = pytz.timezone('US/Central') # Clashfinder is in Central time + +MAKE_ARR_FILE = 1 # 0 = Don't make the file; 1= Make the file; 2 = Make the file and print it to the console +PRINT_RANKINGs = 1 +USE_TEST_ARR = 0 +PRINT_SEARCH_RESULTS = 1 +SORT_POP_BY_FOLLOWERS = 0 +GENRE_DEFAULT = "NO_GENRE" +STAGES = [ "RANCH_ARENA", "SHERWOOD_COURT", "TRIPOLEE", "CAROUSEL_CLUB", "OBSERVATORY", "HONEYCOMB"] +STAGE_DEFAULT = "NO_STAGE" +URL = "https://clashfinder.com/m/elecfor24/" + +junNine = [{'name': 'ACRAZE', 'followers': 127991, 'popularity': 63}, {'name': 'AK Sports', 'followers': 3347, 'popularity': 19}, + {'name': 'ALLEYCVT', 'followers': 37270, 'popularity': 41}, {'name': 'ATLiens', 'followers': 99093, 'popularity': 45}, + {'name': 'AYYBO', 'followers': 53574, 'popularity': 56}, {'name': 'Baggi', 'followers': 4171, 'popularity': 20}, + {'name': 'Barclay Crenshaw', 'followers': 31513, 'popularity': 35}, {'name': 'Ben Böhmer', 'followers': 499334, 'popularity': 63}, + {'name': 'Black Tiger Sex Machine', 'followers': 167574, 'popularity': 46}, {'name': 'Blastoyz', 'followers': 112730, 'popularity': 44}, + {'name': 'Boogie T', 'followers': 90700, 'popularity': 43}, {'name': 'Boogie T.rio', 'followers': 19383, 'popularity': 28}, + {'name': 'Brandi Cyrus', 'followers': 0, 'popularity': 0}, {'name': 'Calussa', 'followers': 8695, 'popularity': 45}, + {'name': 'CanaBliss', 'followers': 13543, 'popularity': 34}, {'name': 'Cannons', 'followers': 363542, 'popularity': 60}, + {'name': 'Caspa', 'followers': 68386, 'popularity': 31}, {'name': 'Cassian', 'followers': 57359, 'popularity': 53}, + {'name': 'Chaos in the CBD', 'followers': 102272, 'popularity': 47}, {'name': 'Charlotte De Witte', 'followers': 941867, 'popularity': 59}, + {'name': 'Chase & Status', 'followers': 921270, 'popularity': 68}, {'name': 'Coco & Breezy', 'followers': 20356, 'popularity': 45}, + {'name': 'Cuco', 'followers': 1059, 'popularity': 32}, {'name': 'Dave Yaden', 'followers': 668, 'popularity': 5}, + {'name': 'Dimension', 'followers': 127706, 'popularity': 61}, {'name': 'Dirtwire', 'followers': 97097, 'popularity': 42}, + {'name': 'The Disco Biscuits', 'followers': 86290, 'popularity': 33}, {'name': "Dixon's Violin", 'followers': 4937, 'popularity': 11}, + {'name': 'DJ Brownie', 'followers': 248, 'popularity': 5}, {'name': 'DJ Susan', 'followers': 32295, 'popularity': 32}, + {'name': 'DRAMA', 'followers': 144295, 'popularity': 56}, {'name': 'Dumpstaphunk', 'followers': 52008, 'popularity': 30}, + {'name': 'Eggy', 'followers': 10710, 'popularity': 28}, {'name': 'Emo Nite', 'followers': 0, 'popularity': 0}, + {'name': 'Equanimous', 'followers': 49140, 'popularity': 46}, {'name': 'Excision', 'followers': 728513, 'popularity': 59}, + {'name': 'Exclusive 6 In The Forest Celebration', 'followers': 0, 'popularity': 0}, {'name': 'G jones', 'followers': 91272, 'popularity': 37}, + {'name': 'Goodboys', 'followers': 67202, 'popularity': 66}, {'name': 'Green Velvet', 'followers': 231089, 'popularity': 52}, + {'name': 'H&RRY', 'followers': 0, 'popularity': 0}, {'name': 'Hamdi', 'followers': 53651, 'popularity': 55}, + {'name': 'Hiatus Kaiyote', 'followers': 629430, 'popularity': 56}, {'name': 'INZO', 'followers': 154911, 'popularity': 51}, + {'name': "it's murph", 'followers': 47756, 'popularity': 58}, {'name': 'Ivy Lab', 'followers': 85173, 'popularity': 40}, + {'name': 'Jason Leech', 'followers': 7228, 'popularity': 26}, {'name': 'Jjuujjuu', 'followers': 9052, 'popularity': 21}, + {'name': 'John Summit', 'followers': 304278, 'popularity': 71}, {'name': 'Juelz', 'followers': 31279, 'popularity': 43}, + {'name': 'Kallaghan', 'followers': 517, 'popularity': 16}, {'name': 'Kenny Beats', 'followers': 204558, 'popularity': 61}, + {'name': 'Kiltro', 'followers': 34151, 'popularity': 42}, {'name': 'Knock2', 'followers': 94524, 'popularity': 57}, + {'name': 'Layton Giordani', 'followers': 76037, 'popularity': 57}, {'name': 'Le Youth', 'followers': 113548, 'popularity': 52}, + {'name': 'League of Sound Disciples', 'followers': 0, 'popularity': 0}, {'name': 'Lettuce', 'followers': 212698, 'popularity': 41}, + {'name': 'LEVEL UP', 'followers': 36418, 'popularity': 44}, {'name': 'levity', 'followers': 35512, 'popularity': 45}, + {'name': 'Libianca', 'followers': 468493, 'popularity': 64}, {'name': 'Little stranger', 'followers': 57325, 'popularity': 49}, + {'name': 'LP Giobbi', 'followers': 75790, 'popularity': 59}, {'name': 'Lucii', 'followers': 67428, 'popularity': 42}, + {'name': 'Ludacris', 'followers': 2966337, 'popularity': 75}, {'name': 'LYNY', 'followers': 14540, 'popularity': 42}, + {'name': "Maddy O'Neal", 'followers': 18515, 'popularity': 35}, {'name': 'Major League Djz', 'followers': 850784, 'popularity': 49}, + {'name': 'marsh', 'followers': 70606, 'popularity': 51}, {'name': 'Mascolo', 'followers': 2373, 'popularity': 37}, + {'name': 'MASONIC', 'followers': 102, 'popularity': 0}, {'name': 'Matroda', 'followers': 126927, 'popularity': 57}, + {'name': 'Mau P', 'followers': 103816, 'popularity': 62}, {'name': 'Michaël Brun', 'followers': 49231, 'popularity': 49}, + {'name': 'Mojave Grey', 'followers': 3796, 'popularity': 25}, {'name': 'Moontricks', 'followers': 58681, 'popularity': 44}, + {'name': 'NEIL FRANCES', 'followers': 210548, 'popularity': 64}, {'name': 'Nelly Furtado', 'followers': 3882375, 'popularity': 75}, + {'name': 'Neoma', 'followers': 12670, 'popularity': 27}, {'name': 'ODEN & Fatzo', 'followers': 37472, 'popularity': 56}, + {'name': 'Only fire', 'followers': 41241, 'popularity': 38}, {'name': 'PAPERWATER', 'followers': 1449, 'popularity': 20}, + {'name': 'Peach Tree Rascals', 'followers': 228444, 'popularity': 54}, {'name': 'Politik', 'followers': 330, 'popularity': 6}, + {'name': 'Polyrhythmics', 'followers': 28934, 'popularity': 31}, {'name': 'Pretty Lights', 'followers': 559572, 'popularity': 49}, + {'name': 'Pretty Pink', 'followers': 45668, 'popularity': 47}, {'name': 'Próxima Parada', 'followers': 66700, 'popularity': 49}, + {'name': 'Ranger Trucco', 'followers': 10676, 'popularity': 32}, {'name': 'Rawayana', 'followers': 665608, 'popularity': 64}, + {'name': 'Rayben', 'followers': 42199, 'popularity': 40}, {'name': 'Redrum', 'followers': 2622, 'popularity': 13}, + {'name': 'Sammy Virji', 'followers': 109855, 'popularity': 61}, {'name': 'Sara Landry', 'followers': 201195, 'popularity': 52}, + {'name': 'Seven lions', 'followers': 488549, 'popularity': 58}, {'name': 'Shae District', 'followers': 2679, 'popularity': 15}, + {'name': 'Shaun Ross', 'followers': 5632, 'popularity': 41}, {'name': 'Slayyyter', 'followers': 398930, 'popularity': 53}, + {'name': 'The String Cheese Incident', 'followers': 226925, 'popularity': 41}, {'name': 'Subtronics', 'followers': 309365, 'popularity': 61}, + {'name': 'Sultan + Shepard', 'followers': 126480, 'popularity': 55}, {'name': 'Super Future', 'followers': 13470, 'popularity': 28}, + {'name': 'Swaylo', 'followers': 3, 'popularity': 0}, {'name': 'Thought process', 'followers': 8411, 'popularity': 29}, + {'name': 'Tripp St.', 'followers': 14955, 'popularity': 29}, {'name': 'TSHA', 'followers': 71923, 'popularity': 49}, + {'name': "Umphrey's McGee", 'followers': 201758, 'popularity': 41}, {'name': 'Unusual demont', 'followers': 43679, 'popularity': 41}, + {'name': 'venbee', 'followers': 79458, 'popularity': 55}, {'name': 'Vini Vici', 'followers': 528186, 'popularity': 62}, + {'name': 'Westend', 'followers': 47387, 'popularity': 57}, {'name': 'Whyte Fang', 'followers': 15233, 'popularity': 28}, + {'name': 'Will Clarke', 'followers': 50419, 'popularity': 42}, {'name': 'Wooli', 'followers': 113411, 'popularity': 56}, + {'name': 'Yoga', 'followers': 0, 'popularity': 0}, {'name': 'Zen Selekta', 'followers': 3820, 'popularity': 16}, + {'name': 'Gigantic NGHTMRE', 'followers': 432631, 'popularity': 55}, {'name': 'EVERYTHING ALWAYS', 'followers': 426118, 'popularity': 72}, + {'name': 'LSZEE', 'followers': 202854, 'popularity': 53}, {'name': 'VNSSA B2B Nala', 'followers': 16650, 'popularity': 37}, + {'name': 'Hyperbeam', 'followers': 56415, 'popularity': 56}] + +test_content = """ +

Thurs​day 20th June

+

Ranch Arena

+
15:00 - 16:00Brandi Cyrus
16:30 - 17:30Westend
18:00 - 19:00Drama
19:45 - 21:00Green Velvet
21:30 - 22:30Nelly Furtado
23:30 - 01:00Everything Always
+

Sherwood Court

+
17:00 - 18:00Maddy O'Neal
18:45 - 20:00Eggy
21:30 - 23:30The Disco Biscuits
00:45 - 02:00Knock 2
+

Tripolee

+
14:30 - 15:30Maesonic
16:30 - 17:45Chaos in the CBD
17:45 - 19:00VNSSA b2b Nala
19:00 - 20:15TSHA
20:15 - 21:30Sultan + Shepard
21:30 - 22:45Cassian
22:45 - 00:00Le Youth
00:00 - 01:30Ben Bohmer
+

Carousel Club

+
18:00 - 19:15Dumpstaphunk
19:45 - 21:00Lettuce
22:15 - 23:30Goodboys
00:30 - 02:00Emo Nite
+

The Observatory

+
16:30 - 17:30Dixon's Violin
18:00 - 19:00Redrum
19:00 - 20:00Zen Selecta
20:00 - 21:00Super Future
21:00 - 22:00Tripp St.
22:00 - 23:00Zingara
23:00 - 00:05TBA
00:30 - 01:45Marsh
+

Honeycomb

+
15:30 - 16:30H&RRY
17:00 - 18:00Moontricks
18:15 - 19:10Equanimous
19:30 - 20:30Levity
21:00 - 22:00SWAYLO
22:30 - 23:30Paperwater
23:45 - 01:00Politik
01:00 - 02:00Brandi Cyrus
+

Fri​day 21st June

+

Ranch Arena

+
19:00 - 20:15Ludacris
21:00 - 23:00The String Cheese Incident
00:00 - 02:00Pretty Lights
+

Sherwood Court

+
18:15 - 19:30Rawayana
20:15 - 21:30Cannons
22:45 - 23:45Whyte Fang
02:00 - 03:00Seven Lions
+

Tripolee

+
12:00 - 13:15Yoga
16:00 - 17:00Canabliss
17:00 - 18:15Alleycvt
18:15 - 19:15Level Up
19:15 - 20:30Ivy Lab
20:30 - 21:30Caspa
21:30 - 22:30Boogie T
22:30 - 23:45Dimension
23:45 - 00:45ATLiens
00:45 - 01:50Wooli
02:00 - 03:15Black Tiger Sex Machine
+

Carousel Club

+
17:30 - 18:30Neoma
19:00 - 20:00jjuujjuu
20:30 - 21:30DJ Susan
22:00 - 23:15Oden & Fatzo
02:00 - 03:15It's Murph
+

The Observatory

+
19:15 - 20:15Baggi
20:15 - 21:30SWAYLO
21:30 - 22:45Pretty Pink
22:45 - 00:15Vini Vici
00:15 - 01:45Blastoyz
02:00 - 03:15Layton Giordani
+

Honeycomb

+
14:30 - 15:30Humanity Circle
16:05 - 18:00Rumble in the Bumble
19:00 - 20:15Boogie T.Rio
20:45 - 22:00Westend
22:30 - 23:45Sultan + Shepard
00:15 - 01:15DJ Susan
01:45 - 02:55DJ Brownie
+

Sat​urday 22nd June

+

Ranch Arena

+
17:30 - 18:45Kenny Beats
19:30 - 23:30The String Cheese Incident
00:15 - 01:45Subtronics
+

Sherwood Court

+
17:15 - 18:30Polyrhythmics
19:15 - 20:15Juelz
21:30 - 22:30Barclay Crenshaw
23:00 - 00:15G Jones
01:45 - 03:00LSZEE (CloZee + LSDream)
03:45 - 04:30Unusual Demont
+

Tripolee

+
12:00 - 13:10Yoga
17:00 - 18:00Cardio
18:00 - 19:15Ranger Trucco
19:15 - 20:30Luci
20:30 - 21:45It's Murph
21:45 - 23:00Will Clarke
23:00 - 00:15Sammy Virji
00:15 - 01:45Mau P
01:45 - 03:15John Summit
+

Carousel Club

+
18:00 - 19:00Libianca
19:30 - 20:45Peach Tree Rascals
21:15 - 22:15Unusual Demont
23:00 - 00:00Neil Frances
00:30 - 01:45Hiatus Kaiyote
+

The Observatory

+
17:00 - 18:00RAYBEN
18:30 - 19:30Kiltro
19:45 - 20:45Cuco (DJ Set)
20:45 - 21:45Calussa
21:45 - 22:45Michael Brun
22:45 - 23:45Major League DJz
23:45 - 01:00AK Sports
01:00 - 02:00Jenna Shaw
02:00 - 03:15Sara Landry
+

Honeycomb

+
15:30 - 16:30Femme Identifying Circle
17:00 - 19:00Rumble in the Bumble
19:15 - 20:00Dixon's Violin
21:30 - 22:30Mascolo
23:00 - 00:00Mojave Grey
00:30 - 02:30LP Giobbi (Dead House Set)
+

Sun​day 23rd June

+

Ranch Arena

+
17:00 - 18:00Dirtwire
18:45 - 20:30Umphrey's Mcgee
21:15 - 22:45Gigantic NGHTMRE
23:30 - 01:00Excision
+

Sherwood Court

+
15:30 - 16:45Lightcode by LSDream
17:30 - 18:30Little Stranger
20:15 - 21:30Inzo
22:00 - 23:15Chase & Status
00:00 - 01:15Charlotte De Witte presents Overdrive
+

Tripolee

+
12:00 - 13:15Yoga
18:15 - 19:30Ayybo
19:30 - 20:45Coco & Breezy
20:45 - 22:00Odd Mob & Omnom present Hyperbeam
22:00 - 23:00acraze
23:00 - 00:15Matroda
00:30 - 02:00Closing Party
+

Carousel Club

+
16:30 - 18:00Pride Party
18:00 - 19:15Only Fire
19:45 - 21:00Shaun Ross
21:30 - 22:30Slayyyter
23:00 - 00:30LP Giobbi
+

The Observatory

+
16:45 - 17:45Shae District
18:15 - 19:15Thought Process
19:30 - 20:30Levity
20:45 - 21:45LYNY
22:00 - 22:55Venbee
23:15 - 00:30Hamdi
01:00 - 02:00Dixon's Violin
+

Honeycomb

+
17:45 - 18:45Proxima Parada
19:15 - 20:15Jason Leech
20:30 - 21:30Maddy O'Neal
22:00 - 23:00Kallaghan
01:00 - 02:00acraze
+
+""" + +# https://old.reddit.com/r/ElectricForest/comments/1bqbwlv/electric_forest_2024_lineup_broken_down_by_genre/ +dicto = {"BASS" : ["Barclay Crenshaw", + "Whyte Fang", + "LSZEE", + "Seven lions", + "Pretty Lights", + "Zingara"], + "DUBSTEP": ["ALLEYCVT", + "ATLiens", + "Black Tiger Sex Machine", + "Caspa", + "Excision", + "Gigantic NGHTMRE", + "Hamdi", + "LEVEL UP", + "Lucii", + "LYNY", + "Subtronics", + "Boogie T", + "CanaBliss", + "levity", + "Hyperbeam", + ], + "DnB": ["Chase & Status", + "Dimension", + "AK Sports", + "Sammy Virji", + "venbee", + "Wooli", + "G jones", + "Ivy Lab", + "Super Future", + "Zen Selekta",], + "HOUSE": ["ACRAZE", + "AYYBO", + "Ben Böhmer", + "Calussa", + "Cassian", + "EVERYTHING ALWAYS", + "Green Velvet", + "John Summit", + "Knock2", + "Major League Djz", + "Matroda", + "Mau P", + "ODEN & Fatzo", + "Ranger Trucco", + "TSHA", + "VNSSA B2B Nala", + "Westend", + "Will Clarke", + "Baggi", + "Brandi Cyrus", + "Chaos in the CBD", + "H&RRY", + "marsh", + "MASONIC", + "Mojave Grey", + "Only fire", + "Rayben", + "Shae District", + "Swaylo", + "DJ Brownie", + "Kallaghan", + "Pretty Pink",], + "DANCE": ["Coco & Breezy", + "DRAMA", + "it's murph", + "LP Giobbi", + "Michaël Brun", + "DJ Susan", + "Jason Leech", + "Shaun Ross",], + "TECHNO" :["Charlotte De Witte", + "Sara Landry", + "Layton Giordani", + "Vini Vici", + "Jenna Shaw", + "Blastoyz",], + "INDIE": ["Cuco", + "Cannons", + "NEIL FRANCES", + "Peach Tree Rascals", + "Emo Nite", + "Equanimous", + "Kiltro", + "Goodboys",], + "POP": ["Mascolo", + "Nelly Furtado", + "Slayyyter", + "Neoma", + "Unusual demont",], + "JAM": ["Dirtwire", + "Dumpstaphunk", + "Eggy", + "Lettuce", + + "The Disco Biscuits", + "The String Cheese Incident", + "Umphrey's McGee", + "Jjuujjuu", + "Próxima Parada", + "League of Sound Disciples", + "Boogie T.rio", + "Dave Yaden"], + "CHILL": ["INZO", + "Juelz", + "Maddy O'Neal", + "Redrum", + "Thought process", + "Tripp St.", + "Politik", + "Le Youth", + "Sultan + Shepard", + ], + "RAP": ["Kenny Beats", + "Libianca", + "Ludacris", + "PAPERWATER", + "Little stranger",], + "SOUL": ["Dixon's Violin", + "Rawayana", + "Hiatus Kaiyote", + "Polyrhythmics", + "Moontricks"], + GENRE_DEFAULT : ["Yoga", + "Rumble in the Bumble", + "Exclusive: 6 in the Forest", + "Pride Party", + "Cardio", + "Closing Party", + "Femme Identifying Circle", + "Humanity Circle", + "Lightcode by LSDream", + "TBA" + ] + } + + +duoActs = {"Gigantic NGHTMRE" : ["Big Gigantic","NGHTMRE"], + "EVERYTHING ALWAYS" : ["Dom Dolla","John Summit"], + "LSZEE": ["CloZee","LSDREAM"], + "VNSSA B2B Nala" : ["VNSSA","Nala"], + "Hyperbeam" : ["odd Mob", "Omnom"]} + +forClashFinder = {"Odd Mob & Omnom present Hyperbeam" : "Hyperbeam"} # k:v is name in clash finder : name in genre list + +def get_client_credentials(file_path="creds.json"): + try: + with open(file_path, 'r') as file: + data = json.load(file) + client_id = data['client_id'] + client_secret = data['client_secret'] + return client_id, client_secret + except (KeyError, FileNotFoundError, json.decoder.JSONDecodeError) as e: + print(f"No Spotify credentials found. {e}. Setting all acts to a popularity of 0.") + return None, None + +def get_artist(artist_name, client_id, client_secret): + first_match = False + if "'" in artist_name: + first_match = True + artist_name = artist_name.replace("'", "") + + # Authenticate with Spotify + auth_manager = SpotifyClientCredentials(client_id=client_id, client_secret=client_secret) + sp = spotipy.Spotify(auth_manager=auth_manager) + + + # Search for the artist + result = sp.search(q='artist:' + artist_name, type='artist') + artist_results = result['artists']['items'] + if not artist_results: + if PRINT_SEARCH_RESULTS: + print(f"FAILED TO FIND: {artist_name}") + return None + artist_info = None + names_in_search = [] + for option in artist_results: + names_in_search.append(option['name'] ) + dupes = names_in_search.count(artist_name) + if PRINT_SEARCH_RESULTS and dupes > 1: + print(f"{dupes} EXACT MATCHES FOUND FOR: {artist_name}: {names_in_search}") + for option in artist_results: + name = option['name'] + if first_match: + if PRINT_SEARCH_RESULTS: + print(f"Best for {artist_name} is {name} | Choices were {names_in_search}") + artist_info = option + break + if name.upper() == artist_name.upper(): + if PRINT_SEARCH_RESULTS and dupes <= 1: + print(f"Found {name} | Choices were {names_in_search}") + artist_info = option + break + if artist_info is None: + if PRINT_SEARCH_RESULTS: + print(f"FAILED TO FIND: {artist_name}, BUT FOUND {names_in_search}") + return None + return artist_info + +def get_artist_followers_popularity(artist_name, client_id, client_secret): + if findGenre(artist_name) == GENRE_DEFAULT: + return 0,0 + artist_info = get_artist(artist_name, client_id, client_secret) + if artist_info is None: + return 0,0 + followers = artist_info['followers']['total'] + popularity = artist_info['popularity'] + return followers, popularity + +def strip_word(text, words_to_remove): + words = text.split() + stripped_words = [word for word in words if word.lower() not in map(str.lower, words_to_remove)] + stripped_text = ' '.join(stripped_words) + return stripped_text + +def rank_artists_by_popularity(artists): + # Sort artists by popularity in descending order + sorted_artists = sorted(artists, key=lambda x: x['followers'], reverse=True) + + # Create a dictionary to store the ranks + artist_ranks = {} + + # Assign ranks starting from 1 + for rank, artist in enumerate(sorted_artists, start=1): + artist_ranks[artist['name']] = rank + + return artist_ranks + +def findGenre(act): + for key in dicto: + for i in dicto[key]: + if i.upper() == act.upper(): + return key + return "GENRE_COUNT" + +def get_ranking(artists, name): + for artist in artists: + if artist['name'].lower() == name.lower(): + if artist['popularity'] == 0: + return 0 + return artist['overall'] + return 0 + +def set_pop_follow_manually(artists, name, pop, follower): + for artist in artists: + if artist['name'].lower() == name.lower(): + artist['popularity'] = pop + artist['followers'] = follower + return artists + +def convert_timestamp(ts): + ts = datetime.fromtimestamp(ts / 1000, TZ) + return ts + +def get_html_data(content): + fullData = {} + # Parsing the HTML + soup = BeautifulSoup(content, 'html.parser') + day_div = soup.find_all('div', class_='day') + for day in day_div: + # Extracting stages and acts + stages = day.find_all('div', class_='stage') + for stage in stages: + stage_name = stage.find('h3', class_='stageName').text.strip() + acts = stage.find_all('div', class_='act') + for act in acts: + artist = act.find('span', class_='actNm').text.strip() + + start_time = convert_timestamp(int(act['data-start-time'])) + end_time = convert_timestamp(int(act['data-end-time'])) + act_info = {"stage" : stage_name, "start_time" : start_time, "end_time" : end_time} + if artist in fullData: # For when an act has multiple sets + if isinstance(fullData[artist], dict): + fullData[artist] = [fullData[artist]] + fullData[artist].append(act_info) + else: + fullData[artist] = act_info + return fullData + +def get_url_content(url): + fp = urllib.request.urlopen(url) + mybytes = fp.read() + mystr = mybytes.decode("utf8") + fp.close() + return mystr + +def getFullArray(listActs): + # Spits out the array of acts in alphabetically order + duo_mult = 1.2 # Since duos are more hype, add a multiplier to their popularity and follower averages + listActsPop = [] + listActsInListDuos = [] + client_id, client_secret = get_client_credentials() + if client_id is None and client_secret is None: # If there aren't any Spotify credentials, just set the popularity of all acts to zero + for act in listActs: + listActsPop.append({'name':act, 'followers' : 0, "popularity" : 0}) + return listActsPop + for act in listActs: + act_spot = duoActs.get(act, act) + if isinstance(act_spot,list): + listActsInListDuos.append(act) + continue # We'll get and average the duos later. + followers, popularity = get_artist_followers_popularity(act_spot, client_id, client_secret) + listActsPop.append({'name':act, 'followers' : followers, "popularity" : popularity}) + # This logic is to average duos + for duo in duoActs: + if duo not in listActsInListDuos: + continue + artists = duoActs[duo] + if not isinstance(artists,list): + continue + followers = 0 + popularity = 0 + for artist in artists: + fol, pop = get_artist_followers_popularity(artist, client_id, client_secret) + followers += fol + popularity += pop + followers = int((followers / len(artists)) * duo_mult) + popularity = int((popularity / len(artists)) * duo_mult) + listActsPop.append({'name':duo, 'followers' : followers, "popularity" : popularity}) + listActsPop = set_pop_follow_manually(listActsPop, "Cuco", 32, 1059) # Wrong Cuco is the first option in Spotify query. + return listActsPop + +def dates_to_act(act, day_info, genre_list, element = 0): + defaultStart = datetime(2024, 6, 20, 15, 0) + defaultEnd = datetime(2024, 6, 20, 16, 0) + if act not in genre_list: + return STAGE_DEFAULT, defaultStart, defaultEnd, 1 + + datInfoAct = day_info[act] + timesTheyPlay = 1 + if isinstance(datInfoAct, list): + timesTheyPlay = len(datInfoAct) + datInfoAct = datInfoAct[element] + stageToSearch = datInfoAct.get("stage") + if stageToSearch is None: + stage = None + else: + stageToSearch = stageToSearch.upper() + stage = difflib.get_close_matches(stageToSearch, STAGES, n=1) + if not stage: + stage = f"[{stageToSearch}]" # Returns the stage found, around curly brackets if it couldn't be matched in the list + else: + stage = stage[0] + return stage, datInfoAct["start_time"], datInfoAct["end_time"], timesTheyPlay + + +def slow_sort(popList): + sortedFully = False + pop_priority = 0.4 # Sorts the acts combining the followers and popularity and weighting the popularity by this amount. + maxPop = 0 + maxFol = 0 + for pop in popList: + maxPop = max(maxPop, pop['popularity']) + maxFol = max(maxFol, pop['followers']) + if maxPop == 0: + return popList + fol_pop_ratio = int((pop_priority / (1 - pop_priority)) * (maxFol / maxPop)) + print(f"Ratio for prioritizing followers to popularity: {fol_pop_ratio} followers for 1 pop") + while not sortedFully: + sortedFully = True + for i, _ in enumerate(popList): + if i + 1 == len(popList): + break + scoreCurr = (pop_priority * (popList[i]['popularity']/maxPop)) + ((1 - pop_priority) * (popList[i]['followers']/maxFol)) + scoreNext = (pop_priority * (popList[i+1]['popularity']/maxPop)) + ((1 - pop_priority) * (popList[i+1]['followers']/maxFol)) + if scoreNext > scoreCurr: + sortedFully = False + popList[i+1], popList[i] = popList[i], popList[i+1] + return popList + + +def print_md_lst(sorted_listing, genre_list): + longestNum = 5 + longestAct = 27 + longestPop = 15 + longestFol = 10 + longestStg = 15 + stageDict = {} + for item in sorted_listing: + stage, _, _, _ = dates_to_act(item['name'], day_info, genre_list) + stageDict[item['name']] = stage + longestAct = max(longestAct, len(item['name'])) + longestPop = max(longestPop, len(str(item['popularity']))) + longestFol = max(longestFol, len(str(item['followers']))) + longestStg = max(longestStg, len(stage)) + numTitle = "Num" + actTitle = "Act" + popTitle = "Popularity" + folTitle = "Followers" + stgTitle = "Stage" + print(f"| {numTitle: ^{longestNum}} | {actTitle: ^{longestAct}} | {popTitle : ^{longestPop}} | {folTitle : ^{longestFol}} | {stgTitle : ^{longestStg}} |") + print(f"| {'-' * longestNum} | {'-' * longestAct} | {'-' * longestPop} | {'-' * longestFol} | {'-' * longestStg} |") + for num, item in enumerate(sorted_listing): + act = item['name'] + popularity = item['popularity'] + followers = item['followers'] + stage = stageDict[act] + print(f"| {num + 1 : ^{longestNum}} | {act : ^{longestAct}} | {popularity : ^{longestPop}} | {followers : ^{longestFol}} | {stage : ^{longestStg}} |") + + +def get_name_to_display(act, i=0): + actToDisp = unidecode(act) + actToDisp = strip_word(actToDisp,["The"]) + actToDisp = f"{actToDisp.upper()[:6]: <6}" + if act == "Yoga": + actToDisp = f" YOGA{i+1}" + elif act == "Pretty Lights": + actToDisp = "PRETYL" + elif act == "Pretty Pink": + actToDisp = "PRETYP" + elif act == "TBA": + actToDisp = " TBA " + elif act == "it's murph": + actToDisp = "MURPH" + elif act == "Exclusive: 6 in the Forest": + actToDisp = "6,nFor" + return actToDisp + +def writeAndPrint(file, text): + file.write(f"{text}\n") + if MAKE_ARR_FILE == 2: + print(text) + +def print_array_for_watch(listActs, sorted_listing, day_info, filename, genre_list): + with open(f'{filename}.txt', 'w') as f: + artistDateNotFound = [] + writeAndPrint(f, "// Genre - https://old.reddit.com/r/ElectricForest/comments/1bqbwlv/electric_forest_2024_lineup_broken_down_by_genre/") + writeAndPrint(f, f"// Line-up - {URL}") + writeAndPrint(f, '#include "festival_schedule_face.h"') + writeAndPrint(f, "") + totalActs = 0 + listActsPrint = [] + for act in listActs: + stage, start, end, timesPerformed = dates_to_act(act, day_info, genre_list, element=0) + for i in range(timesPerformed): + stage, start, end, timesPerformed = dates_to_act(act, day_info, genre_list, element=i) + if stage == STAGE_DEFAULT: + artistDateNotFound.append(act) + actToDisp = get_name_to_display(act, i) + listActsPrint.append({'dispAct': actToDisp, 'act': act, 'start':start, 'end':end, 'stage':stage}) + totalActs += 1 + listActsPrint = sorted(listActsPrint, key=lambda d: d['dispAct'].lstrip().lower()) + writeAndPrint(f, f"#define NUM_ACTS {totalActs}") + writeAndPrint(f, "") + writeAndPrint(f, "const schedule_t festival_acts[NUM_ACTS + 1]=") + writeAndPrint(f, "{") + for actData in listActsPrint: + writeAndPrint(f, " {") + writeAndPrint(f, f' .artist = "{actData["dispAct"]}",') + writeAndPrint(f, f' .stage = {actData["stage"].upper()},') + writeAndPrint(f, f' .start_time = {{.unit.year = {actData["start"].year - 2020}, .unit.month = {actData["start"].month}, .unit.day = {actData["start"].day}, .unit.hour = {actData["start"].hour}, .unit.minute = {actData["start"].minute}}},') + writeAndPrint(f, f' .end_time = {{.unit.year = {actData["end"].year - 2020}, .unit.month = {actData["end"].month}, .unit.day = {actData["end"].day}, .unit.hour = {actData["end"].hour}, .unit.minute = {actData["end"].minute}}},') + writeAndPrint(f, f' .genre = {findGenre(actData["act"])},') + writeAndPrint(f, f' .popularity = {get_ranking(sorted_listing, actData["act"])}') + writeAndPrint(f, " },") + writeAndPrint(f, ' [NUM_ACTS] = { //Fall back') + writeAndPrint(f, ' .artist = "No Act",') + writeAndPrint(f, ' .stage = STAGE_COUNT,') + writeAndPrint(f, ' .start_time = {.unit.year = 0, .unit.month = 0, .unit.day = 0, .unit.hour = 0, .unit.minute = 0},') + writeAndPrint(f, ' .end_time = {.unit.year = 63, .unit.month = 15, .unit.day = 31, .unit.hour = 31, .unit.minute = 63},') + writeAndPrint(f, ' .genre = GENRE_COUNT,') + writeAndPrint(f, ' .popularity = 0') + writeAndPrint(f, ' }') + writeAndPrint(f, '};') + + if PRINT_SEARCH_RESULTS and artistDateNotFound: + print(f"\nFAILED TO FIND DATE INFO FOR {artistDateNotFound}\n") + + +if __name__ == "__main__": + html_str = test_content if USE_TEST_ARR else get_url_content(URL) + day_info = get_html_data(html_str) + day_info = {key.split(" (")[0]: value for key, value in day_info.items()} + + listActs = [] + for key in dicto: + for i in dicto[key]: + listActs.append(i) + listActs = sorted(listActs, key=lambda x: x.lower().replace("the ","")) + in_genre_list = [] + not_in_genre_list = [] + + day_info_keys = day_info.keys() + + for key in list(day_info_keys): + if key in forClashFinder.keys(): + val = forClashFinder[key] + day_info[val] = day_info.pop(key) + + for actDate in list(day_info_keys): + actInDates = difflib.get_close_matches(actDate.upper(), list(map(lambda x: x.upper(), listActs)), n=1) + if actInDates: + actInDates = actInDates[0] + for key1 in listActs: + if key1.upper() == actInDates: + day_info[key1] = day_info.pop(actDate) + in_genre_list.append(key1) + break + else: + not_in_genre_list.append(actDate) + + in_genre_list = sorted(in_genre_list, key=lambda x: x.lower().replace("the ", "") if x[0].startswith("the ") else x[0].lower()) + not_in_genre_list = sorted(not_in_genre_list, key=lambda x: x.lower().replace("the ", "") if x[0].startswith("the ") else x[0].lower()) + listActsPop = junNine if USE_TEST_ARR else getFullArray(listActs) + listActsPopMissing = [] if USE_TEST_ARR else getFullArray(not_in_genre_list) + + if SORT_POP_BY_FOLLOWERS: + sortKey = lambda x: (x['followers']) + sorted_listing = sorted(listActsPop, key=sortKey, reverse=True) + sorted_listing_missing = sorted(listActsPopMissing, key=sortKey, reverse=True) + else: + sortKey = lambda x: (x['popularity'], x['followers']) # Sort by Spotify popularity w/ followers being the tie-breaker + sorted_listing = sorted(listActsPop, key=sortKey, reverse=True) + sorted_listing = slow_sort(sorted_listing) + sorted_listing_missing = sorted(listActsPopMissing, key=sortKey, reverse=True) + sorted_listing_missing = slow_sort(sorted_listing_missing) + + for i, artist in enumerate(sorted_listing): + artist['overall'] = i + 1 + + + for i, artist in enumerate(sorted_listing_missing): + artist['overall'] = i + 1 + + if PRINT_RANKINGs: + print_md_lst(sorted_listing, in_genre_list) + if sorted_listing_missing: + print("\r\n\r\nAND THESE WERE MISSING") + print_md_lst(sorted_listing_missing, not_in_genre_list) + + if MAKE_ARR_FILE: + print_array_for_watch(listActs, sorted_listing, day_info ,"festival_schedule_arr", in_genre_list) + if sorted_listing_missing: + print_array_for_watch(not_in_genre_list, sorted_listing_missing, day_info, "missing_festival_schedule_arr", not_in_genre_list) \ No newline at end of file diff --git a/utils/SpotifyActPopularitySorting/creds -example.json b/utils/SpotifyActPopularitySorting/creds -example.json new file mode 100644 index 000000000..4d3b14e3a --- /dev/null +++ b/utils/SpotifyActPopularitySorting/creds -example.json @@ -0,0 +1,4 @@ +{ + "client_id": "REDACTED", + "client_secret": "REDACTED" +} \ No newline at end of file diff --git a/utils/SpotifyActPopularitySorting/requirements.txt b/utils/SpotifyActPopularitySorting/requirements.txt new file mode 100644 index 000000000..7d2a84749 --- /dev/null +++ b/utils/SpotifyActPopularitySorting/requirements.txt @@ -0,0 +1,4 @@ +beautifulsoup4==4.9.3 +pytz==2020.1 +spotipy==2.24.0 +Unidecode==1.2.0