diff options
| author | Kelly Rauchenberger <fefferburbia@gmail.com> | 2018-03-28 15:49:50 -0400 |
|---|---|---|
| committer | Kelly Rauchenberger <fefferburbia@gmail.com> | 2018-03-28 15:49:50 -0400 |
| commit | 251248adfab36b061eb8fdd08aa54b318462da65 (patch) | |
| tree | 4ff1eb792a1e1b442ec2fa6a99e01b28518c5cf4 | |
| parent | af49d3435302118d036c512aef1def848fb88089 (diff) | |
| download | cadence-251248adfab36b061eb8fdd08aa54b318462da65.tar.gz cadence-251248adfab36b061eb8fdd08aa54b318462da65.tar.bz2 cadence-251248adfab36b061eb8fdd08aa54b318462da65.zip | |
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
| -rw-r--r-- | cadence.cpp | 49 | ||||
| -rw-r--r-- | generator/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | generator/generator.cpp | 109 | ||||
| -rw-r--r-- | generator/mood.cpp | 94 | ||||
| -rw-r--r-- | generator/mood.h | 60 | ||||
| -rw-r--r-- | generator/schema.sql | 10 | ||||
| m--------- | vendor/hkutil | 0 |
7 files changed, 121 insertions, 203 deletions
| diff --git a/cadence.cpp b/cadence.cpp index 704f946..97a74b7 100644 --- a/cadence.cpp +++ b/cadence.cpp | |||
| @@ -13,8 +13,6 @@ | |||
| 13 | #include <hkutil/string.h> | 13 | #include <hkutil/string.h> |
| 14 | #include <hkutil/database.h> | 14 | #include <hkutil/database.h> |
| 15 | 15 | ||
| 16 | const std::vector<std::string> moods = {"party", "chill", "crazy", "happy", "sad", "instrumental", "vocal"}; | ||
| 17 | |||
| 18 | int main(int argc, char** argv) | 16 | int main(int argc, char** argv) |
| 19 | { | 17 | { |
| 20 | if (argc != 2) | 18 | if (argc != 2) |
| @@ -81,21 +79,25 @@ int main(int argc, char** argv) | |||
| 81 | { | 79 | { |
| 82 | std::cout << "Generating tweet..." << std::endl; | 80 | std::cout << "Generating tweet..." << std::endl; |
| 83 | 81 | ||
| 84 | std::string mood = moods[std::uniform_int_distribution<int>(0, moods.size()-1)(random_engine)]; | 82 | hatkirby::row songRow = |
| 83 | abdb.queryFirst( | ||
| 84 | "SELECT song_id, title, artist FROM songs ORDER BY RANDOM() LIMIT 1"); | ||
| 85 | 85 | ||
| 86 | hatkirby::row song = | 86 | hatkirby::row moodRow = |
| 87 | abdb.queryFirst( | 87 | abdb.queryFirst( |
| 88 | "SELECT title, artist FROM songs WHERE category = ? ORDER BY RANDOM() LIMIT 1", | 88 | "SELECT mood FROM moods WHERE song_id = ? ORDER BY RANDOM() LIMIT 1", |
| 89 | { mood }); | 89 | { mpark::get<int>(songRow[0]) }); |
| 90 | 90 | ||
| 91 | std::string songTitle = mpark::get<std::string>(song[0]); | 91 | std::string songTitle = mpark::get<std::string>(songRow[1]); |
| 92 | std::string songArtist = mpark::get<std::string>(song[1]); | 92 | std::string songArtist = mpark::get<std::string>(songRow[2]); |
| 93 | std::string mood = mpark::get<std::string>(moodRow[0]); | ||
| 93 | 94 | ||
| 94 | std::string action = "{" + hatkirby::uppercase(mood) + "}"; | 95 | std::string action = "{" + hatkirby::uppercase(mood) + "}"; |
| 95 | int tknloc; | 96 | int tknloc; |
| 96 | while ((tknloc = action.find("{")) != std::string::npos) | 97 | while ((tknloc = action.find("{")) != std::string::npos) |
| 97 | { | 98 | { |
| 98 | std::string token = action.substr(tknloc+1, action.find("}")-tknloc-1); | 99 | std::string token = action.substr(tknloc+1, action.find("}")-tknloc-1); |
| 100 | |||
| 99 | std::string modifier; | 101 | std::string modifier; |
| 100 | int modloc; | 102 | int modloc; |
| 101 | if ((modloc = token.find(":")) != std::string::npos) | 103 | if ((modloc = token.find(":")) != std::string::npos) |
| @@ -104,10 +106,7 @@ int main(int argc, char** argv) | |||
| 104 | token = token.substr(0, modloc); | 106 | token = token.substr(0, modloc); |
| 105 | } | 107 | } |
| 106 | 108 | ||
| 107 | std::string canontkn; | 109 | std::string canontkn = hatkirby::uppercase(token); |
| 108 | std::transform(std::begin(token), std::end(token), std::back_inserter(canontkn), [] (char ch) { | ||
| 109 | return std::toupper(ch); | ||
| 110 | }); | ||
| 111 | 110 | ||
| 112 | std::string result; | 111 | std::string result; |
| 113 | if (canontkn == "\\N") | 112 | if (canontkn == "\\N") |
| @@ -125,18 +124,23 @@ int main(int argc, char** argv) | |||
| 125 | 124 | ||
| 126 | result = group[dist(random_engine)]; | 125 | result = group[dist(random_engine)]; |
| 127 | } else { | 126 | } else { |
| 128 | std::cout << "Badly formatted forms file:" << std::endl; | 127 | throw std::logic_error("No such form as " + canontkn); |
| 129 | std::cout << "No such form as " + canontkn << std::endl; | ||
| 130 | |||
| 131 | return 1; | ||
| 132 | } | 128 | } |
| 133 | 129 | ||
| 134 | if (modifier == "indefinite") | 130 | if (modifier == "indefinite") |
| 135 | { | 131 | { |
| 136 | if ((result.length() > 1) && (isupper(result[0])) && (isupper(result[1]))) | 132 | if ( |
| 133 | (result.length() > 1) && | ||
| 134 | isupper(result[0]) && | ||
| 135 | isupper(result[1])) | ||
| 137 | { | 136 | { |
| 138 | result = "an " + result; | 137 | result = "an " + result; |
| 139 | } else if ((result[0] == 'a') || (result[0] == 'e') || (result[0] == 'i') || (result[0] == 'o') || (result[0] == 'u')) | 138 | } else if ( |
| 139 | (result[0] == 'a') || | ||
| 140 | (result[0] == 'e') || | ||
| 141 | (result[0] == 'i') || | ||
| 142 | (result[0] == 'o') || | ||
| 143 | (result[0] == 'u')) | ||
| 140 | { | 144 | { |
| 141 | result = "an " + result; | 145 | result = "an " + result; |
| 142 | } else { | 146 | } else { |
| @@ -147,9 +151,7 @@ int main(int argc, char** argv) | |||
| 147 | std::string finalresult; | 151 | std::string finalresult; |
| 148 | if (islower(token[0])) | 152 | if (islower(token[0])) |
| 149 | { | 153 | { |
| 150 | std::transform(std::begin(result), std::end(result), std::back_inserter(finalresult), [] (char ch) { | 154 | finalresult = hatkirby::lowercase(result); |
| 151 | return std::tolower(ch); | ||
| 152 | }); | ||
| 153 | } else if (isupper(token[0]) && !isupper(token[1])) | 155 | } else if (isupper(token[0]) && !isupper(token[1])) |
| 154 | { | 156 | { |
| 155 | auto words = hatkirby::split<std::list<std::string>>(result, " "); | 157 | auto words = hatkirby::split<std::list<std::string>>(result, " "); |
| @@ -158,7 +160,10 @@ int main(int argc, char** argv) | |||
| 158 | word[0] = std::toupper(word[0]); | 160 | word[0] = std::toupper(word[0]); |
| 159 | } | 161 | } |
| 160 | 162 | ||
| 161 | finalresult = hatkirby::implode(std::begin(words), std::end(words), " "); | 163 | finalresult = hatkirby::implode( |
| 164 | std::begin(words), | ||
| 165 | std::end(words), | ||
| 166 | " "); | ||
| 162 | } else { | 167 | } else { |
| 163 | finalresult = result; | 168 | finalresult = result; |
| 164 | } | 169 | } |
| 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( | |||
| 9 | vendor/json | 9 | vendor/json |
| 10 | ../vendor/hkutil) | 10 | ../vendor/hkutil) |
| 11 | 11 | ||
| 12 | add_executable(generator mood.cpp generator.cpp main.cpp) | 12 | add_executable(generator generator.cpp main.cpp) |
| 13 | set_property(TARGET generator PROPERTY CXX_STANDARD 11) | 13 | set_property(TARGET generator PROPERTY CXX_STANDARD 11) |
| 14 | set_property(TARGET generator PROPERTY CXX_STANDARD_REQUIRED ON) | 14 | set_property(TARGET generator PROPERTY CXX_STANDARD_REQUIRED ON) |
| 15 | target_link_libraries(generator ${sqlite3_LIBRARIES}) | 15 | 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 @@ | |||
| 6 | #include <json.hpp> | 6 | #include <json.hpp> |
| 7 | #include <hkutil/progress.h> | 7 | #include <hkutil/progress.h> |
| 8 | #include <hkutil/string.h> | 8 | #include <hkutil/string.h> |
| 9 | #include "mood.h" | ||
| 10 | 9 | ||
| 11 | namespace cadence { | 10 | namespace cadence { |
| 12 | namespace generator { | 11 | namespace generator { |
| @@ -93,7 +92,8 @@ namespace cadence { | |||
| 93 | DIR* subdir; | 92 | DIR* subdir; |
| 94 | if ((subdir = opendir(directory.c_str())) == nullptr) | 93 | if ((subdir = opendir(directory.c_str())) == nullptr) |
| 95 | { | 94 | { |
| 96 | throw std::invalid_argument("Invalid AcousticBrainz data directory"); | 95 | throw std::invalid_argument( |
| 96 | "Invalid AcousticBrainz data directory"); | ||
| 97 | } | 97 | } |
| 98 | 98 | ||
| 99 | struct dirent* subent; | 99 | struct dirent* subent; |
| @@ -106,7 +106,8 @@ namespace cadence { | |||
| 106 | DIR* subsubdir; | 106 | DIR* subsubdir; |
| 107 | if ((subsubdir = opendir(subdirectory.c_str())) == nullptr) | 107 | if ((subsubdir = opendir(subdirectory.c_str())) == nullptr) |
| 108 | { | 108 | { |
| 109 | throw std::invalid_argument("Invalid AcousticBrainz data directory"); | 109 | throw std::invalid_argument( |
| 110 | "Invalid AcousticBrainz data directory"); | ||
| 110 | } | 111 | } |
| 111 | 112 | ||
| 112 | struct dirent* subsubent; | 113 | struct dirent* subsubent; |
| @@ -149,27 +150,87 @@ namespace cadence { | |||
| 149 | 150 | ||
| 150 | try | 151 | try |
| 151 | { | 152 | { |
| 152 | std::vector<mood> moods; | 153 | auto& hl = jsonData["highlevel"]; |
| 153 | moods.emplace_back(mood::type::danceable, jsonData["highlevel"]["danceability"]["all"]["danceable"]); | 154 | |
| 154 | moods.emplace_back(mood::type::acoustic, jsonData["highlevel"]["mood_acoustic"]["all"]["acoustic"]); | 155 | double danceable = hl["danceability"]["all"]["danceable"]; |
| 155 | moods.emplace_back(mood::type::aggressive, jsonData["highlevel"]["mood_aggressive"]["all"]["aggressive"]); | 156 | double acoustic = hl["mood_acoustic"]["all"]["acoustic"]; |
| 156 | moods.emplace_back(mood::type::electronic, jsonData["highlevel"]["mood_electronic"]["all"]["electronic"]); | 157 | double aggressive = hl["mood_aggressive"]["all"]["aggressive"]; |
| 157 | moods.emplace_back(mood::type::happy, jsonData["highlevel"]["mood_happy"]["all"]["happy"]); | 158 | double electronic = hl["mood_electronic"]["all"]["electronic"]; |
| 158 | moods.emplace_back(mood::type::party, jsonData["highlevel"]["mood_party"]["all"]["party"]); | 159 | double happy = hl["mood_happy"]["all"]["happy"]; |
| 159 | moods.emplace_back(mood::type::relaxed, jsonData["highlevel"]["mood_relaxed"]["all"]["relaxed"]); | 160 | double party = hl["mood_party"]["all"]["party"]; |
| 160 | moods.emplace_back(mood::type::sad, jsonData["highlevel"]["mood_sad"]["all"]["sad"]); | 161 | double relaxed = hl["mood_relaxed"]["all"]["relaxed"]; |
| 161 | moods.emplace_back(mood::type::instrumental, jsonData["highlevel"]["voice_instrumental"]["all"]["instrumental"]); | 162 | double sad = hl["mood_sad"]["all"]["sad"]; |
| 162 | 163 | double instrumental = hl["voice_instrumental"]["all"]["instrumental"]; | |
| 163 | std::sort(std::begin(moods), std::end(moods), [] (const mood& left, const mood& right) -> bool { | 164 | |
| 164 | return left.getProbability() > right.getProbability(); | 165 | std::string title = jsonData["metadata"]["tags"]["title"][0]; |
| 165 | }); | 166 | std::string artist = jsonData["metadata"]["tags"]["artist"][0]; |
| 166 | 167 | ||
| 167 | std::list<hatkirby::column> columns; | 168 | uint64_t songId = db_.insertIntoTable( |
| 168 | columns.emplace_back("title", jsonData["metadata"]["tags"]["title"][0].get<std::string>()); | 169 | "songs", |
| 169 | columns.emplace_back("artist", jsonData["metadata"]["tags"]["artist"][0].get<std::string>()); | 170 | { |
| 170 | columns.emplace_back("category", moods.front().getCategory()); | 171 | { "title", title }, |
| 171 | 172 | { "artist", artist } | |
| 172 | db_.insertIntoTable("songs", std::move(columns)); | 173 | }); |
| 174 | |||
| 175 | std::list<std::string> moods; | ||
| 176 | |||
| 177 | // ~38% | ||
| 178 | if ((party > 0.5) || (danceable > 0.75)) | ||
| 179 | { | ||
| 180 | moods.push_back("party"); | ||
| 181 | } | ||
| 182 | |||
| 183 | // ~38% | ||
| 184 | if ((relaxed > 0.81) || (acoustic > 0.5)) | ||
| 185 | { | ||
| 186 | moods.push_back("chill"); | ||
| 187 | } | ||
| 188 | |||
| 189 | // ~42% | ||
| 190 | if ((aggressive > 0.5) || (electronic > 0.95)) | ||
| 191 | { | ||
| 192 | moods.push_back("crazy"); | ||
| 193 | } | ||
| 194 | |||
| 195 | // ~30% | ||
| 196 | if (happy > 0.5) | ||
| 197 | { | ||
| 198 | moods.push_back("happy"); | ||
| 199 | } | ||
| 200 | |||
| 201 | // ~30% | ||
| 202 | if (sad > 0.5) | ||
| 203 | { | ||
| 204 | moods.push_back("sad"); | ||
| 205 | } | ||
| 206 | |||
| 207 | // ~38% | ||
| 208 | if (instrumental > 0.9) | ||
| 209 | { | ||
| 210 | moods.push_back("instrumental"); | ||
| 211 | } | ||
| 212 | |||
| 213 | // ~34% | ||
| 214 | if (instrumental < 0.2) | ||
| 215 | { | ||
| 216 | moods.push_back("vocal"); | ||
| 217 | } | ||
| 218 | |||
| 219 | // ~1% | ||
| 220 | if (moods.empty()) | ||
| 221 | { | ||
| 222 | moods.push_back("unknown"); | ||
| 223 | } | ||
| 224 | |||
| 225 | for (const std::string& mood : moods) | ||
| 226 | { | ||
| 227 | db_.insertIntoTable( | ||
| 228 | "moods", | ||
| 229 | { | ||
| 230 | { "song_id", static_cast<int>(songId) }, | ||
| 231 | { "mood", mood } | ||
| 232 | }); | ||
| 233 | } | ||
| 173 | } catch (const std::domain_error& ex) | 234 | } catch (const std::domain_error& ex) |
| 174 | { | 235 | { |
| 175 | // Weird data. Ignore silently. | 236 | // 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 @@ | |||
| 1 | #include "mood.h" | ||
| 2 | |||
| 3 | namespace cadence { | ||
| 4 | namespace generator { | ||
| 5 | |||
| 6 | // The categories are: | ||
| 7 | // - party (+danceable, -acoustic, +electronic, +party) = ~.21 | ||
| 8 | // - chill (-danceable, +acoustic, -aggressive, -electronic, -party, +relaxed) = ~.49 | ||
| 9 | // - crazy (+aggressive, -relaxed) = ~.02 | ||
| 10 | // - happy (+happy, -sad) = ~.009 | ||
| 11 | // - sad (-happy, +sad) = ~.02 | ||
| 12 | // - instrumental (+instrumental) = ~.12 | ||
| 13 | // - vocal (-instrumental) = ~.10 | ||
| 14 | |||
| 15 | mood::mood(type t, double prob) : type_(t) | ||
| 16 | { | ||
| 17 | if (prob >= 0.5) | ||
| 18 | { | ||
| 19 | probability_ = prob; | ||
| 20 | positive_ = true; | ||
| 21 | } else { | ||
| 22 | probability_ = 1.0 - prob; | ||
| 23 | positive_ = false; | ||
| 24 | } | ||
| 25 | |||
| 26 | switch (t) | ||
| 27 | { | ||
| 28 | case type::danceable: | ||
| 29 | { | ||
| 30 | category_ = (positive_ ? "party" : "chill"); | ||
| 31 | |||
| 32 | break; | ||
| 33 | } | ||
| 34 | |||
| 35 | case type::acoustic: | ||
| 36 | { | ||
| 37 | category_ = (positive_ ? "chill" : "party"); | ||
| 38 | |||
| 39 | break; | ||
| 40 | } | ||
| 41 | |||
| 42 | case type::aggressive: | ||
| 43 | { | ||
| 44 | category_ = (positive_ ? "crazy" : "chill"); | ||
| 45 | |||
| 46 | break; | ||
| 47 | } | ||
| 48 | |||
| 49 | case type::electronic: | ||
| 50 | { | ||
| 51 | category_ = (positive_ ? "party" : "chill"); | ||
| 52 | |||
| 53 | break; | ||
| 54 | } | ||
| 55 | |||
| 56 | case type::happy: | ||
| 57 | { | ||
| 58 | category_ = (positive_ ? "happy" : "sad"); | ||
| 59 | |||
| 60 | break; | ||
| 61 | } | ||
| 62 | |||
| 63 | case type::party: | ||
| 64 | { | ||
| 65 | category_ = (positive_ ? "party" : "chill"); | ||
| 66 | |||
| 67 | break; | ||
| 68 | } | ||
| 69 | |||
| 70 | case type::relaxed: | ||
| 71 | { | ||
| 72 | category_ = (positive_ ? "chill" : "crazy"); | ||
| 73 | |||
| 74 | break; | ||
| 75 | } | ||
| 76 | |||
| 77 | case type::sad: | ||
| 78 | { | ||
| 79 | category_ = (positive_ ? "sad" : "happy"); | ||
| 80 | |||
| 81 | break; | ||
| 82 | } | ||
| 83 | |||
| 84 | case type::instrumental: | ||
| 85 | { | ||
| 86 | category_ = (positive_ ? "instrumental" : "vocal"); | ||
| 87 | |||
| 88 | break; | ||
| 89 | } | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | }; | ||
| 94 | }; | ||
| 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 @@ | |||
| 1 | #ifndef MOOD_H_B9A39F40 | ||
| 2 | #define MOOD_H_B9A39F40 | ||
| 3 | |||
| 4 | #include <string> | ||
| 5 | |||
| 6 | namespace cadence { | ||
| 7 | namespace generator { | ||
| 8 | |||
| 9 | class mood { | ||
| 10 | public: | ||
| 11 | enum class type { | ||
| 12 | danceable, | ||
| 13 | acoustic, | ||
| 14 | aggressive, | ||
| 15 | electronic, | ||
| 16 | happy, | ||
| 17 | party, | ||
| 18 | relaxed, | ||
| 19 | sad, | ||
| 20 | instrumental | ||
| 21 | }; | ||
| 22 | |||
| 23 | // Constructor | ||
| 24 | |||
| 25 | mood(type t, double prob); | ||
| 26 | |||
| 27 | // Accessors | ||
| 28 | |||
| 29 | type getType() const | ||
| 30 | { | ||
| 31 | return type_; | ||
| 32 | } | ||
| 33 | |||
| 34 | double getProbability() const | ||
| 35 | { | ||
| 36 | return probability_; | ||
| 37 | } | ||
| 38 | |||
| 39 | bool getPositive() const | ||
| 40 | { | ||
| 41 | return positive_; | ||
| 42 | } | ||
| 43 | |||
| 44 | std::string getCategory() const | ||
| 45 | { | ||
| 46 | return category_; | ||
| 47 | } | ||
| 48 | |||
| 49 | private: | ||
| 50 | type type_; | ||
| 51 | double probability_; | ||
| 52 | bool positive_; | ||
| 53 | std::string category_; | ||
| 54 | |||
| 55 | }; | ||
| 56 | |||
| 57 | }; | ||
| 58 | }; | ||
| 59 | |||
| 60 | #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 @@ | |||
| 1 | CREATE TABLE `songs` ( | 1 | CREATE TABLE `songs` ( |
| 2 | `song_id` INTEGER PRIMARY KEY, | ||
| 2 | `title` VARCHAR(255) NOT NULL, | 3 | `title` VARCHAR(255) NOT NULL, |
| 3 | `artist` VARCHAR(255) NOT NULL, | 4 | `artist` VARCHAR(255) NOT NULL |
| 4 | `category` VARCHAR(255) NOT NULL | ||
| 5 | ); | 5 | ); |
| 6 | |||
| 7 | CREATE TABLE `moods` ( | ||
| 8 | `song_id` INTEGER, | ||
| 9 | `mood` VARCHAR(255), | ||
| 10 | PRIMARY KEY (`song_id`, `mood`) | ||
| 11 | ) WITHOUT ROWID; | ||
| diff --git a/vendor/hkutil b/vendor/hkutil | |||
| Subproject 41f2eb087900d4f2ea95c3e11087cf2c71395ba | Subproject 57e2f2262cd89f502f7a801b8ee7796e454a5e7 | ||
