Skip to content

Commit

Permalink
Added better chord generation support
Browse files Browse the repository at this point in the history
- Added default values to generation algorithms
- Disabled zupply unused variable spam in compilation
- Added logger to generator
- Solved warnings
  • Loading branch information
RandyParedis committed Dec 1, 2018
1 parent 15b67c1 commit 2284e35
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 82 deletions.
8 changes: 4 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ message(STATUS "TRNG location: ${TRNG_LOCATION}")

add_subdirectory(main)

target_include_directories(autoplay PUBLIC ${TRNG_LOCATION}/include)
target_include_directories(autoplay PUBLIC ${TRNG_LOCATION}/lib)
target_include_directories(autoplay PUBLIC ${DEPENDENCY_DIR})
target_include_directories(autoplay SYSTEM PUBLIC ${TRNG_LOCATION}/include)
target_include_directories(autoplay SYSTEM PUBLIC ${TRNG_LOCATION}/lib)
target_include_directories(autoplay SYSTEM PUBLIC ${DEPENDENCY_DIR})
#target_include_directories(autoplay PUBLIC ${DEPENDENCY_DIR}/zupply/src)
target_include_directories(autoplay PUBLIC ${Boost_INCLUDE_DIRS})
target_include_directories(autoplay SYSTEM PUBLIC ${Boost_INCLUDE_DIRS})

add_subdirectory(test)

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,5 @@ compile `cmake` of this project with the flag `-DTRNG_LOC` set to your home dire
| 22-11-2018 | Added **Brownian Motion** as a rhythm algorithm.
| 24-11-2018 | Added support for chords, **BUT** `MIDIPlayer` currently only plays bottom note of each chord and no multiple-note chords are being generated by the algorithms (yet).
| 25-11-2018 | Config file now requires `styles.json`, `instruments.json` and `clefs.json`.<br>Also added automatic beaming of notes of a type less than a quarter note.
| 28-11-2018 | Fixed MidiPlayer to a better algorithm.
| 01-12-2018 | Added better chord generation support (or rather: made it better).
26 changes: 22 additions & 4 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,37 @@ The file contains of the following tree structure and fields:
the current stave, generate notes whose chance of being chosen corresponds with
its value on the gauss-curve.
- Anything else will generate completely random pitches within the entire music range.
- `rhythm`: The algoruthm to use for rhythms. The `options.rhythm.smallest` and
- `rhythm`: The algorithm to use for rhythms. The `options.rhythm.smallest` and
`options.rhythm.largest` options allow the user to specify the minimal and maximal
duration of a note. This is respected by all algorithm.<br>Possible algorithms are:
- `random`: A random rhythm out of the possible range.
- `brownian-motion`: Implementation of the Brownian motion as a rhythmic algorithm.
- Anything else will create a constant tempo of `options.rhythm.duration` notes
(representations according to the MusicXML note types, or the corresponding fractions
when less than one). If this option cannot be found, `quarter` will be used instead.
- `chord`: This is the algorithm that is being used in deciding how many notes to
play in a chord at a given time. The possible values are:
- `random`: Choose a random value in the inclusive range from `options.chord.min`
to `options.chord.max`.
- `weighted`: Give each value a certain percentage of occurring (by setting the
valid elements in `options.chord`). For instance:
```
"chord": {
"2": 0.2,
"3": 0.1
}
```
This can be read as: a two-note chord has a 20% chance of occurring, whilst a 3-
note chord has a 10% chance to appear. The missing 70% is appointed towards single
notes.<br>It is possible to set the value for a single note (key is `"1"`), but if the
percentages don't add up to 1, this value will be changed and a warning
will be shown.<br>If the sum of all these chances is greater than 1, a warning will
also be issued and all values will be normalized (divided by the sum).<br>
Missing values in this list will be mapped to `0`, except for a single note, as
mentioned above.
- All other values are mapped to generate chords of `options.chord.amount` notes.
- `options`: An object, representing the additional options of the above-mentioned
algorithms.
- `chord-ratio`: when this object is present, it represents the chance that a chord
of `n` notes is played. For instance, if this object is `{"1":0.5, "2":0.5}`, there
is a 50/50 chance of a single note, or a two-note chord.
- `rest-ratio`: A floating point number, representing the approximated percentage of
notes that should be turned into rests.<br>_(**Note:** This percentage is merely an
approximation and thus is not a precise representation of the exact percentage.)_
Expand Down
3 changes: 3 additions & 0 deletions docs/Future.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ This file describes all planned features to be added in the future
- Most Common Structures (MCS)
- Markov Chains
- ...
- Chord Note Count algorithms
- Markov Chains
- ...
- _(Dynamic algorithms)_
- _(Custom algorithm parsing)_
- MidiPlayer
Expand Down
4 changes: 2 additions & 2 deletions main/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
"generation": {
"pitch": "brownian-motion",
"rhythm": "random",
"chord": "weighted",
"options": {
"rhythm": {
"smallest": "eighth",
"largest": "half"
},
"chord-ratio": {
"1": 0.9,
"chord": {
"3": 0.1
}
},
Expand Down
2 changes: 1 addition & 1 deletion main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ int main(int argc, char** argv) {
std::shared_ptr<music::MIDIPlayer> midiPlayer = music::MIDIPlayer::instance();
midiPlayer->probe(config);

util::Generator generator{config};
util::Generator generator{config, logger};
music::Score score = generator.generate();

if(!config.isLeaf("export")) {
Expand Down
4 changes: 2 additions & 2 deletions main/util/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ namespace autoplay {
auto from = sty.get<std::string>("from", "default");

// For loop to prevent circular dependencies to link to one another
for(int i = 0; i < m_styles.get_child("styles").size() && from != "default"; ++i) {
for(unsigned int i = 0; i < m_styles.get_child("styles").size() && from != "default"; ++i) {
merge(sty, m_styles.get_child("styles." + from), true);
from = m_styles.get_child("styles." + from).get<std::string>("from", "default");

Expand All @@ -236,7 +236,7 @@ namespace autoplay {
auto from = sty.get<std::string>("from", "default");

// For loop to prevent circular dependencies to link to one another
for(int i = 0; i < m_styles.get_child("styles").size() && from != "default"; ++i) {
for(unsigned int i = 0; i < m_styles.get_child("styles").size() && from != "default"; ++i) {
merge(sty, m_styles.get_child("styles." + from), true);
from = m_styles.get_child("styles." + from).get<std::string>("from", "default");

Expand Down
6 changes: 3 additions & 3 deletions main/util/FileHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,9 @@ namespace autoplay {

// Set beams
for(unsigned int b = 1; b < 6; ++b) {
int ref = measure->getDivisions() / (int)std::pow(2, b - 1);
unsigned int curdur = note->getDuration();
int prevdur = -1;
int ref = measure->getDivisions() / (int)std::pow(2, b - 1);
int curdur = note->getDuration();
int prevdur = -1;
if(_v_ == 0) {
if(_c_ != 0) {
auto x = measure->getNotes()
Expand Down
108 changes: 86 additions & 22 deletions main/util/Generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

namespace autoplay {
namespace util {
Generator::Generator(const Config& config) : m_config(config) {
Generator::Generator(const Config& config, const zz::log::LoggerPtr& logger)
: m_config(config), m_logger(logger) {
// Set engine
auto engine_name = m_config.conf<std::string>("engine");
auto seed = m_config.conf<unsigned long>("seed");
Expand All @@ -21,7 +22,6 @@ namespace autoplay {

music::Score Generator::generate() {
// Setup default values
// TODO: Randomize these
auto length = (unsigned)m_config.conf<int>("length", 10); // Total amount of measures
auto parts = m_config.conf_child("parts");
unsigned long part_count = parts.size(); // Number of parts
Expand All @@ -39,7 +39,7 @@ namespace autoplay {

pt::ptree options;
options.put("stave", i);
options.put("_p1fn._reinit", true);
options.put("_reinit", true);
if(pt_part.count("generation") == 0) {
if(m_config.conf_child("generation").count("options") == 1) {
BOOST_FOREACH(auto& var, m_config.conf_child("generation.options")) {
Expand All @@ -54,6 +54,7 @@ namespace autoplay {

auto pitch_algo = getPitchAlgorithm(pt_part.get<std::string>("generation.pitch", ""));
auto rhythm_algo = getRhythmAlgorithm(pt_part.get<std::string>("generation.rhythm", ""));
auto chord_algo = getChordNoteCountAlgorithm(pt_part.get<std::string>("generation.chord", ""));

// Set Instrument(s)
bool percussion;
Expand Down Expand Up @@ -126,21 +127,7 @@ namespace autoplay {
}

// Ability to do chords
int num_notes = 1;
const double q = 1.0;
const double y = 4.0;
if(pt_part.count("chord-ratio") == 1) {
std::function<float(const double&)> func = [&pt_part](const double& f) -> float {
return pt_part.get<float>("chord-ratio." + std::to_string((int)f), 0.0f);
};
num_notes = (int)Randomizer::pick_weighted(m_rnengine, q, y, q, func);
} else if(m_config.conf_child("generation.options").count("chord-ratio") == 1) {
std::function<float(const double&)> func = [this](const double& f) -> float {
return m_config.conf<float>("generation.options.chord-ratio." + std::to_string((int)f),
0.0f);
};
num_notes = (int)Randomizer::pick_weighted(m_rnengine, q, y, q, func);
}
int num_notes = chord_algo(m_rnengine, prev.get(), conc, options);
music::Chord chord;
for(int nn = 0; nn < num_notes; ++nn) {
uint8_t pitch = pitch_algo(m_rnengine, prev.get(), conc, options);
Expand All @@ -165,6 +152,8 @@ namespace autoplay {

measure.append(chord);
j += duration;

options.put("_reinit", false);
}

part->setMeasures(measure);
Expand Down Expand Up @@ -227,7 +216,7 @@ namespace autoplay {
Generator::getPitchAlgorithm(std::string algo) const {
// Get algorithm variables
if(algo.empty()) {
algo = m_config.conf<std::string>("generation.pitch");
algo = m_config.conf<std::string>("generation.pitch", "random");
}

m_config.getLogger()->debug("Using Pitch Algorithm '{}'", algo);
Expand All @@ -253,7 +242,6 @@ namespace autoplay {
return [this](RNEngine& gen, music::Chord* prev, std::vector<music::Chord*>& conc,
pt::ptree& pt) -> uint8_t {
auto res = pitch1FNoise(gen, pt);
pt.put("_p1fn._reinit", false);
return res;
};
} else if(algo == "centralized") {
Expand Down Expand Up @@ -295,7 +283,7 @@ namespace autoplay {
Generator::getRhythmAlgorithm(std::string algo) const {
// Get algorithm variables
if(algo.empty()) {
algo = m_config.conf<std::string>("generation.rhythm");
algo = m_config.conf<std::string>("generation.rhythm", "constant");
}

m_config.getLogger()->debug("Using Rhythm Algorithm '{}'", algo);
Expand Down Expand Up @@ -324,6 +312,82 @@ namespace autoplay {
}
}

std::function<int(RNEngine& gen, music::Chord* prev, std::vector<music::Chord*>& conc, pt::ptree& pt)>
Generator::getChordNoteCountAlgorithm(std::string algo) const {
// Get algorithm variables
if(algo.empty()) {
algo = m_config.conf<std::string>("generation.chord", "constant");
}

m_config.getLogger()->debug("Using Chord Note Count Algorithm '{}'", algo);

if(algo == "random") {
return [](RNEngine& gen, music::Chord* prev, std::vector<music::Chord*>& conc, pt::ptree& pt) -> int {
auto min = pt.get<int>("chord.min", 1);
auto max = pt.get<int>("chord.max", 1);
return Randomizer::pick_uniform(gen, min, max + 1);
};
} else if(algo == "weighted") {
return [this](RNEngine& gen, music::Chord* prev, std::vector<music::Chord*>& conc,
pt::ptree& pt) -> int {
static std::map<int, float> mapping;
if(pt.get<bool>("_reinit", false)) {
mapping.clear();
}
if(mapping.empty()) {
float sum = 0.0f;
BOOST_FOREACH(const auto& var, pt.get_child("chord")) {
auto s = var.second.get<float>("");
sum += s;
mapping.insert({std::stoi(var.first), s});
}

int min = mapping.begin()->first;

// Fix chances if required
if(sum > 1.0f) { // Normalize
m_logger->warn("Sum of all weighted elements exceeds 1! Normalizing the values...");
for(auto& kv : mapping) {
kv.second /= sum;
}
m_logger->warn("New values:");
for(const auto& kv : mapping) {
m_logger->warn("\t{} --> {}", kv.first, kv.second);
}
} else if(sum < 1.0f) {
float one = 1.0f - sum;
if(mapping.count(1) == 1) {
one += mapping.at(1);
m_logger->warn(
"Invalid sum of {}. Changing chance that a single note occurs from {} to {}.", sum,
mapping.at(1), one);
}
mapping[1] = one;
if(min != 1) {
for(int j = 2; j < min; ++j) {
mapping[j] = 0.0f;
}
}
}
}

std::map<int, float> map_ref{mapping};

std::function<float(const int&)> func = [&map_ref](const int& val) -> float {
if(map_ref.count(val) == 1) {
return map_ref.at(val);
}
return 0.0f;
};
return Randomizer::pick_weighted(gen, mapping.begin()->first, mapping.rbegin()->first + 1, 1, func);
};
} else {
return [](RNEngine& gen, music::Chord* prev, std::vector<music::Chord*>& conc, pt::ptree& pt) -> int {
return pt.get<int>("chord.amount", 1);
};
}
}

std::vector<uint8_t> Generator::getPitches(uint8_t min, uint8_t max, int stave) const {
std::vector<uint8_t> ret = {};
auto scale = m_config.conf<std::string>("style.scale");
Expand Down Expand Up @@ -444,7 +508,7 @@ namespace autoplay {
static uint8_t state = 0;
static std::vector<std::pair<uint8_t, uint8_t>> dice{num_dice, {0, 0}};

if(pt.get<bool>("_p1fn._reinit", false)) {
if(pt.get<bool>("_reinit", false)) {
state = 0;
dice.assign(num_dice, {0, 0});

Expand Down
18 changes: 14 additions & 4 deletions main/util/Generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ namespace autoplay {
public:
/**
* Default Constructor
* @param config The Config that has been initialized with the system
* @param config The Config that has been initialized with the system
* @param logger The logger to use to output all info to.
*/
explicit Generator(const Config& config);
explicit Generator(const Config& config, const zz::log::LoggerPtr& logger);

/**
* Generates a random Score
Expand All @@ -46,6 +47,14 @@ namespace autoplay {
std::function<float(RNEngine& gen, music::Chord* prev, std::vector<music::Chord*>& conc, pt::ptree& pt)>
getRhythmAlgorithm(std::string algo = "") const;

/**
* Get the randomization algorithm for the Chord Note count
* @param algo If not empty, it will use this algorithm to check, instead of the generation.chord value
* @return A lambda function that implements the algorithm
*/
std::function<int(RNEngine& gen, music::Chord* prev, std::vector<music::Chord*>& conc, pt::ptree& pt)>
getChordNoteCountAlgorithm(std::string algo = "") const;

private:
/**
* Get all the possible pitches within a range, according to the given scale.
Expand Down Expand Up @@ -103,8 +112,9 @@ namespace autoplay {
const pt::ptree& pt) const;

private:
Config m_config; ///< The Config of the system
RNEngine m_rnengine; ///< The Random Engine
Config m_config; ///< The Config of the system
RNEngine m_rnengine; ///< The Random Engine
zz::log::LoggerPtr m_logger; ///< The Logger Object
};
}
}
Expand Down
Loading

0 comments on commit 2284e35

Please sign in to comment.