From 251248adfab36b061eb8fdd08aa54b318462da65 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Wed, 28 Mar 2018 15:49:50 -0400 Subject: Revamped bot with new algorithm The percentages currently used by the generator are trial-and-error and may be changed in the future. refs #1 --- cadence.cpp | 49 +++++++++++---------- generator/CMakeLists.txt | 2 +- generator/generator.cpp | 109 ++++++++++++++++++++++++++++++++++++----------- generator/mood.cpp | 94 ---------------------------------------- generator/mood.h | 60 -------------------------- generator/schema.sql | 10 ++++- vendor/hkutil | 2 +- 7 files changed, 122 insertions(+), 204 deletions(-) delete mode 100644 generator/mood.cpp delete mode 100644 generator/mood.h diff --git a/cadence.cpp b/cadence.cpp index 704f946..97a74b7 100644 --- a/cadence.cpp +++ b/cadence.cpp @@ -13,8 +13,6 @@ #include #include -const std::vector moods = {"party", "chill", "crazy", "happy", "sad", "instrumental", "vocal"}; - int main(int argc, char** argv) { if (argc != 2) @@ -81,21 +79,25 @@ int main(int argc, char** argv) { std::cout << "Generating tweet..." << std::endl; - std::string mood = moods[std::uniform_int_distribution(0, moods.size()-1)(random_engine)]; + hatkirby::row songRow = + abdb.queryFirst( + "SELECT song_id, title, artist FROM songs ORDER BY RANDOM() LIMIT 1"); - hatkirby::row song = + hatkirby::row moodRow = abdb.queryFirst( - "SELECT title, artist FROM songs WHERE category = ? ORDER BY RANDOM() LIMIT 1", - { mood }); + "SELECT mood FROM moods WHERE song_id = ? ORDER BY RANDOM() LIMIT 1", + { mpark::get(songRow[0]) }); - std::string songTitle = mpark::get(song[0]); - std::string songArtist = mpark::get(song[1]); + std::string songTitle = mpark::get(songRow[1]); + std::string songArtist = mpark::get(songRow[2]); + std::string mood = mpark::get(moodRow[0]); std::string action = "{" + hatkirby::uppercase(mood) + "}"; int tknloc; while ((tknloc = action.find("{")) != std::string::npos) { std::string token = action.substr(tknloc+1, action.find("}")-tknloc-1); + std::string modifier; int modloc; if ((modloc = token.find(":")) != std::string::npos) @@ -104,10 +106,7 @@ int main(int argc, char** argv) token = token.substr(0, modloc); } - std::string canontkn; - std::transform(std::begin(token), std::end(token), std::back_inserter(canontkn), [] (char ch) { - return std::toupper(ch); - }); + std::string canontkn = hatkirby::uppercase(token); std::string result; if (canontkn == "\\N") @@ -125,18 +124,23 @@ int main(int argc, char** argv) result = group[dist(random_engine)]; } else { - std::cout << "Badly formatted forms file:" << std::endl; - std::cout << "No such form as " + canontkn << std::endl; - - return 1; + throw std::logic_error("No such form as " + canontkn); } if (modifier == "indefinite") { - if ((result.length() > 1) && (isupper(result[0])) && (isupper(result[1]))) + if ( + (result.length() > 1) && + isupper(result[0]) && + isupper(result[1])) { result = "an " + result; - } else if ((result[0] == 'a') || (result[0] == 'e') || (result[0] == 'i') || (result[0] == 'o') || (result[0] == 'u')) + } else if ( + (result[0] == 'a') || + (result[0] == 'e') || + (result[0] == 'i') || + (result[0] == 'o') || + (result[0] == 'u')) { result = "an " + result; } else { @@ -147,9 +151,7 @@ int main(int argc, char** argv) std::string finalresult; if (islower(token[0])) { - std::transform(std::begin(result), std::end(result), std::back_inserter(finalresult), [] (char ch) { - return std::tolower(ch); - }); + finalresult = hatkirby::lowercase(result); } else if (isupper(token[0]) && !isupper(token[1])) { auto words = hatkirby::split>(result, " "); @@ -158,7 +160,10 @@ int main(int argc, char** argv) word[0] = std::toupper(word[0]); } - finalresult = hatkirby::implode(std::begin(words), std::end(words), " "); + finalresult = hatkirby::implode( + std::begin(words), + std::end(words), + " "); } else { finalresult = result; } diff --git a/generator/CMakeLists.txt b/generator/CMakeLists.txt index dffbe0e..8031f6a 100644 --- a/generator/CMakeLists.txt +++ b/generator/CMakeLists.txt @@ -9,7 +9,7 @@ include_directories( vendor/json ../vendor/hkutil) -add_executable(generator mood.cpp generator.cpp main.cpp) +add_executable(generator generator.cpp main.cpp) set_property(TARGET generator PROPERTY CXX_STANDARD 11) set_property(TARGET generator PROPERTY CXX_STANDARD_REQUIRED ON) target_link_libraries(generator ${sqlite3_LIBRARIES}) diff --git a/generator/generator.cpp b/generator/generator.cpp index 19eba70..4244fd2 100644 --- a/generator/generator.cpp +++ b/generator/generator.cpp @@ -6,7 +6,6 @@ #include #include #include -#include "mood.h" namespace cadence { namespace generator { @@ -93,7 +92,8 @@ namespace cadence { DIR* subdir; if ((subdir = opendir(directory.c_str())) == nullptr) { - throw std::invalid_argument("Invalid AcousticBrainz data directory"); + throw std::invalid_argument( + "Invalid AcousticBrainz data directory"); } struct dirent* subent; @@ -106,7 +106,8 @@ namespace cadence { DIR* subsubdir; if ((subsubdir = opendir(subdirectory.c_str())) == nullptr) { - throw std::invalid_argument("Invalid AcousticBrainz data directory"); + throw std::invalid_argument( + "Invalid AcousticBrainz data directory"); } struct dirent* subsubent; @@ -149,27 +150,87 @@ namespace cadence { try { - std::vector moods; - moods.emplace_back(mood::type::danceable, jsonData["highlevel"]["danceability"]["all"]["danceable"]); - moods.emplace_back(mood::type::acoustic, jsonData["highlevel"]["mood_acoustic"]["all"]["acoustic"]); - moods.emplace_back(mood::type::aggressive, jsonData["highlevel"]["mood_aggressive"]["all"]["aggressive"]); - moods.emplace_back(mood::type::electronic, jsonData["highlevel"]["mood_electronic"]["all"]["electronic"]); - moods.emplace_back(mood::type::happy, jsonData["highlevel"]["mood_happy"]["all"]["happy"]); - moods.emplace_back(mood::type::party, jsonData["highlevel"]["mood_party"]["all"]["party"]); - moods.emplace_back(mood::type::relaxed, jsonData["highlevel"]["mood_relaxed"]["all"]["relaxed"]); - moods.emplace_back(mood::type::sad, jsonData["highlevel"]["mood_sad"]["all"]["sad"]); - moods.emplace_back(mood::type::instrumental, jsonData["highlevel"]["voice_instrumental"]["all"]["instrumental"]); - - std::sort(std::begin(moods), std::end(moods), [] (const mood& left, const mood& right) -> bool { - return left.getProbability() > right.getProbability(); - }); - - std::list columns; - columns.emplace_back("title", jsonData["metadata"]["tags"]["title"][0].get()); - columns.emplace_back("artist", jsonData["metadata"]["tags"]["artist"][0].get()); - columns.emplace_back("category", moods.front().getCategory()); - - db_.insertIntoTable("songs", std::move(columns)); + auto& hl = jsonData["highlevel"]; + + double danceable = hl["danceability"]["all"]["danceable"]; + double acoustic = hl["mood_acoustic"]["all"]["acoustic"]; + double aggressive = hl["mood_aggressive"]["all"]["aggressive"]; + double electronic = hl["mood_electronic"]["all"]["electronic"]; + double happy = hl["mood_happy"]["all"]["happy"]; + double party = hl["mood_party"]["all"]["party"]; + double relaxed = hl["mood_relaxed"]["all"]["relaxed"]; + double sad = hl["mood_sad"]["all"]["sad"]; + double instrumental = hl["voice_instrumental"]["all"]["instrumental"]; + + std::string title = jsonData["metadata"]["tags"]["title"][0]; + std::string artist = jsonData["metadata"]["tags"]["artist"][0]; + + uint64_t songId = db_.insertIntoTable( + "songs", + { + { "title", title }, + { "artist", artist } + }); + + std::list moods; + + // ~38% + if ((party > 0.5) || (danceable > 0.75)) + { + moods.push_back("party"); + } + + // ~38% + if ((relaxed > 0.81) || (acoustic > 0.5)) + { + moods.push_back("chill"); + } + + // ~42% + if ((aggressive > 0.5) || (electronic > 0.95)) + { + moods.push_back("crazy"); + } + + // ~30% + if (happy > 0.5) + { + moods.push_back("happy"); + } + + // ~30% + if (sad > 0.5) + { + moods.push_back("sad"); + } + + // ~38% + if (instrumental > 0.9) + { + moods.push_back("instrumental"); + } + + // ~34% + if (instrumental < 0.2) + { + moods.push_back("vocal"); + } + + // ~1% + if (moods.empty()) + { + moods.push_back("unknown"); + } + + for (const std::string& mood : moods) + { + db_.insertIntoTable( + "moods", + { + { "song_id", static_cast(songId) }, + { "mood", mood } + }); + } } catch (const std::domain_error& ex) { // Weird data. Ignore silently. diff --git a/generator/mood.cpp b/generator/mood.cpp deleted file mode 100644 index b74211c..0000000 --- a/generator/mood.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "mood.h" - -namespace cadence { - namespace generator { - - // The categories are: - // - party (+danceable, -acoustic, +electronic, +party) = ~.21 - // - chill (-danceable, +acoustic, -aggressive, -electronic, -party, +relaxed) = ~.49 - // - crazy (+aggressive, -relaxed) = ~.02 - // - happy (+happy, -sad) = ~.009 - // - sad (-happy, +sad) = ~.02 - // - instrumental (+instrumental) = ~.12 - // - vocal (-instrumental) = ~.10 - - mood::mood(type t, double prob) : type_(t) - { - if (prob >= 0.5) - { - probability_ = prob; - positive_ = true; - } else { - probability_ = 1.0 - prob; - positive_ = false; - } - - switch (t) - { - case type::danceable: - { - category_ = (positive_ ? "party" : "chill"); - - break; - } - - case type::acoustic: - { - category_ = (positive_ ? "chill" : "party"); - - break; - } - - case type::aggressive: - { - category_ = (positive_ ? "crazy" : "chill"); - - break; - } - - case type::electronic: - { - category_ = (positive_ ? "party" : "chill"); - - break; - } - - case type::happy: - { - category_ = (positive_ ? "happy" : "sad"); - - break; - } - - case type::party: - { - category_ = (positive_ ? "party" : "chill"); - - break; - } - - case type::relaxed: - { - category_ = (positive_ ? "chill" : "crazy"); - - break; - } - - case type::sad: - { - category_ = (positive_ ? "sad" : "happy"); - - break; - } - - case type::instrumental: - { - category_ = (positive_ ? "instrumental" : "vocal"); - - break; - } - } - } - - }; -}; diff --git a/generator/mood.h b/generator/mood.h deleted file mode 100644 index c36e5ee..0000000 --- a/generator/mood.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef MOOD_H_B9A39F40 -#define MOOD_H_B9A39F40 - -#include - -namespace cadence { - namespace generator { - - class mood { - public: - enum class type { - danceable, - acoustic, - aggressive, - electronic, - happy, - party, - relaxed, - sad, - instrumental - }; - - // Constructor - - mood(type t, double prob); - - // Accessors - - type getType() const - { - return type_; - } - - double getProbability() const - { - return probability_; - } - - bool getPositive() const - { - return positive_; - } - - std::string getCategory() const - { - return category_; - } - - private: - type type_; - double probability_; - bool positive_; - std::string category_; - - }; - - }; -}; - -#endif /* end of include guard: MOOD_H_B9A39F40 */ diff --git a/generator/schema.sql b/generator/schema.sql index 879ebb3..7d211ba 100644 --- a/generator/schema.sql +++ b/generator/schema.sql @@ -1,5 +1,11 @@ CREATE TABLE `songs` ( + `song_id` INTEGER PRIMARY KEY, `title` VARCHAR(255) NOT NULL, - `artist` VARCHAR(255) NOT NULL, - `category` VARCHAR(255) NOT NULL + `artist` VARCHAR(255) NOT NULL ); + +CREATE TABLE `moods` ( + `song_id` INTEGER, + `mood` VARCHAR(255), + PRIMARY KEY (`song_id`, `mood`) +) WITHOUT ROWID; diff --git a/vendor/hkutil b/vendor/hkutil index 41f2eb0..57e2f22 160000 --- a/vendor/hkutil +++ b/vendor/hkutil @@ -1 +1 @@ -Subproject commit 41f2eb087900d4f2ea95c3e11087cf2c71395bae +Subproject commit 57e2f2262cd89f502f7a801b8ee7796e454a5e75 -- cgit 1.4.1