Skip to content

Commit

Permalink
Added chord progression
Browse files Browse the repository at this point in the history
- Started work on accompaniment
  • Loading branch information
RandyParedis committed Dec 1, 2018
1 parent 2284e35 commit deef827
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 36 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,4 @@ compile `cmake` of this project with the flag `-DTRNG_LOC` set to your home dire
| 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).
| 01-12-2018 | Added better chord generation support (or rather: made it better).<br>Also added `chord-progression` as a style option.
8 changes: 8 additions & 0 deletions docs/Future.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ This file describes all planned features to be added in the future
- Algorithms
- Melodic algorithms
- Accompaniment
- Uses a repetition schema / arrangement option.
(based on [this site](https://chordchord.com/))
- Centralized
- (Gaussian) Voicing
- Markov Chains
- ...
- Rhythmic algorithms
- 1/f Noise
- Accompaniment
- Most Common Structures (MCS)
- Markov Chains
- ...
Expand All @@ -20,12 +23,17 @@ This file describes all planned features to be added in the future
- _(Custom algorithm parsing)_
- MidiPlayer
- _(Force playing at all times)_
- Styles
- Add styles:
- `Boogie`: `C-C-F-C-G-F-C`
- `Pop`: `E-B-C#-A`
- Config
- Make it so the execution does not break on invalid input (preprocessing)
- Allow `autoplayer` to be executed anywhere.
- _(Ability to randomize anything)_
- General Todo
- Simple Installation
- Copyright Notices
- Markov Chain Preparation
- conversion from `MIDI`-files to `MusicXML`-files can be done on linux if `MuseScore 2` is installed via `musescore -o output.xml input.mid`
- Add `MusicXML` note parser for pitch and rhythm.
Expand Down
5 changes: 4 additions & 1 deletion docs/Styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ defined beat-type.
- `time`: The time signature of the score. Contains two fields:
- `beats`: The amount of beats per measure.
- `type`: The beat-type of the score.
- `chord-progression`: A string, containing the chord progression per measure, separated
by a `-` sign.
- `from`: This helper field allows a user to simply identify with which style to merge.

None of the above fields are required. If they are not set, the value of the default style
Expand All @@ -46,7 +48,8 @@ of:
- `chromatic` or `dodecatonic`: All 12 notes/pitches of a certain octave are used.
- `diatonic.major`: The general `major` scale.
- `diatonic.minor`: The general `minor` scale.
- `whole-tone` or `hexatonic`: There are only whole tones in between each step in the scale.
- `whole-tone` or `hexatonic`: There - Add `chord-progression` option, containing a sequence of chords
(as list of chords, joined to a string, using the `-`) are only whole tones in between each step in the scale.
- `heptatonic.melodic`: The melodic minor scale.
- `heptatonic.harmonic`: The harmonic minor scale, commonly used from the 17th century to the
20th century.
Expand Down
6 changes: 3 additions & 3 deletions main/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"3": 0.1
}
},
"rest-ratio": 0.05
"rest-ratio": 0
},
"export": {
"filename": "music.xml",
Expand All @@ -31,8 +31,8 @@
"rights": "Copyright \u00A9 2018 autoplay v@VERSION@, created by Randy Paredis"
},
"style": {
"from": "C-major",
"bpm": 160
"bpm": 160,
"chord-progression": "E-B-C#-A"
},
"parts": [
{
Expand Down
3 changes: 2 additions & 1 deletion main/config/styles.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"time": {
"beats": 4,
"type": 4
}
},
"chord-progression": ""
},

"white-keys": {
Expand Down
116 changes: 86 additions & 30 deletions main/util/Generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#include "Generator.h"
#include "Randomizer.h"

#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/foreach.hpp>
#include <bitset>
#include <sstream>
Expand Down Expand Up @@ -33,6 +35,12 @@ namespace autoplay {
// Get Logger
auto logger = m_config.getLogger();

std::vector<std::string> chord_progression;
auto chord_progression_string = m_config.conf<std::string>("style.chord-progression", "");
if(!chord_progression_string.empty()) {
boost::split(chord_progression, chord_progression_string, boost::is_any_of("-"));
}

music::Score score{m_config.conf_child("export")};
for(unsigned int i = 0; i < part_count; ++i) {
auto pt_part = ptree_at(parts, i);
Expand Down Expand Up @@ -108,7 +116,8 @@ namespace autoplay {
part->setInstrumentName(pt_part.get<std::string>("name", ""));

std::shared_ptr<music::Chord> prev;
for(unsigned int j = 0; j < length * measure.max_length();) {
auto mlen = measure.max_length();
for(unsigned int j = 0; j < length * mlen;) {
std::vector<music::Chord*> conc = {};
for(unsigned int p = 0; p < i; ++p) {
music::Chord* n = score.getParts().at(p)->at(j);
Expand All @@ -122,16 +131,23 @@ namespace autoplay {
auto duration = (unsigned)(divisions * 4 * rh);

// Prevent overflowing over final measure
if(j + duration > length * measure.max_length()) {
duration = length * measure.max_length() - j;
if(j + duration > length * mlen) {
duration = length * mlen - j;
}

// Ability to do chords
int num_notes = chord_algo(m_rnengine, prev.get(), conc, options);
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);

// Remap the percussion depending on the chord progression
if(!percussion && !chord_progression.empty() && j % mlen == 0) {
std::string value = chord_progression.at((j / mlen) % chord_progression.size());
pitch = remapPitch(pitch, value, clef.range());
}

if(chord.in(pitch)) {
--nn;
continue;
Expand All @@ -157,35 +173,12 @@ namespace autoplay {
}

part->setMeasures(measure);
auto picked = Randomizer::pick_uniform<float>(m_rnengine, 0.0f, 1.0f);

// Change the last Note to the root note with a chance of style.chance
if(!percussion && picked <= m_config.conf<float>("style.chance")) {
music::Note::Semitone s;

if(!percussion) {
auto c = part->back()->back().bottom()->getPitch();
auto r = music::Note::pitchRepr(c);
auto p = music::Note::splitPitch(r, s);

// Find nearest root note.
// To do this, there are 3 possibilities: same octave, or one octave up or down.
auto p1 = music::Note::pitch(m_config.conf<std::string>("style.root") + std::to_string(p.second));
auto p2 =
music::Note::pitch(m_config.conf<std::string>("style.root") + std::to_string(p.second - 1));
auto p3 =
music::Note::pitch(m_config.conf<std::string>("style.root") + std::to_string(p.second + 1));
auto ap1 = std::abs(c - p1);
auto ap2 = std::abs(c - p2);
auto ap3 = std::abs(c - p3);
if(ap1 < ap2 && ap1 < ap3) {
part->back()->back().bottom()->setPitch(p1);
} else if(ap2 < ap1 && ap2 < ap3) {
part->back()->back().bottom()->setPitch(p2);
} else if(ap3 < ap2 && ap3 < ap1) {
part->back()->back().bottom()->setPitch(p3);
} else {
part->back()->back().bottom()->setPitch(p1);
}
part->back()->back().bottom()->setPitch(
remapPitch(c, m_config.conf<std::string>("style.root"), clef.range()));
}

// Generate random rests (for this part)
Expand Down Expand Up @@ -461,6 +454,39 @@ namespace autoplay {
return clef.range();
}

uint8_t Generator::remapPitch(uint8_t pitch, const std::string& to, const std::pair<uint8_t, uint8_t>& range) {
auto picked = Randomizer::pick_uniform<float>(m_rnengine, 0.0f, 1.0f);

if(picked <= m_config.conf<float>("style.chance")) {
music::Note::Semitone s;

auto r = music::Note::pitchRepr(pitch);
auto p = music::Note::splitPitch(r, s);

// Find nearest "to" pitch.
// To do this, there are 3 possibilities: same octave, or one octave up or down.
// But, we have to be aware of the range constraint that has been set on the remapping!
std::vector<uint8_t> pitches;
for(const auto& i : {-1, 0, 1}) {
auto o = music::Note::pitch(to + std::to_string(p.second + i));
if(o >= range.first && o <= range.second) {
pitches.emplace_back(o);
}
}
if(!pitches.empty()) {
uint8_t smallest = pitches.at(0);
for(const auto& o : pitches) {
if(std::abs(pitch - o) < std::abs(pitch - smallest)) {
smallest = o;
}
}
pitch = smallest;
}
}

return pitch;
}

uint8_t Generator::pitchBrownianMotion(autoplay::util::RNEngine& gen, autoplay::music::Chord* prev,
std::vector<autoplay::music::Chord*>& conc, const pt::ptree& pt) const {
auto stave = pt.get<int>("stave", 0);
Expand Down Expand Up @@ -543,6 +569,36 @@ namespace autoplay {
return pitches.at(sum);
}

uint8_t Generator::pitchAccompaniment(const std::string& schematic, const std::string& chordname,
unsigned int timestep, unsigned int measure_length) const {
// Check the length of the schematic
auto sl = (unsigned int)schematic.length();
float sm = std::log2((float)sl);
if(std::floor(sm) - sm < 0.0f) {
throw std::invalid_argument(
"The schematic for the accompaniment has an invalid length. A power of 2 was expected, " +
std::to_string(sl) + " was obtained.");
}

// Find the separation/group point of each new "note"
unsigned int group = measure_length / sl;
timestep %= measure_length;
auto idx = (unsigned int)std::floor((float)timestep / group);

char letter = schematic.at(idx);
switch(letter) {
case 'A': break;

case 'B': break;

case 'C': break;

default:
throw std::invalid_argument("The schematic contains an invalid letter '" + std::to_string(letter) +
"'. Only A, B and C are allowed!");
}
}

float Generator::rhythmBrownianMotion(autoplay::util::RNEngine& gen, autoplay::music::Chord* prev,
std::vector<autoplay::music::Chord*>& conc, const pt::ptree& pt) const {
auto divisions = pt.get<unsigned int>("rhythm._divs");
Expand Down
22 changes: 22 additions & 0 deletions main/util/Generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ namespace autoplay {
*/
std::pair<uint8_t, uint8_t> staveRange(int stave) const;

/**
* Generate a new pitch according to its chance of occurring and map it to a certain Chord name
* @param pitch The pitch to change
* @param to The letter of the Chord to change the pitch to; e.g. C, A#...
* @param range The range to comply with the remapping.
* @return A new pitch to map.
*/
uint8_t remapPitch(uint8_t pitch, const std::string& to, const std::pair<uint8_t, uint8_t>& range);

private:
/**
* A pitch generation algorithm, based upon the movements of small particles that are randomly bombarded by
* molecules of the surrounding medium.
Expand All @@ -97,6 +107,18 @@ namespace autoplay {
*/
uint8_t pitch1FNoise(RNEngine& gen, const pt::ptree& pt) const;

/**
* A pitch generation algorithm that tries to generate a good-sounding accompaniment of the played music.
* @param schematic The string, containing the accompaniment schematic.
* @param chordname The chord to generate accompaniment for. This must be taken from the chord
* progression.
* @param timestep The moment when the Chord needs to be played.
* @param measure_length The duration of a current measure.
* @return The new pitch to be played.
*/
uint8_t pitchAccompaniment(const std::string& schematic, const std::string& chordname,
unsigned int timestep, unsigned int measure_length) const;

/**
* A rhythm generation algorithm, based upon the movements of small particles that are randomly bombarded by
* molecules of the surrounding medium.
Expand Down

0 comments on commit deef827

Please sign in to comment.